Spring事务的介绍,以及基于注解@Transactional的声明式事务
ccwgpt 2024-11-25 10:23 75 浏览 0 评论
前言
事务是一个非常重要的知识点,前面的文章已经有介绍了关于SpringAOP代理的实现过程;事务管理也是AOP的一个重要的功能。
事务的基本介绍
数据库事务特性:
- 原子性
- 一致性
- 隔离性
- 持久性
事务的隔离级别
SQL 标准定义了四种隔离级别,MySQL 全都支持。这四种隔离级别分别是:
- 读未提交(READ UNCOMMITTED)
- 读已提交(READ COMMITTED)
- 可重复读(REPEATABLE READ)
- 串行化(SERIALIZABLE)
至于为什么会设定数据库的隔离级别,原因是由于在并发操作数据库的时候可能会引起脏读、不可重复读、幻读、第一类丢失更新、第二类更新丢失等现象。
脏读:
事物A读取事物B尚未提交的更改数据,并做了修改;此时如果事物B回滚,那么事物A读取到的数据是无效的,此时就发生了脏读。
不可重复读:
一个事务执行相同的查询两次或两次以上,每次都得到不同的数据。如:A事物下查询账户余额,此时恰巧B事物给账户里转账100元,A事物再次查询账户余额,那么A事物的两次查询结果是不一致的。
幻读:
A事物读取B事物提交的新增数据,此时A事物将出现幻读现象。幻读与不可重复读容易混淆,如何区分呢?幻读是读取到了其他事物提交的新数据,不可重复读是读取到了已经提交事物的更改数据(修改或删除)
第一类丢失更新现象:
撤销一个事务的时候,把其它事务已提交的更新数据覆盖了。这是完全没有事务隔离级别造成的。如果事务1被提交,另一个事务被撤销,那么会连同事务1所做的更新也被撤销。
第二类丢失更新现象:
它和不可重复读本质上是同一类并发问题,通常将它看成不可重复读的特例。当两个或多个事务查询相同的记录,然后各自基于查询的结果更新记录时会造成第二类丢失更新问题。每个事务不知道其它事务的存在,最后一个事务对记录所做的更改将覆盖其它事务之前对该记录所做的更改。
针对以上问题,其实可以有其它的解决方法,设置数据库隔离级别就是其中的一种,简单说一下数据库四个隔离级别的作用,见下表
简单总结:
- Read Uncommitted存在:脏读、不可重复读、第二类丢失更新和幻读问题。
- Read committed存在:不可重复读、第二类丢失更新和幻读问题。
- Repeatable Read存在:幻读问题。
- Serializable 不存在问题。
接下来我们看一下Spring支持事务的核心接口:
概要图:
TransactionDefinition
- 看源码(TransactionDefinition.java)
public interface TransactionDefinition {
/**
* 如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中。
*/
int PROPAGATION_REQUIRED = 0;
/**
* 支持当前事物,如果当前没有事物,则以非事物方式执行。
*/
int PROPAGATION_SUPPORTS = 1;
/**
* 使用当前事物,如果当前没有事物,则抛出异常
*/
int PROPAGATION_MANDATORY = 2;
/**
* 新建事物,如果当前已经存在事物,则挂起当前事物。
*/
int PROPAGATION_REQUIRES_NEW = 3;
/**
* 以非事物方式执行,如果当前存在事物,则挂起当前事物。
*/
int PROPAGATION_NOT_SUPPORTED = 4;
/**
* 以非事物方式执行,如果当前存在事物,则抛出异常。
*/
int PROPAGATION_NEVER = 5;
/**
* 如果当前存在事物,则在嵌套事物内执行;如果当前没有事物,则与PROPAGATION_REQUIRED传播特性相同
*/
int PROPAGATION_NESTED = 6;
/**
* 使用后端数据库默认的隔离级别。
*/
int ISOLATION_DEFAULT = -1;
/**
* READ_UNCOMMITTED 隔离级别
*/
int ISOLATION_READ_UNCOMMITTED = 1; // same as java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;
/**
* READ_COMMITTED 隔离级别
*/
int ISOLATION_READ_COMMITTED = 2; // same as java.sql.Connection.TRANSACTION_READ_COMMITTED;
/**
* REPEATABLE_READ 隔离级别
*/
int ISOLATION_REPEATABLE_READ = 4; // same as java.sql.Connection.TRANSACTION_REPEATABLE_READ;
/**
* SERIALIZABLE 隔离级别
*/
int ISOLATION_SERIALIZABLE = 8; // same as java.sql.Connection.TRANSACTION_SERIALIZABLE;
/**
* 默认超时时间
*/
int TIMEOUT_DEFAULT = -1;
/**
* 获取事物传播特性
* @return
*/
default int getPropagationBehavior() {
return PROPAGATION_REQUIRED;
}
/**
* 获取事物隔离级别
* @return
*/
default int getIsolationLevel() {
return ISOLATION_DEFAULT;
}
/**
* 获取事物超时时间
* @return
*/
default int getTimeout() {
return TIMEOUT_DEFAULT;
}
/**
* 判断事物是否可读
* @return
*/
default boolean isReadOnly() {
return false;
}
/**
* 获取事物名称
* @return
*/
@Nullable
default String getName() {
return null;
}
static TransactionDefinition withDefaults() {
return StaticTransactionDefinition.INSTANCE;
}
}
Spring事务传播行为
事务传播行为类型 | 说明 |
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
Spring支持的隔离级别
隔离级别 | 描述 |
DEFAULT | 使用数据库本身使用的隔离级别 ORACLE(读已提交) MySQL(可重复读) |
READ_UNCOMMITTED | 读未提交(脏读)最低的隔离级别,一切皆有可能。 |
READ_COMMITTEN | 读已提交,ORACLE默认隔离级别,有幻读以及不可重复读风险。 |
REPEATABLE_READ | 可重复读,解决不可重复读的隔离级别,但还是有幻读风险。MySQL默认隔离级别 |
SERLALIZABLE | 串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了 |
Spring事务基础结构中的中心接口(PlatformTransactionManager.JAVA)
- 看源码
/**
* Spring事务基础结构中的中心接口
*/
public interface PlatformTransactionManager extends TransactionManager {
/**
* 根据指定的传播行为,返回当前活动的事务或创建新事务。
* @param definition
* @return
* @throws TransactionException
*/
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
/**
* 就给定事务的状态提交给定事务
* @param status
* @throws TransactionException
*/
void commit(TransactionStatus status) throws TransactionException;
/**
* 执行给定事务的回滚
* @param status
* @throws TransactionException
*/
void rollback(TransactionStatus status) throws TransactionException;
}
- 源码分析Spring将事务管理委托给底层的持久化框架来完成,因此,Spring为不同的持久化框架提供了不同的PlatformTransactionManager接口实现类,我们看一下具体有哪些事务管理器:
简单说一下图中标注的几个事务管理器:
事务管理器 | 描述 |
DataSourceTransactionManager | 提供对单个javax.sql.DataSource事务管理,用于Spring JDBC抽象框架、iBATIS或MyBatis框架的事务管理 |
JpaTransactionManager | 提供对单个javax.persistence.EntityManagerFactory事务支持,用于集成JPA实现框架时的事务管理 |
JtaTransactionManager | 提供对分布式事务管理的支持,并将事务管理委托给Java EE应用服务器事务管理器 |
看完事务管理器,我们看一下概览图中的第三部分TransactionStatus事务状态描述接口类
Spring事务状态描述
- 看源码(TransactionStatus.java)
// 事务状态描述
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
/**
* 返回该事务是否在内部携带保存点,也就是说,已经创建为基于保存点的嵌套事务。
* @return
*/
boolean hasSavepoint();
/**
* 将会话刷新到数据存储区
*/
@Override
void flush();
}
继续看一下它的父类TransactionExecution.java 和SavepointManager.java
public interface TransactionExecution {
/**
* 返回当前事务是否为新事务(否则将参与到现有事务中,或者可能一开始就不在实际事务中运行)
* @return
*/
boolean isNewTransaction();
/**
* 设置事务仅回滚。
*/
void setRollbackOnly();
/**
* 返回事务是否已标记为仅回滚
* @return
*/
boolean isRollbackOnly();
/**
* 返回事物是否已经完成,无论提交或者回滚。
* @return
*/
boolean isCompleted();
}
public interface SavepointManager {
/**
* 创建一个新的保存点。
* @return
* @throws TransactionException
*/
Object createSavepoint() throws TransactionException;
/**
* 回滚到给定的保存点。
* 注意:调用此方法回滚到给定的保存点之后,不会自动释放保存点,
* 可以通过调用releaseSavepoint方法释放保存点。
* @param savepoint
* @throws TransactionException
*/
void rollbackToSavepoint(Object savepoint) throws TransactionException;
/**
* 显式释放给定的保存点。(大多数事务管理器将在事务完成时自动释放保存点)
* @param savepoint
* @throws TransactionException
*/
void releaseSavepoint(Object savepoint) throws TransactionException;
}
到这里Spring的事务相关概念已经大概介绍完了,我们先来熟悉一下Spring的编程式事务的应用实例:
Spring编程式事务
package com.vipbbo.spring.transaction;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.sql.DataSource;
public class MyTransaction {
private JdbcTemplate jdbcTemplate;
private DataSourceTransactionManager transactionManager;
private DefaultTransactionDefinition transactionDefinition;
private String insertSql = "insert into account (balance) values ('100')";
public void save(){
// 初始化jdbcTemplate
DataSource dataSource = getDataSource();
jdbcTemplate = new JdbcTemplate(dataSource);
// 创建事务管理器
transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
// 定义事务属性
transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 开启事务
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
// 执行业务逻辑
try {
jdbcTemplate.execute(insertSql);
//int i = 1/0;
jdbcTemplate.execute(insertSql);
transactionManager.commit(transactionStatus);
} catch (DataAccessException e) {
e.printStackTrace();
} catch (TransactionException e) {
transactionManager.rollback(transactionStatus);
e.printStackTrace();
}
}
public DataSource getDataSource(){
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://192.168.1.100:3306/spring_aop?" +
"useSSL=false&useUnicode=true&characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
}
测试类:
package com.vipbbo.spring.transaction;
import org.junit.jupiter.api.Test;
public class MyTransactionTest {
@Test
public void test1() {
MyTransaction myTransaction = new MyTransaction();
myTransaction.save();
}
}
分析
运行测试类,一旦放开int i = 1/0;这段代码,再抛出异常之后手动回滚事务,所以数据库表不会增加记录。
基于@Transactional注解的声明式事务
其底层建立在`AOP`的基础之上,对方法前后进行拦截,然后在目标方法开始之前创建一个或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。通过声明式事务,无需在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相应的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。
非XMl方式配置声明式事务
package com.vipbbo.spring.transaction;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Transactional(propagation = Propagation.REQUIRED)
public interface AccountByAnnotationService {
void save() throws RuntimeException;
}
实现类:
package com.vipbbo.spring.transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
/**
* 账户接口实现
*
* @author paidaxing
*/
@Service("accountByAnnotationService")
public class AccountByAnnotationServiceImpl implements AccountByAnnotationService {
@Autowired
private JdbcTemplate jdbcTemplate;
private static String insertSql = "insert into account(balance) values (100)";
@Override
public void save() throws RuntimeException {
System.out.println("======开始执行sql======");
jdbcTemplate.execute(insertSql);
System.out.println("======sql执行结束======");
System.out.println("======准备抛出异常======");
throw new RuntimeException("手动抛出异常");
}
}
注解开启声明式事务
配置类
package com.vipbbo.spring.config;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.net.ProtocolException;
@ComponentScan(basePackages = {"com.vipbbo"})
@Configuration
//开启基于注解的声明式事务
@EnableTransactionManagement
public class SpringConfig {
/**
* 注解数据源
* @return
* @throws ProtocolException
*/
@Bean
public DataSource dataSource() throws ProtocolException {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setUrl("jdbc:mysql://192.168.1.100:3306/spring_aop?" +
"useSSL=false&useUnicode=true&characterEncoding=UTF-8");
return dataSource;
}
/**
* 注册JdbcTemplate
* @return
* @throws ProtocolException
*/
@Bean
public JdbcTemplate jdbcTemplate() throws ProtocolException{
// 两种方式获取DataSOurce
//1. 直接在方法上放置参数 public JdbcTemplate jdbcTemplate(DataSource dataSource)
// 默认会去容器去取
// 2. 如下: 调用上面的方法
//spring对@Configuration类有特殊处理,注册组件的方法多次调用只是在IOC容器中找组件
return new JdbcTemplate(dataSource());
}
/**
* 注册事务管理器
* @return
* @throws ProtocolException
*/
@Bean
public PlatformTransactionManager transactionManager() throws ProtocolException{
//需要传入dataSource
return new DataSourceTransactionManager(dataSource());
}
}
测试代码类
package com.vipbbo.spring.transaction;
import com.vipbbo.spring.config.SpringConfig;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyTransactionByAnnotationTest {
@Test
public void test(){
ApplicationContext tx = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountByAnnotationService annotationService =
tx.getBean("accountByAnnotationService",AccountByAnnotationService.class);
annotationService.save();
}
}
测试类运行截图:
我们在上述实现类中手动抛出了一个异常,Spring会自动回滚事务,我们查看数据库可以知道并没有新增数据。。
注意重中之重
默认情况下Spring中的事务处理只对RuntimeException方法进行回滚,所以,如果此处将RuntimeException替换成普通的Exception不会产生回滚效果
相关推荐
- 团队管理“布阵术”:3招让你的团队战斗力爆表!
-
为何古代军队能够以一当十?为何现代企业有的团队高效似“特种部队”,有的却松散若“游击队”?**答案正隐匿于“布阵术”之中!**今时今日,让我们从古代兵法里萃取3个核心要义,助您塑造一支战斗力爆棚的...
- 知情人士回应字节大模型团队架构调整
-
【知情人士回应字节大模型团队架构调整】财联社2月21日电,针对原谷歌DeepMind副总裁吴永辉加入字节跳动后引发的团队调整问题,知情人士回应称:吴永辉博士主要负责AI基础研究探索工作,偏基础研究;A...
- 豆包大模型团队开源RLHF框架,训练吞吐量最高提升20倍
-
强化学习(RL)对大模型复杂推理能力提升有关键作用,但其复杂的计算流程对训练和部署也带来了巨大挑战。近日,字节跳动豆包大模型团队与香港大学联合提出HybridFlow。这是一个灵活高效的RL/RL...
- 创业团队如何设计股权架构及分配(创业团队如何设计股权架构及分配方案)
-
创业团队的股权架构设计,决定了公司在随后发展中呈现出的股权布局。如果最初的股权架构就存在先天不足,公司就很难顺利、稳定地成长起来。因此,创业之初,对股权设计应慎之又慎,避免留下巨大隐患和风险。两个人如...
- 消息称吴永辉入职后引发字节大模型团队架构大调整
-
2月21日,有消息称前谷歌大佬吴永辉加入字节跳动,并担任大模型团队Seed基础研究负责人后,引发了字节跳动大模型团队架构大调整。多名原本向朱文佳汇报的算法和技术负责人开始转向吴永辉汇报。简单来说,就是...
- 31页组织效能提升模型,经营管理团队搭建框架与权责定位
-
分享职场干货,提升能力!为职场精英打造个人知识体系,升职加薪!31页组织效能提升模型如何拿到分享的源文件:请您关注本头条号,然后私信本头条号“文米”2个字,按照操作流程,专人负责发送源文件给您。...
- 异形柱结构(异形柱结构技术规程)
-
下列关于混凝土异形柱结构设计的说法,其中何项正确?(A)混凝土异形柱框架结构可用于所有非抗震和抗震设防地区的一般居住建筑。(B)抗震设防烈度为6度时,对标准设防类(丙类)采用异形柱结构的建筑可不进行地...
- 职场干货:金字塔原理(金字塔原理实战篇)
-
金字塔原理的适用范围:金字塔原理适用于所有需要构建清晰逻辑框架的文章。第一篇:表达的逻辑。如何利用金字塔原理构建基本的金字塔结构受众(包括读者、听众、观众或学员)最容易理解的顺序:先了解主要的、抽象的...
- 底部剪力法(底部剪力法的基本原理)
-
某四层钢筋混凝土框架结构,计算简图如图1所示。抗震设防类别为丙类,抗震设防烈度为8度(0.2g),Ⅱ类场地,设计地震分组为第一组,第一自振周期T1=0.55s。一至四层的楼层侧向刚度依次为:K1=1...
- 结构等效重力荷载代表值(等效重力荷载系数)
-
某五层钢筋混凝土框架结构办公楼,房屋高度25.45m。抗震设防烈度8度,设防类别丙类,设计基本地震加速度0.2g,设计地震分组第二组,场地类别为Ⅱ类,混凝土强度等级C30。该结构平面和竖向均规则。假定...
- 体系结构已成昭告后世善莫大焉(体系构架是什么意思)
-
实践先行也理论已初步完成框架结构留余后人后世子孙俗话说前人栽树后人乘凉在夏商周大明大清民国共和前人栽树下吾之辈已完成结构体系又俗话说青出于蓝而胜于蓝各个时期任务不同吾辈探索框架结构体系经历有限肯定发展...
- 框架柱抗震构造要求(框架柱抗震设计)
-
某现浇钢筋混凝土框架-剪力墙结构高层办公楼,抗震设防烈度为8度(0.2g),场地类别为Ⅱ类,抗震等级:框架二级,剪力墙一级,混凝土强度等级:框架柱及剪力墙C50,框架梁及楼板C35,纵向钢筋及箍筋均采...
- 梁的刚度、挠度控制(钢梁挠度过大会引起什么原因)
-
某办公楼为现浇钢筋混凝土框架结构,r0=1.0,混凝土强度等级C35,纵向钢筋采用HRB400,箍筋采用HPB300。其二层(中间楼层)的局部平面图和次梁L-1的计算简图如图1~3(Z)所示,其中,K...
- 死要面子!有钱做大玻璃窗,却没有钱做“柱和梁”,不怕房塌吗?
-
活久见,有钱做2层落地大玻璃窗,却没有钱做“柱子和圈梁”,这样的农村自建房,安全吗?最近刷到个魔幻施工现场,如下图,这栋5开间的农村自建房,居然做了2个全景落地窗仔细观察,这2个落地窗还是飘窗,为了追...
- 不是承重墙,物业也不让拆?话说装修就一定要拆墙才行么
-
最近发现好多朋友装修时总想拆墙“爆改”空间,别以为只要避开承重墙就能随便砸!我家楼上邻居去年装修,拆了阳台矮墙想扩客厅,结果物业直接上门叫停。后来才知道,这种配重墙拆了会让阳台承重失衡,整栋楼都可能变...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- bootstrap框架 (43)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- laravel框架 (46)
- express框架 (43)
- scrapy框架 (52)
- beego框架 (42)
- java框架spring (43)
- grpc框架 (55)
- 前端框架bootstrap (42)
- orm框架有哪些 (43)
- ppt框架 (48)
- 内联框架 (52)
- winform框架 (46)
- gui框架 (44)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)