java事务传播机制事例_(02)验证Spring的事务及其7种传播机制真实案例

本文详细探讨了Spring的事务传播机制,通过实例展示了如何在不同情况下使用@Transactional注解,包括defaultAutoCommit对事务的影响以及@Transactional与异常回滚的关系。此外,还介绍了Spring的7种事务传播行为,如REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW等,并解释了它们的工作原理和应用场景。
摘要由CSDN通过智能技术生成

原文:

说起Spring的事务,仿佛是很熟悉的老朋友了,然鹅。。。现实却很残酷。。。起初我以 Spring、Mybatis、druid、Mysql尝试,发现其中一个问题,无论数据源的defaultAutoCommit设置为true或者false,事务总会自动提交。确定配置无误后,发现网上有一种说法是把数据库的autocommit设置为OFF,即关闭数据库的自动提交,且不说这样是否可以,这种方法本身就有问题。因为在代码中获取到一个Connection,执行commit即可提交,不执行commit就不会提交,完全可以由代码控制,去设置数据库本身,这很不合理。经过一番周折还是没有搞定这个问题。

无奈之下我把Mybatis换成JdbcTemplate,终于正常了。下面基于Spring+JdbcTemplate+druid+Mysql说说事务。

1、事务、事务传播机制的简单说明

事务是一个单体行为,只有提交了事务,数据才会保存到数据库,否则不会保存到数据库中。事务传播行要求至少有两个东西,才可以发生传播。指的是当一个事务方法被另一个事务方法调用时,这个被调用方法的事务方法应该如何进行。例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

2、defaultAutoCommit与Transactional的关系

配置数据源时参数defaultAutoCommit设置为ture、false代表自动、不自动提交。Transactional注解也控制事务,他们有什么关系?下面用例子说明。

(1)defaultAutoCommit为false,不使用Transactional注解。结论:不会提交

public voidsave() {

testDao.save();

}

ab6d48a10eb475117835ad58e0d9010f.png

(2)defaultAutoCommit为false,使用Transactional注解。结论:会提交

@Transactionalpublic voidsave() {

testDao.save();

}

1fd9a831b1a931543e63cd0712cd4d06.png

(3)defaultAutoCommit为true,不使用Transactional注解。结论:会提交

public voidsave() {

testDao.save();

}

5ab1ef99817cf299942ecdc88c757931.png

(4)defaultAutoCommit为true,使用Transactional注解。结论:会提交

@Transactionalpublic voidsave() {

testDao.save();

}

10232158a3d04d2024222a0a11f93da7.png

总结:只要defaultAutoCommit或者Transactional有一项设置为可提交即可。

3、Transactional与异常自动回滚的关系

在项目中希望当方法产生异常时自动回滚事务,下面我们在defaultAutoCommit设置为false的情况下进行验证

(1)使用Transactional的默认配置,抛出检查型异常。事务不会回滚

@Transactionalpublic void save () throwsException {

testDao.save();throw newClassNotFoundException();

}

377411669ec88a8809213a6e56efc511.png

(2)使用Transactional的默认配置,抛出运行时异常。事务会回滚

@Transactionalpublic voidsave (){

testDao.save();throw newRuntimeException();

}

ec7cc3b31091351321fd45eade8f4b70.png

(3)使用Transactional注解,指定rollbackFor为抛出的异常或其父类时,检查型异常会回滚

@Transactional(rollbackFor=Exception.class)public void save () throwsException {

testDao.save();throw newClassNotFoundException();

}

94623fb550c19977257d001e6a59445c.png

(4)使用Transactional注解,指定rollbackFor不是抛出的异常或其父类时,运行时异常会回滚(运行时异常与rollbackFor无关,肯定回滚)

@Transactional(rollbackFor=FileNotFoundException.class)public void save () throwsException {

testDao.save();throw newRuntimeException();

}

6b93c185fffc7d89fc4d6d2f73073ce6.png

(5)使用Transactional注解,捕获异常,事务不会回滚

