Entity 'auto-persisted' on ajax reRender
grundor Jul 25, 2008 9:52 PMI have a simple situation where I have a page that allows editing of an entity called EventType. Part of the EventType is an array of SubEventType. My page allows the user to click an Add button to add new SubEventTypes.
I would like to only persist these new SubEventTypes when the user clicks the Save button on the form. However, what I am seeing is that the entities are being persisted prior to this.
I have this problem in a variety of places and incarnations in my application, and believe I am just not understanding something correctly - some key insight I am missing that will be an Aha moment.
I am hoping someone can take a few minutes and maybe help me fill in that blank.
More specific details - the link to the page is an s:link which begins the conversation using propagation=join. So a conversation is begun when the page is accessed. When the user clicks the Add SubType button, an action is called which creates a new SubEventType entity and adds it to the list of SubEventTypes in the current EventType. It does not call persist(). The button has a reRender attribute to reRender the table that lists the SubEventTypes for the current EventType.
When I click the button, the new SubEventType is created, the database is somehow updated, and the table is refreshed to show the newly added SubEventType. I don't understand why the database was updated.
Here is my code:
The relevant code from the page:
<s:decorate id="typeSubEventsDecoration" template="../layout/edit.xhtml"> <ui:define name="label">#{messages['com.leagueunited.admin.subeventtypes']}</ui:define> <rich:panel id="subeventspanel" style="width:500px;border:0px;background:transparent;"> <h:outputText value="#{messages['com.leagueunited.admin.eventtype.nosubevents']}" rendered="#{empty eventType.subEventTypes}" /> <rich:dataTable id="subEventTypeTable" var="_subEventType" value="#{eventType.subEventTypes}" rendered="#{not empty eventType.subEventTypes}" onRowMouseOver="this.style.backgroundColor='#F1F1F1'" onRowMouseOut="this.style.backgroundColor='#{a4jSkin.tableBackgroundColor}'" rowClasses="rvgRowOne,rvgRowTwo" cellpadding="0" cellspacing="0"> <f:facet name="header"> <rich:columnGroup> <rich:column colspan="2"> <h:outputText value="#{messages['com.leagueunited.admin.subeventtypes']}" /> </rich:column> </rich:columnGroup> </f:facet> <rich:column> <f:facet name="header"> #{messages['com.leagueunited.admin.subeventtype']} </f:facet> <h:inputText value="#{_subEventType.subEventTypeName}" size="100" maxlength="100" required="true"/> </rich:column> <rich:column style="text-align:center"> <f:facet name="header"> #{messages['com.leagueunited.admin.eventtype.trackstats']} </f:facet> <h:selectBooleanCheckbox value="#{_subEventType.subEventTypeTrackStats}"/> </rich:column> </rich:dataTable> <s:div styleClass="actionButtons"> <a4j:commandButton id="addsub" action="#{eventTypeAction.newSubEventType()}" value="#{messages['com.leagueunited.admin.eventtype.addsubeventtype']}" reRender="subEventTypeTable"/> </s:div> </rich:panel> </s:decorate>
And here is the page.xml for the page:
<page xmlns="http://jboss.com/products/seam/pages" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.0.xsd"> <action execute="#{eventTypeHome.wire}"/> <param name="eventTypeId" value="#{eventTypeHome.eventTypeId}"/> <param name="from"/> <navigation from-action="#{eventTypeAction.persist}"> <rule if="#{not empty from}" if-outcome="persisted"> <begin-conversation join="true"/> <redirect view-id="/manager/event.xhtml"> <param name="from" value="#{from}"/> </redirect> </rule> <rule if="#{empty from}" if-outcome="persisted"> <end-conversation before-redirect="true"/> <redirect view-id="/admin/eventtypes.xhtml"/> </rule> </navigation> <navigation from-action="#{eventTypeAction.update}"> <rule if="#{not empty from}" if-outcome="updated"> <begin-conversation join="true"/> <redirect view-id="/manager/event.xhtml"> <param name="from" value="#{from}"/> </redirect> </rule> <rule if="#{empty from}" if-outcome="updated"> <end-conversation before-redirect="true"/> <redirect view-id="/admin/eventtypes.xhtml"/> </rule> </navigation> <navigation from-action="event_cancel"> <begin-conversation join="true"/> <redirect view-id="/manager/event.xhtml"> <param name="from" value="#{from}"/> </redirect> </navigation> </page>
Now the eventTypeAction code:
@Name("eventTypeAction") @Scope(ScopeType.PAGE) public class EventTypeAction { @Logger Log log; @In @Out protected EventTypeHome eventTypeHome; @In(required=true) @Out protected SportHome sportHome; @In private Map<String, String> messages; public String persist() { if (eventTypeHome.persist() == "persisted") { sportHome.getDefinedInstance().getEventTypes().add(eventTypeHome.getDefinedInstance()); return "persisted"; } return "failed"; } public String update() { return eventTypeHome.update(); } public void newSubEventType() { EventType eventType = eventTypeHome.getInstance(); if (eventType == null) return; // Create a new sub event type with default name and same track stats as parent type SubEventType subEventType = new SubEventType(); subEventType.setSubEventTypeName(messages.get("com.leagueunited.admin.eventtype.addsubeventtype")); subEventType.setSubEventTypeTrackStats(eventType.getEventTypeTrackStats()); subEventType.setParentEventType(eventType); // Add to parent eventType.getSubEventTypes().add(subEventType); } }
Here is the EventTypeHome:
@Name("eventTypeHome") public class EventTypeHome extends EntityHome<EventType> { private static final long serialVersionUID = 443828533084198271L; @In(create = true) private SportHome sportHome; @Factory(value = "eventType", scope = ScopeType.PAGE) public EventType initEventType() { return super.getInstance(); } public void setEventTypeId(Long id) { setId(id); } public Long getEventTypeId() { return (Long) getId(); } @Override protected EventType createInstance() { EventType event = new EventType(); return event; } public void wire() { Sport sport = sportHome.getDefinedInstance(); if (sport != null) { getInstance().setSport(sport); } } public boolean isWired() { if (getInstance() == null) return false; if (getInstance().getSport() == null) return false; return true; } public EventType getDefinedInstance() { return isIdDefined() ? getInstance() : null; } }
And EventType entity:
@Entity @Table(name = "eventtype", catalog = "ludb") public class EventType implements java.io.Serializable, Comparable<EventType> { private static final long serialVersionUID = 420261612760471976L; private Long eventTypeId; private Sport sport; private String eventTypeName; private Boolean eventTypeTrackStats; private List<SubEventType> subEventTypes = new ArrayList<SubEventType>(0); public EventType() { } public EventType(Sport sport, String name, Boolean bTrackStats) { this.sport = sport; this.eventTypeName = name; this.eventTypeTrackStats = bTrackStats; } @Id @GeneratedValue(strategy = IDENTITY) @Column(name = "EVENTTYPE_ID", unique = true, nullable = false) public Long getEventTypeId() { return this.eventTypeId; } public void setEventTypeId(Long eventTypeId) { this.eventTypeId = eventTypeId; } @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "SPORT_ID", nullable = false) @NotNull public Sport getSport() { return this.sport; } public void setSport(Sport sport) { this.sport = sport; } @Column(name = "EVENTTYPE_NAME", nullable = false, length = 100) @NotNull @Length(max = 100) public String getEventTypeName() { return this.eventTypeName; } public void setEventTypeName(String eventTypeName) { this.eventTypeName = eventTypeName; } @Column(name="EVENTTYPE_TRACKSTATS", nullable=false) @NotNull public Boolean getEventTypeTrackStats() { return this.eventTypeTrackStats; } public void setEventTypeTrackStats(Boolean bTrackStats) { this.eventTypeTrackStats = bTrackStats; } @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "parentEventType") public List<SubEventType> getSubEventTypes() { return this.subEventTypes; } public void setSubEventTypes(List<SubEventType> subEventTypes) { this.subEventTypes = subEventTypes; } public int compareTo(EventType anotherType) throws ClassCastException { if (!(anotherType instanceof EventType)) throw new ClassCastException("EventType object expected."); if (anotherType.eventTypeName == null) return 1; // Sort alphabetically based on type name return this.eventTypeName.compareTo(anotherType.eventTypeName); } }
Here is the SubEventType entity:
@Entity @Table(name = "subeventtype", catalog = "ludb") public class SubEventType implements java.io.Serializable, Comparable<SubEventType> { private static final long serialVersionUID = -7314484403043744447L; private Long subEventTypeId; private EventType parentEventType; private String subEventTypeName; private Boolean subEventTypeTrackStats; public SubEventType() { } @Id @GeneratedValue(strategy = IDENTITY) @Column(name = "SUBEVENTTYPE_ID", unique = true, nullable = false) public Long getSubEventTypeId() { return this.subEventTypeId; } public void setSubEventTypeId(Long subEventTypeId) { this.subEventTypeId = subEventTypeId; } @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "EVENTTYPE_ID", nullable = false) @NotNull public EventType getParentEventType() { return this.parentEventType; } public void setParentEventType(EventType eventType) { this.parentEventType = eventType; } @Column(name = "SUBEVENTTYPE_NAME", nullable = false, length = 100) @NotNull @Length(max = 100) public String getSubEventTypeName() { return this.subEventTypeName; } public void setSubEventTypeName(String subEventTypeName) { this.subEventTypeName = subEventTypeName; } @Column(name="SUBEVENTTYPE_TRACKSTATS", nullable=false) @NotNull public Boolean getSubEventTypeTrackStats() { return this.subEventTypeTrackStats; } public void setSubEventTypeTrackStats(Boolean bTrackStats) { this.subEventTypeTrackStats = bTrackStats; } public int compareTo(SubEventType anotherType) throws ClassCastException { if (!(anotherType instanceof SubEventType)) throw new ClassCastException("SubEventType object expected."); if (anotherType.subEventTypeName == null) return 1; // Sort alphabetically based on type name return this.subEventTypeName.compareTo(anotherType.subEventTypeName); } }