我是靠谱客的博主 眯眯眼龙猫,最近开发中收集的这篇文章主要介绍Spring事务管理——回滚(rollback-for)控制代码实践,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

1.代码中事务控制的3种方式[1]

  • 编程式事务:就是直接在代码里手动开启事务,手动提交,手动回滚。优点就是可以灵活控制,缺点就是太麻烦了,太多重复的代码了。
  • 声明式事务:就是使用SpringAop配置事务,这种方式大大的简化了编码。需要注意的是切入点表达式一定要写正确。
  • 注解事务:直接在Service层的方法上面加上@Transactional注解,个人比较喜欢用这种方式。

2.事务不回滚的原因

    在工作中,看过别人写的代码出现了事务不回滚的现象。当然,事务不回滚的都是采用的声明式事务或者是注解事务;编程式事务都是自己写代码手动回滚的,因此是不会出现不回滚的现象。

    再说下 声明式 事务和注解事务回滚的原理:当被切面切中或者是加了注解的方法中抛出了RuntimeException异常时,Spring会进行事务回滚。默认 情况下是 捕获到方法的 RuntimeException 异常 ,也就是说抛出只要属于运行时的异常(即 RuntimeException及其子类 )都能回滚;但当抛出一个不属于运行时异常时,事务是不会回滚的。

    下面说说我经常见到的3种事务不回滚的产生原因:
  • (1)声明式事务配置切入点表达式写错了,没切中Service中的方法
  • (2)Service方法中,把异常给try catch了,但catch里面只是打印了异常信息,没有手动抛出RuntimeException异常
  • (3)Service方法中,抛出的异常不属于运行时异常(如IO异常),因为Spring默认情况下是捕获到运行时异常就回滚


代码实践

1.不捕获异常(一般处理方式)[2]

代码,其中contentMappger.updateWithErrTest(31L); 是SQL语句错误,用来测试回滚。


/**
* 删除多条记录
*/
@Override
public ShopResult deleteContentGroup(String[] ids) {
if (null == ids || ids.length == 0)
{
return ShopResult.error();
}
for (String idStr : ids)
{
Long id = new Long(idStr);
contentMappger.deleteByPrimaryKey(id);
}
contentMappger.updateWithErrTest(31L);
//错误代码,SQL语句错误。用来测试事务,看是否回滚
return ShopResult.ok();
}

运行结果:报错,事务发生了回滚,即由于错误代码,前面的for循环删除记录事务被回滚了。

### SQL: delete form tb_content
where kid = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'tb_content
where kid = 31' at line 1
; bad SQL grammar []; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'tb_content
where kid = 31' at line 1] with root cause
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'tb_content
where kid = 31' at line 1
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:406)
at com.mysql.jdbc.Util.getInstance(Util.java:381)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1030)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3536)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3468)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1957)
..................
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:53)
at com.sun.proxy.$Proxy35.updateWithErrTest(Unknown Source)
at com.shop.manager.service.impl.ContentServiceImpl.deleteContentGroup(ContentServiceImpl.java:94)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
.......

2. 捕获异常,但不处理,不抛出

代码


/**
* 删除多条记录
*/
@Override
public ShopResult deleteContentGroup(String[] ids) {
if (null == ids || ids.length == 0)
{
return ShopResult.error();
}
for (String idStr : ids)
{
Long id = new Long(idStr);
contentMappger.deleteByPrimaryKey(id);
}
try {
contentMappger.updateWithErrTest(31L);
//错误代码,SQL语句错误。用来测试事务,看是否回滚
} catch (Exception e) {
//捕获异常,但不处理
System.out.println("-----nothing to do-------");
}
return ShopResult.ok();
}

运行结果:事务提交,未回滚。 
事务提交

### The error occurred while setting parameters
### SQL: delete form tb_content
where kid = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'tb_content
where kid = 31' at line 1
]
-----nothing to do-------
2017-06-18 14:27:59,493 [http-bio-8080-exec-4] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization committing SqlSession //(事务提交)
[org.apache.ibatis.session.defaults.DefaultSqlSession@616a85a9]

