博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring 事务使用详解
阅读量:6278 次
发布时间:2019-06-22

本文共 18112 字,大约阅读时间需要 60 分钟。

hot3.png

相关文章

前言

 什么是事务?根据  介绍,数据库事务(简称:事务)是执行过程中的一个逻辑单位,由一个有限的操作序列构成。简单来说,事务就是将一系列操作当成一个不可拆分的执行逻辑单元,这些要么都成功,要么都失败。事务具有4个属性:原子性、一致性、隔离性、持久性。称为ACID特性。

Spring 事务

在使用 Spring 进行开发过程中,一般都会使用 Spring 来进行事务的控制,接下来就来看下 Spring 使用事务的详细过程,包括事务的传播方式等。本文根据 的介绍,结合例子来进行说明。

Spring 事务支持两种方式,编程式事务和声明式事务,下面的栗子会使用声明式事务来举例,即使用  注解的方式

栗子

首先来看个简单栗子,后面再来对事务的一些属性进行详细的分析和介绍。

1. 首先来看下两个数据库表结构 user 表和 address 表:

user 表:

0f762ae1acf1177d937b95c2d78d8a84998.jpg

address 表:

552199a99f6e690b269304f689a1ee7abf3.jpg

2. Spring 配置文件启动事务:

3. 定义需要事务执行的方法:

接口

public interface IUserService {    void add(User user) throws RuntimeException;}

实现类:

