Spring Boot With Transaction

Spring Boot中,配置一套使用MySQL的环境非常简单,而且当我们使用了spring-boot-starter-jdbc的时候,Spring Boot会自动注入DataSourceTransactionManager启用帮助配置数据库事务相关的类。

Maven配置

pom.xml中添加



        
        
            org.springframework.boot
            spring-boot-starter-web
            
                
                    org.springframework.boot
                    spring-boot-starter-tomcat
                
            
            ${spring-boot.version}
        
        
            org.springframework.boot
            spring-boot-starter-undertow
            ${spring-boot.version}
        

        
            org.springframework.boot
            spring-boot-starter-jdbc
            ${spring-boot.version}
        

        
            mysql
            mysql-connector-java
        

        

        
            org.springframework.boot
            spring-boot-starter-test
            test
            ${spring-boot.version}
        


然后构建一个主类Application.java:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        new SpringApplicationBuilder().sources(Application.class).run(args);
    }
}

application.properties中添加mysql相关的配置

spring.datasource.url=jdbc:mysql://localhost:3306/spring_demo
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

这个时候,Spring Boot已经默认添加好了事务相关的配置,@Transactional注解标记的方法或类就会被加上事务。

事务名词

在解Spring中事务的概念主要是4个隔离级别7个传播行为

隔离级别

隔离级别是指若干个并发的事务之间的隔离程度。

我们开发时候主要相关的场景包括:

  • 脏读取:指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
  • 重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。在第一个事务中的两 次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
  • 幻读:是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。

所以在Spring中提供了这四种隔离级别:

public enum Isolation {
    // 底层数据库默认的隔离等级
    DEFAULT(-1),
    // 一个事务可以读取另一个事务修改但还没有提交的数据。
    READ_UNCOMMITTED(1),
    // 该隔离级别表示一个事务只能读取另一个事务已经提交的数据。
    READ_COMMITTED(2),
    // 该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。
    REPEATABLE_READ(4),
    // 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
    SERIALIZABLE(8);

    private final int value;

    private Isolation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}

传播行为

传播行为指的是如果在开始当前事务之前,一个事务上下文(Context)已经存在,此时有若干选项可以指定一个事务性方法的执行行为。

这里打一个比方:

@Transactional(propagation = Propagation[A])
class ServiceA{
    public void insertOne(){
        // code statement
    } 
}

@Transactional(propagation = Propagation[B])
class ServiceB{
    public void insertMany(list){
        
        // 循环调用
        for(int i;i

ServiceBinsertMany()方法中已经存在Propagation[B]事务的上下文,ServiceAinsertOne()方法在insertMany()中被执行,那么它需要如何处理自己的方法(也就是insertOne()方法)中的事务问题。

在Spring中,提供了以下几种事务传播行为:

public enum Propagation {

    // 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
    // @Transactional 注解默认采用这个方案
    REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

    // 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
    SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

    // 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
    MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

     // 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
    REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

    // 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
    NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

    // 以非事务方式运行,如果当前存在事务,则抛出异常。
    NEVER(TransactionDefinition.PROPAGATION_NEVER),

    // 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED
    // 并非所有的TransactionManager都能支持这个传播级别
    NESTED(TransactionDefinition.PROPAGATION_NESTED);

    private final int value;

    Propagation(int value) { this.value = value; }

    public int value() { return this.value; }

}

一般而言,被调用方法上定义的传播行为是高于调用方法的传播行为的,也就是上面例子中,insertOne()在这个例子中的传播行为高于insertMany()

构建测试方法

通过构建一个简单的应用,可以比较清晰地了解以上所说的是4个隔离级别7个传播行为

MySQL中定义一张用户表:

CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(11) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

java中对应构建一个实体类(UserEntity):

public class UserEntity {

    private Integer id;

    private String name;

    private Integer age;

    public UserEntity(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

定义一个UserService,然后在insertMany()insertOne()方法上分别定义事务的级别:

@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 添加多条数据
     * 
     * @param entities
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertMany(List entities) {

        String sql = "insert into user (name, age) values(?, ?)";

        final List paramList = entities.stream().map(userEntity -> {

            Object arg1 = userEntity.getName();
            Object arg2 = userEntity.getAge();

            Object objects[] = { arg1, arg2 };

            return objects;

        }).collect(Collectors.toList());

        jdbcTemplate.batchUpdate(sql, paramList);

    }
    
    /**
     * 模拟方法的的反复调用
     * 
     * @param entities
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertManyOneByOne(List entities) {

        for (int i = 0; i < entities.size(); i++) {

            // 假设到第三个用户插入的时候发生异常
            insertOne(entities.get(i),i == 3);
        }

    }

    /**
     * 添加单个数据
     * 
     * @param userEntity
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertOne(UserEntity userEntity, boolean isException) {

        if (isException){
            throw new RuntimeException();
        }

        String sql = "insert into user (name, age) values(?, ?)";

        jdbcTemplate.update(sql, userEntity.getName(), userEntity.getAge());

    }

}

测试代码:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class UserServiceTest extends ApplicationTest {

    @Autowired
    private UserService userService;

    @Test
    public void insertTest() {

        List userEntityList = new ArrayList<>(10);

        userEntityList.add(new UserEntity("AAA",20));
        userEntityList.add(new UserEntity("BBB",21));
        userEntityList.add(new UserEntity("CCC",22));
        userEntityList.add(new UserEntity("DDD",23));
        userEntityList.add(new UserEntity("EEE",24));
        userEntityList.add(new UserEntity("FFF",25));

        userService.insertManyOneByOne(userEntityList);

    }

}

通过更个UserService中方法的事务级别,不断地换跑测试方法,就能测试Spring中的事务配置了。

你可能感兴趣的:(Spring Boot With Transaction)