@Transactionalpublic void save () throwsException {try{

testDao.save();throw newRuntimeException();

}catch(Exception e) {//TODO: handle exception

}

}

ec218c78b100880b3f7778796b5a2ed0.png

@Transactionalpublic void save () throwsException {try{

testDao.save();throw newClassNotFoundException();

}catch(Exception e) {//TODO: handle exception

}

}

6e4555d29dd2e48eae5f190786488c16.png

(6)捕获的异常需要手动回滚,手动回滚时检查型异常可以不指定rollbackFor

@Transactionalpublic voidsave () {try{

testDao.save();throw newClassNotFoundException();

}catch(Exception e) {

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

}

}

81ae35fc8b126fa3092bb97914805105.png

@Transactional(rollbackFor=FileNotFoundException.class)public voidsave () {try{

testDao.save();throw newClassNotFoundException();

}catch(Exception e) {

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

}

}

98c7b759207128aad89f386c542524dd.png

@Transactionalpublic voidsave () {try{

testDao.save();throw newRuntimeException();

}catch(Exception e) {

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

}

}

c39b2da786f1e6f11445582a4990d706.png

(7) Transactional要加在主动(直接)调用的方法上面,以下事务不会提交,没有开启事务(spring容器管理的类直接调用test)

public voidtest(){

save();

}

@Transactionalpublic voidsave () {try{

testDao.save();throw newRuntimeException();

}catch(Exception e) {

}

}

194a493d653472e5cc12505880f1ee8f.png

4、spring中的事务传播行为

spring中共有7种事务传播行为,分别介绍如下:

(1)PROPAGATION_REQUIRED:如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

方法A加注解,方法B也加注解,当方法A运行时会开启事务A,调用方法B时,方法B也加入到事务A中

@Transactional(propagation =Propagation.REQUIRED)public voidmethodA() {

methodB();

testDao.methodA();

}

@Transactional(propagation=Propagation.REQUIRED)public voidmethodB() {

testDao.methodB();

}

e4e711c2e55e22af7725034027c73648.png

如上图,总共开启了一个事务。

(2)PROPAGATION_SUPPORTS:如果存在一个事务,支持当前事务,如果没有事务,则不会开启事务。

@Transactional(propagation =Propagation.REQUIRED)public voidmethodA() {

methodB();

testDao.methodA();

}

@Transactional(propagation=Propagation.SUPPORTS)public voidmethodB() {

testDao.methodB();

}

如果直接调用methodA,methodA会开启一个事务,methodA调用methodB,则methodB支持当前methodA开启的事务,如下图:

6869a31750efa1526c2849d85f487445.png

如果直接调用methodB,不会开启事务,如下图:

4c618c3e8f42aaea17908f26b8497208.png

如果直接调用methodA,由于methodA是SUPPORTS,不会开始事务,methodB不是直接调用,也不会开启事务

@Transactional(propagation =Propagation.SUPPORTS)public voidmethodA() {

methodB();

testDao.methodA();

}

@Transactional(propagation=Propagation.REQUIRED)public voidmethodB() {

testDao.methodB();

}

4d69fed10b91bda8bca2b0b07c8dd71c.png

(3)PROPAGATION_MANDATORY:必须在一个事务中运行,否则报异常

@Transactional(propagation =Propagation.REQUIRED)public voidmethodA() {

methodB();

testDao.methodA();

}

@Transactional(propagation=Propagation.MANDATORY)public voidmethodB() {

testDao.methodB();

}

直接调用methodA,开启一个事务,methodB也在该事务中运行

18279058d836e4442ce1f9b785d3de66.png

直接调用methodB,报异常No existing transaction found for transaction marked with propagation 'mandatory'

@Transactional(propagation =Propagation.MANDATORY)public voidmethodB() {

testDao.methodB();

}

3f543e469bcb5bce0a513ed9068bc4cd.png

(4)PROPAGATION_REQUIRES_NEW:开启一个新事务。如果一个事务已经存在,则先将存在的事务挂起,执行完这个新事务,再执行挂起的事务,两个事务的成功或失败没有联系。

