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 {
// 存放String类型的值,主要用来指定不同的事务管理器
@AliasFor("transactionManager")
String value() default "";
// 与value作用一样
@AliasFor("value")
String transactionManager() default "";
// 事务传播行为
Propagation propagation() default Propagation.REQUIRED;
// 事务隔离级别,默认值,表示使用底层数据库的默认隔离级别
Isolation isolation() default Isolation.DEFAULT;
// 事务的超时时间,默认值是-1,如果超过了设置的时间还没有执行完成,就会自动回滚当前事务
int timeout() default -1;
// 当前事务是不是只读事务,默认值是false。通常可以设置读取数据的事务的属性值为true
boolean readOnly() default false;
// 可以设置触发事务的指定异常,允许指定多个类型的异常。
Class<? extends Throwable>[] rollbackFor() default {};

String[] rollbackForClassName() default {};
// 与rollbackFor相反,可以设置不触发事务的指定异常,允许指定多个类型的异常。
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 事务执行机制

image-20200922172340644

2. 使用事务

2.1 创建用户服务接口

新建文件: UserService.java

package com.hui.javalearn.service;

import com.hui.javalearn.model.UserModel;

/**
* @author liuqh
*/
public interface UserService {

/**
* 添加用户
* @param userRegisterParam
* @return
*/
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;

/**
* @author liuqh
*/
@Service
public class UserServiceImpl implements UserService {

@Autowired
private UserDao userDao;

@Override
// 开启事务,并设置事务隔离级别为: 读已提交; 事务的超时时间为 1 秒
@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;

// !!!注意,这里注释了@Service
// @Service
public class UserServiceImpl implements UserService {

@Autowired
private UserDao userDao;

@Override
// 开启事务,并设置事务隔离级别为: 读已提交; 事务的超时时间为 1 秒
@Transactional(isolation= Isolation.READ_COMMITTED, timeout = 1)
public int insertUser(UserModel user) {
return userDao.insertUser(user);
}
}

此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。

3.3 方法不是Public

修改上面代码UserServiceImpl.javapublic改为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
// 开启事务,并设置事务隔离级别为: 读已提交; 事务的超时时间为 1 秒
@Transactional(isolation= Isolation.READ_COMMITTED, timeout = 1)
// !!!!!注意这里方法修饰符是 protected
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
// 指定抛出Exception类型异常时回滚
@Transactional(rollbackFor = Exception.class)
public int insertUser(UserModel user) {
return userDao.insertUser(user);
}
}