springboot+mybatis+mysql事务回滚

本文详细介绍了Spring Boot中基于@Transactional的事务管理,包括默认的异常回滚规则,如何处理Checked异常,以及如何设置回滚点和自定义回滚策略。示例代码展示了在Service和Controller中不同异常处理方式对事务的影响,强调了事务回滚必须在@Transactional作用域内。同时,文章探讨了事务的隔离级别和传播行为,并提供了多种解决异常回滚问题的方法。
摘要由CSDN通过智能技术生成

1.介绍

参考:

对于mysql本质上是利用数据库回滚,表的引擎要InnoDB

使用各个版本如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>
	<!-- 打包配置 -->
	<packaging>jar</packaging>
	<properties>
		<java.version>1.8</java.version>
		<mysql-driver.version>8.0.28</mysql-driver.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- 热部署 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional>
		</dependency>

		<!-- thymeleaf,for html,jsp... -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

		<!-- json -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.79</version>
		</dependency>

		<!-- mybatis -->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.2.2</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

		<!-- 数据库连接池druid -->
		<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.10</version>
		</dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

		<!--
			redis
			springboot 2.x:默认用lettuce实现的
			springboot 1.x:默认用Jedis实现的

			springboot 2.x改用jedis,如下代码
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-starter-data-redis</artifactId>
				<exclusions>
					排除lettuce包
					<exclusion>
						<groupId>io.lettuce</groupId>
						<artifactId>lettuce-core</artifactId>
					</exclusion>
				</exclusions>
			</dependency>
			添加jedis客户端
			<dependency>
				<groupId>redis.clients</groupId>
				<artifactId>jedis</artifactId>
			</dependency>
		-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<!-- redis使用pool -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-pool2</artifactId>
<!--			<version>2.11.1</version>-->
		</dependency>
    </dependencies>

	<build>
		<plugins>
			<!--
				运用SpringBoot 插件
				使用spring-boot-devtools模块的应用,
				当classpath中的文件有改变时,会自动重启!
			-->
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<!-- 不加版本号,会报红 -->
				<version>2.6.3</version>
			</plugin>

			<!-- mybatis-generator.start -->
			<plugin>
				<groupId>org.mybatis.generator</groupId>
				<artifactId>mybatis-generator-maven-plugin</artifactId>
				<version>1.3.5</version>
				<dependencies>
					<dependency>
						<groupId> mysql</groupId>
						<artifactId>mysql-connector-java</artifactId>
						<version>${mysql-driver.version}</version>
						<scope>runtime</scope>
					</dependency>
					<!--
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.3.5</version>
                    </dependency>
                    -->
				</dependencies>
				<!--
                <executions>
                    <execution>
                        <id>Generate MyBatis Artifacts</id>
                        <phase>package</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                -->
				<configuration>
					<!--允许移动生成的文件 -->
					<verbose>true</verbose>
					<!-- 是否覆盖 -->
					<!-- 此处要特别注意,如果不加这个设置会导致每次运行都会在原目录再次创建-->
					<overwrite>true</overwrite>
					<!-- generator 工具配置文件的位置 -->
					<configurationFile>
						src/main/resources/mybatis-generator.xml
					</configurationFile>
				</configuration>
			</plugin>
			<!-- mybatis-generator.end -->
		</plugins>
	</build>

</project>

2.默认情况,事务会在unchecked异常时回滚

2.1注意抛出异常要在@Transactional注解的类/方法中及其子方法

2.1.1Service

package com.demo.services;

import com.demo.models.mapper.AppadminMapper;
import com.demo.models.mapper.UserMapper;
import com.demo.models.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class UserService {

    //自动注入
    @Autowired
    private UserMapper userMapper;

    public User getUser(Integer id) {
        return userMapper.selectByPrimaryKey(id);
    }

    public int insertUser(User user) throws Exception {
        int count = userMapper.insert(user);
        if( count > 0){
            //这里抛出unchecked异常,会导致回滚
            throw new RuntimeException("测试uncheck异常导致回滚");
        }
        return count;
    }

    @Transactional
    public int insertUsers(List<User> userList) throws Exception {
        int count = 0;
        for(User user : userList){
            //调用子方法insertUser,将使用注解@Transactional定义的传播行为
            //insertUser方法没有定义传播行为,它会采用REQUIRED(默认)
            //也就是沿用当前的事务,所以它将与insertUsers方法使用同一个事务
            count += insertUser(user);
        }
        return count;
    }
}

