Announcement Announcement Module
Collapse
No announcement yet.
Hibernate Transaction question Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Hibernate Transaction question

    hello to all.
    i'm using spring 2.0.5
    and hibernate 3.2.4sp1
    with hibernate annotatation 3.3.0
    and hibernate validator 3.0.0

    i'm using
    spring's @Transactional for my transaction needs

    on a good service.registerUser(User user)
    the service is called,
    the service calls the dao,
    the dao does the insert,
    the dao calls flush
    everything is good.

    but...
    on a bad case..
    for example
    when an operation fails validators's
    @Email
    validation in the dao code when flush is called,
    the dao generates an exception which is handled by the service.

    service call dao. dao inserts. dao flushes. this generates an exception. service handles it gracefully.

    so far so good.

    but when the aop portion kicks in,

    there is a commit operation, so i think, ok.
    the commit operations tries to insert the failed email data
    and i get an exception that was handled by the service.

    here is my test case
    Code:
    	@Test
    	public void testRegisterBadMail() {
    		final User user = new User();
    		user.setName("name");
    		user.setMail("mail");
    		assertEquals(0, userCount());
    		boolean register = userService.registerUser(user);
    		assertEquals(false, register);
    		assertEquals(0, userCount());
    	}
    this is the dao code
    Code:
    	public void insert(final User user) 
    	{
    		getHibernateTemplate().save(user);
    		getHibernateTemplate().flush();
    	}
    and this is the service code
    Code:
    	@Transactional
    	public boolean registerUser(final User user) {
    		boolean result;
    		final String rawPass = user.getPassword();
    		final String encoded = passwordEncoder.encodePassword(rawPass, null);
    		user.setPassword(encoded);
    		user.setRegistrationDate(new Date());
    		try {
    			userDAO.insert(user);
    			result = true;
    		} catch (DataIntegrityViolationException e) {
    			log.error(e);
    			result = false;
    		} catch (InvalidStateException e) {
    			log.error((new InvalidStateTranslator(e)).toString());
    			result = false;
    		}
    		return result;
    	}
    from my domain object
    Code:
    	@Column(name = "mail", unique = true, nullable = false, insertable = true, updatable = true, length = 80)
    	@Email
    	@NotEmpty
    	public String getMail() {
    		return this.mail;
    	}
    here is the stack trace

    Code:
    org.hibernate.validator.InvalidStateException: validation failed for: com.hypercart.model.entity.User
    	at org.hibernate.validator.event.ValidateEventListener.validate(ValidateEventListener.java:148)
    	at org.hibernate.validator.event.ValidateEventListener.onPreInsert(ValidateEventListener.java:172)
    	at org.hibernate.action.EntityInsertAction.preInsert(EntityInsertAction.java:156)
    	at org.hibernate.action.EntityInsertAction.execute(EntityInsertAction.java:49)
    	at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
    	at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:234)
    	at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:141)
    	at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
    	at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
    	at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
    	at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
    	at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
    	at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:575)
    	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:662)
    	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:632)
    	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:314)
    	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:117)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:161)
    	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
    	at $Proxy22.registerUser(Unknown Source)
    	at service.TestUserServiceImpl.testRegisterBadMail(TestUserServiceImpl.java:68)

    i think hibernate transaction's commit, should not re-insert the bad record

    when i do a debug, i see that the exception (InvalidStateException is properly handled by the service)

    the stack trace shows that the handled exception InvalidStateException
    is re-thrown in the doCommit function

    doCommit should not insert anything, right?
    coz insertion was done, a flush was done, and an exception was handled,
    right?

  • #2
    You aren't throwing the exception from the service, you're swallowing it. Therefore you won't be forcing the rollback. This image should explain it more, put simply you need to throw the exception through the transaction boundary or programmatically force rollback.
    http://www.springframework.org/docs/...on-declarative
    Last edited by karldmoore; Aug 30th, 2007, 05:21 AM.

    Comment


    • #3
      i see
      but the problem is not rollback/commit
      its,
      i've made an insert, and flushed, and an exception was thrown, i handled the exception.
      the flush from the hibernate should not commit the insert, it should commit everything between last flush and itself.
      i'm trying to use a non hibernate dao an see if the problem is the same...
      i'll get back to you..

      Comment


      • #4
        Ok, but after leaving the registerUser method you are going to perform the actual transaction handling e.g. rollback or commit. As registerUser didn't throw an exception nor did it tell the current transaction to rollback, it's going to try and commit the transaction.
        Code:
        	at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
        	at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:575)
        	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:662)
        	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:632)
        	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:314)
        
        Here we are, after we've exited the method successfully commit
        
        	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:117)
        	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:161)
        	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
        	at $Proxy22.registerUser(Unknown Source)
        	at service.TestUserServiceImpl.testRegisterBadMail(TestUserServiceImpl.java:68)
        Last edited by karldmoore; Aug 30th, 2007, 05:21 AM.

        Comment


        • #5
          ok
          i've made a new dao..
          jdbc based..

          here is the portion of the insert part
          everything else is exactly the same as before
          except for the user dao
          here is the code
          for the insertion
          Code:
          	public void insert(User user) throws ConstraintViolationException,
          			InvalidStateException {
          		final String sql = "insert into user (name, mail, password, registration_date) values (?, ?, ?, ?)";
          		// if its a bad mail.. then cause the insert operation to generate an
          		// exception
          		if (user.getMail().contains("@") == false) {
          			user.setMail(StringUtils.repeat("X", 500));
          		}
          		final Object[] args = { user.getName(), user.getMail(),
          				user.getPassword(), user.getRegistrationDate() };
          		getJdbcTemplate().update(sql, args);
          	}
          this is the code for the service
          Code:
          	@Transactional
          	public boolean registerUser(final User user) {
          		boolean result;
          		final String rawPass = user.getPassword();
          		final String encoded = passwordEncoder.encodePassword(rawPass, null);
          		user.setPassword(encoded);
          		user.setRegistrationDate(new Date());
          		try {
          			userDAO.insert(user);
          			result = true;
          		} catch (DataIntegrityViolationException e) {
          			log.error(e);
          			result = false;
          		} catch (InvalidStateException e) {
          			log.error((new InvalidStateTranslator(e)).toString());
          			result = false;
          		} catch (DataAccessException e) {
          			log.error(e);
          			result = false;
          		}
          		return result;
          	}
          i just added a code to handle general data access exception
          Code:
          		} catch (DataAccessException e) {
          			log.error(e);
          			result = false;
          		}
          validation is a simple, almost too simple..
          anyways its almost the same as the hibernate version in logic.

          but my test case passes on this one..

          i think same logic
          but in case of jdbc code
          call service > call dao > aop intercepts > starts transaction > insert > exception thrown in dao > exception caught in service > commit commits nothing.

          Comment


          • #6
            i think the flow is

            userservice->register()
            aop transaction started
            * dao.save()

            * exception thrown
            caught by service
            --- here there transaction contains nothing on datasource transaction but contains a pending insert on the hibernateTransaction

            aop does a commit
            --here the transaction commits nothing in jdbcTemplate, and commits does an insert in hibernateTemplate
            end userService.registerUser(user)


            apparently the dao i coded behaves differently.
            so rephrasing question.

            how can i make my hibernate based dao behave like the jdbc based dao so that it is truly pluggable to the userService?

            Comment


            • #7
              The difference is that the Hibernate Session is going to detect changes and still try and flush before the commit. Hence the problem you are seeing. You could move the transaction down to the dao which would fix this issue, but it's not the ideal fix (the transaction is currently in the right place). The other option is to actually throw the exception from the service which would rollback the transaction. Alternatively, tell the transaction to rollback progmatically (e.g. setRollbackOnly()).
              http://www.springframework.org/docs/...e-rolling-back
              Last edited by karldmoore; Aug 30th, 2007, 05:21 AM.

              Comment


              • #8
                erm..
                i just found something interesting..
                you were wrong
                and i was wrong
                its nothing to do with the sessions
                i removed the
                @Email
                from the User class
                and inserted a 1024 character long value on a 80 character column
                the operation works exactly as expected

                to recap
                if i insert a record and call flush
                and it fails on hibernate validation,
                (hibernate generated error, does not hit the db yet)
                the aop portion of the transaction will try to insert the record
                resulting in an exception again

                if i insert a record and call flush
                and it fails because, record already exists or column too long,
                (database generated error)
                the aop portion of the transaction will NOT insert the record
                resulting in a good function call

                i think this is inconsistent

                from the programmer's point of view
                call save, call flush, handle exception

                it shouldn't matter who generates the error (db or hibernate)

                alternatively
                i modified my code on the dao layer
                Code:
                		try {
                			getHibernateTemplate().save(user);
                			getHibernateTemplate().flush();
                		} catch (InvalidStateException e) {
                			getHibernateTemplate().clear();
                			throw e;
                		}

                Comment


                • #9
                  The change in behaviour is because you are calling clear. This means that when the transaction commits and flushes, there are no changes. I suggest turning up the logging and seeing what's happening in each instance. Alternatively debug it and see for yourself.
                  Last edited by karldmoore; Aug 30th, 2007, 05:21 AM.

                  Comment


                  • #10
                    ok
                    i've debuged..

                    the code is different in

                    org.hibernate.engine.ActionQueue
                    Code:
                    	private void executeActions(List list) throws HibernateException {
                    		int size = list.size();
                    		for ( int i = 0; i < size; i++ ) {
                    			execute( (Executable) list.get(i) );
                    		}
                    		list.clear();
                    		session.getBatcher().executeBatch();
                    	}
                    InvalidStateException is thrown in execute( (Executable) list.get(i) );

                    before the list.clear();

                    org.hibernate.exception.GenericJDBCException is thrown in
                    session.getBatcher().executeBatch();

                    therefore the list was cleared.

                    i'll ask the hibernate people about this..

                    but it seems that nobody else is bother by this
                    probably my coding is not correct..

                    Comment

                    Working...
                    X