3. 捕获异常,并抛出RuntimeException异常

Spring碰到Unchecked Exceptions都会回滚,不仅是RuntimeException,也包括Error。 
代码


/**
* 删除多条记录
*/
@Override
public ShopResult deleteContentGroup(String[] ids) {
if (null == ids || ids.length == 0)
{
return ShopResult.error();
}
for (String idStr : ids)
{
Long id = new Long(idStr);
contentMappger.deleteByPrimaryKey(id);
}
try {
contentMappger.updateWithErrTest(31L);
//错误代码,SQL语句错误。用来测试事务,看是否回滚
} catch (Exception e) {
System.out.println("----throw Exception-----");
throw new RuntimeException();
}
return ShopResult.ok();
}

运行结果:如预期的一样,抛出RuntimeException,事务发生回滚。

### SQL: delete form tb_content
where kid = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'tb_content
where kid = 31' at line 1
]
----throw Exception-----
2017-06-18 14:21:27,928 [http-bio-8080-exec-1] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3ef56e3a]
...............
2017-06-18 14:21:27,941 [http-bio-8080-exec-1] [org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver]-[DEBUG] Resolving exception from handler [public com.shop.common.pojo.ShopResult com.shop.manager.controller.ContentController.deleteContentGroup(java.lang.String)]: java.lang.RuntimeException
2017-06-18 14:21:27,941 [http-bio-8080-exec-1] [org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver]-[DEBUG] Resolving exception from handler [public com.shop.common.pojo.ShopResult com.shop.manager.controller.ContentController.deleteContentGroup(java.lang.String)]: java.lang.RuntimeException
2017-06-18 14:21:27,942 [http-bio-8080-exec-1] [org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver]-[DEBUG] Resolving exception from handler [public com.shop.common.pojo.ShopResult com.shop.manager.controller.ContentController.deleteContentGroup(java.lang.String)]: java.lang.RuntimeException
2017-06-18 14:21:27,942 [http-bio-8080-exec-1] [org.springframework.web.servlet.DispatcherServlet]-[DEBUG] Could not complete request
java.lang.RuntimeException
at com.shop.manager.service.impl.ContentServiceImpl.deleteContentGroup(ContentServiceImpl.java:98)
//异常
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

4.捕获异常,并继续抛出原捕获的异常

代码:


/**
* 删除多条记录
*/
@Override
public ShopResult deleteContentGroup(String[] ids) {
if (null == ids || ids.length == 0)
{
return ShopResult.error();
}
for (String idStr : ids)
{
Long id = new Long(idStr);
contentMappger.deleteByPrimaryKey(id);
}
try {
contentMappger.updateWithErrTest(31L);
//错误代码,SQL语句错误。用来测试事务,看是否回滚
} catch (Exception e) {
//捕获异常,继续抛出
System.out.println("-----throw Exception-------");
throw e;
}
return ShopResult.ok();
}

运行结果:抛出异常,事务发生回滚

### The error occurred while setting parameters
### SQL: delete form tb_content
where kid = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'tb_content
where kid = 31' at line 1
]
-----throw Exception-------
2017-06-18 14:36:25,308 [http-bio-8080-exec-9] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@45fe0f70]
2017-06-18 14:36:25,308 [http-bio-8080-exec-9] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization closing SqlSession //事务回滚
[org.apache.ibatis.session.defaults.DefaultSqlSession@45fe0f70]

5. 捕获异常,并抛出新new的异常(或自定义Exception异常) new Exception

代码:


