Spring事务的介绍,以及基于注解@Transactional的声明式事务
ccwgpt 2024-11-25 10:23 85 浏览 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不会产生回滚效果
相关推荐
- 一个基于.Net Core遵循Clean Architecture原则开源架构
-
今天给大家推荐一个遵循CleanArchitecture原则开源架构。项目简介这是基于Asp.netCore6开发的,遵循CleanArchitecture原则,可以高效、快速地构建基于Ra...
- AI写代码翻车无数次,我发现只要提前做好这3步,bug立减80%
-
写十万行全是bug之后终于找到方法了开发"提示词管理助手"新版本那会儿,我差点被bug整崩溃。刚开始两周,全靠AI改代码架构,结果十万行程序漏洞百出。本来以为AI说没问题就稳了,结果...
- OneCode低代码平台的事件驱动设计:架构解析与实践
-
引言:低代码平台的事件驱动范式在现代软件开发中,事件驱动架构(EDA)已成为构建灵活、松耦合系统的核心范式。OneCode低代码平台通过创新性的注解驱动设计,将事件驱动理念深度融入平台架构,实现了业务...
- 国内大厂AI插件评测:根据UI图生成Vue前端代码
-
在IDEA中安装大厂的AI插件,打开ruoyi增强项目:yudao-ui-admin-vue31.CodeBuddy插件登录腾讯的CodeBuddy后,大模型选择deepseek-v3,输入提示语:...
- AI+低代码技术揭秘(二):核心架构
-
本文档介绍了为VTJ低代码平台提供支持的基本架构组件,包括Engine编排层、Provider服务系统、数据模型和代码生成管道。有关UI组件库和widget系统的信息,请参阅UI...
- GitDiagram用AI把代码库变成可视化架构图
-
这是一个名为gitdiagram的开源工具,可将GitHub仓库实时转换为交互式架构图,帮助开发者快速理解代码结构。核心功能一键可视化:替换GitHubURL中的"hub...
- 30天自制操作系统:第六天:代码架构整理与中断处理
-
1.拆开bootpack.c文件。根据设计模式将对应的功能封装成独立的文件。2.初始化pic:pic(可编程中断控制器):在设计上,cpu单独只能处理一个中断。而pic是将8个中断信号集合成一个中断...
- AI写代码越帮越忙?2025年研究揭露惊人真相
-
近年来,AI工具如雨后春笋般涌现,许多人开始幻想程序员的未来就是“对着AI说几句话”,就能轻松写出完美的代码。然而,2025年的一项最新研究却颠覆了这一期待,揭示了一个令人意外的结果。研究邀请了16位...
- 一键理解开源项目:两个自动生成GitHub代码架构图与说明书工具
-
一、GitDiagram可以一键生成github代码仓库的架构图如果想要可视化github开源项目:https://github.com/luler/reflex_ai_fast,也可以直接把域名替换...
- 5分钟掌握 c# 网络通讯架构及代码示例
-
以下是C#网络通讯架构的核心要点及代码示例,按协议类型分类整理:一、TCP协议(可靠连接)1.同步通信//服务器端usingSystem.Net.Sockets;usingTcpListene...
- 从复杂到优雅:用建造者和责任链重塑代码架构
-
引用设计模式是软件开发中的重要工具,它为解决常见问题提供了标准化的解决方案,提高了代码的可维护性和可扩展性,提升了开发效率,促进了团队协作,提高了软件质量,并帮助开发者更好地适应需求变化。通过学习和应...
- 低代码开发当道,我还需要学习LangChain这些框架吗?| IT杂谈
-
专注LLM深度应用,关注我不迷路前两天有位兄弟问了个问题:当然我很能理解这位朋友的担忧:期望效率最大化,时间用在刀刃上,“不要重新发明轮子”嘛。铺天盖地的AI信息轰炸与概念炒作,很容易让人浮躁与迷茫。...
- 框架设计并不是简单粗暴地写代码,而是要先弄清逻辑
-
3.框架设计3.框架设计本节我们要开发一个UI框架,底层以白鹭引擎为例。框架设计的第一步并不是直接撸代码,而是先想清楚设计思想,抽象。一个一个的UI窗口是独立的吗?不是的,...
- 大佬用 Avalonia 框架开发的 C# 代码 IDE
-
AvalonStudioAvalonStudio是一个开源的跨平台的开发编辑器(IDE),AvalonStudio的目标是成为一个功能齐全,并且可以让开发者快速使用的IDE,提高开发的生产力。A...
- 轻量级框架Lagent 仅需20行代码即可构建自己的智能代理
-
站长之家(ChinaZ.com)8月30日消息:Lagent是一个专注于基于LLM模型的代理开发的轻量级框架。它的设计旨在简化和提高这种模型下代理的开发效率。LLM模型是一种强大的工具,可以...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- mfc框架 (52)
- abb框架断路器 (48)
- beego框架 (52)
- java框架spring (58)
- grpc框架 (65)
- tornado框架 (48)
- 前端框架bootstrap (54)
- orm框架有哪些 (51)
- 知识框架图 (52)
- ppt框架 (55)
- 框架图模板 (59)
- 内联框架 (52)
- cad怎么画框架 (58)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)