2.1.2控制器

package com.demo.controllers;

import com.demo.models.po.User;
import com.demo.services.UserService;
import com.demo.utils.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.HashMap;

@RestController
@RequestMapping("/user")
public class UserController {
    //自动注入
    @Autowired
    private UserService service;
    
    /**
     * http://localhost:9000/demo/user/addlist
     * @param user
     * @return
     */
    @RequestMapping(value = "/addlist", method = RequestMethod.POST)
    public AjaxResult insertUsers(User user){
        try{
            int count = service.insertUsers(Arrays.asList(user));
            return AjaxResult.success("插入成功", new HashMap<String, Object>(){
                {
                    put("count", count);
                    put("user", user);
                }
            });
        }catch(Exception e){
            return AjaxResult.error(e.getMessage());
        }
    }
}

2.1.3测试类

package com.demo.controllers;

import com.alibaba.fastjson.JSON;
import com.demo.models.po.User;
import com.example.demo.DemoApplication;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;

import javax.servlet.http.Cookie;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.*;

/**
 * 想要执行的时候,鼠标放在对应的方法,右键选择run该方法即可。
 */
@RunWith(SpringRunner.class)
//不写classes会报错:you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test
//classes指向启动类
@SpringBootTest(classes= DemoApplication.class)
public class UserControllerTest {
    @Autowired
    private WebApplicationContext wac;

    private MockMvc mvc;
    private MockHttpSession session;

    /**
     * 开始前,模拟登录
     */
    @Before
    public void setupMockMvc(){
        //初始化MockMvc对象
        mvc = MockMvcBuilders.webAppContextSetup(wac).build();
        session = new MockHttpSession();
        //拦截器那边会判断用户是否登录,所以这里注入一个用户
        session.setAttribute("user", "root");
    }

    /**
     * 注意url:这里url不必加上/demo(配置文件的二级目录server.servlet.context-path=/demo)
     * @throws Exception
     */
    @Test
    public void insertUser() throws Exception {
        MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
        params.add("id", "4");
        params.add("name", "test4");
        params.add("birthday", "2020-1-4");
        params.add("address", "测试城市4");

        String uri = "/user/addlist";
        String result = mvc.perform(MockMvcRequestBuilders.post(uri)
                //模拟cookie
                .cookie( new Cookie("token", "123456") )
                //模拟session
                .session(session)
                //主流浏览器如谷歌符合正常规范,不需要设置字符编码了,但考虑非主流浏览器,仍然保留
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON_UTF8)
                .params(params)

//                .contentType(MediaType.APPLICATION_JSON)
//                .content(paramsJSON)  //传过去为Null????
          )
          .andExpect(MockMvcResultMatchers.status().is2xxSuccessful())
          .andDo(MockMvcResultHandlers.print())
          //返回请求的字符串信息
          .andReturn().getResponse().getContentAsString();


        Assert.assertEquals("调用成功","demo8-1",result);
    }

}

结果:回滚

2.2抛出异常不在@Transactional注解的类/方法中及其子方法

如果@Transactional在Service类,而在控制器抛出异常(不管是不是unchecked)都不会回滚

2.2.1控制器类

package com.demo.controllers;

import com.demo.models.po.User;
import com.demo.services.UserService;
import com.demo.utils.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.HashMap;

@RestController
@RequestMapping("/user")
public class UserController {
    //自动注入
    @Autowired
    private UserService service;
    
