我在给WEB App业务处理模块定义在com.xxx.appname.modules包里面, 每一个模块的具体处理操作都由一个独立的XXXModule.java完成, 而XXXModule是基于BaseModule的, 而我的事务控制通过注解BaseModule父类来完成:
@Transactional
public class BaseModule {
...
}
那么如果需要回滚如何处理?首先我们将需要回滚的情况分为两类, 第一类是做更新/删除操作前的回滚, 比如外键检查,基本字段合法性检查等;第二类是做了更新/删除后的回滚,比如累计数量是否合法。。
第一种,我放在onSaveCheck和onDeleteCheck事件中检查,在这两个事件中,一言不合就抛异常就得了,HibernateEntityDao的save/remove这样处理的:
@Overridepublic void save(Object o) throws ModifyDataException {try{onSaveCheck(o,Locale.CHINA,!getSession().contains(o));}catch (Exception e){clear();String[] lvFields=null;if (e instanceof ModifyDataException){lvFields=((ModifyDataException)e).getRefDataFields().toArray(new String[((ModifyDataException)e).getRefDataFields().size()]);}String lvMsg="";try{lvMsg=ContextHolder.getApplicationContext().getMessage("common.check_failed",new Object[]{e.getMessage()}, null);}catch (Exception e1){}throw new ModifyDataException(lvMsg,lvFields);}getSession().saveOrUpdate(o);// getHibernateTemplate().saveOrUpdate(o);}@Overridepublic void remove(Object o) throws ModifyDataException {try{onDeleteCheck(o,Locale.CHINA);}catch (Exception e){clear();String lvMsg="";try{lvMsg=ContextHolder.getApplicationContext().getMessage("common.delete_failed",new Object[]{e.getMessage()}, null);}catch (Exception e1){}throw new ModifyDataException(lvMsg);}getSession().delete(o);}
看到没,onSaveCheck/onDeleteCheck是事前的检查,save/remove里头分别捕获前者抛出的exception,catch的第一句就是clear(),为什么呢? 因为hibernate异常傻瓜,对于entity实体, 只要属性有所改变即使你不用save,在事务结束前亦会将此entity持久化,clear()的作用是解除此entity与数据库的关系使hibernate自动保存失效。
第二种是事后回滚,比如有些计算方面的检查需要更新了数据库,然后在同一个事务里面再累计出一个数并对其进行验证是否合法,这时候的回滚,就需要显式调用transaction的rollback(),因为basemodule在注解下已经处理事务当中,所以可以使用getsession/gettransaction,下面尝试这种方式:
mTpSendNoteDetDao.save(pvData); //将将数据保存至数据库mTpSendNoteDetDao.flush();try{checkQty(mTpSendNoteDetDao.getSession(),pvData.getId(),pvLocale); //合法性检查}catch (ModifyDataException e){ //捕获检查不通过抛出的异常mTpSendNoteDetDao.getSession().getTransaction().rollback(); //调用当前session的当前事务回滚mTpSendNoteDetDao.clear(); //清除缓冲,避免hibernate自动保存//mTpSendNoteDetDao.getSession().getTransaction().begin(); //非常奇怪这句基本没有用throw e;}
尽管这样能成功回滚,但checkQty抛出的异常并没有反映到前端,而前端收到的Exception则是hibernate内部的exception:org.springframework.transaction.TransactionSystemException: Could not commit Hibernate transaction;
nested exception is org.hibernate.TransactionException: Transaction not successfully started, 后来我觉得Catch代码里面回滚了事务,那么事务处理没工作状态,所以加上一句getTransaction().begin(),想让其继续处理事务中,但结果还是一样。 显然这么长的错误信息不是我们想要的。
所以还有第二种处理方法,那就是自己开一个新的session来处理:
Session lvSess=mTpSendNoteDetDao.newSession();Transaction lvTx= lvSess.getTransaction();try{//mTpSendNoteDetDao.save(pvData);lvTx.begin();lvSess.saveOrUpdate(pvData);lvSess.flush();mTpSendNoteDetDao.clear();checkQty(lvSess,pvData.getId(),pvLocale);lvTx.commit();} catch (ModifyDataException e){ lvTx.rollback();throw e;}finally{lvSess.close();}
这种处理方法完美解决事后回滚的问题 。回想第一种处理方法,因为事务的处理是交给spring处理,getTransaction().begin()可能没有用(事务只能用一次?),而且在在方法结束时,它没有判断当前是否处理事务中就来个commit()。