项目中如何配置事务
项目中使用事务有好几种方式,本文章的项目都是使用的Spring,如果你使用的是JDBC编程,那么请看这个。
事务管理是应用系统开发中必不可少的一部分。Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为 编程式
和 声明式
的两种方式。
-
编程式:指的是通过编码方式实现事务,看这个:
-
声明式:基于 AOP, 将具体业务逻辑与事务处理解耦
声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多(
所以本篇文章只讲声明式
)。
声明式事务有两种方式配置:
- 基于XML配置(
本篇文章不讲,请自行百度
) - 基于 @Transactional 注解(
本篇文章使用此方式
)
一、SSM
配置:
spring-mybatis.xml
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 使用基于注解方式配置事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
使用:
只需在 Service 的类或方法上面写 @Transactional
注解即可, @Transactional
中的属性,下面会详细讲解。
二、SpringBoot
开启事务:
在 Application
类上面打个 @EnableTransactionManagement
注解即可。如下:
@EnableTransactionManagement
有两个属性可以看看:
-
proxyTargetClass 默认false(标准的 JDK 基于接口的代理)
该属性用于控制代理是基于接口的还是基于类被创建 设置为
true
表示使用基于子类实现的代理(CGLIB),设置为false
表示使用基于接口实现的代理 -
mode 默认PROXY
该属性表示是使用哪种事务切面,有
PROXY
和ASPECTJ
,想要更深入了解,可以看看源码
使用也是在 Service
上面加 @Transactional
注解即可。
三、我们在使用 @Transactional
注解的时候,需要注意的一些问题:
配置好了,先别着急去用,先来看看有哪些需要我们注意的。
1、默认配置下 Spring 只会回滚运行时、未检查异常(继承自 RuntimeException 的异常)或者 Error,但是我们可以配置 rollbackFor
来指定我们要处理的异常(下面会讲)
2、 @Transactional
注解只能应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。
3、 @Transactional
注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上,但是Spring团队建议在具体的类(或类的方法)上使用 @Transactional
注解,而不要使用在类所要实现的任何接口上(因为 Spring 的声明式事务是默认基于 SpringAOP 实现,而 SpringAOP 默认是使用 java 动态代理实现(基于接口实现),如果此时将代理改为cglib(基于子类实现),在接口上面加注解就没用了,所以为了保持兼容注解最好都写到实现类方法上)
4、如果 @Transactional
注解被写在 Service 的类上面,则表示类中所有的方法都被事务管理,但是有些查询方法是不需要事务管理,那么可以这样做(为什么这样,看完下面就明白了):
/**
* 获取用户列表
* <br/>
* 查询不需要事务
*/
@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
public PageInfo<User> listUsers(Page<User> page) {
PageHelper.startPage(page.getPageNum(), page.getPageSize());
List<User> users = userMapper.listUsers();
return new PageInfo<>(users);
}
5、有些时候我们需要手动回滚事务,那么,有两种方法:
-
service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw new RuntimeExcetpion()语句,以便让Aop捕获异常再去回滚,并且在service上层(webservice客户端,view层Controller)要继续捕获这个异常并处理
-
在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常(现在项目的做法)
四、@Transactional 详解
@Transactional 中的属性:
1、value、transactionManager
这两个作用一样,当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器
2、propagation
事务的传播行为,默认值为 Propagation.REQUIRED
可选的值有:
-
Propagation.REQUIRED
如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。
-
Propagation.SUPPORTS
如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
-
Propagation.MANDATORY
如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
-
Propagation.REQUIRES_NEW
重新创建一个新的事务,如果当前存在事务,暂停当前的事务。
-
Propagation.NOT_SUPPORTED
以非事务的方式运行,如果当前存在事务,暂停当前的事务。
-
Propagation.NEVER
以非事务的方式运行,如果当前存在事务,则抛出异常。
-
Propagation.NESTED
和
Propagation.REQUIRED
效果一样。
3、isolation
事务的隔离级别,默认值为 Isolation.DEFAULT
可选的值有:
-
Isolation.DEFAULT
使用底层数据库默认的隔离级别。(MySQl 默认:Repeatable read)
-
Isolation.READ_UNCOMMITTED
(读未提交):最低级别,任何情况都无法保证。
-
Isolation.READ_COMMITTED
(读已提交):可避免脏读的发生。
-
Isolation.REPEATABLE_READ
(可重复读):可避免脏读、不可重复读的发生
-
Isolation.SERIALIZABLE
(串行化):可避免脏读、不可重复读、幻读的发生。
4、timeout
事务的超时时间,默认值为-1,表示事务超时将依赖于底层事务系统,
如果超过该时间限制但事务还没有完成,则自动回滚事务。
5、readOnly
指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
6、rollbackFor
需要触发回滚的异常定义,可定义多个,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚
7、noRollbackFor
抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
下面重点讲讲 Propagation.REQUIRES_NEW 这种传播行为:
注:
下面有几个使用案例,如果你也想打印详细日志,你需要下面这样配置:
需要控制台打印MyBatis执行SQL日志的这样配置:
# ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ MyBatis 配置 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ #
mybatis:
configuration:
# spring boot集成mybatis的方式打印sql:https://blog.csdn.net/qq_22194659/article/details/81120712
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
需要看到事务的创建详细日志的这样配置:
# ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 日志 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ #
# 默认情况下,spring boot从控制台打印出来的日志级别只有ERROR, WARN 还有INFO,如果你想要打印debug级别的日志,可以通过配置debug=true
debug: true
logging:
# 配置logging.level.*来具体输出哪些包的日志级别
level:
root: info
org.springframework.web: debug
# 打开 jdbc 事务执行日志
org.springframework.jdbc.datasource: debug
先来看一段代码:
package com.blog.www.service;
import com.blog.www.domain.entity.User;
import com.blog.www.domain.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 事务测试服务
*/
@Transactional
@Service
public class TransactionTestService {
@Autowired
private UserMapper userMapper;
public void test1() {
test2();
User user = new User();
user.setAge((short)1);
user.setUserName("lei");
user.setPassword("212121");
user.setDeleted(false);
userMapper.insert(user);
throw new RuntimeException("测试事务回滚");
}
public void test2() {
User user = new User();
user.setAge((short)2);
user.setUserName("tom");
user.setPassword("2121DSASDAS");
user.setDeleted(false);
userMapper.insert(user);
}
}
这个Service里面有两个方法,使用test1去调用test2,在test2和test1都执行完之后,抛一个异常,结果是两条数据都回滚了,因为我们在类上面加了 @Transactional
注解,此注解传播行为默认是:Propagation.REQUIRED
,所以此时test1和test2事务是同一个。
可以看看执行日志:
从日志可以看出,只创建了一个事务,事务也正常回滚了。
那么,如果现在有这样的一个需求:test1抛异常仅仅只回滚test1,test2不受影响,如何实现?
有人可能会想到,在test2上面加个 @Transactional
注解,设置传播行为为:Propagation.REQUIRES_NEW
,这样test2就会创建新的事务,真的是这样么?我们试试
修改上面的代码为:
package com.blog.www.service;
import com.blog.www.domain.entity.User;
import com.blog.www.domain.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* 事务测试服务
*/
@Transactional
@Service
public class TransactionTestService {
@Autowired
private UserMapper userMapper;
public void test1() {
test2();
User user = new User();
user.setAge((short)1);
user.setUserName("lei");
user.setPassword("212121");
user.setDeleted(false);
userMapper.insert(user);
throw new RuntimeException("测试事务回滚");
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void test2() {
User user = new User();
user.setAge((short)2);
user.setUserName("tom");
user.setPassword("2121DSASDAS");
user.setDeleted(false);
userMapper.insert(user);
}
}
查看执行日志:
从日志可以看出,两个方法还是处于同一个事务中,两条数据都回滚了。
为什么会出现这种情况?
这就得看看 Spring 官方文档 了
In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.
大概意思:
在默认的代理模式下,只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。在同一个类中的两个方法直接调用,是不会被 Spring 的事务拦截器拦截。
就像上面的 test1 方法直接调用了同一个类中的 test2 方法,test2 方法不会被 Spring 的事务拦截器拦截。可以使用 AspectJ 取代 Spring AOP 代理来解决这个问题,但是这里暂不讨论。
为了解决这个问题,我们可以新建一个类,将 test2 放入新建的类,如下:
test1修改后:
package com.blog.www.service;
import com.blog.www.domain.entity.User;
import com.blog.www.domain.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 事务测试服务
*/
@Transactional
@Service
public class TransactionTest1Service {
@Autowired
private UserMapper userMapper;
@Autowired
private TransactionTest2Service transactionTest2Service;
public void test1() {
transactionTest2Service.test2();
User user = new User();
user.setAge((short) 1);
user.setUserName("lei");
user.setPassword("212121");
user.setDeleted(false);
userMapper.insert(user);
throw new RuntimeException("测试事务回滚");
}
}
test2修改后:
package com.blog.www.service;
import com.blog.www.domain.entity.User;
import com.blog.www.domain.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* 事务测试服务
*/
@Transactional
@Service
public class TransactionTest2Service {
@Autowired
private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void test2() {
User user = new User();
user.setAge((short)2);
user.setUserName("tom");
user.setPassword("2121DSASDAS");
user.setDeleted(false);
userMapper.insert(user);
}
}
查看执行日志:
从日志可以清晰的看出,在执行test2时,test1的事务被挂起,test2重新创建了一个新的事务,此时,再去看看数据库:
只有test1回滚了,test2数据正常插入。
还有一个比较常见的场景,就是在循环体内使用事务,比如,现在需要循环插入1000条数据,需要保证中途有一条插入失败,只回滚失败的那条,前面已经执行的不受影响。
这个和上面差不多,只需要把循环执行的方法单独放入一个类中,将事务的传播行为设置成 Propagation.REQUIRES_NEW
即可。
测试代码如下:
package com.blog.www.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 事务测试服务
*/
@Transactional
@Service
@Slf4j
public class TransactionTest1Service {
@Autowired
private TransactionTest2Service transactionTest2Service;
public void test1() {
for (int i = 0; i < 10; i++) {
transactionTest2Service.test2(i);
if (i == 5) {
// 模拟抛异常让 AOP 处理事务回滚,这里抛异常回滚,只能保证已经执行了的不会回滚,程序会停止执行
throw new RuntimeException("测试循环体内事务");
// 推荐使用手动回滚事务,手动回滚不影响程序继续执行
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}
}
package com.blog.www.service;
import com.blog.www.domain.entity.User;
import com.blog.www.domain.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* 事务测试服务
*/
@Transactional
@Service
public class TransactionTest2Service {
@Autowired
private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void test2(int i) {
User user = new User();
user.setAge((short)2);
user.setUserName("tom" + i);
user.setPassword("2121DSASDAS");
user.setDeleted(false);
userMapper.insert(user);
}
}
执行日志如下:
2019-11-04 22:48:09.996 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [com.blog.www.service.TransactionTest1Service.test1]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2019-11-04 22:48:09.996 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@29abba49] for JDBC transaction
2019-11-04 22:48:09.996 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@29abba49] to manual commit
2019-11-04 22:48:09.997 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Suspending current transaction, creating new transaction with name [com.blog.www.service.TransactionTest2Service.test2]
2019-11-04 22:48:09.997 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] for JDBC transaction
2019-11-04 22:48:09.998 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] to manual commit
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6a5de853]
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] will be managed by Spring
==> Preparing: insert into user (deleted, age, user_name, password, create_time, update_time) values (?, ?, ?,?, ?, ?)
==> Parameters: false(Boolean), 2(Short), tom0(String), 2121DSASDAS(String), null, null
<== Updates: 1
==> Preparing: SELECT LAST_INSERT_ID()
==> Parameters:
<== Columns: LAST_INSERT_ID()
<== Row: 52
<== Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6a5de853]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6a5de853]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6a5de853]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6a5de853]
2019-11-04 22:48:10.005 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Initiating transaction commit
2019-11-04 22:48:10.005 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc]
2019-11-04 22:48:10.007 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] after transaction
2019-11-04 22:48:10.008 DEBUG 576 --- [nio-8085-exec-2] o.s.jdbc.datasource.DataSourceUtils : Returning JDBC Connection to DataSource
2019-11-04 22:48:10.008 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Resuming suspended transaction after completion of inner transaction
2019-11-04 22:48:10.008 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Suspending current transaction, creating new transaction with name [com.blog.www.service.TransactionTest2Service.test2]
2019-11-04 22:48:10.008 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] for JDBC transaction
2019-11-04 22:48:10.008 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] to manual commit
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@af833aa]
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] will be managed by Spring
==> Preparing: insert into user (deleted, age, user_name, password, create_time, update_time) values (?, ?, ?,?, ?, ?)
==> Parameters: false(Boolean), 2(Short), tom1(String), 2121DSASDAS(String), null, null
<== Updates: 1
==> Preparing: SELECT LAST_INSERT_ID()
==> Parameters:
<== Columns: LAST_INSERT_ID()
<== Row: 53
<== Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@af833aa]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@af833aa]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@af833aa]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@af833aa]
2019-11-04 22:48:10.016 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Initiating transaction commit
2019-11-04 22:48:10.016 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc]
2019-11-04 22:48:10.019 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] after transaction
2019-11-04 22:48:10.019 DEBUG 576 --- [nio-8085-exec-2] o.s.jdbc.datasource.DataSourceUtils : Returning JDBC Connection to DataSource
2019-11-04 22:48:10.019 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Resuming suspended transaction after completion of inner transaction
2019-11-04 22:48:10.020 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Suspending current transaction, creating new transaction with name [com.blog.www.service.TransactionTest2Service.test2]
2019-11-04 22:48:10.020 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] for JDBC transaction
2019-11-04 22:48:10.020 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] to manual commit
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ca9b0f]
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] will be managed by Spring
==> Preparing: insert into user (deleted, age, user_name, password, create_time, update_time) values (?, ?, ?,?, ?, ?)
==> Parameters: false(Boolean), 2(Short), tom2(String), 2121DSASDAS(String), null, null
<== Updates: 1
==> Preparing: SELECT LAST_INSERT_ID()
==> Parameters:
<== Columns: LAST_INSERT_ID()
<== Row: 54
<== Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ca9b0f]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ca9b0f]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ca9b0f]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25ca9b0f]
2019-11-04 22:48:10.028 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Initiating transaction commit
2019-11-04 22:48:10.029 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc]
2019-11-04 22:48:10.031 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] after transaction
2019-11-04 22:48:10.031 DEBUG 576 --- [nio-8085-exec-2] o.s.jdbc.datasource.DataSourceUtils : Returning JDBC Connection to DataSource
2019-11-04 22:48:10.031 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Resuming suspended transaction after completion of inner transaction
2019-11-04 22:48:10.032 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Suspending current transaction, creating new transaction with name [com.blog.www.service.TransactionTest2Service.test2]
2019-11-04 22:48:10.032 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] for JDBC transaction
2019-11-04 22:48:10.032 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] to manual commit
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66ddaba0]
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] will be managed by Spring
==> Preparing: insert into user (deleted, age, user_name, password, create_time, update_time) values (?, ?, ?,?, ?, ?)
==> Parameters: false(Boolean), 2(Short), tom3(String), 2121DSASDAS(String), null, null
<== Updates: 1
==> Preparing: SELECT LAST_INSERT_ID()
==> Parameters:
<== Columns: LAST_INSERT_ID()
<== Row: 55
<== Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66ddaba0]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66ddaba0]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66ddaba0]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66ddaba0]
2019-11-04 22:48:10.037 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Initiating transaction commit
2019-11-04 22:48:10.037 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc]
2019-11-04 22:48:10.039 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] after transaction
2019-11-04 22:48:10.039 DEBUG 576 --- [nio-8085-exec-2] o.s.jdbc.datasource.DataSourceUtils : Returning JDBC Connection to DataSource
2019-11-04 22:48:10.040 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Resuming suspended transaction after completion of inner transaction
2019-11-04 22:48:10.040 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Suspending current transaction, creating new transaction with name [com.blog.www.service.TransactionTest2Service.test2]
2019-11-04 22:48:10.040 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] for JDBC transaction
2019-11-04 22:48:10.040 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] to manual commit
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@535d39c7]
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] will be managed by Spring
==> Preparing: insert into user (deleted, age, user_name, password, create_time, update_time) values (?, ?, ?,?, ?, ?)
==> Parameters: false(Boolean), 2(Short), tom4(String), 2121DSASDAS(String), null, null
<== Updates: 1
==> Preparing: SELECT LAST_INSERT_ID()
==> Parameters:
<== Columns: LAST_INSERT_ID()
<== Row: 56
<== Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@535d39c7]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@535d39c7]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@535d39c7]
2019-11-04 22:48:10.046 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Initiating transaction rollback
2019-11-04 22:48:10.046 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Rolling back JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc]
2019-11-04 22:48:10.048 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@185e0cdc] after transaction
2019-11-04 22:48:10.048 DEBUG 576 --- [nio-8085-exec-2] o.s.jdbc.datasource.DataSourceUtils : Returning JDBC Connection to DataSource
2019-11-04 22:48:10.049 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Resuming suspended transaction after completion of inner transaction
2019-11-04 22:48:10.049 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Initiating transaction rollback
2019-11-04 22:48:10.049 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Rolling back JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@29abba49]
2019-11-04 22:48:10.050 DEBUG 576 --- [nio-8085-exec-2] o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@29abba49] after transaction
2019-11-04 22:48:10.050 DEBUG 576 --- [nio-8085-exec-2] o.s.jdbc.datasource.DataSourceUtils : Returning JDBC Connection to DataSource
2019-11-04 22:48:10.051 DEBUG 576 --- [nio-8085-exec-2] .m.m.a.ExceptionHandlerExceptionResolver : Resolving exception from handler [public void com.blog.www.controller.TransactionTestController.testRollBack()]: java.lang.RuntimeException: 测试循环体内事务
2019-11-04 22:48:10.052 DEBUG 576 --- [nio-8085-exec-2] .m.m.a.ExceptionHandlerExceptionResolver : Invoking @ExceptionHandler method: public com.blog.www.bean.common.Response com.blog.www.web.GlobalExceptionHandNew.handleException(java.lang.Exception)
2019-11-04 22:48:10.055 ERROR 576 --- [nio-8085-exec-2] com.blog.www.web.GlobalExceptionHandNew : 服务内部异常!测试循环体内事务
java.lang.RuntimeException: 测试循环体内事务
at com.blog.www.service.TransactionTest2Service.test2(TransactionTest2Service.java:30) ~[classes/:na]
at com.blog.www.service.TransactionTest2Service$$FastClassBySpringCGLIB$$ae552b84.invoke(<generated>) ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:747) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294) ~[spring-tx-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at com.blog.www.service.TransactionTest2Service$$EnhancerBySpringCGLIB$$d21401df.test2(<generated>) ~[classes/:na]
at com.blog.www.service.TransactionTest1Service.test1(TransactionTest1Service.java:21) ~[classes/:na]
at com.blog.www.service.TransactionTest1Service$$FastClassBySpringCGLIB$$4673fea5.invoke(<generated>) ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:747) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294) ~[spring-tx-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689) ~[spring-aop-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at com.blog.www.service.TransactionTest1Service$$EnhancerBySpringCGLIB$$55d8f7e2.test1(<generated>) ~[classes/:na]
at com.blog.www.controller.TransactionTestController.testRollBack(TransactionTestController.java:23) ~[classes/:na]
at sun.reflect.GeneratedMethodAccessor188.invoke(Unknown Source) ~[na:na]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:45005) ~[na:1.8.0_151]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_151]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:635) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) [tomcat-embed-websocket-8.5.32.jar:8.5.32]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109) [spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
at com.blog.www.web.filter.xss.XssFilter.doFilter(XssFilter.java:31) [classes/:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
at com.blog.www.web.filter.i18n.I18nFilter.doFilter(I18nFilter.java:77) [classes/:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123) [druid-1.1.10.jar:1.1.10]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:90) [spring-boot-actuator-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:155) [spring-boot-actuator-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:123) [spring-boot-actuator-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108) [spring-boot-actuator-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) [spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.core.StandardContextValve.__invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:41002) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:800) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1471) [tomcat-embed-core-8.5.32.jar:8.5.32]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.32.jar:8.5.32]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_151]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_151]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.32.jar:8.5.32]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_151]
再看看数据库:
可以发现前面插入的不受影响。
五、@Transactional 事务实现机制
AOP 代理后的方法调用执行流程:
在应用系统调用声明了 @Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据 @Transactional 的属性配置信息,这个代理对象决定该声明 @Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器 AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务。
Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 两种,以 CglibAopProxy 为例,对于 CglibAopProxy,需要调用其内部类的 DynamicAdvisedInterceptor 的 intercept 方法。对于 JdkDynamicAopProxy,需要调用其 invoke 方法。
事务管理的框架是由抽象事务管理器 AbstractPlatformTransactionManager 来提供的,而具体的底层事务处理实现,由 PlatformTransactionManager 的具体实现类来实现,如事务管理器 DataSourceTransactionManager。不同的事务管理器管理不同的数据资源 DataSource,比如 DataSourceTransactionManager 管理 JDBC 的 Connection。
作者:不敲代码的攻城狮
出处:https://www.cnblogs.com/leigq/
任何傻瓜都能写出计算机可以理解的代码。好的程序员能写出人能读懂的代码。