事务
事务:将一组操作封装成一个执行单元(封装到一起),要么全部成功,要么全部失败。
Spring中事务的实现分为两类:
-
编程式(手动)操作事务
-
申明式自动提交事务
事务的使用:
--开启事务
start transaction;
--业务执行
...
--提交事务
commit
--回滚事务
rollback
编程式(手动)操作事务
-
开启事务(获取事务)
-
提交事务
-
回滚事务
上面的过程要依赖两个重要对象:DataSourceTransactionManager、TransactionDefinition
DataSourceTransactionManager用来获取事务(开启事务)、提交或回滚事务;
TransactionDefinition是事务的属性,在获取事务的时候需要将TransactionDefinition传递进去从而获得一个事务TransactionStatus
@RestController
public class UserController {
@Resource
private UserService userService;
@Autowired
private DataSourceTransactionManager transactionManager;
@Autowired
private TransactionDefinition transactionfinition;
//在此方法中使用编程式的事务
@RequestMapping("/add")
public int add(UserInfo userInfo){
//非空效验【验证用户名和密码不为空】
if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword()))return 0;
//开启事务(获取事务)
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionfinition);
int result = userService.add(userInfo);
System.out.println("add方法受影响的行数: "+ result);
//回滚事务
transactionManager.rollback(transactionStatus);
//提交事务
transactionManager.commit(transactionStatus);
return result;
}
}


申明式事务
申明式事务只需要在方法上添加@Transactional注解即可,该注解的作用是在方法之前,自动开启事务,在方法执行完之后,自动提交事务,如果出现异常,自动回滚
基于注解方式实现
@RestController
public class UserController {
@Resource
private UserService userService;
@Transactional
@RequestMapping("/add2")
public int add2(UserInfo userInfo){
//非空效验【验证用户名和密码不为空】
if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword()))return 0;
int result = userService.add(userInfo);
System.out.println("add 受影响的行数:"+result);
return result;
}
}

@RestController
public class UserController {
@Resource
private UserService userService;
@Transactional
@RequestMapping("/add2")
public int add2(UserInfo userInfo){
//非空效验【验证用户名和密码不为空】
if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword()))return 0;
int result = userService.add(userInfo);
System.out.println("add 受影响的行数:"+result);
int num = 10/0;
return result;
}
}


