Spring 事务使用详解
前言
什么是事务?根据 维基百科事务 介绍,数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。简单来说,事务就是将一系列操作当成一个不可拆分的执行逻辑单元,这些要么都成功,要么都失败。事务具有4个属性:原子性、一致性、隔离性、持久性。称为ACID特性。
Spring 事务
在使用 Spring
进行开发过程中,一般都会使用 Spring
来进行事务的控制,接下来就来看下 Spring
使用事务的详细过程,包括事务的传播方式等。本文根据 官方文档 的介绍,结合例子来进行说明。
Spring
事务支持两种方式,编程式事务和声明式事务,下面的栗子会使用声明式事务来举例,即使用 @Transactional
注解的方式.
栗子
首先来看个简单栗子,后面再来对事务的一些属性进行详细的分析和介绍。
1. 首先来看下两个数据库表结构 user
表和 address
表:
user 表:
address表:
2.Spring 配置文件启动事务:
1<tx:annotation-driven transaction-manager="transactionManager"/>2<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">3 <property name="dataSource" ref="dataSource"/>4</bean>5<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">6 <property name="driverClassName" value="com.mysql.jdbc.Driver"/>7 <property name="url" value="jdbc:mysql://localhost:3306/tsmyk"/>8 <property name="username" value="root"/>9 <property name="password" value="root"/>
10</bean>
3.定义需要事务执行的方法:
1public class UserServiceImpl implements IUserService {23 private JdbcTemplate jdbcTemplate;4 public void setDataSource(DataSource dataSource) {5 this.jdbcTemplate = new JdbcTemplate(dataSource);6 }78 @Transactional9 @Override
10 public void add(User user) throws RuntimeException {
11 String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";
12 Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};
13 jdbcTemplate.update(sql, args);
14 throw new RuntimeException("保存出现异常...");
15 }
16}
这里使用 JdbcTemplate
来操作数据库
4.在上述的配置文件中配置该 bean:
1<bean id="txUserService" class="main.tsmyk.transaction.UserServiceImpl">
2 <property name="dataSource" ref="dataSource"/>
3</bean>
5.单元测试:
1@RunWith(SpringJUnit4ClassRunner.class)2@ContextConfiguration("/resources/myspring.xml")3public class TestTransaction {45 @Autowired6 private IUserService userServiceImpl;78 @Test9 public void test1() throws RuntimeException {
10 User user = new User("xiaoqi", 20, 1, 1000, "java");
11 userServiceImpl.add(user);
12 }
13}
6.运行结果会抛出异常:
7.此时数据库的数据还是原来的。
上述的栗子中,在 add()
方法加上了事务注解 @Transactional
,当该方法抛出异常的时候,数据库会进行回滚,数据插入失败。
事务的原理
Spring
事务是使用 AOP
来实现的,在 Spring AOP 注解方式源码解析 和 Spring AOP 创建代理的源码解析 文章中,了解到,在执行目标方法之前和之后,我们可以进行一些增强操作,而这恰恰可以符合事务的使用情况,在目标方法执行成功后,提交事务,失败的时候,回滚事务。当然,我们还可以通过 AOP
来自定义事务的行为。从概念上讲,在事务代理上调用方法看起来如下所示:
当客户端调用的时候,调用的是代理对象,在执行目标方法之前,会创建事务,即事务增强,之后会执行我们自定义的增强行为(如果有的话,可以在事务增强之前或之后执行),之后执行目标方法,执行目标方法之后,又会执行自定义增强和事务增强,事务要么提交要么回滚,之后再返回给客户端;这就是它的一个流程,和 分析 AOP
的流程是一致的。
Spring 事务详解
事务只会对 public
方法有效,对 protected
,private
和 package-visible
的方法,事务不会有效,在 proxy
模式下,如果是对象内部的方法自我调用,则调用的内部方法事务也不会生效.
下面通过栗子来验证下方法的自我调用看下事务是否生效
1public class UserServiceImpl implements IUserService {23 private JdbcTemplate jdbcTemplate;4 public void setDataSource(DataSource dataSource) {5 this.jdbcTemplate = new JdbcTemplate(dataSource);6 }78 @Override9 public void add(User user) throws RuntimeException {
10 this.add_2(user);
11 }
12
13 @Transactional
14 @Override
15 public void add_2(User user) throws RuntimeException {
16 String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";
17 Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};
18 jdbcTemplate.update(sql, args);
19 throw new RuntimeException("保存出现异常2...");
20 }
21}
现在 add()
方法调用 add_2()
方法,add()
方法不加事务,而 add_2()
方法添加了事务,此时客户端调用 add()
方法:
虽然 add_2()
方法抛出了异常,但是不会回滚,数据还是成功的插入:
这是为什么呢?因为 add()
方法通过 this
来调用 add_2()
方法,而 this
代表的是目标对象而不是代理对象,所以 this.add_2()
不会被代理,也就不会被事务控制,即事务不生效;这种情况下,怎么使 add_2()
也被事务进行控制呢?当然不能使用 this
来调用了,而是使用 代理对象 来调用: ((IUserService)AopContext.currentProxy()).add_2(user)
,但是,使用这种方式有个前提,需要把我们的代理对象暴露出来,放到 ThreadLocal
中,即在 配置文件 配置 expose-proxy
属性,即 <aop:aspectj-autoproxy expose-proxy="true"/>
,在 Spring AOP 注解方式源码解析 中已经分析过该属性。如下所示:
1<aop:aspectj-autoproxy expose-proxy="true"/>2<tx:annotation-driven transaction-manager="transactionManager" mode="proxy"/>3@Override4public void add(User user) throws RuntimeException {5 ((IUserService)AopContext.currentProxy()).add_2(user);6}78@Transactional9@Override
10public void add_2(User user) throws RuntimeException {
11 String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";
12 Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};
13 jdbcTemplate.update(sql, args);
14 throw new RuntimeException("保存出现异常2...");
15}
再运行上面的测试代码,会发现,事务回滚了,数据插入失败。
如果我们在 add()
方法和 add_2()
方法都加上注解,且通过 this
来调用 add_2()
方法,事务会不会生效呢?
1@Transactional2@Override3public void add(User user) throws RuntimeException {4 this.add_2(user);5}67@Transactional8@Override9public void add_2(User user) throws RuntimeException {
10 String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";
11 Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};
12 jdbcTemplate.update(sql, args);
13 throw new RuntimeException("保存出现异常2...");
14}
运行上面的测试代码,事务是生效的,这是因为它们属于同一个事务,且 add()
方法被事务进行管理。
@Transactional
注解可以放在接口上,接口方法上,类上,类的 public 方法上,但是 Spring
建议的是 @Transactional
尽量放在 类 或 类方法上,而不建议放在接口或接口方法;这是为什么呢?我们知道,事务是通过 Spring AOP
来实现的,而 Spring AOP
是通过动态代理来实现的,而 Spring
使用的动态代理主要有 JDK 动态代理
和 CGLIB 代理
,JDK 动态代理
主要代理接口,这时 把 @Transactional
放在接口或接口方法上,事务是有效的;而 CGLIB 代理则
是代理类,通过继承的方式来实现,这时把 @Transactional
放在接口或接口方法上,则事务就不会生效:
1@Transactional
2publicinterfaceIUserService{
3 voidadd(Useruser)throwsRuntimeException;
4}
针对该配置方式,把事务注解放在接口上,则只会对 JDK 代理有效
1@Transactional
2@Override
3publicvoidadd(Useruser)throwsRuntimeException{
4 Stringsql="insert into user(name,age,sex,money,job)values(?,?,?,?,?)";
5 Object[]args=newObject[] {user.getName(),user.getAge(),user.getSex(),user.getMoney(),user.getJob()};
6 jdbcTemplate.update(sql,args);
7 thrownewRuntimeException("保存出现异常...");
8}
把事务注解放在实现类上,则对 CGLIB 和 JDK 代理都有效
使用 CGLIB
代理,需要配置 proxy-target-class
属性为true
:<tx:annotation-driven proxy-target-class="true"/>
<tx:annotation-driven/> 的属性
<tx:annotation-driven/>
标签用来表示开启事务功能,它有 4 个属性:
1<tx:annotation-driven transaction-manager="transactionManager" mode="proxy" proxy-target-class="true" order="1"/>
transaction-manager :事务管理器的名称,默认为 transactionManager,因为可以不写,如果管理器的名称不是这个才需要写。
mode : 模式,两种,proxy 模式和 aspectj 模式,proxy 仅适用于通过代理进入的方法调用,aspectj 适用于任何类型的方法调用
proxy-target-class : 使用 CGLIB 进行代理,代理类而不是代理接口
order:代理顺序
@Transactional 属性
@Transactional
有很多属性来控制事务的行为,共 9 个属性
属性 | 类型 | 描述 | 默认值 |
---|---|---|---|
value | String | 该事务对应的事务管理器 | transactionManager |
propagation | 枚举:Propagation | 事务传播方式 | REQUIRED |
isolation | 枚举: Isolation | 事务隔离级别 | DEFAULT |
readOnly | boolean | 读/写 与 只读 | false |
timeout | int(以秒为单位) | 超时时间 | -1 |
rollbackFor | Class对象数组 | 需要回滚的异常 | 空数组 {} |
rollbackForClassName | 类名数组 | 需要回滚的异常类名 | 空数组 {} |
noRollbackFor | Class对象数组 | 不需要回滚的异常 | 空数组 {} |
noRollbackForClassName | 类名数组 | 不需要回滚的异常类名 | 空数组 {} |
事务的名称就是方法的全限定名,无法设置
事务的传播方式
接下来看下事务的传播方式,事务的传播方式在 Spring
事务中非常重要,需要理解清楚,否则有时候事务不回滚不知道问题出在哪里。
事务的传播方式使用 propagation
属性来表示,它是一个枚举类型,共有 7 个,即事务的传播方式有 7 种:
1public enum Propagation {
2 REQUIRED //required:需要事务,如果事务不存在,则创建一个新事务
3 REQUIRES_NEW //required_new:需要创建一个新事务,如果已存在事务,则把当前事务挂起
4 NESTED //nested:嵌套事务
5 SUPPORTS //supports:支持事务,如果没有事务,则以非事务的方式运行
6 NOT_SUPPORTED //not_supported:不支持事务,以非事务的方式运行,如果存在事务,则挂起
7 NEVER //never:不支持事务,如果存在事务,则抛出异常
8 MANDATORY //mandatory:支持事务,如果没有事务,则抛出异常
9}
下面以栗子的方式来验证这几种传播方式,数据库的相关表结构还是文章开头的 user
表和 address
表,代码如下:
1public class UserServiceImpl implements IUserService {2 private JdbcTemplate jdbcTemplate;3 public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);}45 @Autowired6 private IAddressService addressService;78 @Override9 public void addUser(User user) throws RuntimeException {
10 String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";
11 Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};
12 jdbcTemplate.update(sql, args);
13 addressService.addAddress(3, "shanghai");
14 }
15}
16
17public class AddressServiceImpl implements IAddressService {
18 private JdbcTemplate jdbcTemplate;
19 public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);}
20
21 @Override
22 public void addAddress(int id, String name) throws RuntimeException{
23 String sql = "insert into address(id, name) values (?, ?)";
24 Object[] args = new Object[]{id, name};
25 jdbcTemplate.update(sql, args);
26 throw new RuntimeException("保存Address出现异常...");
27 }
28}
29
30// 测试代码
31@Test
32public void testUser() throws RuntimeException {
33 User user = new User("xiaoqi", 20, 1, 1000, "java");
34 userServiceImpl.addUser(user);
35}
即就是在 UserServiceImpl
的 addUser()
方法里面调用 AddressServiceImpl
的 addAddress()
方法
REQUIRED
required
这种传播方式,它是需要在事务中运行的,如果事务不存在,则创建一个新事务
在 addAddress()
方法加上注解 @Transactional(propagation = Propagation.REQUIRED)
,如下所示:
1public void addUser(User user) throws RuntimeException {2 String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";3 Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};4 jdbcTemplate.update(sql, args);5 addressService.addAddress(3, "shanghai");6}78@Transactional(propagation = Propagation.REQUIRED)9public void addAddress(int id, String name) throws RuntimeException{
10 String sql = "insert into address(id, name) values (?, ?)";
11 Object[] args = new Object[]{id, name};
12 jdbcTemplate.update(sql, args);
13 throw new RuntimeException("保存Address出现异常...");
14}
因为 addUser
没要添加事务,所以 addAddress
会创建一个新事务,运行上面的测试代码,日志如下(IDEA 把 log4j 日志输出到文件查看):
可以看到,第一步,执行user
插入操作,插入成功,并没有创建事务,第二步,创建事务,事务名称为 main.tsmyk.transaction.AddressServiceImpl.addAddress
,执行 address
插入,插入成功,又因为 address
抛出异常,所以 address
插入进行回滚,回滚的数据库连接是 515809288
,即执行 address
插入的连接,并没有回滚 user
插入的连接,所以结果是 user
正常插入,而 address
插入失败。为什么addUser
没有进行回滚呢,因为 它又没有在事务中运行,自然就不会回滚了。
在 addUser
和addAddress
方法都加上注解 @Transactional(propagation = Propagation.REQUIRED)
,如下所示:
1@Transactional(propagation = Propagation.REQUIRED)2public void addUser(User user) throws RuntimeException {3 String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";4 Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};5 jdbcTemplate.update(sql, args);6 addressService.addAddress(3, "shanghai");7}89@Transactional(propagation = Propagation.REQUIRED)
10public void addAddress(int id, String name) throws RuntimeException{
11 String sql = "insert into address(id, name) values (?, ?)";
12 Object[] args = new Object[]{id, name};
13 jdbcTemplate.update(sql, args);
14 throw new RuntimeException("保存Address出现异常...");
15}
运行测试代码,日志如下:
可以看到,首先会创建事务,名称为 addUser
的全限定名,获取数据库连接 418958713
,之后会在该连接中执行 user
和 address
的插入操作,即在同一个事务中,address
插入抛出异常,进行回滚,回滚的是连接 418958713
,所以 user
和 address
的操作都会被回滚,都插入失败。
如果在 addUser
调用 addAddress
的时候,进行异常的捕获,addUser
会进行回滚嘛?如下:
1@Transactional(propagation = Propagation.REQUIRED)2public void addUser(User user) throws RuntimeException {3 String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";4 Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};5 jdbcTemplate.update(sql, args);6 try {7 addressService.addAddress(3, "shanghai");8 }catch (Exception e){}9}
10
11@Transactional(propagation = Propagation.REQUIRED)
12public void addAddress(int id, String name) throws RuntimeException{
13 String sql = "insert into address(id, name) values (?, ?)";
14 Object[] args = new Object[]{id, name};
15 jdbcTemplate.update(sql, args);
16 throw new RuntimeException("保存Address出现异常...");
17}
运行日志如下:
可以看到,它们还是在同一个事务中运行,同一个连接中进行插入,回滚的是同一个连接,所以都会插入失败,即使进行了异常捕获。
总结:所以 REQUIRED
这种传播方式,必须要在事务中运行,不存在事务在,则创建一个,即使进行的异常的捕获,外部还是会进行回滚,这是因为虽然在每个方法都加上了事务注解,看起来是独立的事务,可是都会映射到底层数据库中的同一个物理事务中,所以只要有一个进行了回滚,则都会进行回滚。
REQUIRES_NEW
·required_new·,需要创建一个新事务,如果已存在事务,则把当前事务挂起
在 addUser
方法加上注解 @Transactional(propagation = Propagation.REQUIRED)
在 addAddress
方法加上注解 @Transactional(propagation = Propagation.REQUIRES_NEW)
1@Transactional(propagation = Propagation.REQUIRED)2public void addUser(User user) throws RuntimeException {3 String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";4 Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};5 jdbcTemplate.update(sql, args);6 addressService.addAddress(3, "shanghai");7}89@Transactional(propagation = Propagation.REQUIRES_NEW)
10public void addAddress(int id, String name) throws RuntimeException{
11 String sql = "insert into address(id, name) values (?, ?)";
12 Object[] args = new Object[]{id, name};
13 jdbcTemplate.update(sql, args);
14 throw new RuntimeException("保存Address出现异常...");
15}
运行日志如下:
可以看到,创建了两个事务,获取了两个数据库连接 1164799006
和 418958713
,当执行 address
插入的时候,会把已经存在的事务挂起,新创建一个事务进行运行,当 address
插入抛出异常的时候,这两个事务都进行了回滚,所以都会插入失败。
在 addUser
调用 addAddress
的时候,进行异常捕获,则会怎么样呢?
1@Transactional(propagation = Propagation.REQUIRED)2public void addUser(User user) throws RuntimeException {3 String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";4 Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};5 jdbcTemplate.update(sql, args);6 try {7 addressService.addAddress(3, "shanghai");8 }catch (Exception e){}9}
10
11@Transactional(propagation = Propagation.REQUIRES_NEW)
12public void addAddress(int id, String name) throws RuntimeException{
13 String sql = "insert into address(id, name) values (?, ?)";
14 Object[] args = new Object[]{id, name};
15 jdbcTemplate.update(sql, args);
16 throw new RuntimeException("保存Address出现异常...");
17}
运行日志如下:
可以看到,还是创建两个事务,获取了两个连接,进行异常捕获了之后,只会回滚一个事务,
总结:REQUIRES_NEW
它是创建了一个新的事务进行运行,它们是完全独立的事务范围,对应到底层数据库的物理事务也是不同的,所以它们可以独立提交或回滚,外部事务不受内部事务的回滚状态的影响; 对于上述栗子来说,如果 addAddress
抛异常且 addUser
不进行异常捕获,则两个事务都会进行回滚,如果 addUser
进行了异常捕获,则 addUser
可以进行提交的,它们是两个独立的事务;如果 addAddress
执行成功,外层的 addUser
执行失败,则 addUser
会回滚,则内部执行成功的 addAddress
不会回滚。
NESTED
nested
,嵌套事务,它是外部事务的一个子事务,新建一个子事务进行运行;它们并不是独立,如果外部事务提交,则嵌套事务也会提交,外部事务回滚,则嵌套事务也会回滚。嵌套事务主要支持 saveponit
保存点。
在addUser
方法加上注解@Transactional(propagation = Propagation.REQUIRED)
在addAddress
方法加上注解@Transactional(propagation = Propagation.NESTED)
1@Transactional(propagation = Propagation.REQUIRED)2public void addUser(User user) throws RuntimeException {3 String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";4 Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};5 jdbcTemplate.update(sql, args);6 addressService.addAddress(3, "shanghai");7}89@Transactional(propagation = Propagation.NESTED)
10public void addAddress(int id, String name) throws RuntimeException{
11 String sql = "insert into address(id, name) values (?, ?)";
12 Object[] args = new Object[]{id, name};
13 jdbcTemplate.update(sql, args);
14 throw new RuntimeException("保存Address出现异常...");
15}
运行日志如下:
可以看到,新建了两个事务,一个是 nested
嵌套事务,而且只是获取了一个数据库连接 418958713
,在同一个连接中执行两条SQL
,当 addAddress
出现异常进行回滚的时候,只是回滚到 savepoint
保存到,由于外层的 addUser
没有进行异常捕获,所以外部事务回滚,即回滚连接 418958713
。
如果在外层进行异常捕获,则外层的 addUser
会插入成功嘛?
1@Transactional(propagation = Propagation.REQUIRED)2public void addUser(User user) throws RuntimeException {3 String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";4 Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};5 jdbcTemplate.update(sql, args);6 try {7 addressService.addAddress(3, "shanghai");8 }catch (Exception e){}9}
10
11@Transactional(propagation = Propagation.NESTED)
12public void addAddress(int id, String name) throws RuntimeException{
13 String sql = "insert into address(id, name) values (?, ?)";
14 Object[] args = new Object[]{id, name};
15 jdbcTemplate.update(sql, args);
16 throw new RuntimeException("保存Address出现异常...");
17}
运行日志如下:
可以看到,内层的事务会回滚到 savepoint
保存点,而外层的事务则可以正常提交,结果就是 address
插入失败,user
插入成功。
现在如果内层的 addAddress
执行成功,不抛异常,外层的 addUser
抛异常,则 内层的 addAddress
会回滚嘛?
1@Transactional(propagation = Propagation.REQUIRED)2public void addUser(User user) throws RuntimeException {3 String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";4 Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};5 jdbcTemplate.update(sql, args);6 addressService.addAddress(3, "shanghai");7 throw new RuntimeException("保存User出现异常...");8}9
10@Transactional(propagation = Propagation.NESTED)
11public void addAddress(int id, String name) throws RuntimeException{
12 String sql = "insert into address(id, name) values (?, ?)";
13 Object[] args = new Object[]{id, name};
14 jdbcTemplate.update(sql, args);
15}
运行日志如下:
如果外部事务回滚了,内部事务也会回滚,因为它们属于同一个底层数据库的物理事务。
总结:嵌套事务, 它是已经存在事务的子事务. 嵌套事务开始执行时, 它将取得一个savepoint
. 如果这个嵌套事务失败, 将回滚到此savepoint
. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交. 使用NESTED
有限制,它只支持 JDBC
,且数据库要支持 savepoint
保存点,还要 JDBC
的驱动在3.0
以上
SUPPORTS
supports,支持事务,如果没有事务,则以非事务的方式运行
NOT_SUPPORTED
not_supported,不支持事务,以非事务的方式运行,如果存在事务,则挂起
NEVER
never,不支持事务,如果存在事务,则抛出异常
MANDATORY
mandatory,支持事务,如果没有事务,则抛出异常
这几种传播方式比较好理解,就不把栗子贴出来了。
事务的隔离级别
事务的隔离级别使用 isolation
表示,它是一个枚举 Isolation
1public enum Isolation {
2
3 DEFAULT // 默认的隔离级别,和底层数据库有关,MySQL 是可重复读(Repeated Read)
4 READ_UNCOMMITTED // 读未提交,允许脏读,可能读取到其他会话中未提交事务修改的数据
5 READ_COMMITTED // 读已提交,只能读取到已经提交的数据
6 REPEATABLE_READ // 可重复读,该隔离级别消除了不可重复读,但是还存在幻象读
7 SERIALIZABLE // 序列化,完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞
8
9}
总结
1. Spring 事务原理:
Spring 事务是通过 Spring AOP 来实现的,还可以通过 AOP 来自定义事务的行为。
2. 事务注解 @Transactional
事务注解 @Transactional
可以放在接口上,接口方法上,类上,类的public
方法上,如果放在接口或接口方法上,则只会对 JDK 代理
有效,对 CGLIB 代理
无效,我们知道,事务是通过 Spring AOP
来实现的,而 Spring AOP
是通过动态代理来实现的,而Spring
使用的动态代理主要有 JDK 动态代理
和 CGLIB 代理
,JDK 动态代理
主要代理接口,这时 把 @Transactional
放在接口或接口方法上,事务是有效的;而 CGLIB 代理则
是代理类,通过继承的方式来实现,这时把 @Transactional
放在接口或接口方法上,则事务就不会生效的。
3. 内部调用事务不生效的解决方法
一是把该方法放到其他的对象中,不过不太实用,二是不通过 this
来调用方法,而是通过代理来调用,如 AopContext.currentProxy().xxxx
,但是,使用这种方式有个前提,需要把我们的代理对象暴露出来,放到 ThreadLocal
中,即在 配置文件配置 expose-proxy
属性,即<aop:aspectj-autoproxy expose-proxy="true"/>
4. 事务的传播方式,7 种
1REQUIRED //required:需要事务,如果事务不存在,则创建一个新事务
2REQUIRES_NEW //required_new:需要创建一个新事务,如果已存在事务,则把当前事务挂起
3NESTED //nested:嵌套事务
4SUPPORTS //supports:支持事务,如果没有事务,则以非事务的方式运行
5NOT_SUPPORTED //not_supported:不支持事务,以非事务的方式运行,如果存在事务,则挂起
6NEVER //never:不支持事务,如果存在事务,则抛出异常
7MANDATORY //mandatory:支持事务,如果没有事务,则抛出异常
REQUIRED : 必须要在事务中运行,不存在事务在,则创建一个,即使进行的异常的捕获,外部还是会进行回滚,这是因为虽然在每个方法都加上了事务注解,看起来是独立的事务,可是都会映射到底层数据库中的同一个物理事务中,所以只要有一个进行了回滚,则都会进行回滚。
REQUIRES_NEW 它是创建了一个新的事务进行运行,它们是完全独立的事务范围,对应到底层数据库的物理事务也是不同的,所以它们可以独立提交或回滚,外部事务不受内部事务的回滚状态的影响.
NESTED 嵌套事务, 它是已经存在事务的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 将回滚到此 savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交. 使用 NESTED 有限制,它只支持 JDBC,且数据库要支持 savepoint 保存点,还要 JDBC 的驱动在3.0以上。
5. 事务的隔离级别
1DEFAULT // 默认的隔离级别,和底层数据库有关,MySQL 是可重复读(Repeated Read)
2READ_UNCOMMITTED // 读未提交,允许脏读,可能读取到其他会话中未提交事务修改的数据
3READ_COMMITTED // 读已提交,只能读取到已经提交的数据
4REPEATABLE_READ // 可重复读,该隔离级别消除了不可重复读,但是还存在幻象读
5SERIALIZABLE // 序列化,完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞
Spring 事务使用详解相关推荐
- Spring事务管理详解_基本原理_事务管理方式
Spring事务管理详解_基本原理_事务管理方式 1. 事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,使用JDBC的事务管理机制,就是利用java.sql.Connection对象 ...
- 关于事务管理的理解和Spring事务管理详解
转载于:http://www.mamicode.com/info-detail-1248286.html 1 初步理解 理解事务之前,先讲一个你日常生活中最常干的事:取钱. 比如你去ATM机取1000 ...
- Spring事务属性详解
spring,是一个Java开源框架,是为了解决企业应用程序开发复杂性由Rod Johnson创建的.框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 J2EE 应用程序 ...
- Spring事务管理详解
什么是事务 事务是逻辑上的一组操作,要么都执行,要么都不执行. 需要注意的是:事务能否生效数据库引擎是否支持事务是关键.比如常用的 MySQL 数据库默认使用支持事务的 innodb引擎.但是,如果把 ...
- Spring事务原理详解
一.使用 spring事务开启和使用比较简单,需要有数据源和事务管理器,然后在启动门面类上开启事务,在需要使用事务的地方添加注解就可以了,我们简单做一下回顾. 1.配置数据源 spring.datas ...
- 什么是事务的传播_这么漂亮的Spring事务管理详解,你不来看看?
事务概念回顾 什么是事务? 事务是逻辑上的一组操作,要么都执行,要么都不执行. 事物的特性(ACID): 原子性: 事务是最小的执行单位,不允许分割.事务的原子性确保动作要么全部完成,要么完全不起作用 ...
- Spring 事务机制详解
Spring事务机制主要包括声明式事务和编程式事务,此处侧重讲解声明式事务,编程式事务在实际开发中得不到广泛使用,仅供学习参考. Spring声明式事务让我们从复杂的事务处理中得到解脱.使得我们再也无 ...
- 多数据源 事务管理_可能是最漂亮的Spring事务管理详解
事务概念回顾 什么是事务? 事务是逻辑上的一组操作,要么都执行,要么都不执行. 事物的特性(ACID): 原子性: 事务是最小的执行单位,不允许分割.事务的原子性确保动作要么全部完成,要么完全不起作用 ...
- batch spring 重复执行_可能是最漂亮的Spring事务管理详解
作者:Guide哥 事务概念回顾 什么是事务? 事务是逻辑上的一组操作,要么都执行,要么都不执行. 事物的特性(ACID): 原子性: 事务是最小的执行单位,不允许分割.事务的原子性确保动作要么全部完 ...
最新文章
- Eclipse字体设置
- 解读2019华为第001号文件:AI时代软件开发的第一要义是可信
- 学python需要哪些基础-python 学习,需要有哪些基础呢?
- 【整理】SAP 看板简介
- python 如何判断一组数据是否符合正态分布
- 【小白学PyTorch】9.tensor数据结构与存储结构
- 读博文学Android
- 关于一些电脑使用的小技巧
- 学习 trajectory.txt
- leetcode之字符串中的第一个唯一字符
- 苹果ppt_ppt制作苹果版下载 1.3.0
- W10系统如何查看笔记本电脑电池损耗情况?
- bzoj4484[Jsoi2015]最小表示 拓补排序+bitset
- BootStrap之标签页切换
- 计算机毕设项目:毕业论文管理系统(一)
- 插件式换肤框架搭建 - 资源加载源码分析
- USB Type-A/Type-B/Type-C/mini-AB/micro-AB接口简介
- 【自考】——考后总结
- 国内9大免费CDN汇总,除了加速乐,你还用过哪些?
- 如何在java中产生随机数