本篇文章主要介绍的是SpringBoot的事物Transaction使用的教程。html
说明:若是想直接获取工程那么能够直接跳到底部,经过连接下载工程代码。java
在Spring中,事务有两种实现方式,分别是编程式事务管理和声明式事务管理两种方式。mysql
默认状况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,若是执行成功则隐式的提交事务,若是执行失败则隐式的回滚事务。
对于正常的事务管理,是一组相关的操做处于一个事务之中,所以必须关闭数据库的自动提交模式。不过,这个咱们不用担忧,spring会将底层链接的自动提交特性设置为false。也就是在使用spring进行事物管理的时候,spring会将是否自动提交设置为false,等价于JDBC中的 connection.setAutoCommit(false);
,在执行完以后在进行提交,connection.commit();
。git
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:github
所谓事务的传播行为是指,若是在开始当前事务以前,一个事务上下文已经存在,此时有若干选项能够指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了以下几个表示传播行为的常量:web
指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,而后依据规则决定是否回滚抛出异常的事务。
默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会致使事务回滚),而抛出checked异常则不会致使事务回滚。
能够明确的配置在抛出那些异常时回滚事务,包括checked异常。也能够明肯定义那些异常抛出时不回滚事务。spring
rollbackFor
注解指定须要回滚的异常或者将异常抛出交给调用的方法处理。一句话就是在使用事物的时候子方法最好将异常抛出!throw new RuntimeException();
。环境要求sql
JDK:1.8数据库
SpringBoot:1.5.17.RELEASE编程
首先仍是Maven的相关依赖:
pom.xml文件以下:
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.17.RELEASE</version> <relativePath /> </parent> <dependencies> <!-- Spring Boot Web 依赖 核心 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot Test 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.2.0</version> </dependency> <!-- MySQL 链接驱动依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.44</version> </dependency> <!-- Druid 数据链接池依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.8</version> </dependency> </dependencies>
application.properties
的文件的配置:
banner.charset=UTF-8 server.tomcat.uri-encoding=UTF-8 spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true spring.http.encoding.force=true spring.messages.encoding=UTF-8 spring.application.name=springboot-transactional server.port=8182 spring.datasource.url=jdbc:mysql://localhost:3306/springBoot?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driverClassName=com.mysql.jdbc.Driver spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.initialSize=5 spring.datasource.minIdle=5 spring.datasource.maxActive=20 spring.datasource.maxWait=60000 spring.datasource.timeBetweenEvictionRunsMillis=60000 spring.datasource.minEvictableIdleTimeMillis=300000 spring.datasource.validationQuery=SELECT 1 FROM DUAL spring.datasource.testWhileIdle=true spring.datasource.testOnBorrow=false spring.datasource.testOnReturn=false spring.datasource.poolPreparedStatements=true spring.datasource.maxPoolPreparedStatementPerConnectionSize=20 spring.datasource.filters=stat,wall,log4j spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 logging.level.com.pancm.dao=debug
SpringBoot在使用事物Transactional的时候,要在main方法上加上 @EnableTransactionManagement
注解开发事物声明,在使用的service层的公共方法加上 @Transactional
(spring)注解。
那么首先咱们来看下 @Transactional
这个注解的使用方法吧,只须要你在须要添加公共方法上面添加该注解便可。可是这么使用的话须要你将异常抛出,由spring进行去控制。
代码示例:
@Transactional public boolean test1(User user) throws Exception { long id = user.getId(); System.out.println("查询的数据1:" + udao.findById(id)); // 新增两次,会出现主键ID冲突,看是否能够回滚该条数据 udao.insert(user); System.out.println("查询的数据2:" + udao.findById(id)); udao.insert(user); return false; }
若是咱们在使用事物 @Transactional
的时候,想本身对异常进行处理的话,那么咱们能够进行手动回滚事物。在catch中加上 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
方法进行手动回滚。不过须要注意的是发生异常须要第一时间进行手动回滚事物,也就是要在异常抛出以前!
代码示例:
@Transactional public boolean test2(User user) { long id = user.getId(); try { System.out.println("查询的数据1:" + udao.findById(id)); // 新增两次,会出现主键ID冲突,看是否能够回滚该条数据 udao.insert(user); System.out.println("查询的数据2:" + udao.findById(id)); udao.insert(user); } catch (Exception e) { System.out.println("发生异常,进行手动回滚!"); // 手动回滚事物 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); e.printStackTrace(); } return false; }
若是咱们在使用事物 @Transactional
的时候,调用了其余的子方法进行了数据库的操做,可是咱们想使其事物生效的话,咱们可使用rollbackFor
注解或者将该子方法的异常抛出由调用的方法进行处理,不过这里须要注意的是,子方法也必须是公共的方法!
代码示例:
@Transactional public boolean test3(User user) { /* * 子方法出现异常进行回滚 */ try { System.out.println("查询的数据1:" + udao.findById(user.getId())); deal1(user); deal2(user); deal3(user); } catch (Exception e) { System.out.println("发生异常,进行手动回滚!"); // 手动回滚事物 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); e.printStackTrace(); } return false; } public void deal1(User user) throws SQLException { udao.insert(user); System.out.println("查询的数据2:" + udao.findById(user.getId())); } public void deal2(User user) throws SQLException{ if(user.getAge()<20){ //SQL异常 udao.insert(user); }else{ user.setAge(21); udao.update(user); System.out.println("查询的数据3:" + udao.findById(user.getId())); } } @Transactional(rollbackFor = SQLException.class) public void deal3(User user) { if(user.getAge()>20){ //SQL异常 udao.insert(user); } }
若是咱们不想使用事物 @Transactional
注解,想本身进行事物控制(编程事物管理),控制某一段的代码事物生效,可是又不想本身去编写那么多的代码,那么可使用springboot中的DataSourceTransactionManager
和TransactionDefinition
这两个类来结合使用,可以达到手动控制事物的提交回滚。不过在进行使用的时候,须要注意在回滚的时候,要确保开启了事物可是未提交,若是未开启或已提交的时候进行回滚是会在catch里面发生异常的!
代码示例:
@Autowired private DataSourceTransactionManager dataSourceTransactionManager; @Autowired private TransactionDefinition transactionDefinition; public boolean test4(User user) { /* * 手动进行事物控制 */ TransactionStatus transactionStatus=null; boolean isCommit = false; try { transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition); System.out.println("查询的数据1:" + udao.findById(user.getId())); // 进行新增/修改 udao.insert(user); System.out.println("查询的数据2:" + udao.findById(user.getId())); if(user.getAge()<20) { user.setAge(user.getAge()+2); udao.update(user); System.out.println("查询的数据3:" + udao.findById(user.getId())); }else { throw new Exception("模拟一个异常!"); } //手动提交 dataSourceTransactionManager.commit(transactionStatus); isCommit= true; System.out.println("手动提交事物成功!"); throw new Exception("模拟第二个异常!"); } catch (Exception e) { //若是未提交就进行回滚 if(!isCommit){ System.out.println("发生异常,进行手动回滚!"); //手动回滚事物 dataSourceTransactionManager.rollback(transactionStatus); } e.printStackTrace(); } return false; }
上述的这几种示例是比较常见使用的,基本能够知足平常咱们对事物的使用,spring里面还有一种事物的控制方法,就是设置断点进行回滚。可是这种方法我的还没实际验证过,可靠性待确认。
使用方法以下:
Object savePoint =null; try{ //设置回滚点 savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); }catch(Exception e){ //出现异常回滚到savePoint。 TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint); }
上面的使用示例介绍完毕以后,咱们再来介绍一下几个主要的类。
首先仍是实体类:
实体类
又是万能的用户表
public class User { private Long id; private String name; private Integer age; //getter 和 setter 略 }
Controller 控制层
而后即是控制层,控制层这块的我作了下最后的查询,用于校验事物是否成功生效!
控制层代码以下:
@RestController @RequestMapping(value = "/api/user") public class UserRestController { @Autowired private UserService userService; @Autowired private UserDao userDao; @PostMapping("/test1") public boolean test1(@RequestBody User user) { System.out.println("请求参数:" + user); try { userService.test1(user); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("最后查询的数据:" + userDao.findById(user.getId())); return true; } @PostMapping("/test2") public boolean test2(@RequestBody User user) { System.out.println("请求参数:" + user); userService.test2(user); System.out.println("最后查询的数据:" + userDao.findById(user.getId())); return true; } @PostMapping("/test3") public boolean test3(@RequestBody User user) { System.out.println("请求参数:" + user); userService.test3(user); System.out.println("最后查询的数据:" + userDao.findById(user.getId())); return true; } @PostMapping("/test4") public boolean test4(@RequestBody User user) { System.out.println("请求参数:" + user); userService.test4(user); System.out.println("最后查询的数据:" + userDao.findById(user.getId())); return true; } }
App 入口
和普通的SpringBoot项目基本同样,只不过须要加上 @EnableTransactionManagement
注解!
代码以下:
@EnableTransactionManagement @SpringBootApplication public class TransactionalApp { public static void main( String[] args ) { SpringApplication.run(TransactionalApp.class, args); System.out.println("Transactional 程序正在运行..."); } }
咱们在启动程序以后,来进行上述的几个示例测试,这里的测试示例分别对应上述的使用示例,有的示例须要测试两边以上才能验证事物是否可以生效!这里咱们使用Postman进行测试!
两次测试,第一次不使用@Transactional
注解,第二次使用!
第一次测试:
注释掉@Transactional
注解!
使用进行POST请求
http://localhost:8182/api/user/test1
Body参数为:
{"id":1,"name":"xuwujing","age":18}
控制台打印的数据:
请求参数:User [id=1, name=xuwujing, age=18] 查询的数据1:null 查询的数据2:User [id=1, name=xuwujing, age=18] Duplicate entry '1' for key 'PRIMARY' 最后查询的数据:User [id=1, name=xuwujing, age=18]
第二次测试:
解除@Transactional
注解注释!
使用进行POST请求
http://localhost:8182/api/user/test1
Body参数为:
{"id":1,"name":"xuwujing","age":18}
控制台打印的数据:
请求参数:User [id=1, name=xuwujing, age=18] 查询的数据1:null 查询的数据2:User [id=1, name=xuwujing, age=18] Duplicate entry '1' for key 'PRIMARY' 最后查询的数据:null
注: 在第二次测试的以前是把第一次测试写入数据库的id为1的数据个删除了!
第一次测试中因为没有添加@Transactional
注解,所以发生了异常数据仍是写入了,可是第二次测试中添加了@Transactional
注解,发现即便数据已经写入了,可是出现了异常以后,数据最终被回滚了,没有写入!
从上述的测试用例中能够看到测试用例一种的事物已经生效了!
因为使用示例二中的代码几乎和使用示例一种的同样,不一样的是异常由咱们本身进行控制!
使用进行POST请求
http://localhost:8182/api/user/test2
Body参数为:
{"id":1,"name":"xuwujing","age":18}
控制台打印的数据:
请求参数:User [id=1, name=xuwujing, age=18] 查询的数据1:null 查询的数据2:User [id=1, name=xuwujing, age=18] 发生异常,进行手动回滚! Duplicate entry '1' for key 'PRIMARY' 最后查询的数据:null
能够看到事物生效了!
因为使用示例三中进行了子方法调用,这里咱们进行两次测试,根据不一样的请求条件来进行测试!
第一次测试:
使用进行POST请求
http://localhost:8182/api/user/test3
Body参数为:
{"id":1,"name":"xuwujing","age":18}
控制台打印的数据:
请求参数:User [id=1, name=xuwujing, age=18] 查询的数据1:null 查询的数据2:User [id=1, name=xuwujing, age=18] 发生异常,进行手动回滚! Duplicate entry '1' for key 'PRIMARY' 最后查询的数据:null
第二次测试:
使用进行POST请求
http://localhost:8182/api/user/test3
Body参数为:
{"id":1,"name":"xuwujing","age":21}
控制台打印的数据:
请求参数:User [id=1, name=xuwujing, age=21] 查询的数据1:null 查询的数据2:User [id=1, name=xuwujing, age=21] 查询的数据3:User [id=1, name=xuwujing2, age=21] 发生异常,进行手动回滚! Duplicate entry '1' for key 'PRIMARY' 最后查询的数据:null
根据上述的两次测试,能够得出使用rollbackFor
注解或者将该子方法的异常抛出由调用的方法进行处理均可以使事物生效!
因为使用示例四中进行了手动控制事物,这里咱们进行两次测试,根据不一样的请求条件来进行测试!
第一次测试:
使用进行POST请求
http://localhost:8182/api/user/test4
Body参数为:
{"id":1,"name":"xuwujing","age":18}
控制台打印的数据:
请求参数:User [id=1, name=xuwujing, age=18] 查询的数据1:null 查询的数据2:User [id=1, name=xuwujing, age=18] 查询的数据3:User [id=1, name=xuwujing2, age=20] 手动提交事物成功! 模拟第二个异常! 最后查询的数据:User [id=1, name=xuwujing, age=20]
第二次测试:
事先仍是把数据库id为1的数据给删除!
使用进行POST请求
http://localhost:8182/api/user/test4
Body参数为:
{"id":1,"name":"xuwujing","age":21}
控制台打印的数据:
请求参数:User [id=1, name=xuwujing, age=21] 查询的数据1:null 查询的数据2:User [id=1, name=xuwujing, age=21] 发生异常,进行手动回滚! 模拟一个异常! 最后查询的数据:null
根据上述的两次测试,咱们能够得出使用手动控制事物彻底ok,只要提交了事物,即便后面发生了异常也不回影响以前的写入!若是在控制的范围之类发生了异常,也能够进行回滚!
测试示例图:
参考:
https://www.cnblogs.com/yepei/p/4716112.html
SpringBoot 事物Transaction的项目工程地址:
https://github.com/xuwujing/springBoot-study/tree/master/springboot-transactional
SpringBoot整个集合的地址:
https://github.com/xuwujing/springBoot-study
原创不易,若是感受不错,但愿给个推荐!您的支持是我写做的最大动力! 版权声明: 做者:虚无境 博客园出处:http://www.cnblogs.com/xuwujing CSDN出处:http://blog.csdn.net/qazwsxpcm 我的博客出处:http://www.panchengming.com