public class UserServiceImpl implements IUserService {    private JdbcTemplate jdbcTemplate;    public void setDataSource(DataSource dataSource) {        this.jdbcTemplate = new JdbcTemplate(dataSource);    }    @Transactional    @Override    public void add(User user) throws RuntimeException {        String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";        Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};        jdbcTemplate.update(sql, args);        throw new RuntimeException("保存出现异常...");    }}

这里使用 JdbcTemplate 来操作数据库

4. 在上述的配置文件中配置该 bean:

5. 单元测试:

@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("/resources/myspring.xml")public class TestTransaction {        @Autowired    private IUserService userServiceImpl;    @Test    public void test1() throws RuntimeException {        User user = new User("xiaoqi", 20, 1, 1000, "java");        userServiceImpl.add(user);    }}

6. 运行结果会抛出异常:

76464c493c95e82d8154c868c47971a6999.jpg

7. 此时数据库的数据还是原来的,数据插入失败:

3815dd98b4b3b6bdfb9d733de1abb385cd3.jpg

上述的栗子中,在 add() 方法加上了事务注解  ,当该方法抛出异常的时候,数据库会进行回滚,数据插入失败。

事务原理

Spring 事务是使用 AOP 来实现的,在  和  文章中,了解到,在执行目标方法之前和之后,我们可以进行一些增强操作,而这恰恰可以符合事务的使用情况,在目标方法执行成功后,提交事务,失败的时候,回滚事务。当然,我们还可以通过 AOP 来自定义事务的行为。从概念上讲,在事务代理上调用方法看起来如下所示:

58e5e3f5ea0244d2e950f069247c2a302f8.jpg

当客户端调用的时候,调用的是代理对象,在执行目标方法之前,会创建事务,即事务增强,之后会执行我们自定义的增强行为(如果有的话,可以在事务增强之前或之后执行),之后执行目标方法,执行目标方法之后,又会执行自定义增强和事务增强,事务要么提交要么回滚,之后再返回给客户端;这就是它的一个流程,和 分析 AOP 的流程是一致的。

Spring 事务详解

事务只会对 public 方法有效,对 protected,private 和 package-visible 的方法,事务不会有效,在 proxy 模式下,如果是对象内部的方法自我调用,则调用的内部方法事务也不会生效.

下面通过栗子来验证下方法的自我调用看下事务是否生效

public class UserServiceImpl implements IUserService {    private JdbcTemplate jdbcTemplate;    public void setDataSource(DataSource dataSource) {        this.jdbcTemplate = new JdbcTemplate(dataSource);    }    @Override    public void add(User user) throws RuntimeException {        this.add_2(user);    }    @Transactional    @Override    public void add_2(User user) throws RuntimeException {        String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";        Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};        jdbcTemplate.update(sql, args);        throw new RuntimeException("保存出现异常2...");    }}

现在 add() 方法调用 add_2() 方法,add() 方法不加事务,而 add_2() 方法添加了事务,此时客户端调用 add() 方法:

227f783a6b88cd2abf2bb9799a5f822e6b4.jpg

虽然 add_2() 方法抛出了异常,但是不会回滚,数据还是成功的插入:

26678d9a6bfc4862cf2a2a2c15731d36bb7.jpg

这是为什么呢?因为 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"/> ,在  中已经分析过该属性。如下所示:

@Overridepublic void add(User user) throws RuntimeException {	((IUserService)AopContext.currentProxy()).add_2(user);}@Transactional@Overridepublic void add_2(User user) throws RuntimeException {	String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";	Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};	jdbcTemplate.update(sql, args);	throw new RuntimeException("保存出现异常2...");}

再运行上面的测试代码,会发现,事务回滚了,数据插入失败。

如果我们在  add() 方法和 add_2() 方法都加上注解,且通过 this 来调用 add_2() 方法,事务会不会生效呢?

@Transactional@Overridepublic void add(User user) throws RuntimeException {	this.add_2(user);}@Transactional@Overridepublic void add_2(User user) throws RuntimeException {	String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";	Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};	jdbcTemplate.update(sql, args);	throw new RuntimeException("保存出现异常2...");}

运行上面的测试代码,事务是生效的,这是因为它们属于同一个事务,且 add() 方法被事务进行管理。

注解可以放在接口上,接口方法上,类上,类的 public 方法上,但是 Spring 建议的是  尽量放在 类 或 类方法上,而不建议放在接口或接口方法;这是为什么呢?我们知道,事务是通过 Spring AOP 来实现的,而 Spring AOP 是通过动态代理来实现的,而 Spring 使用的动态代理主要有 JDK 动态代理和 CGLIB 代理,JDK 动态代理主要代理接口,这时 把  放在接口或接口方法上,事务是有效的;而 CGLIB 代理则是代理类,通过继承的方式来实现,这时把 @Transactional 放在接口或接口方法上,则事务就不会生效:

@TransactionalpublicinterfaceIUserService{      voidadd(Useruser)throwsRuntimeException;}

针对该配置方式,把事务注解放在接口上,则只会对 JDK 代理有效

@Transactional@Overridepublicvoidadd(Useruser)throwsRuntimeException{    Stringsql="insert into user(name,age,sex,money,job)values(?,?,?,?,?)";    Object[]args=newObject[] {user.getName(),user.getAge(),user.getSex(),user.getMoney(),user.getJob()};    jdbcTemplate.update(sql,args);    thrownewRuntimeException("保存出现异常...");}

把事务注解放在实现类上,则对 CGLIB 和 JDK 代理都有效

使用 CGLIB 代理,需要配置 proxy-target-class 属性为true:<tx:annotation-driven proxy-target-class="true"/> 

<tx:annotation-driven/> 的属性

<tx:annotation-driven/> 标签用来表示开启事务功能,它有 4 个属性:

1. transaction-manager :事务管理器的名称,默认为 transactionManager,因为可以不写,如果管理器的名称不是这个才需要写。

2. mode : 模式,两种,proxy 模式和 aspectj 模式,proxy 仅适用于通过代理进入的方法调用,aspectj 适用于任何类型的方法调用

3. proxy-target-class : 使用 CGLIB 进行代理,代理类而不是代理接口

4. order:代理顺序

@Transactional 属性

@Transactional 有很多属性来控制事务的行为,共 9 个属性:

@Transaction 属性设置

属性 类型 描述 默认值
value String     该事务对应的事务管理器 transactionManager
枚举:Propagation 事务传播方式 Propagation.REQUIRED
isolation 枚举: Isolation        事务隔离级别 Isolation.DEFAULT
readOnly boolean     读/写 与 只读 事务 false
timeout     int(以秒为单位) 超时时间 TransactionDefinition.TIMEOUT_DEFAULT
rollbackFor Class对象数组,异常必须是Throwable的子类   需要回滚的异常 空数组 {}
rollbackForClassName 类名数组。类必须派是Throwable的子类 需要回滚的异常类名 空数组 {}
noRollbackFor Class对象数组,异常必须是Throwable的子类  不需要回滚的异常 空数组 {}
noRollbackForClassName 类名数组。类必须派是Throwable的子类 不需要回滚的异常类名 空数组 {}

事务的名称就是方法的全限定名,无法设置。

事务的传播方式

接下来看下事务的传播方式,事务的传播方式在 Spring 事务中非常重要,需要理解清楚,否则有时候事务不回滚不知道问题出在哪里。

事务的传播方式使用  属性来表示,它是一个枚举类型,共有 7 个,即事务的传播方式有 7 种:

public enum Propagation {		REQUIRED //required:需要事务,如果事务不存在,则创建一个新事务    	REQUIRES_NEW //required_new:需要创建一个新事务,如果已存在事务,则把当前事务挂起		NESTED //nested:嵌套事务	SUPPORTS //supports:支持事务,如果没有事务,则以非事务的方式运行    	NOT_SUPPORTED //not_supported:不支持事务,以非事务的方式运行,如果存在事务,则挂起    	NEVER //never:不支持事务,如果存在事务,则抛出异常		MANDATORY //mandatory:支持事务,如果没有事务,则抛出异常	}

下面以栗子的方式来验证这几种传播方式,数据库的相关表结构还是文章开头的 user 表和 address 表,代码如下:

public class UserServiceImpl implements IUserService {    private JdbcTemplate jdbcTemplate;    public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);}    @Autowired    private IAddressService addressService;    @Override    public void addUser(User user) throws RuntimeException {        String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";        Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};        jdbcTemplate.update(sql, args);        addressService.addAddress(3, "shanghai");    }}public class AddressServiceImpl implements IAddressService {    private JdbcTemplate jdbcTemplate;    public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);}    @Override    public void addAddress(int id, String name) throws RuntimeException{        String sql = "insert into address(id, name) values (?, ?)";        Object[] args = new Object[]{id, name};        jdbcTemplate.update(sql, args);        throw new RuntimeException("保存Address出现异常...");    }}// 测试代码@Testpublic void testUser() throws RuntimeException {	User user = new User("xiaoqi", 20, 1, 1000, "java");	userServiceImpl.addUser(user);}

即就是在 UserServiceImpl 的 addUser() 方法里面调用 AddressServiceImpl 的 addAddress() 方法

REQUIRED

required 这种传播方式,它是需要在事务中运行的,如果事务不存在,则创建一个新事务;在 addAddress() 方法加上注解 @Transactional(propagation = Propagation.REQUIRED),如下所示:

public void addUser(User user) throws RuntimeException {	String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";	Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};	jdbcTemplate.update(sql, args);	addressService.addAddress(3, "shanghai");}@Transactional(propagation = Propagation.REQUIRED)public void addAddress(int id, String name) throws RuntimeException{	String sql = "insert into address(id, name) values (?, ?)";	Object[] args = new Object[]{id, name};	jdbcTemplate.update(sql, args);	throw new RuntimeException("保存Address出现异常...");}

 因为 addUser 没要添加事务,所以 addAddress 会创建一个新事务,运行上面的测试代码,日志如下(IDEA 把 log4j 日志输出到文件查看):

911b62598bfbaeb8936ec3ae59cd04c289f.jpg

可以看到,第一步,执行user插入操作,插入成功,并没有创建事务,第二步,创建事务,事务名称为 main.tsmyk.transaction.AddressServiceImpl.addAddress,执行 address 插入,插入成功,又因为 address 抛出异常,所以  address 插入进行回滚,回滚的数据库连接是 515809288,即执行 address 插入的连接,并没有回滚 user 插入的连接,所以结果是 user 正常插入,而 address 插入失败。为什么addUser 没有进行回滚呢,因为 它又没有在事务中运行,自然就不会回滚了。

addUser 和 addAddress 方法都加上注解 @Transactional(propagation = Propagation.REQUIRED),如下所示:

@Transactional(propagation = Propagation.REQUIRED)public void addUser(User user) throws RuntimeException {	String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";	Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};	jdbcTemplate.update(sql, args);	addressService.addAddress(3, "shanghai");}@Transactional(propagation = Propagation.REQUIRED)public void addAddress(int id, String name) throws RuntimeException{	String sql = "insert into address(id, name) values (?, ?)";	Object[] args = new Object[]{id, name};	jdbcTemplate.update(sql, args);	throw new RuntimeException("保存Address出现异常...");}

运行测试代码,日志如下:

85c7ffbf3f1beb33ac56adea938180328db.jpg

可以看到,首先会创建事务,名称为 addUser 的全限定名,获取数据库连接 418958713,之后会在该连接中执行 user 和 address 的插入操作,即在同一个事务中,address 插入抛出异常,进行回滚,回滚的是连接 418958713,所以 user 和 address 的操作都会被回滚,都插入失败。

如果在 addUser 调用 addAddress 的时候,进行异常的捕获,addUser 会进行回滚嘛?如下:

@Transactional(propagation = Propagation.REQUIRED)public void addUser(User user) throws RuntimeException {	String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";	Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};	jdbcTemplate.update(sql, args);	try {		addressService.addAddress(3, "shanghai");	}catch (Exception e){}}@Transactional(propagation = Propagation.REQUIRED)public void addAddress(int id, String name) throws RuntimeException{	String sql = "insert into address(id, name) values (?, ?)";	Object[] args = new Object[]{id, name};	jdbcTemplate.update(sql, args);	throw new RuntimeException("保存Address出现异常...");}

运行日志如下:

64fbcbea5b3f01826a0c2f8d1e0d862f108.jpg

可以看到,它们还是在同一个事务中运行,同一个连接中进行插入,回滚的是同一个连接,所以都会插入失败,即使进行了异常捕获。

总结:所以 REQUIRED 这种传播方式,必须要在事务中运行,不存在事务在,则创建一个,即使进行的异常的捕获,外部还是会进行回滚,这是因为虽然在每个方法都加上了事务注解,看起来是独立的事务,可是都会映射到底层数据库中的同一个物理事务中,所以只要有一个进行了回滚,则都会进行回滚。

REQUIRES_NEW

required_new,需要创建一个新事务,如果已存在事务,则把当前事务挂起 

addUser 方法加上注解 @Transactional(propagation = Propagation.REQUIRED)
addAddress 方法加上注解 @Transactional(propagation = Propagation.REQUIRES_NEW)

@Transactional(propagation = Propagation.REQUIRED)public void addUser(User user) throws RuntimeException {	String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";	Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};	jdbcTemplate.update(sql, args);	addressService.addAddress(3, "shanghai");}@Transactional(propagation = Propagation.REQUIRES_NEW)public void addAddress(int id, String name) throws RuntimeException{	String sql = "insert into address(id, name) values (?, ?)";	Object[] args = new Object[]{id, name};	jdbcTemplate.update(sql, args);	throw new RuntimeException("保存Address出现异常...");}

运行日志如下:

7d5102d63e4a0e13930bb8d4e8c198f3518.jpg

可以看到,创建了两个事务,获取了两个数据库连接 1164799006 和 418958713,当执行 address 插入的时候,会把已经存在的事务挂起,新创建一个事务进行运行,当 address 插入抛出异常的时候,这两个事务都进行了回滚,所以都会插入失败。

在 addUser 调用 addAddress 的时候,进行异常捕获,则会怎么样呢?

@Transactional(propagation = Propagation.REQUIRED)public void addUser(User user) throws RuntimeException {	String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";	Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};	jdbcTemplate.update(sql, args);	try {		addressService.addAddress(3, "shanghai");	}catch (Exception e){}}@Transactional(propagation = Propagation.REQUIRES_NEW)public void addAddress(int id, String name) throws RuntimeException{	String sql = "insert into address(id, name) values (?, ?)";	Object[] args = new Object[]{id, name};	jdbcTemplate.update(sql, args);	throw new RuntimeException("保存Address出现异常...");}

运行日志如下:

d02cfe2fb6996f997d4a70abc14653cb736.jpg

可以看到,还是创建两个事务,获取了两个连接,进行异常捕获了之后,只会回滚一个事务,

总结:REQUIRES_NEW 它是创建了一个新的事务进行运行,它们是完全独立的事务范围,对应到底层数据库的物理事务也是不同的,所以它们可以独立提交或回滚,外部事务不受内部事务的回滚状态的影响;对于上述栗子来说,如果 addAddress 抛异常且  addUser 不进行异常捕获,则两个事务都会进行回滚,如果  addUser 进行了异常捕获,则 addUser 可以进行提交的,它们是两个独立的事务;如果 addAddress 执行成功,外层的 addUser 执行失败,则 addUser 会回滚,则内部执行成功的 addAddress 不会回滚。

NESTED

nested,嵌套事务,它是外部事务的一个子事务,新建一个子事务进行运行;它们并不是独立,如果外部事务提交,则嵌套事务也会提交,外部事务回滚,则嵌套事务也会回滚。嵌套事务主要支持 saveponit 保存点。

addUser 方法加上注解 @Transactional(propagation = Propagation.REQUIRED)

addAddress 方法加上注解 @Transactional(propagation = Propagation.NESTED)

@Transactional(propagation = Propagation.REQUIRED)public void addUser(User user) throws RuntimeException {	String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";	Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};	jdbcTemplate.update(sql, args);	addressService.addAddress(3, "shanghai");}@Transactional(propagation = Propagation.NESTED)public void addAddress(int id, String name) throws RuntimeException{	String sql = "insert into address(id, name) values (?, ?)";	Object[] args = new Object[]{id, name};	jdbcTemplate.update(sql, args);	throw new RuntimeException("保存Address出现异常...");}

运行日志如下:

dbcea56581280c5953c39d415545adc4347.jpg

可以看到,新建了两个事务,一个是 nested 嵌套事务,而且只是获取了一个数据库连接 418958713,在同一个连接中执行两条SQL,当 addAddress 出现异常进行回滚的时候,只是回滚到 savepoint 保存到,由于外层的 addUser 没有进行异常捕获,所以外部事务回滚,即回滚连接 418958713。

如果在外层进行异常捕获,则外层的 addUser 会插入成功嘛?

@Transactional(propagation = Propagation.REQUIRED)public void addUser(User user) throws RuntimeException {	String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";	Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};	jdbcTemplate.update(sql, args);	try {		addressService.addAddress(3, "shanghai");	}catch (Exception e){}}@Transactional(propagation = Propagation.NESTED)public void addAddress(int id, String name) throws RuntimeException{	String sql = "insert into address(id, name) values (?, ?)";	Object[] args = new Object[]{id, name};	jdbcTemplate.update(sql, args);	throw new RuntimeException("保存Address出现异常...");}

运行日志如下:

可以看到,内层的事务会回滚到 savepoint 保存点,而外层的事务则可以正常提交,结果就是 address 插入失败,user 插入成功。

现在如果内层的 addAddress 执行成功,不抛异常,外层的 addUser 抛异常,则 内层的 addAddress 会回滚嘛?

@Transactional(propagation = Propagation.REQUIRED)public void addUser(User user) throws RuntimeException {	String sql = "insert into user(name, age, sex, money, job) values (?, ?, ?, ?, ?)";	Object[] args = new Object[]{user.getName(), user.getAge(),user.getSex(),user.getMoney(), user.getJob()};	jdbcTemplate.update(sql, args);	addressService.addAddress(3, "shanghai");	throw new RuntimeException("保存User出现异常...");}@Transactional(propagation = Propagation.NESTED)public void addAddress(int id, String name) throws RuntimeException{	String sql = "insert into address(id, name) values (?, ?)";	Object[] args = new Object[]{id, name};	jdbcTemplate.update(sql, args);}

运行日志如下:

9357e66f1b458d7082832e3e8633e290de2.jpg

如果外部事务回滚了,内部事务也会回滚,因为它们属于同一个底层数据库的物理事务。

总结:嵌套事务,  它是已经存在事务的子事务. 嵌套事务开始执行时,  它将取得一个 savepoint. 如果这个嵌套事务失败, 将回滚到此 savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交. 使用 NESTED 有限制,它只支持 JDBC,且数据库要支持 savepoint 保存点,还要 JDBC 的驱动在3.0以上

SUPPORTS

supports,支持事务,如果没有事务,则以非事务的方式运行    

NOT_SUPPORTED

not_supported,不支持事务,以非事务的方式运行,如果存在事务,则挂起    

NEVER

never,不支持事务,如果存在事务,则抛出异常  

MANDATORY

mandatory,支持事务,如果没有事务,则抛出异常    

这几种传播方式比较好理解,就不把栗子贴出来了。

事务的隔离级别

事务的隔离级别使用 isolation 表示,它是一个枚举 Isolation

public enum Isolation {	DEFAULT // 默认的隔离级别,和底层数据库有关,MySQL 是可重复读(Repeated Read)	READ_UNCOMMITTED // 读未提交,允许脏读,可能读取到其他会话中未提交事务修改的数据	READ_COMMITTED // 读已提交,只能读取到已经提交的数据	REPEATABLE_READ // 可重复读,该隔离级别消除了不可重复读,但是还存在幻象读	SERIALIZABLE // 序列化,完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞}

总结

1. Spring 事务原理:

Spring 事务是通过 Spring AOP 来实现的,还可以通过 AOP 来自定义事务的行为。

2. 事务注解 @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 种

​​​​​​​    REQUIRED //required:需要事务,如果事务不存在,则创建一个新事务        REQUIRES_NEW //required_new:需要创建一个新事务,如果已存在事务,则把当前事务挂起        NESTED //nested:嵌套事务    SUPPORTS //supports:支持事务,如果没有事务,则以非事务的方式运行        NOT_SUPPORTED //not_supported:不支持事务,以非事务的方式运行,如果存在事务,则挂起        NEVER //never:不支持事务,如果存在事务,则抛出异常        MANDATORY //mandatory:支持事务,如果没有事务,则抛出异常    

REQUIRED : 必须要在事务中运行,不存在事务在,则创建一个,即使进行的异常的捕获,外部还是会进行回滚,这是因为虽然在每个方法都加上了事务注解,看起来是独立的事务,可是都会映射到底层数据库中的同一个物理事务中,所以只要有一个进行了回滚,则都会进行回滚。

REQUIRES_NEW 它是创建了一个新的事务进行运行,它们是完全独立的事务范围,对应到底层数据库的物理事务也是不同的,所以它们可以独立提交或回滚,外部事务不受内部事务的回滚状态的影响.

NESTED 嵌套事务,  它是已经存在事务的子事务. 嵌套事务开始执行时,  它将取得一个 savepoint. 如果这个嵌套事务失败, 将回滚到此 savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交. 使用 NESTED 有限制,它只支持 JDBC,且数据库要支持 savepoint 保存点,还要 JDBC 的驱动在3.0以上。

5. 事务的隔离级别

DEFAULT // 默认的隔离级别,和底层数据库有关,MySQL 是可重复读(Repeated Read)	READ_UNCOMMITTED // 读未提交,允许脏读,可能读取到其他会话中未提交事务修改的数据	READ_COMMITTED // 读已提交,只能读取到已经提交的数据	REPEATABLE_READ // 可重复读,该隔离级别消除了不可重复读,但是还存在幻象读	SERIALIZABLE // 序列化,完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

 

PS: 更多文章可以搜索公众号 “Java技术大杂烩” 进行关注查看

转载于:https://my.oschina.net/mengyuankan/blog/3000836

你可能感兴趣的文章
PLM产品技术的发展趋势 来源:e-works 作者:清软英泰 党伟升 罗先海 耿坤瑛
查看>>
vue part3.3 小案例ajax (axios) 及页面异步显示
查看>>
浅谈MVC3自定义分页
查看>>
.net中ashx文件有什么用?功能有那些,一般用在什么情况下?
查看>>
select、poll、epoll之间的区别总结[整理]【转】
查看>>
CSS基础知识(上)
查看>>
PHP中常见的面试题2(附答案)
查看>>
26.Azure备份服务器(下)
查看>>
mybatis学习
查看>>
LCD的接口类型详解
查看>>
Spring Boot Unregistering JMX-exposed beans on shutdown
查看>>
poi 导入导出的api说明(大全)
查看>>
Mono for Android 优势与劣势
查看>>
将图片转成base64字符串并在JSP页面显示的Java代码
查看>>
js 面试题
查看>>
sqoop数据迁移(基于Hadoop和关系数据库服务器之间传送数据)
查看>>
腾讯云下安装 nodejs + 实现 Nginx 反向代理
查看>>
Javascript 中的 Array 操作
查看>>
java中包容易出现的错误及权限问题
查看>>
AngularJS之初级Route【一】(六)
查看>>