FlushModeType.MANUAL in conversation
kai222 Dec 16, 2007 5:30 PMHi,
I cannot control the synchronization from a persistent object to the database. The more I read and the more I try out the more I get confused. I hope that somebody can give me a helping hint.
For testing I generated a simple ear project with seam-gen (Seam 2.0.0.GA) and added an entity class and a stateful session bean.
The use case:
A logged in user wants to change his email address (unique field in table).
A server-side validation with a database selection is effected and fails if the new email address exists in the database.
What happens:
If I enter a new email and press Save, a synchronization (flush) takes place just before the selection of the existing email addresses has been started. In FlushMode.AUTO this is normal, but not in FlushMode.MANUAL. The effect is that EVERY new email is known to the system and the validation does not work as it should.
The entity class MyUser is:
package some.model; // imports... @Entity @Table @Name("user") public class MyUser implements Serializable { @Id @GeneratedValue private Long id = null; // username is the business key @Column(name="username", nullable = false, unique = true) private String username; @Email @Column(name="email", nullable = false, unique = true) private String email; private String firstname; private String lastname; public MyUser() { } //... getter and setter ... }
The stateful session bean implementation is:
package some.controller; // imports ... @Name( "userAction" ) @Scope( ScopeType.CONVERSATION ) @Stateful public class UserActionBean implements Serializable, UserAction { private static final long serialVersionUID = 1L; @PersistenceContext(type=PersistenceContextType.EXTENDED) private EntityManager entityManager; @In(create=true) private transient FacesMessages facesMessages; @In( value = "user", required = false ) @Out( value = "user", required = false ) private MyUser selectedUser; @In private Identity identity; private String backupEmail; @Begin(flushMode=FlushModeType.MANUAL, join=true) public String selectLoggedInUser() { System.out.println( "Selected logged in User: " + identity.getUsername() ); List<MyUser> existing = (List<MyUser>) entityManager .createQuery("from MyUser u where u.username = :uname)") .setParameter("uname", identity.getUsername()) .getResultList(); if (existing.size() == 1) { selectedUser = (MyUser) existing.get(0); backupEmail = selectedUser.getEmail(); System.out.println( "Redirecting to /editLoggedInUser.xhtml" ); return "/editLoggedInUser.xhtml"; } else { return "/home.xhtml"; } } @End public String saveLoggedInUser() { // Try if email exists // There should NOT be a flush before the query is effected // But there IS a flush before the query is effected if (emailExists()) { selectedUser.setEmail(backupEmail); return null; } // Normal flush (success case) entityManager.flush(); FacesMessages.instance().add( "#{user.username} has been saved." ); return "/home.xhtml"; } private boolean emailExists() { List<String> existing = (List<String>) entityManager.createQuery("from MyUser u where u.email = :email)") .setParameter("email", selectedUser.getEmail()) .getResultList(); if(existing.size() != 0) { facesMessages.add("#{messages['NewEmailExists']}" + selectedUser.getEmail()); return true; } return false; } @Remove @Destroy public void destroy() {} }
Extension of "menu.xhtml" to start the conversation:
... <rich:toolBarGroup rendered="#{identity.loggedIn}"> <s:link action="#{userAction.selectLoggedInUser}" value="#{messages['MyUser']}"/> </rich:toolBarGroup> ...
The form "editLoggedInUser.xhtml" is:
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:s="http://jboss.com/products/seam/taglib" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:j4j="http://javascript4jsf.dev.java.net/" xmlns:rich="http://richfaces.ajax4jsf.org/rich" template="layout/template.xhtml"> <ui:define name="body"> <h:messages styleClass="message" globalOnly="true"/> <h:form id="editLoggedInUser"> <rich:panel> <f:facet name="header">#{messages['MyUser']}</f:facet> <div class="dialog"> <s:validateAll> <h:panelGrid columns="2" rowClasses="prop" columnClasses="name,value"> <h:outputLabel>#{messages['Username']}</h:outputLabel> <h:outputText value="#{user.username}"/> <h:outputLabel for="firstname">#{messages['FirstName']}</h:outputLabel> <s:decorate> <h:inputText id="firstname" required="true" value="#{user.firstname}"/> </s:decorate> <h:outputLabel for="lastname">#{messages['LastName']}</h:outputLabel> <s:decorate> <h:inputText id="lastname" required="true" value="#{user.lastname}"/> </s:decorate> <h:outputLabel for="email">#{messages['Email']}</h:outputLabel> <s:decorate> <h:inputText id="email" required="true" value="#{user.email}"/> </s:decorate> </h:panelGrid> </s:validateAll> </div> </rich:panel> <div class="actionButtons"> <h:commandButton id="saveLoggedInUser" value="#{messages['Save']}" action="#{userAction.saveLoggedInUser}"/> </div> </h:form> </ui:define> </ui:composition>
The persistence.xml shows that Hibernate is the persistence provider, so MANUAL FlushMode should work:
... <persistence-unit name="aValidationTest"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>java:/aValidationTestDatasource</jta-data-source> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/> <property name="hibernate.hbm2ddl.auto" value="create-drop"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> <property name="jboss.entity.manager.factory.jndi.name" value="java:/aValidationTestEntityManagerFactory"/> </properties> </persistence-unit> ...
components.xml:
... <persistence:managed-persistence-context name="entityManager" auto-create="true" persistence-unit-jndi-name="java:/aValidationTestEntityManagerFactory"/> ...
import-dev.sql:
insert into MyUser (ID, USERNAME, FIRSTNAME, LASTNAME, EMAIL) values (1, 'admin', 'Frederic', 'Chopin', 'one@email.com'); insert into MyUser (ID, USERNAME, FIRSTNAME, LASTNAME, EMAIL) values (2, 'user', 'Baby', 'Love', 'two@email.com');
What's wrong in my thinking?
Thank you very much for all answers.