/**
* 删除多条记录
* @throws Exception
*/
@Override
public ShopResult deleteContentGroup(String[] ids) throws Exception {
if (null == ids || ids.length == 0)
{
return ShopResult.error();
}
for (String idStr : ids)
{
Long id = new Long(idStr);
contentMappger.deleteByPrimaryKey(id);
}
try {
contentMappger.updateWithErrTest(31L);
//错误代码,SQL语句错误。用来测试事务,看是否回滚
} catch (Exception e) {
//捕获异常,抛出新异常
System.out.println("-----throw new Exception(e)-------");
throw new Exception(e);
}
return ShopResult.ok();
}

运行结果:事务提交,未回滚。(Spring的默认回滚异常类型不包括Exception)

### The error occurred while setting parameters
### SQL: delete form tb_content
where kid = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'tb_content
where kid = 31' at line 1
]
-----throw new Exception(e) -------
2017-06-18 14:43:16,098 [http-bio-8080-exec-10] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization committing SqlSession //事务提交
[org.apache.ibatis.session.defaults.DefaultSqlSession@32c4821]
2017-06-18 14:43:16,098 [http-bio-8080-exec-10] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@32c4821]

6. 在事务配置中没有设置rollback-for异常类型为Exception

<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 事务行为控制 -->
<tx:attributes>
<tx:method name="save" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="insert*" propagation="REQUIRED"
rollback-for="Exception"/>
<tx:method name="add*" propagation="REQUIRED"
rollback-for="Exception"/>
<tx:method name="create*" propagation="REQUIRED"
rollback-for="Exception"/>
<tx:method name="delete*" propagation="REQUIRED"
rollback-for="Exception"/>
<tx:method name="update*" propagation="REQUIRED"
rollback-for="Exception"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="select*" propagation="SUPPORTS" read-only="true" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.shop.manager.service.*.*(..))" />
</aop:config>


/**
* 删除多条记录
* @throws Exception
*/
@Override
public ShopResult deleteContentGroup(String[] ids) throws Exception {
if (null == ids || ids.length == 0)
{
return ShopResult.error();
}
for (String idStr : ids)
{
Long id = new Long(idStr);
contentMappger.deleteByPrimaryKey(id);
}
try {
contentMappger.updateWithErrTest(31L);
//错误代码,SQL语句错误。用来测试事务,看是否回滚
} catch (Exception e) {
//捕获异常,继续抛出
System.out.println("-----throw new Exception-------");
throw new Exception("---自定义Exception,事务中已配置rollback-for---");
}
return ShopResult.ok();
}

运行结果:如预期一样发生回滚

### The error may involve com.shop.manager.mapper.TbContentMapper.updateWithErrTest-Inline
### The error occurred while setting parameters
### SQL: delete form tb_content
where kid = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'tb_content
where kid = 31' at line 1
]
-----throw new Exception-------
2017-06-18 15:07:02,273 [http-bio-8080-exec-8] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@f177061]
2017-06-18 15:07:02,273 [http-bio-8080-exec-8] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@f177061]


总结:

  1. Spring事务管理是根据异常来进行回滚操作;
  2. Spring与Mybatis整合时,虽然在Service方法中并没有check异常,但是如果数据库有异常发生,默认会进行事务回滚。
  3. Spring 如果不添加rollbackFor等属性,Spring碰到Unchecked Exceptions都会回滚,不仅是RuntimeException,也包括Error。
  4. 如果在事务方法中捕获异常并进行处理,一定要继续抛出异常并在Spring事务管理中进行rollbak-for配置。

原文出处:

[1] zeng1994, 浅谈Spring中的事务回滚, https://www.cnblogs.com/zeng1994/p/8257763.html
[2] JeangLee, Spring事务管理——回滚(rollback-for)控制, https://blog.csdn.net/ljyhust/article/details/73431968?locationNum=5&fps=1

最后

以上就是眯眯眼龙猫为你收集整理的Spring事务管理——回滚(rollback-for)控制代码实践的全部内容,希望文章能够帮你解决Spring事务管理——回滚(rollback-for)控制代码实践所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(49)

评论列表共有 0 条评论

立即
投稿
返回
顶部