@Transactional(propagation =Propagation.REQUIRED)public voidmethodA() {

methodB();

testDao.methodA();

}

@Transactional(propagation=Propagation.REQUIRES_NEW)public voidmethodB() {

testDao.methodB();

}

801ef1f1073d5e8ea81f28f02a6dd1e4.png

从上图中看到,并没有挂起旧事务,先执行新事务,因为只有使用JtaTransactionManager作为事务管理器时才生效。后面再研究。。。

(5)PROPAGATION_NOT_SUPPORTED:在非事务中运行。如果有事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行。

@Transactional(propagation =Propagation.REQUIRED)public voidmethodA() {

methodB();

testDao.methodA();

}

@Transactional(propagation=Propagation.NOT_SUPPORTED)public voidmethodB() {

testDao.methodB();

}

直接调用methodA,运行到methodB,事务应该挂起,即methodB对应的数据不会保存到数据库。

91e41003b96a9e9a8ab63d247c0dd051.png

但上图与预期的不一致,因为也需要JtaTransactionManager作为事务管理器 。

直接调用methodB不会开启事务,可以自己尝试一下。

(6)PROPAGATION_NEVER:总是非事务地执行,如果存在一个活动事务,则抛出异常。

@Transactional(propagation =Propagation.NEVER)public voidmethodB() {

testDao.methodB();

}

直接调用methodB,不会开启事务

3376979f92d0208094bac13b3f1762a4.png

@Transactional(propagation =Propagation.REQUIRED)public voidmethodA() {

methodB();

testDao.methodA();

}

@Transactional(propagation=Propagation.NEVER)public voidmethodB() {

testDao.methodB();

}

直接调用methodA,报异常,发现下面日志没有报异常,,,是版本问题还是我的理解有误???先留个疑问吧

2e8ad95daa9a15fd6d3a1a8b16a73b42.png

(7) PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务, 则按PROPAGATION_REQUIRED属性执行。

附 相关配置文件和代码

pom.xml

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

4.0.0

com.sl

spring-web-transaction

0.0.1-SNAPSHOT

war

UTF-8

1.8

1.8

2.2

5.1.0.RELEASE

1.7.4

1.1.11

5.1.30

2.5.4

1.7.7

org.springframework

spring-webmvc

${spring.version}

org.aspectj

aspectjweaver

${aspectjweaver.version}

org.springframework

spring-jdbc

${spring.version}

com.alibaba

druid

${druid.version}

mysql

mysql-connector-java

${mysql.driver.version}

runtime

org.slf4j

slf4j-api

${slf4j.version}

org.slf4j

slf4j-log4j12

${slf4j.version}

org.apache.maven.plugins

maven-war-plugin

2.4

${project.build.directory}/${project.artifactId}

${project.artifactId}

org.apache.tomcat.maven

tomcat7-maven-plugin

${tomcat.version}

8080

/${project.artifactId}

${project.build.sourceEncoding}

View Code

spring-context.xml

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

classpath:db.properties

View Code

spring-mvc.xml

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

View Code

log4j.properties

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

# DEBUG,INFO,WARN,ERROR,FATAL

log4j.rootLogger=DEBUG,CONSOLE,FILE

log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender

log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout

log4j.appender.CONSOLE.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss} %C{1}@(%F:%L):%m%n

log4j.appender.FILE=org.apache.log4j.DailyRollingFileAppender

log4j.appender.FILE.File=${catalina.base}/logs/spring-web.log

log4j.appender.FILE.Encoding=utf-8log4j.appender.FILE.DatePattern='.'yyyy-MM-dd

log4j.appender.FILE.layout=org.apache.log4j.PatternLayout

log4j.appender.FILE.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH\:mm\:ss} %C{1}@(%F\:%L)\:%m%n

log4j.logger.com.mybatis=DEBUG

log4j.logger.com.mybatis.common.jdbc.SimpleDataSource=DEBUG

log4j.logger.com.mybatis.common.jdbc.ScriptRunner=DEBUG

log4j.logger.com.mybatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG

log4j.logger.java.sql.Connection=DEBUG

log4j.logger.java.sql.Statement=DEBUG

log4j.logger.java.sql.PreparedStatement=DEBUG

log4j.logger.java.sql.ResultSet=DEBUG

View Code

db.properties

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

url:jdbc:mysql://localhost:3309/mytest?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8

driverClassName:com.mysql.jdbc.Driver

username:root

password:123456filters:stat

maxActive:20initialSize:1maxWait:60000minIdle:10maxIdle:15timeBetweenEvictionRunsMillis:60000minEvictableIdleTimeMillis:300000validationQuery:SELECT'x'testWhileIdle:truetestOnBorrow:falsetestOnReturn:falsemaxOpenPreparedStatements:20removeAbandoned:trueremoveAbandonedTimeout:1800logAbandoned:true

View Code

TestController.java

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.sl.controller;importjava.util.List;importjava.util.Map;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.ResponseBody;importcom.sl.service.TestService;

@Controller

@RequestMapping("/test")public classTestController {

@AutowiredprivateTestService testService;

@RequestMapping("/save")

@ResponseBodypublic voidsave(){//testService.save();

testService.methodA();

}

@RequestMapping("/del")

@ResponseBodypublic void del() throwsException {

testService.del();

}

@RequestMapping("/get")

@ResponseBodypublicString get(){

String str= "...";

List> list =testService.get();for(Mapmap : list) {

str= map.get("name").toString();

}returnstr;

}

@RequestMapping("/update")

@ResponseBodypublic void update() throwsException {

testService.update();

}

}

View Code

TestService.java

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.sl.service;importjava.util.List;importjava.util.Map;public interfaceTestService {public void save() throwsException;public voiddel();public voidtest();public List>get();public voidupdate();public voidmethodA();public voidmethodB();

}

View Code

TestServiceImpl.java

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.sl.service.impl;importjava.util.List;importjava.util.Map;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Propagation;importorg.springframework.transaction.annotation.Transactional;importcom.sl.dao.TestDao;importcom.sl.service.TestService;

@Servicepublic class TestServiceImpl implementsTestService{private static Logger logger=LoggerFactory.getLogger(TestServiceImpl.class);

@AutowiredprivateTestDao testDao;public voidtest(){

save();

}

@Transactionalpublic voidsave () {try{

testDao.save();throw newRuntimeException();

}catch(Exception e) {

}

}

@Transactional(propagation=Propagation.REQUIRED)public voidmethodA() {

methodB();

testDao.methodA();

}

@Transactional(propagation=Propagation.NEVER)public voidmethodB() {

testDao.methodB();

}

@Override

@Transactional(propagation=Propagation.NEVER)public voiddel() {//TODO Auto-generated method stub

testDao.del();

}

@Overridepublic List>get() {//TODO Auto-generated method stub

returntestDao.get();

}

@Override

@Transactional(propagation=Propagation.REQUIRED)public voidupdate() {//TODO Auto-generated method stub

testDao.update();

}

}

View Code

TestDao.java

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.sl.dao;importjava.sql.SQLException;importjava.util.List;importjava.util.Map;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.jdbc.core.JdbcTemplate;importorg.springframework.stereotype.Repository;

@Repositorypublic classTestDao {

@AutowiredprivateJdbcTemplate jdbcTemplate;public voidsave() {

String sql="insert into t_testa(name) values('11111')";

jdbcTemplate.execute(sql);

}public voidmethodA() {

String sql="insert into t_testa(name) values('11111')";

jdbcTemplate.execute(sql);

}public voidmethodB() {

String sql="insert into t_testb(name) values('11111')";

jdbcTemplate.execute(sql);

}public voiddel() {

String sql="delete from t_testa";

jdbcTemplate.execute(sql);

}public List>get() {

String sql="select * from t_testa";

List> list =jdbcTemplate.queryForList(sql);returnlist;

}public voidupdate() {

String sql="update t_testa set name = 'asdfg'";

jdbcTemplate.execute(sql);

}

}

View Code

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值