    /**
     * http://localhost:9000/demo/user/addlist
     * @param user
     * @return
     */
    @RequestMapping(value = "/addlist", method = RequestMethod.POST)
    public AjaxResult insertUsers(User user){
        try{
            int count = service.insertUsers(Arrays.asList(user));
            if( count>0 ){
                //不在注解方法及其子方法抛出异常,不会导致回滚
                throw new RuntimeException("测试uncheck异常不会导致回滚");
            }
            return AjaxResult.success("插入成功", new HashMap<String, Object>(){
                {
                    put("count", count);
                    put("user", user);
                }
            });
        }catch(Exception e){
            return AjaxResult.error(e.getMessage());
        }
    }
}

2.2.2Service

package com.demo.services;

import com.demo.models.mapper.AppadminMapper;
import com.demo.models.mapper.UserMapper;
import com.demo.models.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class UserService {

    //自动注入
    @Autowired
    private UserMapper userMapper;

    public User getUser(Integer id) {
        return userMapper.selectByPrimaryKey(id);
    }

    public int insertUser(User user) throws Exception {
        int count = userMapper.insert(user);
        return count;
    }

    @Transactional
    public int insertUsers(List<User> userList) throws Exception {
        int count = 0;
        for(User user : userList){
            //调用子方法insertUser,将使用注解@Transactional定义的传播行为
            //1.insertUser方法没有定义传播行为,它会采用REQUIRED(默认)
            //也就是沿用当前的事务,所以它将与insertUsers方法使用同一个事务
            count += insertUser(user);
        }
        return count;
    }
}

结果:不会回滚

3.默认情况下,遇到checked异常,则不会回滚

3.1Service

package com.demo.services;

import com.demo.models.mapper.AppadminMapper;
import com.demo.models.mapper.UserMapper;
import com.demo.models.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class UserService {

    //自动注入
    @Autowired
    private UserMapper userMapper;

    public User getUser(Integer id) {
        return userMapper.selectByPrimaryKey(id);
    }

    public int insertUser(User user) throws Exception {
        int count = userMapper.insert(user);
        if( count > 0){
            //check异常,不会导致回滚
            throw new Exception("测试check异常不会导致回滚");
        }
        return count;
    }

    @Transactional
    public int insertUsers(List<User> userList) throws Exception {
        int count = 0;
        for(User user : userList){
            //调用子方法insertUser,将使用注解@Transactional定义的传播行为
            //insertUser方法没有定义传播行为,它会采用REQUIRED(默认)
            //也就是沿用当前的事务,所以它将与insertUsers方法使用同一个事务
            count += insertUser(user);
        }
        return count;
    }
}

3.2控制器

package com.demo.controllers;

import com.demo.models.po.User;
import com.demo.services.UserService;
import com.demo.utils.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.HashMap;

@RestController
@RequestMapping("/user")
public class UserController {
    //自动注入
    @Autowired
    private UserService service;

    /**
     * http://localhost:9000/demo/user/addlist
     * @param user
     * @return
     */
    @RequestMapping(value = "/addlist", method = RequestMethod.POST)
    public AjaxResult insertUsers(User user){
        try{
            int count = service.insertUsers(Arrays.asList(user));
            return AjaxResult.success("插入成功", new HashMap<String, Object>(){
                {
                    put("count", count);
                    put("user", user);
                }
            });
        }catch(Exception e){
            return AjaxResult.error(e.getMessage());
        }
    }
}

结果:不会回滚

3.3解决方法1:设置回滚点

在@Transactional的类/方法及其子方法中设置回滚点,捕获checked异常后就回滚到回滚点

Service:

package com.demo.services;

import com.demo.models.mapper.AppadminMapper;
import com.demo.models.mapper.UserMapper;
import com.demo.models.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import java.util.List;

@Service
public class UserService {

    //自动注入
    @Autowired
    private UserMapper userMapper;

    public User getUser(Integer id) {
        return userMapper.selectByPrimaryKey(id);
    }