注意事项:
@Transactional可以用来修饰方法或类:
-
修饰方法时:需要注意只能应用到public方法上,否者不生效
-
修饰类时:表名该注解对该类中所有的public方法都生效
基于XML方式实现
环境准备
package com.atguigu.spring6.xmltx.controller;
import com.atguigu.spring6.xmltx.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class BookController {
@Autowired
private BookService bookService;
//买书的方法:图书id和用户id
public void buyBook(Integer bookId,Integer userId) {
//调用service方法
bookService.buyBook(bookId,userId);
}
}
package com.atguigu.spring6.xmltx.dao;
public interface BookDao {
//根据图书id查询图书价格
Integer getBookPriceByBookId(Integer bookId);
//更新图书表库存量 -1
void updateStock(Integer bookId);
//更新用户表用户余额 -图书价格
void updateUserBalance(Integer userId, Integer price);
}
package com.atguigu.spring6.xmltx.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//根据图书id查询价格
@Override
public Integer getBookPriceByBookId(Integer bookId) {
String sql = "select price from t_book where book_id=?";
Integer price = jdbcTemplate.queryForObject(sql, Integer.class, bookId);
return price;
}
//更新库存
@Override
public void updateStock(Integer bookId) {
String sql = "update t_book set stock=stock-1 where book_id=?";
jdbcTemplate.update(sql,bookId);
}
//更新用户表用户余额 -图书价格
@Override
public void updateUserBalance(Integer userId, Integer price) {
String sql = "update t_user set balance=balance-? where user_id=?";
jdbcTemplate.update(sql,price,userId);
}
}
package com.atguigu.spring6.xmltx.service;
public interface BookService {
//买书的方法:图书id和用户id
void buyBook(Integer bookId, Integer userId);
}
package com.atguigu.spring6.xmltx.service;
import com.atguigu.spring6.xmltx.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
//买书的方法:图书id和用户id
@Override
public void buyBook(Integer bookId, Integer userId) {
//根据图书id查询图书价格
Integer price = bookDao.getBookPriceByBookId(bookId);
//更新图书表库存量 -1
bookDao.updateStock(bookId);
//更新用户表用户余额 -图书价格
bookDao.updateUserBalance(userId,price);
}
}
创建Spring配置文件
开启组件扫描
创建数据源
创建JdbcTemplate,注入数据源
创建事务管理器,注入数据源
配置事务通知,设置事务相关属性
配置切入点表达式,把事务通知添加到方法上
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="com.atguigu.spring6.xmltx"></context:component-scan>
<!--数据源对象-->
<!--引入外部属性文件,创建数据源对象-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<!--事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<!--配置事务增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="update*" read-only="false" propagation="REQUIRED"></tx:method>
<tx:method name="buy*" read-only="false" propagation="REQUIRED"></tx:method>
</tx:attributes>
</tx:advice>
<!--配置切入点和通知使用的方法-->
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.atguigu.spring6.xmltx.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
</aop:config>
</beans>
package com.atguigu.spring6.xmltx;
import com.atguigu.spring6.xmltx.controller.BookController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(locations = "classpath:beans-xml.xml")
public class TestBookTx {
@Autowired
private BookController bookController;
@Test
public void testBuyBook() {
bookController.buyBook(1,1);
}
}
@Transactional参数说明
参数 | 作用 |
value | 当配置多个事务管理器时,可以使用该属性指定选择那个事务管理器 |
transactionManager | 当配置多个事务管理器时,可以使用该属性指定选择那个事务管理器 |
propagation | 表示当多个方法中出现了嵌套事物的时候,那么这些方法它的一个行为模式就叫做事务的传播行为,也叫事务的传播机制。默认值为Propagation.REQUIRED |
isolation | 事务的隔离级别,默认值为isolation.DEFAULT |
timeout | 事务的超时时间,默认值为-1,如果超过改时间限制但事务还没有完成,则会自动回滚事务 |
readOnly | 指定事务是否为只读事务,默认值为false;为了忽略那些不需要食物的方法,比如读取数据,可以设置read-only为true |
rollbackFor | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型 |
rollbackForClassName | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型 |
noRollbackFor | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型 |
noRollbackForClassName | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型 |
Spring事务的隔离级别
-
Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
-
Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
-
Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
-
Isolation.REPEDTABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)
-
Isolation.SERIALIZABLE:串行化,可以解决所有并发问题,但性能太低。
注意事项:
-
当Spring中设置了事务隔离级别和连接数据库(MySQL)事务隔离级别发生冲突的时候,那么以Spring的为准。
-
Spring中的事务隔离级别机制的实现是依靠连接数据库支持事务隔离级别为基础
解决事物不会自动回滚
对于捕获异常,事务是不会进行自动回滚的
@RestController
public class UserController {
@Transactional(isolation = Isolation.SERIALIZABLE)
@RequestMapping("/add3")
public int add3(UserInfo userInfo){
//非空效验【验证用户名和密码不为空】
if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword()))return 0;
int result = userService.add(userInfo);
System.out.println("add 受影响的行数:"+result);
try {
int num = 10/0;
} catch (Exception e) {
System.out.println(e.getMessage());
}
return result;
}
}


解决方案一:将异常重新抛出
@RestController
public class UserController {
@Resource
private UserService userService;
@Transactional(isolation = Isolation.SERIALIZABLE)
@RequestMapping("/add3")
public int add3(UserInfo userInfo){
//非空效验【验证用户名和密码不为空】
if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword()))return 0;
int result = userService.add(userInfo);
System.out.println("add 受影响的行数:"+result);
try {
int num = 10/0;
} catch (Exception e) {
System.out.println(e.getMessage());
throw e;
}
return result;
}
}


方案二:手动回滚事务,在方法中使用TransactionAspectSupport.currentTransactionStatus()可以以当前的事务,然后设置回滚方法setRollbackOnly就可以实现回滚了
@RestController
public class UserController {
@Transactional(isolation = Isolation.SERIALIZABLE)
@RequestMapping("/add4")
public int add4(UserInfo userInfo){
//非空效验【验证用户名和密码不为空】
if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword()))return 0;
int result = userService.add(userInfo);
System.out.println("add 受影响的行数:"+result);
try {
int num = 10/0;
} catch (Exception e) {
System.out.println(e.getMessage());
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
}
@Transactional的基本原理
@Transactional是基于实现Aop的动态代理来实现的,Aop的动态代理有两种实现。@Transactional在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果途中遇到异常,则会回滚事务。

