【Spring】_Spring事务与事务传播机制

目录

1. 创建项目、数据库及MyBatis配置

1.1 创建数据库及java实体类

 1.2 使用yml配置MyBatis

1.3 对应三层架构开发

2. Spring编程式事务

2.1 编写UserController类

2.2 接口测试

2.23关于事务回滚与事务提交的日志

3. Spring声明式事务

3.1 编写TransController类

3.2 接口测试

3.3 关于@Transactional实现事务回滚的情况

3.3.1 重新抛出异常

3.3.2  手动回滚事务


1. 创建项目、数据库及MyBatis配置

1.1 创建数据库及java实体类

创建数据库trans_test和两张数据表:user_info和log_Info:

DROP DATABASE IF EXISTS trans_test;
CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4;
-- 用户表
DROP TABLE IF EXISTS user_info;
CREATE TABLE user_info (
 `id` INT NOT NULL AUTO_INCREMENT,
 `user_name` VARCHAR (128) NOT NULL,
 `password` VARCHAR (128) NOT NULL,
 `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
 `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT charset= 'utf8mb4' COMMENT = '用户表';
-- 操作日志表
DROP TABLE IF EXISTS log_info;
CREATE TABLE log_info (
 `id` INT PRIMARY KEY auto_increment,
 `user_name` VARCHAR ( 128 ) NOT NULL,
 `op` VARCHAR ( 256 ) NOT NULL,
 `create_time` DATETIME DEFAULT now(),
 `update_time` DATETIME DEFAULT now() ON UPDATE now() 
) DEFAULT charset 'utf8mb4';

创建model包,并在其下分别创建UserInfo和LogInfo的java类对应数据表字段,其中user_name和create_time、update_time均需采用java实体类的小驼峰命名规范:

package com.bite.transaction.model;

import lombok.Data;

import java.util.Date;

@Data
public class UserInfo {
    private Integer id;
    private String userName;
    private String password;
    private Date createTime;
    private Date updateTime;
}
package com.bite.transaction.model;

import lombok.Data;

import java.util.Date;

@Data
public class LogInfo {
    private Integer id;
    private String userName;
    private String op;
    private Date createTime;
    private Date updateTime;
}

 1.2 使用yml配置MyBatis

# 端口配置
server:
  port: 8080
# 数据库连接配置
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/trans_test?characterEncoding=utf8&useSSL=false
    username: root
    password: xxxxxx
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  #配置打印MyBatis日志
    map-underscore-to-camel-case: true   #配置转换驼峰

1.3 对应三层架构开发

创建service包、mapper包,并在其下分别创建UserService、LogService类,以 及UserInfoMapper和LogInfoMapper接口。

编写思路相同,即service调用mapper。

以UserXXX相关信息为例,UserInfoMapper接口内容如下:

package com.bite.transaction.mapper;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface UserInfoMapper {
    @Insert("insert into user_info(user_name, password) values(#{userName},#{password})")
    Integer insert(@Param("userName") String userName,@Param("password") String password);
}

UserService类内容如下:

package com.bite.transaction.service;

import com.bite.transaction.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    public  Integer insertUser(String userName, String password){
        return userInfoMapper.insert(userName,password);
    }
}

编程式事务与声明式事务的不同体现在controller中,不同的事务类型将创建不同的Controller,稍后完成controller对service的调用。

2. Spring编程式事务

2.1 编写UserController类

Springboot有两个内置对象:

(1)DataSourceTransactionManager:

        表示一个事务管理器,用于开启事务、提交事务、回滚事务等; 

(2)TransactionDefinition:

        表示事务的属性,获取事务时需传递该对象从而获得一个事务TransactionStatus;

创建controller包,对于编程式事务,创建UserControlle类,编写UserController类内容:

package com.bite.transaction.controller;

import com.bite.transaction.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @Autowired
    // 定义事务管理器
    private DataSourceTransactionManager dataSourceTransactionManager;
    @Autowired
    // 定义事务属性
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/registry")
    public String registry(String userName, String password){
        // 开启事务
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        // 用户注册
        Integer result = userService.insertUser(userName,password);
        log.info("用户插入成功,result:{}"+result);
//        // 提交事务
//        dataSourceTransactionManager.commit(transaction);
        // 回滚事务
        dataSourceTransactionManager.rollback(transaction);
        return "注册成功";
    }
}

启动项目。

2.2 接口测试

1、仅保留执行事务回滚的代码,根据路由映射测试接口,以userName=zhangsan&password=123456作为参数:

postman响应处显示注册成功,查看数据库的user_info表,并未有数据更新:

【Spring】_Spring事务与事务传播机制_第1张图片

2、仅保留执行事务提交的代码,根据路由映射测试接口,以userName=lisi&password=123456作为参数:

postman响应处显示注册成功,查看数据库的user_info表:

【Spring】_Spring事务与事务传播机制_第2张图片

由于id作为主键采取了自增方式,而此时第二次执行insert操作后id为2,说明第一次执行insert也执行成功,但事务回滚,故数据表user_info中又删除了id=1的用户信息;

2.23关于事务回滚与事务提交的日志

上例中第二次执行(事务提交)的日志如下:

【Spring】_Spring事务与事务传播机制_第3张图片

使用userName=wangwu&password=123456作为参数根据路由映射再次测试接口,并进行事务提交,其日志如下: 

【Spring】_Spring事务与事务传播机制_第4张图片

可见相较于事务回滚,事务提交多了一条committing的日志

3. Spring声明式事务

3.1 编写TransController类

对于@Transactional注解进行声明式事务,其进行事务提交和回滚的原则如下:

(1)所在方法没有抛出异常时,事务就提交;

(2)所在方法抛出异常了(报错却没有捕获,或报错捕获却未处理)且异常为运行时异常,或Error时,事务就回滚;

(其他异常,包括NullPointerException等都不会回滚)

在controller包下创建TransController类用于实现声明式事务,编写TransController类内容如下:

package com.bite.transaction.controller;
import com.bite.transaction.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/trans")
public class TransController {
    @Autowired
    private UserService userService;
     /*
    * 未抛出异常(正常执行)时,进行事务提交*/
    @Transactional
    @RequestMapping("/registry")
    public String registry(String userName, String password){
        Integer result = userService.insertUser(userName, password);
        log.info("用户插入成功,result: "+result);
        return "注册成功";
    }

    /*
    * 抛出异常时且未处理,进行事务回滚*/
    @Transactional
    @RequestMapping("/registry2")
    public String testRegistryException(String userName, String password){
        Integer result = userService.insertUser(userName, password);
        int tmp =  10/0;
        log.info("用户插入成功,result: "+result);
        return "注册成功";
    }
    /*
    * 抛出异常但对异常进行捕获,进行事务提交*/
    @Transactional
    @RequestMapping("/registry3")
    public String testRegistryException2(String userName, String password){
        Integer result = userService.insertUser(userName, password);
        try{
            int tmp =  10/0;
        }catch(Exception e){
            log.info("程序出错");
        }
        log.info("用户插入成功,result: "+result);
        return "注册成功";
    }
}

启动项目。

3.2 接口测试

1、根据路由映射测试接口1,以userName=zhaoliu&password=123456作为参数。postman显示注册成功,查看控制台日志:

【Spring】_Spring事务与事务传播机制_第5张图片

由于存在committing日志,说明当前无异常时进行事务执行。

查看数据库的user_info表,可查看到新增的数据信息。

2、根据路由映射测试接口2,以userName=tiqnqi&password=123456作为参数。postman显示服务器内部错误,查看控制台日志:

【Spring】_Spring事务与事务传播机制_第6张图片

对应数据库的user_info数据表,也没有新增数据。即抛出异常时进行事务回滚。

3、根据路由映射测试接口3,以userName=aaa&password=123456作为参数。postman显示注册成功,查看控制台日志:

【Spring】_Spring事务与事务传播机制_第7张图片

由于存在committing日志,说明当前无异常时进行事务执行。

查看数据库的user_info表,可查看到新增的数据信息。

3.3 关于@Transactional实现事务回滚的情况

现已验证,使用@Transactional进行编程式事务管理时,当接口内部抛出运行时异常,或接口内部抛出异常并未捕获、抛出异常捕获但未正确处理时,都会导致事务回滚。

除了以上方式令事务回滚外,有两种方式可以进行操作。

3.3.1 重新抛出异常

package com.bite.transaction.controller;
import com.bite.transaction.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/trans")
public class TransController {
    @Autowired
    private UserService userService;

    /*
    * 异常捕获后重新抛出,进行事务回滚*/
    @Transactional
    @RequestMapping("/registry4")
    public String testRegistryException3(String userName, String password){
        Integer result = userService.insertUser(userName, password);
        try{
            int tmp =  10/0;
        }catch(Exception e){
            log.info("程序出错");
            throw e;
        }
        log.info("用户插入成功,result: "+result);
        return "注册成功";
    }
}

根据路由映射测试接口4,以userName=bbb&password=123456作为参数。postman显示服务器内部错误,查看控制台日志:

【Spring】_Spring事务与事务传播机制_第8张图片

对应数据库的user_info数据表,也没有新增数据。即捕获异常又重新抛出时进行事务回滚。

3.3.2  手动回滚事务

package com.bite.transaction.controller;
import com.bite.transaction.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/trans")
public class TransController {
    @Autowired
    private UserService userService;
    /*
     * 手动回滚事务*/
    @Transactional
    @RequestMapping("/registry5")
    public String testRegistryException4(String userName, String password){
        Integer result = userService.insertUser(userName, password);
        try{
            int tmp =  10/0;
        }catch(Exception e){
            log.info("程序出错");
            // 获取当前事务状态,令其回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        log.info("用户插入成功,result: "+result);
        return "注册成功";
    }
}

根据路由映射测试接口5,以userName=ccc&password=123456作为参数。postman显示注册成功,查看控制台日志:

【Spring】_Spring事务与事务传播机制_第9张图片

可见实现了手动事务回滚。

@Transactional对于事物的提交或回滚处理是按照完整的方法进行判断的。若方法中存在异常并未捕获,或异常捕获了并未处理时,则会认为方法出错,则进行回滚。

你可能感兴趣的:(Spring,JavaEE,数据库,sql)