    public int insertUser(User user) throws Exception {
        //插入不会回滚的id=5
        User noRollUser = new User();
        noRollUser.setId(5);
        noRollUser.setName("测试5");
        userMapper.insert(noRollUser);
        //设置回滚点
        Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
        try{
            int count = userMapper.insert(user);
            if( count > 0){
//            throw new RuntimeException("测试uncheck异常导致回滚");
                throw new Exception("测试check异常不会导致回滚");
            }
            return count;
        }catch(Exception e){
            //捕获异常后,回滚到savePoint
            TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
            throw e;
        }
    }

    @Transactional
    public int insertUsers(List<User> userList) throws Exception {
        int count = 0;
        for(User user : userList){
            //调用子方法insertUser,将使用注解@Transactional定义的传播行为
            //insertUser方法没有定义传播行为,它会采用REQUIRED(默认)
            //也就是沿用当前的事务,所以它将与insertUsers方法使用同一个事务
            count += insertUser(user);
        }
        return count;
    }
}

 结果:只回滚了4,没回滚5

3.4如果是unchecked异常,也会到回滚点,而不是全部

package com.demo.services;

import com.demo.models.mapper.AppadminMapper;
import com.demo.models.mapper.UserMapper;
import com.demo.models.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import java.util.List;

@Service
public class UserService {

    //自动注入
    @Autowired
    private UserMapper userMapper;

    public User getUser(Integer id) {
        return userMapper.selectByPrimaryKey(id);
    }

    public int insertUser(User user) throws Exception {
        //插入不会滚的id=5
        User noRollUser = new User();
        noRollUser.setId(5);
        noRollUser.setName("测试5");
        userMapper.insert(noRollUser);
        //设置回滚点
        Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
        try{
            int count = userMapper.insert(user);
            if( count > 0){
                throw new RuntimeException("测试uncheck异常导致回滚");
//                throw new Exception("测试check异常不会导致回滚");
            }
            return count;
        }catch(Exception e){
            //捕获异常后,回滚到savePoint
            TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
            throw e;
        }
    }

    @Transactional
    public int insertUsers(List<User> userList) throws Exception {
        int count = 0;
        for(User user : userList){
            //调用子方法insertUser,将使用注解@Transactional定义的传播行为
            //insertUser方法没有定义传播行为,它会采用REQUIRED(默认)
            //也就是沿用当前的事务,所以它将与insertUsers方法使用同一个事务
            count += insertUser(user);
        }
        return count;
    }
}

结果:只回滚了4,没回滚5

 

3.5解决方法2:@Transactional的rollbackFor

直接抛出异常,这里不能捕获异常,否则会让rollbackFor失效

Service

package com.demo.services;

import com.demo.models.mapper.AppadminMapper;
import com.demo.models.mapper.UserMapper;
import com.demo.models.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import java.util.List;

@Service
public class UserService {

    //自动注入
    @Autowired
    private UserMapper userMapper;

    public User getUser(Integer id) {
        return userMapper.selectByPrimaryKey(id);
    }

    public int insertUser(User user) throws Exception {
         int count = userMapper.insert(user);
         if( count > 0){
            throw new Exception("测试check异常不会导致回滚");
         }
         return count;
    }

    @Transactional(
            //隔离级别:
            //一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读
            isolation = Isolation.READ_COMMITTED,
            //传播行为:
            // REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
            propagation = Propagation.REQUIRED,
            //指定异常类
            rollbackFor = Exception.class  //{RuntimeException.class, Exception.class}
    )
    public int insertUsers(List<User> userList) throws Exception {
        int count = 0;
        for(User user : userList){
            //调用子方法insertUser,将使用注解@Transactional定义的传播行为
            //1.insertUser方法没有定义传播行为,它会采用REQUIRED,
            //也就是沿用当前的事务,所以它将与insertUsers方法使用同一个事务
            count += insertUser(user);
        }
        return count;
    }
}

结果:会回滚

3.6 解决方法3:捕获异常,手动回滚

3.6.1 注意这里要在@Transactional的类/方法及其子方法中捕获并处理,在之外无效

3.6.2 捕获异常不再抛出时,rollbackFor指定了也不会回滚

注意这里无论是checked还是unchecked异常,只要捕获了不再抛出,都不会回滚

Service

package com.demo.services;