Spring事务传播机制
定义:Spring事务传播机制定义了多个包含了事务的方法,相互调用时,事务是如何在这些方法间进行传递的。
事务传播机制解决的是一个事务在多个节点(方法)中传递的问题,如下图所示:

事务传播机制:
-
Propagation.REQUIRED:默认的事务传播级别,表示如果当前存在事务,则加入该事物;如果当前没有实物,则创建一个新的事务。
举例:如果有房,一起住,没有房一起赚钱买房子;
-
Propagation.SUPPORTS:如果当前存在事务,则加入该事物;如果当前没有事务,则以非实物的方式继续运行。
举例:如果有房一起住,没有房可以租房;
-
Propagation.MANDATORY:如果当前存在事务,则加入该事物;如果当前没有事务,则抛出异常。
举例:如果有房一起住,没有房则分手;
-
Propagation.REQUIRES_NEW:表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
举例:不住你的房子,必须买新房子;
-
Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
举例:不住你的房子,必须租房子;
-
Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常;
举例:必须一起租房子,你有房子就分手;
-
Propagation.NESTED:如果当前存在事务,者创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。
举例:有房子咱们以房子为根据地做点小生意,赚了钱很好继续发展,赔钱了至少还有房子。如果没房子也没关系,一起赚钱买房子;

Spring事务传播机制使用和各种场景演示
支持当前事务(REQUIRED)
设计:开启事务先插入一条用户数据,然后在执行日志报错
UserController实现代码:
@RestController
public class UserController {
@Resource
private UserService userService;
@Autowired
private LogService logService;
@Transactional(propagation = Propagation.REQUIRED)
@RequestMapping("/add5")
public int add5(UserInfo userInfo){
//当userinfo为空或者userinfo里面的getUsername()为空时或者userinfo里面的getUsername()为空时返回0
if(userInfo == null ||
!StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword())){
return 0;
}
int userResult = userService.add(userInfo);
System.out.println("添加用户:"+userResult);
LogInfo logInfo = new LogInfo();
logInfo.setName("添加用户");
logInfo.setDesc("添加用户结果:"+userResult);
int logResult = logService.add(logInfo);
return userResult;
}
}
UserService实现代码:
@Service
public class UserService {
@Resource
private UserMapper userMapper;
@Resource
private LogMapper logMapper;
@Transactional(propagation = Propagation.REQUIRED)
public int add(UserInfo userInfo){
int result = userMapper.add(userInfo);
return result;
}
}
LogService实现代码:
@Service
public class LogService {
@Resource
private LogMapper logMapper;
@Transactional(propagation = Propagation.REQUIRED)
public int add(LogInfo logInfo){
int number = 10/0;
return logMapper.add(logInfo);
}
}
执行结果:



程序报错,数据库没有插入任何数据。
UserService中的保存方法正常执行完成,LogService保存日志程序报错,因为使用的是Controller中的事务,所以整个事务回滚。数据库中没有插入任何数据,也就是步骤1中的用户插入方法也回滚了。
REQUIRED是默认传播机制
不支持当前事务(REQUIRES_NEW)
UserController类中的代码不变,将添加用户和添加日志的方法修改为REQUIRES_NEW不支持当前事务,重新创建事务
UserService实现代码:
@Service
public class UserService {
@Resource
private UserMapper userMapper;
@Resource
private LogMapper logMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int add(UserInfo userInfo){
int result = userMapper.add(userInfo);
return result;
}
}
LogService实现代码:
@Service
public class LogService {
@Resource
private LogMapper logMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int add(LogInfo logInfo){
int result = logMapper.add(logInfo);
System.out.println("添加日志结果: "+result);
int number = 10/0;
return result;
}
}
执行结果:



UserInfo表里面成功插入了数据,LogInfo表执行失败,但没有影响到UserController中的事务,也就是没有让UserController中的事务起作用。
不支持当前事务,NOT_SUPPORTED
UserController类中的代码不变,将添加用户和添加日志的方法修改为NOT_SUPPORTED不支持当前事务,重新创建事务
UserService实现代码:
@Service
public class UserService {
@Resource
private UserMapper userMapper;
@Resource
private LogMapper logMapper;
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public int add(UserInfo userInfo){
int result = userMapper.add(userInfo);
return result;
}
}
LogService实现代码:
@Service
public class LogService {
@Resource
private LogMapper logMapper;
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public int add(LogInfo logInfo){
int result = logMapper.add(logInfo);
System.out.println("添加日志结果: "+result);
int number = 10/0;
return result;
}
}
执行结果:



虽然UserController里面写了事务,但是在UserService和LogService里面它不支持事务,所以即使LogService里面发生了异常,它也不会回滚。
不支持当前事务,NEVER抛异常
UserController类中的代码不变,将添加用户和添加日志的方法修改为NEVER不支持当前事务,有事务将抛出异常
UserController实现代码:
@RestController
public class UserController {
@Resource
private UserService userService;
@Transactional
@RequestMapping("/add5")
public int add5(UserInfo userInfo){
//当userinfo为空或者userinfo里面的getUsername()为空时或者userinfo里面的getUsername()为空时返回0
if(userInfo == null ||
!StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword())){
return 0;
}
int userResult = userService.add(userInfo);
System.out.println("添加用户:"+userResult);
LogInfo logInfo = new LogInfo();
logInfo.setName("添加用户");
logInfo.setDesc("添加用户结果:"+userResult);
int logResult = logService.add(logInfo);
return userResult;
}
}
UserService实现代码:
@Service
public class UserService {
@Resource
private UserMapper userMapper;
@Resource
private LogMapper logMapper;
@Transactional(propagation = Propagation.NEVER)
public int add(UserInfo userInfo){
int result = userMapper.add(userInfo);
return result;
}
}
LogService实现代码:
@Service
public class LogService {
@Resource
private LogMapper logMapper;
@Transactional(propagation = Propagation.NEVER)
public int add(LogInfo logInfo){
int result = logMapper.add(logInfo);
System.out.println("添加日志结果: "+result);
int number = 10/0;
return result;
}
}
执行结果:



虽然UserController里面写了事务,但是在UserService和LogService里面它不支持事务,而且事务传播机制为NEVER,所以在执行的时候整个事务会抛出异常。
嵌套事务NESTED
UserController实现代码:
@RestController
public class UserController {
@Resource
private UserService userService;
@Autowired
private LogService logService;
@Transactional(propagation = Propagation.NESTED)
@RequestMapping("/add5")
public int add5(UserInfo userInfo){
//当userinfo为空或者userinfo里面的getUsername()为空时或者userinfo里面的getUsername()为空时返回0
if(userInfo == null ||
!StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword())){
return 0;
}
int userResult = userService.add(userInfo);
System.out.println("添加用户:"+userResult);
LogInfo logInfo = new LogInfo();
logInfo.setName("添加用户");
logInfo.setDesc("添加用户结果:"+userResult);
int logResult = logService.add(logInfo);
return userResult;
}
}
UserService实现代码:
@Service
public class UserService {
@Resource
private UserMapper userMapper;
@Resource
private LogMapper logMapper;
@Transactional(propagation = Propagation.NESTED)
public int add(UserInfo userInfo){
int result = userMapper.add(userInfo);
return result;
}
}
LogService实现代码:
@Service
public class LogService {
@Resource
private LogMapper logMapper;
@Transactional(propagation = Propagation.NESTED)
public int add(LogInfo logInfo){
int result = logMapper.add(logInfo);
System.out.println("添加日志结果: "+result);
try {
int number = 10/0;
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
}
执行结果:



嵌套事务的执行结果是:1.用户添加不受影响,添加用户成功了;2.日志添加失败,因为发生异常回滚了事务。嵌套事务他不影响代码的整体逻辑,成功就成功了,不成功它也让它不成功。
嵌套事务和加入事务的区别
嵌套事务可以实现部分事务回滚,之所以会这样是因为事务中有一个保存点,就像玩游戏时的存档一样,嵌套事务进入以后相当于存了一个档,而回滚时只能回滚到当前的存档中。加入事务不能实现部分回滚,只要某一个部分出错,整个调用链上的事务都会回滚。