1. 介绍
使用事务其实只用到了一个注解@Transactional
,这就是Spring
的注解式事务,@Transactional
这个注解可以标注在类或者方法上。当它标注在类上时,代表这个类所有公共(public
)非静态的方法都将启用事务功能。在@Transactional
中,还允许配置许多的属性,如事务的隔离级别和传播行为。
1.1 @Transactional 注解代码清单
package org.springframework.transaction.annotation;
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; Propagation propagation() default Propagation.REQUIRED; Isolation isolation() default Isolation.DEFAULT; int timeout() default -1; boolean readOnly() default false; Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {}; }
|
1.2 事务隔离级别
事务隔离级别是指若干个事务并发时的隔离程度,Spring
声明事务可以通过isolation
属性来设置Spring
的事务隔离级别。其中提供了以下5种事务隔离级别:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
: 读未提交,这是最低的事务隔离级别,允许其他事务读取未提交的数据,这种级别的事务隔离会产生脏读,不可重复读和幻读。
@Transactional(isolation = Isolation.READ_COMMITTED)
: 读已提交,这种级别的事务隔离能读取其他事务已经修改的数据,不能读取未提交的数据,会产生不可重复读和幻读。
@Transactional(isolation = Isolation.REPEATABLE_READ)
: 可重复读,这种级别的事务隔离可以防止不可重复读和脏读,但是会发生幻读。
@Transactional(isolation = Isolation.SERIALIZABLE)
: 串行化,这是最高级别的事务隔离,会避免脏读,不可重复读和幻读。在这种隔离级别下,事务会按顺序进行。
1.3 事务传播行为
事务传播行为是指如果多个事务同时存在,Spring就会处理这些事务的行为。Spring
声明事务可以通过propagation
属性来设置Spring
的事务隔离级别。事务传播行为分为如下几种:
PROPAGATION_REQUIRED
: 如果当前存在事务,就加入该事务;如果当前没有事务,就创建一个新的事务,这是Spring默认的事务传播行为。
PROPAGATION_REQUIRES_NEW
: 创建一个新的事务,如果当前存在事务,就把当前事务挂起。新建事务和被挂起的事务没有任何关系,是两个独立的事务。外层事务回滚失败时,不能回滚内层事务执行结果,内外层事务不能相互干扰。
PROPAGATION_SUPPORTS
: 如果当前存在事务,就加入该事务;如果当前没有事务,就以非事务的方式继续运行。
PROPAGATION_NOT_SUPPORTED
: 以非事务方式运行,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER
: 以非事务方式运行,如果当前存在事务,就抛出异常。
PROPAGATION_MANDATORY
: 如果当前存在事务,就加入该事务;如果当前没有事务,就抛出异常。
PROPAGATION_NESTED
: 如果当前存在事务,就创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,该取值就等价于PROPAGATION_REQUIRED
。
1.4 事务属性
Spring
事务不只拥有事务隔离级别和事务传播行为,另外还包含很多属性供开发者使用,分别说明如下 :
value
: 存放String类型的值,主要用来指定不同的事务管理器,满足在同一个系统中存在不同的事务管理器。比如在Spring容器中声明了多种事务管理器,然后开发者可以根据设置指定需要使用的事务管理器。通常一个系统需要访问多个数据库的场景下,就会设置多个事务管理器,然后进行不同的选择。
transactionManager
: 与value类似,也是用来选择事务管理器。
propagation
: 事务传播行为,默认值是Propagation.REQUIRED。
isolation
: 事务的隔离级别,默认值是Isolation.DEFAULT。
timeout
: 事务的超时时间,默认值是-1,如果超过了设置的时间还没有执行完成,就会自动回滚当前事务。
readOnly
: 当前事务是不是只读事务,默认值是false。通常可以设置读取数据的事务的属性值为true。
rollbackFor
: 可以设置触发事务的指定异常,允许指定多个类型的异常。
noRollbackFor
: 与rollbackFor相反,可以设置不触发事务的指定异常,允许指定多个类型的异常。
1.5 事务执行机制
2. 使用事务
2.1 创建用户服务接口
新建文件: UserService.java
package com.hui.javalearn.service;
import com.hui.javalearn.model.UserModel;
public interface UserService {
int insertUser(UserModel userModel); }
|
2.2 实现用户服务接口
新建文件: UserServiceImpl.java
package com.hui.javalearn.service.Impl;
import com.hui.javalearn.dao.UserDao; import com.hui.javalearn.model.UserModel;
@Service public class UserServiceImpl implements UserService {
@Autowired private UserDao userDao;
@Override @Transactional(isolation= Isolation.READ_COMMITTED, timeout = 1) public int insertUser(UserModel user) { return userDao.insertUser(user); } }
|
2.3 测试
package com.hui.javalearn;
import com.hui.javalearn.model.UserModel; import com.hui.javalearn.service.UserService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest public class testAffairs { @Autowired private UserService userService; @Test void test(){ UserModel userModel = new UserModel(); userModel.setNickName("昵称"); userModel.setPhone("17600000000"); userService.insertUser(userModel); } }
|
3. 事务失效原因
3.1 数据库引擎不支持事务
以 MySQL
为例,其MyISAM
引擎是不支持事务操作的,InnoDB
才是支持事务的引擎,一般要支持事务都会使用 InnoDB
。
3.2 没有被 Spring 管理
修改上面代码UserServiceImpl.java
注释注解@Service
如下:
package com.hui.javalearn.service.Impl;
import com.hui.javalearn.dao.UserDao; import com.hui.javalearn.model.UserModel;
public class UserServiceImpl implements UserService {
@Autowired private UserDao userDao;
@Override @Transactional(isolation= Isolation.READ_COMMITTED, timeout = 1) public int insertUser(UserModel user) { return userDao.insertUser(user); } }
|
此时把 @Service
注解注释掉,这个类就不会被加载成一个 Bean
,那这个类就不会被 Spring
管理了,事务自然就失效了。
3.3 方法不是Public
修改上面代码UserServiceImpl.java
把public
改为protected
package com.hui.javalearn.service.Impl;
import com.hui.javalearn.dao.UserDao; import com.hui.javalearn.model.UserModel;
@Service public class UserServiceImpl implements UserService {
@Autowired private UserDao userDao;
@Override @Transactional(isolation= Isolation.READ_COMMITTED, timeout = 1) protected int insertUser(UserModel user) { return userDao.insertUser(user); } }
|
@Transactional
只能用于 public 的方法上,否则事务不会失效
3.4 自身调用
@Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao;
@Override @Transactional public int insertUser(UserModel user) { return userDao.insertUser(user); }
public int testSelf(){ UserModel userModel = new UserModel(); userModel.setNickName("昵称"); userModel.setPhone("17600000000"); return insertUser(userModel); }
}
|
Spring
数据库事务的约定,其实现原理是AOP
,而AOP
的原理是动态代理,在自调用的过程中,是类自身的调用,而不是代理对象去调用,那么就不会产生AOP
,这样Spring
就不能把你的代码织入到约定的流程中,于是就产生了现在看到的失败场景。
3.5 没有抛出异常
在代码中使用try ... catch
,导致异常没有被抛出,则事务失效。如下代码:
@Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override @Transactional public int insertUser(UserModel user) { try{ return userDao.insertUser(user); } catch (Exception e) { return false; } } }
|
3.6 抛出异常类型错误
默认回滚的是:RuntimeException
,如果你想触发其他异常的回滚,需要使用指定属性rollbackFor
为具体错误的值。如下代码:
@Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override @Transactional(rollbackFor = Exception.class) public int insertUser(UserModel user) { return userDao.insertUser(user); } }
|