2013-02-05 83 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