import com.demo.models.mapper.AppadminMapper;
import com.demo.models.mapper.UserMapper;
import com.demo.models.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import java.util.List;

@Service
public class UserService {

    //自动注入
    @Autowired
    private UserMapper userMapper;

    public User getUser(Integer id) {
        return userMapper.selectByPrimaryKey(id);
    }

    public int insertUser(User user) throws Exception {
        try{
            int count = userMapper.insert(user);
            if( count > 0){
                //throw new RuntimeException("测试uncheck异常导致回滚");
                throw new Exception("测试check异常不会导致回滚");
            }
            return count;
        }catch(Exception e){
            //要处理回滚,不然即使是unchecked也无效
            return 0;
        }
    }

    @Transactional(
            //隔离级别:
            //一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读
            isolation = Isolation.READ_COMMITTED,
            //传播行为:
            // REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
            propagation = Propagation.REQUIRED,
            //指定异常类
            rollbackFor = Exception.class
    )
    public int insertUsers(List<User> userList) throws Exception {
        int count = 0;
        for(User user : userList){
            //调用子方法insertUser,将使用注解@Transactional定义的传播行为
            //1.insertUser方法没有定义传播行为,它会采用REQUIRED,
            //也就是沿用当前的事务,所以它将与insertUsers方法使用同一个事务
            count += insertUser(user);
        }
        return count;
    }
}

结果:不会回滚

 

3.6.3 正确用法:在捕获异常后手动回滚

package com.demo.services;

import com.demo.models.mapper.AppadminMapper;
import com.demo.models.mapper.UserMapper;
import com.demo.models.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import java.util.List;

@Service
public class UserService {

    //自动注入
    @Autowired
    private UserMapper userMapper;

    public User getUser(Integer id) {
        return userMapper.selectByPrimaryKey(id);
    }

    public int insertUser(User user) throws Exception {
        try{
            int count = userMapper.insert(user);
            if( count > 0){
                //throw new RuntimeException("测试uncheck异常导致回滚");
                throw new Exception("测试check异常不会导致回滚");
            }
            return count;
        }catch(Exception e){
            //结合@Transactional(rollbackFor = Exception.class)使用,才会回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return 0;
        }
    }

    @Transactional(
            //隔离级别:
            //一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读
            isolation = Isolation.READ_COMMITTED,
            //传播行为:
            // REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
            propagation = Propagation.REQUIRED,
            //指定异常类
            rollbackFor = Exception.class //{RuntimeException.class, Exception.class}
    )
    public int insertUsers(List<User> userList) throws Exception {
        int count = 0;
        for(User user : userList){
            //调用子方法insertUser,将使用注解@Transactional定义的传播行为
            //1.insertUser方法没有定义传播行为,它会采用REQUIRED,
            //也就是沿用当前的事务,所以它将与insertUsers方法使用同一个事务
            count += insertUser(user);
        }
        return count;
    }
}

 结果:会回滚

4.事务属性设置

@Transactional(
            //隔离级别:
            //一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读
            isolation = Isolation.READ_COMMITTED,
            //传播行为:
            // REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
            propagation = Propagation.REQUIRED
            // SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
//            propagation = Propagation.SUPPORTS
    )
    public int insertUsers(List<User> userList) throws Exception {
        int count = 0;
        for(User user : userList){
            //调用子方法insertUser,将使用注解@Transactional定义的传播行为
            //1.insertUser方法没有定义传播行为,它会采用REQUIRED,
            //也就是沿用当前的事务,所以它将与insertUsers方法使用同一个事务
            //2.SUPPORTS:insertUser方法没有定义传播行为,跟着采用SUPPORTS
            count += insertUser(user);
        }
        return count;
    }

 

 

5.总结

事务的回滚,一定在@Transactional的类/方法及其子方法中,在外面是无效的,所以一般异常捕获可以放在外面

为了同时回滚unchecked和checked异常,在@Transactional范围内,可以采用:

1.捕获异常+设置回滚点

2.捕获异常+rollbackFor+手动回滚setRollbackOnly

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值