由于在系统中使用了关联表,逻辑上是关联的,实际数据库并没有做外键。就需要写大量的数据库事务操作。之前一直没有比较好的方式,直接在dao层侵入的方式进行,虽然该方式可以实现事务的管理,但是总觉得有些问题。实现方式如下:
public int insert(ChuxinRecord t) {
SqlMapClient sqlMapClient = getWriteTemplate().getSqlMapClient();
try {
sqlMapClient.startTransaction();
sqlMapClient.getCurrentConnection().setAutoCommit(false);
Object obj = sqlMapClient.insert("chuxin_record.insertEntry",t);
//更新频道信息
ChuxinChannel channel = new ChuxinChannel();
channel.setId(t.getChannelId());
// channel.setRecordNum(channel.getRecordNum()==null?1:(channel.getRecordNum()+1));
sqlMapClient.update("chuxin_channel.updateEntryByKey", channel);
sqlMapClient.update("chuxin_channel.addOneRecord", t.getChannelId());
//更新未读消息数量
sqlMapClient.update("chuxin_user_channel.addOneRecordNum", t.getChannelId());
sqlMapClient.commitTransaction();
sqlMapClient.getCurrentConnection().commit();
if (null!=obj) {
return (Integer)obj;
}
return 0;
} catch (Exception e) {
logger.error("执行错误", e);
try {
sqlMapClient.getCurrentConnection().rollback();
} catch (SQLException e1) {
logger.error("事物回滚错误", e);
}
} finally {
try {
sqlMapClient.endTransaction();
if (null != sqlMapClient.getCurrentConnection()) {
sqlMapClient.getCurrentConnection().close();
}
} catch (SQLException e) {
logger.error("关闭事物错误", e);
}
}
return 0;
}
从以上代码可以看出我们通过显示的方式进行控制,这样很多逻辑就会写在dao层,逻辑复杂,实用性不强。
那么什么是更好的方式那?利用切面的方式控制。
1. 在ctx-dao.xml 文件中配置切面
<!--配置哪些方法,什么情况下需要回滚-->
<tx:advice id="serviceAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--当代理的service层中的方法抛出异常的时候才回滚,必须加rollback-for参数-->
<tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.RuntionException"/>
<tx:method name="del*" propagation="REQUIRED" rollback-for="java.lang.RuntionException"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="Throwable"/>
<tx:method name="tran*" propagation="REQUIRED" rollback-for="java.lang.RuntionException"/>
<!--除了上面标识的方法,其他方法全是只读方法-->
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 配置哪些类的方法需要进行事务管理 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="servicePointcut" expression="execution(* com.hdd.service.impl.*.*(..))"/>
<aop:advisor pointcut-ref="servicePointcut" advice-ref="serviceAdvice"/>
</aop:config>
我们定义了需要测试的方式为tran*开头的方法
<tx:method name="tran*" propagation="REQUIRED" rollback-for="java.lang.RuntionException"/>
观察在定义中有句:
rollback-for="java.lang.RuntionException"
该句话非常关键,会涉及到是否会回滚事务。之前配置很多一直没有注意这点,所以每次测试的时候事务总错误,并没有回滚。
在接口中定义方法:
void tranTest();
方法实现:
@Override
public void tranTest() {
try {
HddAd hddAd = new HddAd();
hddAd.setAbContent("123456");
hddAdDao.insert(hddAd);
Feedback feedback = new Feedback();
feedback.setContactInfo("wwwwwwwwwwwww");
feedbackDao.insert(feedback);
} catch (Exception e) {
logger.error("会回滚吗",e);
throw new RuntimeException(e);
}
}
feedback类的contactInfo属性为8位,在插入过程中肯定会报错。而HddAd类是可以插入成功的。观察,当出现异常的时候,我们会抛出异常RuntimeException
throw new RuntimeException(e);
这行代码非常关键,刚好为我们之前在tx定义的rollback-for,这也是触发事务回滚的关键点。
编写测试类,测试错误时候是否回滚:
public class Insert {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("ctx-web.xml");
HddAdService hddAdService = (HddAdService)ctx.getBean("hddAdService");
hddAdService.tranTest();
}
}
好了,运行观察事务是否回滚了。
关于tx相关配置,网上有太多说明,我就不逐个解释了,重点是让他可以顺利的实现预期功能,这是实用主义者的信条。
参考文档: