2013-02-05 82 views
1

我試圖在我的服務層中捕獲一個ConstraintViolationException異常,並重新拋出一個用戶定義的檢查異常。我正在捕獲控制器中的異常,並向我的BindingResult添加一個錯誤對象。我正在使用聲明式事務管理我試圖讓我的DAO成爲一個Repository並添加了一個PersistenceExceptionTranslationPostProcessor來捕獲一個Spring翻譯的異常。我還添加了一個txAdvice來回滾所有的throwables。我的例外情況被逮住,但我得到一個錯誤500:事務回滾後,Spring不關閉休眠會話

Hibernate: insert into user (email, password, first_name, last_name, userType) values (?, ?, ?, ?, 1) 
[acme]: [WARN ] - 2013-Feb-05 11:12:43 - SqlExceptionHelper:logExceptions(): SQL Error: 1062, SQLState: 23000 
[acme]: [ERROR] - 2013-Feb-05 11:12:43 - SqlExceptionHelper:logExceptions(): Duplicate entry 'admin' for key 'email_unique' 
[acme]: [DEBUG] - 2013-Feb-05 11:12:43 - HibernateTransactionManager:processCommit(): Initiating transaction commit 
[acme]: [DEBUG] - 2013-Feb-05 11:12:43 - HibernateTransactionManager:doCommit(): Committing Hibernate transaction on Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])] 
[acme]: [ERROR] - 2013-Feb-05 11:12:43 - AssertionFailure:<init>(): HHH000099: an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: null id in com.test.model.AdminUser entry (don't flush the Session after an exception occurs) 
[acme]: [DEBUG] - 2013-Feb-05 11:12:43 - HibernateTransactionManager:doRollbackOnCommitException(): Initiating transaction rollback after commit exception 
org.hibernate.AssertionFailure: null id in com.test.model.AdminUser entry (don't flush the Session after an exception occurs) 
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:79) 
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:194) 
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:156) 
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:225) 
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99) 
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51) 
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1213) 
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:402) 
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101) 
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:175) 
    at org.springframework.orm.hibernate4.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:468) 
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754) 

我的控制器:

@RequestMapping (method = RequestMethod.POST) 
    @Transactional 
    public String registerAdmin(@Valid @ModelAttribute("user") AdminUser user, BindingResult bindingResult, ModelMap model) { 
     if (bindingResult.hasErrors()) { 
      return "admin/admins/form"; 
     } 
     else if (!user.getPassword().equals(user.getConfirmPassword())) { 
      bindingResult.addError(new ObjectError("user.confirmPassword", "Passwords don't match")); 
      return "admin/admins/form"; 
     } 
     else { 
      user.setPassword(passwordEncoder.encodePassword(user.getPassword(), null)); 
      try { 
       userService.save(user); 
       return "redirect:/admin/admins"; 
      } catch(ApplicationException ce) { 
       bindingResult.addError(new ObjectError("user.email", "Email already registered")); 
       return "admin/admins/form"; 
      } 

     } 

    } 

我的Spring配置的部分:

<context:component-scan base-package="com.test.dao, com.test.service" /> 
    <context:property-placeholder location="/WEB-INF/spring.properties"/> 

    <import resource="springapp-security.xml"/> 

    <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/> 

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/> 
    <property name="url" value="jdbc:mysql://localhost:3306/testdb?zeroDateTimeBehavior=convertToNull"/> 
    <property name="username" value="test"/> 
    <property name="password" value="test"/> 
    </bean> 

    <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> 
    <property name="dataSource" ref="myDataSource"/> 
    <property name="mappingLocations" value="classpath*:com/test/model/hbm/**/*.hbm.xml" /> 

    <property name="hibernateProperties"> 
     <value> 
     hibernate.dialect=org.hibernate.dialect.MySQL5Dialect 
     hibernate.show_sql=true 
     </value> 
    </property> 
    </bean> 

<tx:annotation-driven /> 
    <bean id="transactionManager" 
      class="org.springframework.orm.hibernate4.HibernateTransactionManager"> 
    <property name="sessionFactory" ref="sessionFactory"/> 
    </bean> 
    <tx:advice id="txAdvice"> 
    <tx:attributes> 
    <tx:method name="*" rollback-for="Throwable" /> 
    </tx:attributes> 
</tx:advice> 

服務層:

public class UserServiceImpl implements UserDetailsService, UserService { 

    private UserDAO dao; 


    @Override 
    public void save(User c) throws ApplicationException { 
     try { 
      dao.save(c); 
     } catch(DataIntegrityViolationException cve) { 
      throw new ApplicationException("email already registered"); 
     } 
    } 

如果我不趕上運行時異常我沒有得到休眠異常(不刷新會話..)

+0

從你的代碼看來,事務是從控制器開始並捕獲一個異常。在這種情況下,不會發生回滾(不確定這是否是您想要執行的操作)。從你的帖子你說服務層正在啓動一個事務並捕獲一個異常。你可以發佈該代碼嗎? –

+0

我的服務層沒有啓動交易,它在控制器中開始抱歉如果我不清楚。我已經發布了我的服務代碼。同樣從我的stacktrace,我看到它正在回滾..HibernateTransactionManager:doRollbackOnCommitException():提交異常後啓動事務回滾 – user979051

回答

4

您可能想要從控制器中刪除事務註釋並將其添加到服務層。

服務層如下所示。如果你的服務層拋出一個檢查過的異常,你可以將它添加到你的註解中,這樣插入甚至不會被提交。

public class UserServiceImpl implements UserDetailsService, UserService { 

private UserDAO dao; 


@Override 
@Transactional(rollbackFor=ApplicationException.class) 
public void save(User c) throws ApplicationException { 
    try { 
     dao.save(c); 
    } catch(DataIntegrityViolationException cve) { 
     throw new ApplicationException("email already registered"); 
    } 
} 

目前什麼在你的代碼的情況是,該交易沒有被回滾,但有回退,因爲它實際上試圖提交的數據,但由於數據庫約束的交易必須回滾。通過強制使用@Transactional(rollbackFor = ApplicationException.class)進行回滾,它不會允許事務執行提交,但它將回滾,並且您的應用程序仍會將錯誤添加到BindingResult。

+0

非常感謝!你能否告訴我爲什麼我的交易不能在控制器方法開始和結束?此外,我已經嘗試過您的解決方案,但沒有在註釋中指定異常,因爲我的配置中有txAdvice在所有throwables上回滾並且不起作用 – user979051

+1

在您的控制器方法中沒有拋出異常。您將無法通知事務管理器回滾異常。唯一可行的方法是在控制器中以編程方式啓動事務,或從控制器拋出異常。這些是你不希望在你的控制器中進行交易的一些原因。您很可能不希望被迫從控制器拋出異常或處理控制器代碼中的事務管理。 –

+0

謝謝,我開始理解Spring如何在異常發生時使用AOP來回滾事務。謝謝! – user979051