Announcement Announcement Module
Collapse
No announcement yet.
Changes on domain obj are committed without calling update Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Changes on domain obj are committed without calling update

    Hi All,

    i'm new to Spring but I like it very much. I started to use the DAO Support with Hibernate a while back and now converting from WebWork to Spring MVC since the Spring MVC offers a bit more flexibility - especially the FormWizard stuff. Now a very strange problems occurs while using OpenSessionInView Filter.
    If an error occurs in my form (which uses a FormCommand and a nestest domain object), the changes are commited to the database when errors occur (which leads to re-display the form with error messages)!!! But an Hibernate update query is fired and it updates the database with the (in this case) empty form field value.
    But in the error/redisplay case i do not call a save or update method of my service object (or underlying dao).

    If i deactivate OpenSessionInView filter the error case behaviour is fine, but if i call the save-method of my DAO i get LazyInitException (no open session). Which is a very strange error, i can't find the cause. When i'm calling the save method of the dao the session is still open.
    I'd prefer not having an open session in my view, but i don't know hot to do it properly. Init all those relations by manually calling getters seems to be kludgy to me.
    Anyway, I can't get it work properly without OpenSessInViewFilter. I guess it would work if i would use the FormCommand object to update the domain object. but that's double work.

    It would be very cool if you guys could give me a hint what's going wrong (why are updates to the database made when dao.update(obj) is NOT called. The DAOs/Service beans worked perfectly with WebWork btw!

    Is there a "better" documentaion of the MVC implementation? I found it very hard to learn by the examples. I could use WebWork efficiently after 1 day - and after 3 days still trying to find out how the Spring MVC works.

    Here's the code of the FormController:
    Code:
    public class UserFormController extends SimpleFormController {
    
        private static final Log log = LogFactory.getLog(UserFormController.class);
        
        private UserService userService = null;
        
        protected Object formBackingObject(HttpServletRequest request) throws ServletException {
            log.error("formBackingObject called");
            User user = userService.getUser(RequestUtils.getRequiredStringParameter(request, "username"));
            return new UserForm(user);
        }
    
        public ModelAndView onSubmit(Object command) throws ServletException {
            log.error("onSubmit called");
            User user = ((UserForm) command).getUser();
    
            userService.saveUser(user);
            return new ModelAndView(new RedirectView("/admin/user/view"), "username", user.getHandle());
        }
    
        public void setUserService(UserService userService) {
            log.error("setUserService called called");
            this.userService = userService;
        }
    }

    FormBackingObject:
    Code:
    public class UserForm {
        
        private User user;
        private String repeatedPassword;
        private boolean newUser;
        
    	public UserForm(User user) {
    		this.user = user;
    		this.newUser = false;
    	}
    
    	public UserForm() {
    		this.user = new User();
    		this.newUser = true;
    	}
    
        public User getUser() {
            return this.user;
        }
    
        public boolean isNewUser() {
    		return newUser;
    	}
    
        
        public void setRepeatedPassword(String repeatedPassword) {
    		this.repeatedPassword = repeatedPassword;
    	}
    
    	public String getRepeatedPassword() {
    		return repeatedPassword;
    	}    
        
    }
    UserFormValidator:
    Code:
    public class UserFormValidator implements Validator {
    
        public boolean supports(Class clazz) {
    		return UserForm.class.isAssignableFrom(clazz);
    	}
    
        public void validate(Object obj, Errors errors) {
    	    ValidationUtils.rejectIfEmpty(errors, "user.fullname", "FIRST_NAME_REQUIRED", "First name is required.");
    	    ValidationUtils.rejectIfEmpty(errors, "user.email", "FIRST_NAME_REQUIRED", "Email is required.");
    	}
    }
    UserServiceImpl:
    Code:
    public class UserServiceImpl implements UserService {
    
            private UserDao userDao;
            private RoleDao roleDao;
    
            public void setUserDao(UserDao userDao) {
                this.userDao = userDao;
            }
    
            public void setRoleDao(RoleDao roleDao) {
                this.roleDao = roleDao;
            }
    
    
            
            // TODO: throw exception
            public boolean authenticate(String username, String password) {
                User user = getUser(username);
                if (user == null || !user.getPassword().equals(password))
                    return false;
                return true;
            }
    
            
            public User getUser(Long id) {
                return userDao.loadUserById(id);
            }
    
            public List getUsers() {
                return userDao.loadAllUsers();
            }
            
            public User getUser(String username) {
                return userDao.loadUserByName(username);
            }
            
            public void saveUser(User user) {
                userDao.saveOrUpdateUser(user);
            }
    
            public void deactivateUser(User user) {
                //TODO: implement deactivate
            }
            public void reactivateUser(User user) {
                //TODO: implement reactivate
            }
            
            public List getRoles() {
                return roleDao.loadAllRoles();
            }
            
            public Role getRole(String name) {
                return roleDao.loadRoleByName(name);
            }
            
            public Role getRole(Long id) {
                return roleDao.loadRoleById(id);
            }
            
            public List getUnassignedRoles(User user) {
                return userDao.loadUnassignedRolesForUser(user);
            }
      
    }
    UserDaoImpl:
    Code:
     public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
    
         
         public List loadUnassignedRolesForUser(User user) throws DataAccessException {
             List uaRoles = new ArrayList();
             List roles = getHibernateTemplate().loadAll(Role.class);
             for (Iterator i = roles.iterator(); i.hasNext();) {
                 Role current = (Role) i.next();
                 // TODO: using contains could be dangerous, try to use named query
                 // but don't know how to obtain the hibernate dialect!?
                 if (!user.getRoles().contains(current)) {
                     uaRoles.add(current);
                 }
             }
             return uaRoles;
        }
     
             
         public User loadUserById(Long id) throws DataAccessException {
            return (User) getHibernateTemplate().load(User.class, id);
        }
    
        public User loadUserByName(String username) throws DataAccessException {
            return (User) getHibernateTemplate().find("select u from User u where u.handle = ?", username).get(0);
        }
    
        public List loadAllUsers() throws DataAccessException {
            return (List) getHibernateTemplate().loadAll(User.class);
        }
        
        public void saveOrUpdateUser(User user) throws DataAccessException {
            getHibernateTemplate().saveOrUpdate(user);
        }
        
    }
    Formbean defininition:
    Code:
    	<bean id="userValidator" class="com.thyrell.pm.user.web.UserFormValidator"/>
    	<bean id="editUserForm"  class="com.thyrell.pm.user.web.UserFormController">
    		<property name="userService"><ref bean="userService"/></property>
    		<property name="validator"><ref local="userValidator"/></property>
    		<property name="commandClass"><value>com.thyrell.pm.user.web.UserForm</value></property>
    		<property name="commandName"><value>userForm</value></property>
    		<property name="formView"><value>EditUserForm</value></property>
    		<property name="successView"><value>EditUserFormRedirect</value></property>
    	</bean>
    Dao/Service Definitions:
    Code:
    	<bean id="pmHibernateInterceptor" class="org.springframework.orm.hibernate.HibernateInterceptor">
    		<property name="sessionFactory"><ref local="sessionFactory"/></property>
    	</bean> 
    
    	<bean id="pmTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
    
    <!--	<bean id="pmTransactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">-->
    <!--		<property name="sessionFactory"><ref local="sessionFactory"/></property>-->
    <!--	</bean>-->
    
    	<bean id="pmTransactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
    		<property name="transactionManager"><ref bean="pmTransactionManager" /></property>
    		<property name="transactionAttributeSource">
    			<value>
    				com.thyrell.pm.user.UserServiceImpl.*=PROPAGATION_REQUIRED,-DataAccessException
    				com.thyrell.pm.user.UserServiceImpl.get*=PROPAGATION_REQUIRED,readOnly
    			</value>
    		</property>
    	</bean>
    
    
    	<bean id="userDaoTarget" class="com.thyrell.pm.user.dao.UserDaoImpl">
    		<property name="sessionFactory"><ref local="sessionFactory" /></property>
    	</bean>
    	<bean id="userDao" class="org.springframework.aop.framework.ProxyFactoryBean">
    		<property name="proxyInterfaces"><value>com.thyrell.pm.user.dao.UserDao</value></property>
    		<property name="interceptorNames">
    		    <list>
        		  <value>pmHibernateInterceptor</value>
        		  <value>userDaoTarget</value>
        		</list>
    		</property>
    	</bean>
    
    	<bean id="roleDaoTarget" class="com.thyrell.pm.user.dao.RoleDaoImpl">
    		<property name="sessionFactory"><ref local="sessionFactory" /></property>
    	</bean>
    	<bean id="roleDao" class="org.springframework.aop.framework.ProxyFactoryBean">
    		<property name="proxyInterfaces"><value>com.thyrell.pm.user.dao.RoleDao</value></property>
    		<property name="interceptorNames">
    		    <list>
        		  <value>pmHibernateInterceptor</value>
        		  <value>roleDaoTarget</value>
        		</list>
    		</property>
    	</bean>
    
    
    
    	<bean id="userServiceTarget" class="com.thyrell.pm.user.UserServiceImpl">
    		<property name="userDao"><ref bean="userDao" /></property>
    		<property name="roleDao"><ref bean="roleDao" /></property>
    	</bean>
    	<bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean">
    		<property name="proxyInterfaces"><value>com.thyrell.pm.user.UserService</value></property>
    		<property name="interceptorNames">
    			<value>pmTransactionInterceptor,userServiceTarget</value>
    		</property>
    	</bean>
    Ah, yeah, the OpenSessionInViewFilter overridden (caus of the auto-flush thing):

    Code:
    public class FlushingSpringSessionInViewFilter extends OpenSessionInViewFilter &#123;
    
        public FlushingSpringSessionInViewFilter&#40;&#41; &#123;
        &#125;
    
        protected Session getSession&#40;SessionFactory sessionFactory&#41; throws DataAccessResourceFailureException &#123;
    	    Session session = SessionFactoryUtils.getSession&#40;sessionFactory, true&#41;;
    	    session.setFlushMode&#40;FlushMode.AUTO&#41;;
    	    return session;
    	&#125;
        
        protected void closeSession&#40;Session session, SessionFactory sessionFactory&#41; throws CleanupFailureDataAccessException &#123;
            if &#40;session != null && session.isOpen&#40;&#41; && session.isConnected&#40;&#41;&#41; &#123;
                try &#123;
                    session.flush&#40;&#41;;
                &#125; catch &#40;HibernateException e&#41; &#123;
                    throw new CleanupFailureDataAccessException&#40;"Failed to flush session before close&#58; " + e.getMessage&#40;&#41;, e&#41;;
                &#125;
            &#125;
            super.closeSession&#40;session, sessionFactory&#41;;
        &#125;
    &#125;


    Hope the long code listings help you nailing down the problem.

    Thanks a lot,
    Andi

  • #2
    Andi,
    The long listing is very helpfull

    You are applying HibernateInterceptor (which is responsable for handling Hibernate Sessions) to DAOs but the Transactions are applied to the Service Methods. That means:

    a. when OpenSessionInView is not configured:
    When a service method S1 is called, a new transaction will be started, if S1 calls userDAO method D1 to retrieve a user, a new hibernate Session will be opened before the call is done and closed after the call. Now if S1 tries to touch some user's data that are lazily loaded, an Exception will be thrown since there is no session opened!!!

    To solve this problem, you should apply the HibernateInterceptor to Service methods.

    Comment


    • #3
      Hey Omar,

      thanks for the fast and helpful reply. This nailed it. I thought that the HibernateInterceptor is not closing the session (but OpenSessionInViewFilter is) and just opening one session per request. I'd looked at the config in the last instance before madness, caus it worked with WebWork Anyway it works fine now. I also switchted to OpenSessionInViewInterceptor which seems to be a more natural choice with Spring MVC.

      Btw: What would you use in case of the OpenSessionInViewInterceptor: single session mode or deferred close mode?
      http://www.springframework.org/docs/...terceptor.html

      I tend to singlesession=false but it's slower

      Thanks for your help. It works )
      -andi

      Comment


      • #4
        Damn, premature happiness ...

        it does not work properly. when i use singel session in the viewinterceptor, and transactioninterceptor and hibernate interceptor wired up as proposed. i get a read only exception when i try to persist the changes.

        if i set AUTO_FLUSH and singleSession=true the changes get persisted even if i don't call a service method (in case of form errors). that's very odd.

        if i use singelsession = false i get: can't associate collection with two sessions exception.

        argh, i'm really getting lost now. tried all combinations of the openSessioninviewInterceptor settings and with and w/o hibernate interceptor, with hibernate transactionmanager with JTA.... no cure.

        -andi

        Comment


        • #5
          OK, first I thought it could be a dirty hibernate session, but it was not (since update-dynamic/insert-dynamic = false).

          It seems that I found the problem, but i don't understand it. I changed the BO definitions to use the TransactionFactoryBean instead of ProxyFactoryBean with a transaction interceptor:

          Code:
          	<bean id="userServiceTarget" class="com.thyrell.pm.user.UserServiceImpl">
          		<property name="userDao"><ref bean="userDao" /></property>
          		<property name="roleDao"><ref bean="roleDao" /></property>
          	</bean>
          
          	<bean id="userService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
          		<property name="target"><ref bean="userServiceTarget"/></property>
          		<property name="transactionManager"><ref bean="pmTransactionManager"/></property>
          		<property name="transactionAttributes">
          		    <props>
          		      <prop key="*">PROPAGATION_REQUIRED</prop>
          		    </props>
          		</property>
          	</bean>
          Open SessionInViewInterceptor ist now standard:
          Code:
          	<bean name="openSessionInViewInterceptor"  class="org.springframework.orm.hibernate.support.OpenSessionInViewInterceptor">
                 <property name="sessionFactory"><ref bean="sessionFactory"/></property>
          	</bean>
          This works as expected... so far. At least for the "change-user-details" usecase of my app.

          But i do not understand why using the transactionInterceptor (like in the very first post) does not work properly It would rock if you could explain this to me, i really don't get it....

          Thanks,
          Andi

          Comment


          • #6
            Andi,
            Could you please try the following configuration:
            Code:
               <bean id="pmHibernateInterceptor" class="org.springframework.orm.hibernate.HibernateInterceptor">
                 <property name="sessionFactory"><ref bean="sessionFactory"/></property>
               </bean>
            
               <bean id="pmTransactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
                 <property name="sessionFactory"><ref bean="sessionFactory"/></property>
               </bean>
            
               <bean id="pmTransactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
                 <property name="transactionManager"><ref bean="pmTransactionManager"/></property>
                 <property name="transactionAttributeSource">
                   <value>
                     com.thyrell.pm.user.UserServiceImpl.*=PROPAGATION_REQUIRED
                   </value>
                 </property>
               </bean>
            
               <bean id="userDao" class="com.thyrell.pm.user.dao.UserDaoImpl"> 
                  <property name="sessionFactory"><ref local="sessionFactory" /></property> 
               </bean>
            
               <bean id="roleDaoTarget" class="com.thyrell.pm.user.dao.RoleDaoImpl"> 
                  <property name="sessionFactory"><ref local="sessionFactory" /></property> 
               </bean> 
            
               <bean id="userServiceTarget" class="com.thyrell.pm.user.UserServiceImpl"> 
                  <property name="userDao"><ref bean="userDao" /></property> 
                  <property name="roleDao"><ref bean="roleDao" /></property> 
               </bean> 
            
              <bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean">
                <property name="proxyInterfaces">
                 <value>com.thyrell.pm.user.UserService</value>
                </property>
                <property name="interceptorNames">
                 <list>
                  <value>pmTransactionInterceptor</value>
                  <value>pmHibernateInterceptor</value>
                  <value>userServiceTarget</value>
                 </list>
                </property>
              </bean>
            I am too, very interested to know why using configuration TransactionInterceptor did not work.

            Comment


            • #7
              Hi Omar,

              thanks for your patience and help. i tried your changes. I get correct behaviour in case of an input error (i.e. empty field). Form is displayed with error messages and there is no UPDATE query sent to the database. Eveything fine. But then if it comes to persist the changes (onSubmit), i get the unloved "Write Operations not allowed in read only-mode exception" (see log1). Strange caus i did not set a readonly flag in the transaction interceptor.

              According to the API docs of OSIVInterceptor i should set FLUSH_AUTO in the OSIVInterceptor, which is, concerning the API docs, is not a good practice. Anyway... but now i'm back at the beginning. In case of a validation error (in the form) hibernate persists changes without calling a save/saveOrUpdate method (second log). That's really wired.

              Either I'm blind and i misunderstand something essential, or there is a bug somewhere.
              Last edited by bullgod; May 22nd, 2013, 06:27 PM. Reason: remove sensitive data

              Comment


              • #8
                continued....

                FLUSH_AUTO:
                Last edited by bullgod; May 22nd, 2013, 06:27 PM. Reason: remove sensitve data

                Comment


                • #9
                  OK, coming closer now... i guess. the errors seem to occur in ALL cases when i use transaction interceptor with this config:

                  Code:
                  	<bean id="pmTransactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
                      	<property name="transactionManager"><ref bean="pmTransactionManager"/></property>
                  		<property name="transactionAttributeSource">
                  			<value>
                  com.thyrell.pm.user.UserServiceImpl.*=PROPAGATION_REQUIRED
                  			</value>
                  		</property>
                  	</bean>
                  Whereas it works with those:

                  Code:
                  	<bean id="pmTransactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
                      	<property name="transactionManager"><ref bean="pmTransactionManager"/></property>
                  		<property name="transactionAttributes">
                  		    <props>
                  		      <prop key="*">PROPAGATION_REQUIRED</prop>
                  		    </props>
                  		</property>
                  	</bean>
                  BUT only if I use HibernateTransactionManager. With JTA wether this nor the above config work. The only case i get it working with JTA is using TransactionProxyFactoryBean with no interceptors at all.

                  Maybe it's a bug in TransactionInterceptor and the JtaTransactionManager or somewhere in between!?

                  -andi

                  Comment

                  Working...
                  X