From ef784196ba891fe0e328aae5a7c8aa0774686dd6 Mon Sep 17 00:00:00 2001 From: linlei Date: Tue, 28 May 2024 15:22:28 +0800 Subject: [PATCH] =?UTF-8?q?spring=E4=BA=8B=E5=8A=A1=E6=BA=90=E7=A0=81?= =?UTF-8?q?=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 36 +- pom.xml | 12 + spring-transaction/pom.xml | 30 + .../spring-transaction-connection/README.md | 174 ++++ .../spring-transaction-connection/pom.xml | 14 + .../java/com/xcs/spring/ConnectionDemo.java | 50 + .../spring-transaction-dataSource/README.md | 189 ++++ .../spring-transaction-dataSource/pom.xml | 14 + .../java/com/xcs/spring/DataSourceDemo.java | 44 + .../README.md | 90 ++ .../spring-transaction-driverManager/pom.xml | 14 + .../com/xcs/spring/DriverManagerDemo.java | 41 + .../README.md | 496 ++++++++++ .../pom.xml | 14 + .../main/java/com/xcs/spring/AppConfig.java | 42 + .../EnableTransactionManagementDemo.java | 15 + .../java/com/xcs/spring/ScoresService.java | 5 + .../com/xcs/spring/ScoresServiceImpl.java | 28 + .../spring-transaction-jdbcTemplate/README.md | 271 ++++++ .../spring-transaction-jdbcTemplate/pom.xml | 14 + .../java/com/xcs/spring/JdbcTemplateDemo.java | 33 + .../src/main/java/com/xcs/spring/Scores.java | 32 + .../README.md | 865 ++++++++++++++++++ .../pom.xml | 14 + .../PlatformTransactionManagerDemo.java | 84 ++ .../README.md | 250 +++++ .../pom.xml | 14 + .../java/com/xcs/spring/ScoresService.java | 5 + .../com/xcs/spring/ScoresServiceImpl.java | 19 + ...SpringTransactionAnnotationParserDemo.java | 20 + .../README.md | 331 +++++++ .../pom.xml | 14 + .../java/com/xcs/spring/ScoresService.java | 5 + .../com/xcs/spring/ScoresServiceImpl.java | 19 + .../TransactionAttributeSourceDemo.java | 21 + .../README.md | 345 +++++++ .../pom.xml | 14 + .../xcs/spring/TransactionDefinitionDemo.java | 31 + .../README.md | 375 ++++++++ .../pom.xml | 14 + .../java/com/xcs/spring/ScoresService.java | 5 + .../com/xcs/spring/ScoresServiceImpl.java | 30 + .../spring/TransactionInterceptorDemo.java | 50 + .../README.md | 164 ++++ .../pom.xml | 14 + .../xcs/spring/TransactionTemplateDemo.java | 52 ++ spring-transaction/sql/test.sql | 20 + 47 files changed, 4422 insertions(+), 11 deletions(-) create mode 100644 spring-transaction/pom.xml create mode 100644 spring-transaction/spring-transaction-connection/README.md create mode 100644 spring-transaction/spring-transaction-connection/pom.xml create mode 100644 spring-transaction/spring-transaction-connection/src/main/java/com/xcs/spring/ConnectionDemo.java create mode 100644 spring-transaction/spring-transaction-dataSource/README.md create mode 100644 spring-transaction/spring-transaction-dataSource/pom.xml create mode 100644 spring-transaction/spring-transaction-dataSource/src/main/java/com/xcs/spring/DataSourceDemo.java create mode 100644 spring-transaction/spring-transaction-driverManager/README.md create mode 100644 spring-transaction/spring-transaction-driverManager/pom.xml create mode 100644 spring-transaction/spring-transaction-driverManager/src/main/java/com/xcs/spring/DriverManagerDemo.java create mode 100644 spring-transaction/spring-transaction-enableTransactionManagement/README.md create mode 100644 spring-transaction/spring-transaction-enableTransactionManagement/pom.xml create mode 100644 spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/AppConfig.java create mode 100644 spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/EnableTransactionManagementDemo.java create mode 100644 spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/ScoresService.java create mode 100644 spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/ScoresServiceImpl.java create mode 100644 spring-transaction/spring-transaction-jdbcTemplate/README.md create mode 100644 spring-transaction/spring-transaction-jdbcTemplate/pom.xml create mode 100644 spring-transaction/spring-transaction-jdbcTemplate/src/main/java/com/xcs/spring/JdbcTemplateDemo.java create mode 100644 spring-transaction/spring-transaction-jdbcTemplate/src/main/java/com/xcs/spring/Scores.java create mode 100644 spring-transaction/spring-transaction-platformTransactionManager/README.md create mode 100644 spring-transaction/spring-transaction-platformTransactionManager/pom.xml create mode 100644 spring-transaction/spring-transaction-platformTransactionManager/src/main/java/com/xcs/spring/PlatformTransactionManagerDemo.java create mode 100644 spring-transaction/spring-transaction-springTransactionAnnotationParser/README.md create mode 100644 spring-transaction/spring-transaction-springTransactionAnnotationParser/pom.xml create mode 100644 spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/ScoresService.java create mode 100644 spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/ScoresServiceImpl.java create mode 100644 spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/SpringTransactionAnnotationParserDemo.java create mode 100644 spring-transaction/spring-transaction-transactionAttributeSource/README.md create mode 100644 spring-transaction/spring-transaction-transactionAttributeSource/pom.xml create mode 100644 spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/ScoresService.java create mode 100644 spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/ScoresServiceImpl.java create mode 100644 spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/TransactionAttributeSourceDemo.java create mode 100644 spring-transaction/spring-transaction-transactionDefinition/README.md create mode 100644 spring-transaction/spring-transaction-transactionDefinition/pom.xml create mode 100644 spring-transaction/spring-transaction-transactionDefinition/src/main/java/com/xcs/spring/TransactionDefinitionDemo.java create mode 100644 spring-transaction/spring-transaction-transactionInterceptor/README.md create mode 100644 spring-transaction/spring-transaction-transactionInterceptor/pom.xml create mode 100644 spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/ScoresService.java create mode 100644 spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/ScoresServiceImpl.java create mode 100644 spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/TransactionInterceptorDemo.java create mode 100644 spring-transaction/spring-transaction-transactionTemplate/README.md create mode 100644 spring-transaction/spring-transaction-transactionTemplate/pom.xml create mode 100644 spring-transaction/spring-transaction-transactionTemplate/src/main/java/com/xcs/spring/TransactionTemplateDemo.java create mode 100644 spring-transaction/sql/test.sql diff --git a/README.md b/README.md index c0cebdb..deef04a 100644 --- a/README.md +++ b/README.md @@ -239,17 +239,32 @@ - [ExposeInvocationInterceptor](spring-aop/spring-aop-exposeInvocationInterceptor/README.md):暴露Spring AOP方法调用上下文的拦截器。 - [@EnableLoadTimeWeaving](spring-aop/spring-aop-enableLoadTimeWeaving/README.md) :启用Spring加载时编织。 - - + Spring AOT - - + Spring Log - - + Data Buffer 和编解码器 ++ Spring 事务 + + [Connection](spring-transaction/spring-transaction-connection/README.md) + :管理数据库连接,执行SQL,处理事务。 + + [DataSource](spring-transaction/spring-transaction-dataSource/README.md) + :提供高效管理数据库连接的接口。 + + [DriverManager](spring-transaction/spring-transaction-driverManager/README.md) + :管理和建立数据库连接的核心类。 + + [JdbcTemplate](spring-transaction/spring-transaction-jdbcTemplate/README.md) + :简化了JDBC操作,提供了方便的数据库访问抽象。 + + [TransactionDefinition](spring-transaction/spring-transaction-transactionDefinition/README.md) + :定义事务的传播行为和隔离级别。 + + [TransactionAttributeSource](spring-transaction/spring-transaction-transactionAttributeSource/README.md) + :用于获取事务属性的策略接口。 + + [PlatformTransactionManager](spring-transaction/spring-transaction-platformTransactionManager/README.md) + :用于管理和协调事务的生命周期和执行。 + + [TransactionTemplate](spring-transaction/spring-transaction-transactionTemplate/README.md) + :简化事务管理,支持编程式事务控制与异常处理。 + + [SpringTransactionAnnotationParser](spring-transaction/spring-transaction-springTransactionAnnotationParser/README.md) + :解析 `@Transactional` + 注解并转换为事务配置。 + + [TransactionInterceptor](spring-transaction/spring-transaction-transactionInterceptor/README.md) + :事务拦截器,用于管理方法级别的事务处理。 + + [EnableTransactionManagement](spring-transaction/spring-transaction-enableTransactionManagement/README.md) + :启用Spring的注解驱动事务管理。 + Spring MVC - -+ Spring 事务 - + Spring OpenFeign ## 💬与我联系 @@ -276,11 +291,10 @@ logo -## 👥**扫码关注微信公众号** +## 👥**扫码加群** 关注后,回复关键字 **“加群”**,即可加入我们的技术交流群,与更多开发者一起交流学习。
logo
- diff --git a/pom.xml b/pom.xml index efda4c2..5678002 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ 11 11 5.3.10 + 8.0.30 @@ -33,6 +34,7 @@ spring-env spring-dataops spring-spel + spring-transaction @@ -61,6 +63,16 @@ spring-webmvc ${spring.version} + + org.springframework + spring-jdbc + ${spring.version} + + + mysql + mysql-connector-java + ${mysql.version} + diff --git a/spring-transaction/pom.xml b/spring-transaction/pom.xml new file mode 100644 index 0000000..8e03f12 --- /dev/null +++ b/spring-transaction/pom.xml @@ -0,0 +1,30 @@ + + + + com.xcs.spring + spring-reading + 0.0.1-SNAPSHOT + + + pom + 4.0.0 + spring-transaction + + + spring-transaction-enableTransactionManagement + spring-transaction-driverManager + spring-transaction-dataSource + spring-transaction-connection + spring-transaction-platformTransactionManager + spring-transaction-jdbcTemplate + spring-transaction-transactionDefinition + spring-transaction-transactionTemplate + spring-transaction-springTransactionAnnotationParser + spring-transaction-transactionAttributeSource + spring-transaction-transactionInterceptor + spring-transaction-propagation1 + + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-connection/README.md b/spring-transaction/spring-transaction-connection/README.md new file mode 100644 index 0000000..7fbdeda --- /dev/null +++ b/spring-transaction/spring-transaction-connection/README.md @@ -0,0 +1,174 @@ +## Connection + +- [Connection](#connection) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [六、最佳实践](#六最佳实践) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`Connection` 接口是 Java JDBC API 中的一部分,代表着与数据库的连接。它提供了方法来管理数据库连接、执行 SQL +语句以及处理事务,是与数据库交互的重要接口之一。 + +### 三、主要功能 + +1. **执行 SQL 语句** + + + 通过 `createStatement()` 方法创建 `Statement` 对象,或者通过 `prepareStatement(String sql)` + 方法创建 `PreparedStatement` 对象,用于执行 SQL 查询、更新或删除等操作。 + +2. **事务管理** + + + 支持事务操作,可以通过 `commit()` 提交事务或者 `rollback()` 回滚事务。 + +3. **设置事务属性** + + + 可以设置事务的隔离级别、自动提交等属性。 + +### 四、接口源码 + +`Connection` 接口,用于表示与特定数据库的连接。它提供了方法来执行 SQL 语句、管理事务、提交或回滚更改以及关闭连接。 + +```java +/** + *

表示与特定数据库的连接(会话)。SQL 语句在连接的上下文中执行并返回结果。 + *

+ * Connection 对象的数据库能够提供描述其表、支持的 SQL 语法、存储的过程、连接的功能等信息。这些信息是通过 getMetaData 方法获取的。 + * + *

注意:在配置 Connection 时,JDBC 应用程序应使用适当的 Connection 方法,如 setAutoCommitsetTransactionIsolation。应用程序不应直接调用 SQL 命令来更改连接的配置,而应使用 JDBC 方法。默认情况下,Connection 对象处于自动提交模式,这意味着它在执行每个语句后会自动提交更改。如果已禁用自动提交模式,则必须显式调用 commit 方法才能提交更改;否则,数据库更改将不会保存。 + *

+ * 使用 JDBC 2.1 核心 API 创建的新的 Connection 对象具有一个最初为空的类型映射。用户可以为此类型映射中的用户定义类型(UDT)输入自定义映射。 + * 当从数据源检索到 UDT 并使用 ResultSet.getObject 方法时,getObject 方法将检查连接的类型映射以查看是否存在该 UDT 的条目。如果存在,则 getObject 方法将把 UDT 映射到指定的类。如果不存在条目,则使用标准映射映射 UDT。 + *

+ * 用户可以创建一个新的类型映射,它是一个 java.util.Map 对象,向其中添加一个条目,并将其传递给可以执行自定义映射的 java.sql 方法。在这种情况下,方法将使用给定的类型映射而不是与连接关联的类型映射。 + *

+ * 例如,以下代码片段指定 SQL 类型 ATHLETES 将映射到 Java 编程语言中的类 Athletes。该代码片段检索 Connection 对象 con 的类型映射,将条目插入其中,然后将带有新条目的类型映射设置为连接的类型映射。 + *

+ *      java.util.Map map = con.getTypeMap();
+ *      map.put("mySchemaName.ATHLETES", Class.forName("Athletes"));
+ *      con.setTypeMap(map);
+ * 
+ * + * @see DriverManager#getConnection + * @see Statement + * @see ResultSet + * @see DatabaseMetaData + * @since 1.1 + */ +public interface Connection extends Wrapper, AutoCloseable { + + /** + * 创建一个用于向数据库发送 SQL 语句的 Statement 对象。 + * 通常使用 Statement 对象执行无参数的 SQL 语句。如果相同的 SQL 语句需要执行多次,使用 PreparedStatement 对象可能更有效率。 + *

+ * 使用返回的 Statement 对象创建的结果集默认为 TYPE_FORWARD_ONLY 类型,并且并发级别为 CONCUR_READ_ONLY。可以通过调用 {@link #getHoldability} 来确定创建的结果集的可保持性。 + * + * @return 一个新的默认 Statement 对象 + * @exception SQLException 如果发生数据库访问错误,或者在关闭的连接上调用此方法 + */ + Statement createStatement() throws SQLException; + + /** + * 创建一个用于向数据库发送参数化 SQL 语句的 PreparedStatement 对象。 + *

+ * 可以预编译带有或不带有 IN 参数的 SQL 语句,并将其存储在 PreparedStatement 对象中。然后可以使用该对象多次有效地执行此语句。 + *

注意:此方法针对处理受益于预编译的参数化 SQL 语句进行了优化。如果驱动程序支持预编译,则方法 prepareStatement 将向数据库发送语句进行预编译。某些驱动程序可能不支持预编译。在这种情况下,直到执行 PreparedStatement 对象时,语句才会被发送到数据库。这对用户没有直接影响;但是,它影响了哪些方法会抛出某些 SQLException 对象。 + *

+ * 使用返回的 PreparedStatement 对象创建的结果集默认为 TYPE_FORWARD_ONLY 类型,并且并发级别为 CONCUR_READ_ONLY。可以通过调用 {@link #getHoldability} 来确定创建的结果集的可保持性。 + * + * @param sql 可能包含一个或多个 '?' IN 参数占位符的 SQL 语句 + * @return 包含预编译的 SQL 语句的新的默认 PreparedStatement 对象 + * @exception SQLException 如果发生数据库访问错误,或者在关闭的连接上调用此方法 + */ + PreparedStatement prepareStatement(String sql) throws SQLException; + + /** + * 使自上次提交/回滚以来所做的所有更改永久,并释放此 Connection 对象当前持有的任何数据库锁定。 + * 只有在禁用自动提交模式时才应该使用此方法。 + * + * @exception SQLException 如果发生数据库访问错误,如果在参与分布式事务时调用此方法,如果在关闭的连接上调用此方法或者此 Connection 对象处于自动提交模式下 + * @see #setAutoCommit + */ + void commit() throws SQLException; + + /** + * 撤消当前事务中所做的所有更改,并释放此 Connection 对象当前持有的任何数据库锁定。 + * 只有在禁用自动提交模式时才应该使用此方法。 + * + * @exception SQLException 如果发生数据库访问错误,如果在参与分布式事务时调用此方法,如果在关闭的连接上调用此方法或者此 Connection 对象处于自动提交模式下 + * @see #setAutoCommit + */ + void rollback() throws SQLException; + + /** + * 立即释放此 Connection 对象的数据库和 JDBC 资源,而不是等待它们自动释放。 + *

+ * 对已关闭的 Connection 对象调用 close 方法是一个无操作。 + *

+ * 强烈建议在调用 close 方法之前显式地提交或回滚活动事务。如果调用了 close 方法且存在活动事务,则结果是由实现定义的。 + * + * @exception SQLException 如果发生数据库访问错误 + */ + void close() throws SQLException; + + // .... +} +``` + +### 六、最佳实践 + +使用 Java JDBC API 连接到数据库,并执行插入操作。它首先配置了一个 `DataSource` 对象,用于连接到 MySQL +数据库。然后,它使用这个数据源获取了一个数据库连接,并设置了不自动提交,开启了一个事务。接着,它准备了一个 SQL +插入语句,并创建了一个 `PreparedStatement` +对象来执行插入操作。在执行插入操作后,它提交了事务,并打印了影响的行数。如果在执行过程中发生了异常,它会回滚事务并打印异常信息。最后,它处理了关闭连接时可能发生的异常。 + +```java +public class ConnectionDemo { + + public static void main(String[] args) { + // 使用 DataSource 体现了高内聚,有利于后面更改数据库 + MysqlDataSource dataSource = new MysqlDataSource(); + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + dataSource.setUrl("jdbc:mysql://localhost:3306/spring-reading"); + // 数据库用户名 + dataSource.setUser("root"); + // 数据库密码 + dataSource.setPassword("123456"); + + try (Connection connection = dataSource.getConnection()) { + // 设置不自动提交,开启事务 + connection.setAutoCommit(false); + // SQL 插入语句 + String sql = "insert into scores(score) values(?)"; + // 创建 PreparedStatement 对象,用于执行 SQL 插入操作 + try (PreparedStatement statement = connection.prepareStatement(sql)) { + // 设置参数 + statement.setInt(1, new Random().nextInt(100)); + // 执行插入操作 + int row = statement.executeUpdate(); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 提交事务 + connection.commit(); + // 打印影响行数 + System.out.println("row = " + row); + } catch (Exception e) { + // 发生异常时回滚事务 + connection.rollback(); + // 打印异常信息 + e.printStackTrace(); + } + } catch (SQLException e) { + // 处理关闭连接异常 + e.printStackTrace(); + } + } +} +``` \ No newline at end of file diff --git a/spring-transaction/spring-transaction-connection/pom.xml b/spring-transaction/spring-transaction-connection/pom.xml new file mode 100644 index 0000000..d46d69d --- /dev/null +++ b/spring-transaction/spring-transaction-connection/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-connection + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-connection/src/main/java/com/xcs/spring/ConnectionDemo.java b/spring-transaction/spring-transaction-connection/src/main/java/com/xcs/spring/ConnectionDemo.java new file mode 100644 index 0000000..667ce0a --- /dev/null +++ b/spring-transaction/spring-transaction-connection/src/main/java/com/xcs/spring/ConnectionDemo.java @@ -0,0 +1,50 @@ +package com.xcs.spring; + +import com.mysql.cj.jdbc.MysqlDataSource; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Random; + +public class ConnectionDemo { + + public static void main(String[] args) { + // 使用 DataSource 体现了高内聚,有利于后面更改数据库 + MysqlDataSource dataSource = new MysqlDataSource(); + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + dataSource.setUrl("jdbc:mysql://localhost:3306/spring-reading"); + // 数据库用户名 + dataSource.setUser("root"); + // 数据库密码 + dataSource.setPassword("123456"); + + try (Connection connection = dataSource.getConnection()) { + // 设置不自动提交,开启事务 + connection.setAutoCommit(false); + // SQL 插入语句 + String sql = "insert into scores(score) values(?)"; + // 创建 PreparedStatement 对象,用于执行 SQL 插入操作 + try (PreparedStatement statement = connection.prepareStatement(sql)) { + // 设置参数 + statement.setInt(1, new Random().nextInt(100)); + // 执行插入操作 + int row = statement.executeUpdate(); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 提交事务 + connection.commit(); + // 打印影响行数 + System.out.println("row = " + row); + } catch (Exception e) { + // 发生异常时回滚事务 + connection.rollback(); + // 打印异常信息 + e.printStackTrace(); + } + } catch (SQLException e) { + // 处理关闭连接异常 + e.printStackTrace(); + } + } +} diff --git a/spring-transaction/spring-transaction-dataSource/README.md b/spring-transaction/spring-transaction-dataSource/README.md new file mode 100644 index 0000000..dae7be3 --- /dev/null +++ b/spring-transaction/spring-transaction-dataSource/README.md @@ -0,0 +1,189 @@ +## DataSource + +- [DataSource](#datasource) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、最佳实践](#五最佳实践) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`DataSource` 接口是 Java +中用于管理数据库连接的标准接口,它提供了一种抽象的方法来获取数据库连接,使得应用程序能够与不同的数据库进行交互而无需修改代码,通过配置数据源的方式可以更灵活地处理连接池、事务管理以及连接的分配等问题,从而提高了应用程序的性能和可维护性。 + +### 三、主要功能 + +1. **提供数据库连接** + + + 定义了获取数据库连接的方法,通过调用这些方法可以获取与数据库的连接,从而进行数据库操作。 + +2. **连接池管理** + + + 实现连接池,避免了频繁地创建和销毁数据库连接,提高了应用程序的性能。 + +3. **连接配置** + + + 允许配置连接的相关属性,如数据库地址、用户名、密码等信息,这些配置可以在不同的环境中灵活地进行调整。 + +4. **资源管理** + + + 负责管理数据库连接资源,包括连接的分配、回收、超时控制等,确保连接的有效性和可用性。 + +5. **实现数据源的透明性** + + + 应用程序通过 `DataSource` 获取数据库连接,无需关心具体的数据库类型和配置细节,使得代码更具可移植性和扩展性。 + +### 四、接口源码 + +`DataSource`用于获取与物理数据源的连接。`DataSource` 提供了两个获取连接的方法:`getConnection()` +和 `getConnection(String username, String password)` +,分别用于获取默认用户和密码的连接以及指定用户和密码的连接。此外,接口还定义了一些管理连接的方法,如获取和设置日志写入器、设置登录超时时间等。接口说明了 `DataSource` +的作用及其实现的方式,以及与 `DriverManager` 的比较。最后,该接口还提供了一个默认方法 `createConnectionBuilder()` +,用于创建连接构建器的实例。 + +```java +/** + *

此 {@code DataSource} 对象表示对物理数据源的连接工厂。作为 {@code DriverManager} 设施的替代,{@code DataSource} 对象是获取连接的首选方式。实现 {@code DataSource} 接口的对象通常会在基于 Java™ Naming and Directory (JNDI) API 的命名服务中注册。

+ *

{@code DataSource} 接口由驱动程序供应商实现。有三种类型的实现:

+ *
    + *
  1. 基本实现 -- 生成标准的 {@code Connection} 对象
  2. + *
  3. 连接池实现 -- 生成自动参与连接池的 {@code Connection} 对象。该实现与中间层连接池管理器配合使用。
  4. + *
  5. 分布式事务实现 -- 生成可用于分布式事务的 {@code Connection} 对象,几乎总是参与连接池。该实现与中间层事务管理器以及几乎总是与连接池管理器一起使用。
  6. + *
+ *

{@code DataSource} 对象具有在必要时可以修改的属性。例如,如果数据源移动到不同的服务器,则可以更改服务器的属性。好处在于,因为数据源的属性可以更改,因此访问该数据源的任何代码都不需要更改。

+ *

通过 {@code DataSource} 对象访问的驱动程序不会向 {@code DriverManager} 注册自己。相反,通过查找操作检索 {@code DataSource} 对象,然后用于创建 {@code Connection} 对象。对于基本实现,通过 {@code DataSource} 对象获取的连接与通过 {@code DriverManager} 设施获取的连接相同。

+ *

{@code DataSource} 的实现必须包括一个公共的无参数构造函数。

+ * + * @since 1.4 + */ +public interface DataSource extends CommonDataSource, Wrapper { + + /** + *

尝试与此 {@code DataSource} 对象表示的数据源建立连接。

+ * + * @return 与数据源的连接 + * @exception SQLException 如果发生数据库访问错误 + * @throws java.sql.SQLTimeoutException 当驱动程序确定 {@code setLoginTimeout} 方法指定的超时值已超过并且至少尝试取消当前数据库连接尝试时 + */ + Connection getConnection() throws SQLException; + + /** + *

尝试使用指定用户和密码与此 {@code DataSource} 对象表示的数据源建立连接。

+ * + * @param username 连接所代表用户的数据库用户 + * @param password 用户的密码 + * @return 与数据源的连接 + * @exception SQLException 如果发生数据库访问错误 + * @throws java.sql.SQLTimeoutException 当驱动程序确定 {@code setLoginTimeout} 方法指定的超时值已超过并且至少尝试取消当前数据库连接尝试时 + * @since 1.4 + */ + Connection getConnection(String username, String password) throws SQLException; + + /** + * {@inheritDoc} + * @since 1.4 + */ + @Override + java.io.PrintWriter getLogWriter() throws SQLException; + + /** + * {@inheritDoc} + * @since 1.4 + */ + @Override + void setLogWriter(java.io.PrintWriter out) throws SQLException; + + /** + * {@inheritDoc} + * @since 1.4 + */ + @Override + void setLoginTimeout(int seconds) throws SQLException; + + /** + * {@inheritDoc} + * @since 1.4 + */ + @Override + int getLoginTimeout() throws SQLException; + + // JDBC 4.3 + + /** + * 创建一个新的 {@code ConnectionBuilder} 实例 + * @implSpec 默认实现将抛出 {@code SQLFeatureNotSupportedException} + * @return 创建的 ConnectionBuilder 实例 + * @throws SQLException 如果创建构建器时发生错误 + * @throws SQLFeatureNotSupportedException 如果驱动程序不支持分片 + * @since 9 + * @see ConnectionBuilder + */ + default ConnectionBuilder createConnectionBuilder() throws SQLException { + throw new SQLFeatureNotSupportedException("createConnectionBuilder not implemented"); + }; + +} +``` + +### 五、最佳实践 + +使用 `DataSource` 来建立与数据库的连接,体现了高内聚的设计原则,有利于后续更改数据库。通过 `MysqlDataSource` 类设置数据库连接的 +URL、用户名和密码,然后获取数据库连接。接着,执行 SQL 查询语句,将查询结果输出到控制台。最后,关闭结果集、PreparedStatement +和数据库连接,释放资源。 + +```java +public class DataSourceDemo { + + public static void main(String[] args) throws Exception { + // 使用 DataSource 体现了高内聚,有利于后面更改数据库 + MysqlDataSource dataSource = new MysqlDataSource(); + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + dataSource.setUrl("jdbc:mysql://localhost:3306/spring-reading"); + // 数据库用户名 + dataSource.setUser("root"); + // 数据库密码 + dataSource.setPassword("123456"); + + // 建立数据库连接 + Connection connection = dataSource.getConnection(); + // SQL 查询语句 + String sql = "SELECT * FROM scores"; + // 创建 PreparedStatement 对象,用于执行 SQL 查询 + PreparedStatement statement = connection.prepareStatement(sql); + // 执行查询,获取结果集 + ResultSet resultSet = statement.executeQuery(); + + // 遍历结果集 + while (resultSet.next()) { + // 获取 id 列的值 + int id = resultSet.getInt("id"); + // 获取 score 列的值 + String score = resultSet.getString("score"); + // 输出结果 + System.out.println("id: " + id + ", score: " + score); + } + // 关闭结果集、PreparedStatement 和数据库连接 + resultSet.close(); + statement.close(); + connection.close(); + } +} +``` + +运行结果,从 "spring-reading" 的数据库的 "scores" 表中检索到的数据。每行都包括一个 "id" 和一个 "score" 列。 + +```java +id: 1, score: 3.50 +id: 2, score: 3.65 +id: 3, score: 4.00 +id: 4, score: 3.85 +id: 5, score: 4.00 +id: 6, score: 3.65 +``` + diff --git a/spring-transaction/spring-transaction-dataSource/pom.xml b/spring-transaction/spring-transaction-dataSource/pom.xml new file mode 100644 index 0000000..3cdee4e --- /dev/null +++ b/spring-transaction/spring-transaction-dataSource/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-dataSource + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-dataSource/src/main/java/com/xcs/spring/DataSourceDemo.java b/spring-transaction/spring-transaction-dataSource/src/main/java/com/xcs/spring/DataSourceDemo.java new file mode 100644 index 0000000..41294d1 --- /dev/null +++ b/spring-transaction/spring-transaction-dataSource/src/main/java/com/xcs/spring/DataSourceDemo.java @@ -0,0 +1,44 @@ +package com.xcs.spring; + +import com.mysql.cj.jdbc.MysqlDataSource; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; + +public class DataSourceDemo { + + public static void main(String[] args) throws Exception { + // 使用 DataSource 体现了高内聚,有利于后面更改数据库 + MysqlDataSource dataSource = new MysqlDataSource(); + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + dataSource.setUrl("jdbc:mysql://localhost:3306/spring-reading"); + // 数据库用户名 + dataSource.setUser("root"); + // 数据库密码 + dataSource.setPassword("123456"); + + // 建立数据库连接 + Connection connection = dataSource.getConnection(); + // SQL 查询语句 + String sql = "SELECT * FROM scores"; + // 创建 PreparedStatement 对象,用于执行 SQL 查询 + PreparedStatement statement = connection.prepareStatement(sql); + // 执行查询,获取结果集 + ResultSet resultSet = statement.executeQuery(); + + // 遍历结果集 + while (resultSet.next()) { + // 获取 id 列的值 + int id = resultSet.getInt("id"); + // 获取 score 列的值 + String score = resultSet.getString("score"); + // 输出结果 + System.out.println("id: " + id + ", score: " + score); + } + // 关闭结果集、PreparedStatement 和数据库连接 + resultSet.close(); + statement.close(); + connection.close(); + } +} diff --git a/spring-transaction/spring-transaction-driverManager/README.md b/spring-transaction/spring-transaction-driverManager/README.md new file mode 100644 index 0000000..1813055 --- /dev/null +++ b/spring-transaction/spring-transaction-driverManager/README.md @@ -0,0 +1,90 @@ +## DriverManager + +- [DriverManager](#drivermanager) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、最佳实践](#四最佳实践) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`DriverManager` 是 Java 标准库中的一个类,用于管理 JDBC 驱动程序,提供了加载驱动程序和建立数据库连接的静态方法,使得 Java +应用程序能够方便地与各种数据库进行交互。 + +### 三、主要功能 + +1. **加载数据库驱动程序** + + + 通过 `registerDriver()` 方法注册数据库驱动程序,使得 `DriverManager` 能够识别和加载特定数据库的驱动程序。 + +2. **建立数据库连接** + + + 通过 `getConnection()` 方法,根据指定的数据库 URL、用户名和密码获取数据库连接对象,以便后续对数据库进行操作。 + +3. **管理数据库连接** + + + `DriverManager` 负责管理数据库连接对象,确保连接的安全性和可用性,并提供了方法来获取、关闭数据库连接。 + +4. **卸载驱动程序** + + + 通过 `deregisterDriver()` 方法卸载已注册的数据库驱动程序,释放相关资源。 + +### 四、最佳实践 + +使用 `java.sql.DriverManager` 类来连接到 MySQL 数据库,并执行一个简单的查询操作。通过指定数据库连接的 +URL、用户名和密码,它建立了与名为 "spring-reading" 的数据库的连接,然后执行了一个查询语句来获取名为 "scores" +的表中的数据。最后,它遍历结果集并将每行数据的 "id" 和 "score" 列打印出来,然后关闭了结果集、预处理语句对象和数据库连接。 + +```java +public class DriverManagerDemo { + + public static void main(String[] args) throws Exception { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 建立数据库连接 + Connection connection = DriverManager.getConnection(url, username, password); + // SQL 查询语句 + String sql = "SELECT * FROM scores"; + // 创建 PreparedStatement 对象,用于执行 SQL 查询 + PreparedStatement statement = connection.prepareStatement(sql); + // 执行查询,获取结果集 + ResultSet resultSet = statement.executeQuery(); + + // 遍历结果集 + while (resultSet.next()) { + // 获取 id 列的值 + int id = resultSet.getInt("id"); + // 获取 score 列的值 + String score = resultSet.getString("score"); + // 输出结果 + System.out.println("id: " + id + ", score: " + score); + } + // 关闭结果集、PreparedStatement 和数据库连接 + resultSet.close(); + statement.close(); + connection.close(); + } +} +``` + +运行结果,从 "spring-reading" 的数据库的 "scores" 表中检索到的数据。每行都包括一个 "id" 和一个 "score" 列。 + +```java +id:1,score:3.50 +id:2,score:3.65 +id:3,score:4.00 +id:4,score:3.85 +id:5,score:4.00 +id:6,score:3.65 +``` + diff --git a/spring-transaction/spring-transaction-driverManager/pom.xml b/spring-transaction/spring-transaction-driverManager/pom.xml new file mode 100644 index 0000000..25632ae --- /dev/null +++ b/spring-transaction/spring-transaction-driverManager/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-driverManager + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-driverManager/src/main/java/com/xcs/spring/DriverManagerDemo.java b/spring-transaction/spring-transaction-driverManager/src/main/java/com/xcs/spring/DriverManagerDemo.java new file mode 100644 index 0000000..b3f5fb1 --- /dev/null +++ b/spring-transaction/spring-transaction-driverManager/src/main/java/com/xcs/spring/DriverManagerDemo.java @@ -0,0 +1,41 @@ +package com.xcs.spring; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; + +public class DriverManagerDemo { + + public static void main(String[] args) throws Exception { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 建立数据库连接 + Connection connection = DriverManager.getConnection(url, username, password); + // SQL 查询语句 + String sql = "SELECT * FROM scores"; + // 创建 PreparedStatement 对象,用于执行 SQL 查询 + PreparedStatement statement = connection.prepareStatement(sql); + // 执行查询,获取结果集 + ResultSet resultSet = statement.executeQuery(); + + // 遍历结果集 + while (resultSet.next()) { + // 获取 id 列的值 + int id = resultSet.getInt("id"); + // 获取 score 列的值 + String score = resultSet.getString("score"); + // 输出结果 + System.out.println("id: " + id + ", score: " + score); + } + // 关闭结果集、PreparedStatement 和数据库连接 + resultSet.close(); + statement.close(); + connection.close(); + } +} diff --git a/spring-transaction/spring-transaction-enableTransactionManagement/README.md b/spring-transaction/spring-transaction-enableTransactionManagement/README.md new file mode 100644 index 0000000..5b0fc83 --- /dev/null +++ b/spring-transaction/spring-transaction-enableTransactionManagement/README.md @@ -0,0 +1,496 @@ +## EnableTransactionManagement + +- [EnableTransactionManagement](#EnableTransactionManagement) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、最佳实践](#六最佳实践) + - [七、源码分析](#七源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`@EnableTransactionManagement` 是 Spring 框架中的注解,用于启用注解驱动的事务管理。通过在配置类上使用该注解,Spring +会自动扫描和处理应用中的 `@Transactional` 注解,从而实现声明性事务管理。它通常与一个 `PlatformTransactionManager` +实例配合使用,以管理和协调数据库事务。 + +### 三、主要功能 + +1. **启用事务管理** + + + 通过在配置类上添加 `@EnableTransactionManagement`,Spring + 会自动扫描应用上下文中的所有事务注解(例如 `@Transactional`),并为这些注解提供事务管理的支持。 + +2. **事务管理器** + + + `@EnableTransactionManagement` 需要一个 `PlatformTransactionManager` 实例来管理事务。通常情况下,Spring + 会自动检测配置中的事务管理器。如果没有配置,Spring 会尝试创建一个默认的事务管理器。 + +### 四、注解源码 + +`@EnableTransactionManagement` +注解用于在Spring框架中启用注解驱动的事务管理功能,通过在配置类上添加该注解,Spring会自动扫描和处理应用中的 `@Transactional` +注解,实现声明式事务管理,支持传统的命令式事务管理和响应式事务管理;它允许灵活配置事务管理器,通过 `proxyTargetClass` +属性指定代理类型,通过 `mode` 属性选择代理模式(默认是 `PROXY`,可选 `ASPECTJ` +),并可通过实现 `TransactionManagementConfigurer` 接口明确指定使用的事务管理器,从而为复杂的事务管理需求提供高效的解决方案,同时其默认行为和XML配置方式具有很好的兼容性。 + +```java +/** + * 启用Spring的注解驱动事务管理功能,类似于Spring的{@code } XML命名空间中的支持。 + * 该注解用于{@link org.springframework.context.annotation.Configuration @Configuration}类上, + * 以配置传统的命令式事务管理或响应式事务管理。 + * + *

下面的示例演示了使用{@link org.springframework.transaction.PlatformTransactionManager + * PlatformTransactionManager}的命令式事务管理。对于响应式事务管理,配置 + * {@link org.springframework.transaction.ReactiveTransactionManager + * ReactiveTransactionManager}即可。 + * + *

+ * @Configuration
+ * @EnableTransactionManagement
+ * public class AppConfig {
+ *
+ *     @Bean
+ *     public FooRepository fooRepository() {
+ *         // 配置并返回一个包含@Transactional方法的类
+ *         return new JdbcFooRepository(dataSource());
+ *     }
+ *
+ *     @Bean
+ *     public DataSource dataSource() {
+ *         // 配置并返回所需的JDBC DataSource
+ *     }
+ *
+ *     @Bean
+ *     public PlatformTransactionManager txManager() {
+ *         return new DataSourceTransactionManager(dataSource());
+ *     }
+ * }
+ * + *

参考上面的示例,可以与以下Spring XML配置进行比较: + * + *

+ * 
+ *
+ *     
+ *
+ *     
+ *         
+ *     
+ *
+ *     
+ *
+ *     
+ *         
+ *     
+ *
+ * 
+ * 
+ * + * 在上述两种情况下,{@code @EnableTransactionManagement}和{@code + * }负责注册驱动注解事务管理所需的Spring组件, + * 如TransactionInterceptor和在调用{@code JdbcFooRepository}的{@code @Transactional}方法时 + * 将拦截器织入调用堆栈的代理或基于AspectJ的建议。 + * + *

两个示例之间的一个小差异在于{@code TransactionManager} bean的命名: + * 在{@code @Bean}示例中,名称是"txManager"(根据方法名);在XML示例中,名称是 + * "transactionManager"。{@code }默认硬编码查找名为 + * "transactionManager"的bean,而{@code @EnableTransactionManagement}更加灵活; + * 它会回退为按类型查找容器中的任何{@code TransactionManager} bean。因此名称可以是 + * "txManager"、"transactionManager"或"tm":名称无关紧要。 + * + *

对于希望在{@code @EnableTransactionManagement}和要使用的确切事务管理器bean之间建立更直接关系的人, + * 可以实现{@link TransactionManagementConfigurer}回调接口 - 请注意下面的{@code implements}子句和{@code @Override}注解的方法: + * + *

+ * @Configuration
+ * @EnableTransactionManagement
+ * public class AppConfig implements TransactionManagementConfigurer {
+ *
+ *     @Bean
+ *     public FooRepository fooRepository() {
+ *         // 配置并返回一个包含@Transactional方法的类
+ *         return new JdbcFooRepository(dataSource());
+ *     }
+ *
+ *     @Bean
+ *     public DataSource dataSource() {
+ *         // 配置并返回所需的JDBC DataSource
+ *     }
+ *
+ *     @Bean
+ *     public PlatformTransactionManager txManager() {
+ *         return new DataSourceTransactionManager(dataSource());
+ *     }
+ *
+ *     @Override
+ *     public PlatformTransactionManager annotationDrivenTransactionManager() {
+ *         return txManager();
+ *     }
+ * }
+ * + *

这种方法可能只是因为更显式而是可取的,或者可能是为了区分同一容器中存在的两个{@code TransactionManager} bean。 + * 顾名思义,{@code annotationDrivenTransactionManager()}将用于处理{@code @Transactional}方法。 + * 有关详细信息,请参阅{@link TransactionManagementConfigurer} Javadoc。 + * + *

{@link #mode}属性控制如何应用建议:如果模式为{@link AdviceMode#PROXY}(默认),则其他属性控制代理行为。 + * 请注意,代理模式仅允许通过代理拦截调用;同一类中的本地调用无法以这种方式拦截。 + * + *

请注意,如果{@linkplain #mode}设置为{@link AdviceMode#ASPECTJ},则{@link #proxyTargetClass}属性的值将被忽略。 + * 此外,在这种情况下,{@code spring-aspects}模块JAR必须存在于类路径上,并且编译时织入或加载时织入将该方面应用于受影响的类。 + * 这种情况下没有代理;本地调用也会被拦截。 + * + * @作者 Chris Beams + * @作者 Juergen Hoeller + * @自从 3.1 + * @参见 TransactionManagementConfigurer + * @参见 TransactionManagementConfigurationSelector + * @参见 ProxyTransactionManagementConfiguration + * @参见 org.springframework.transaction.aspectj.AspectJTransactionManagementConfiguration + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(TransactionManagementConfigurationSelector.class) +public @interface EnableTransactionManagement { + + /** + * 指示是否创建基于子类的(CGLIB)代理({@code true}),而不是标准的基于Java接口的代理({@code false})。 + * 默认值为{@code false}。仅当{@link #mode()}设置为{@link AdviceMode#PROXY}时适用。 + *

请注意,将此属性设置为{@code true}将影响所有需要代理的Spring管理的bean,不仅仅是那些标有 + * {@code @Transactional}的bean。例如,其他标有Spring的{@code @Async}注解的bean也会同时升级为子类代理。 + * 这种方法在实践中没有负面影响,除非一个明确期望一种类型的代理与另一种类型,例如在测试中。 + */ + boolean proxyTargetClass() default false; + + /** + * 指示应如何应用事务性建议。 + *

默认值为{@link AdviceMode#PROXY}。 + * 请注意,代理模式仅允许通过代理拦截调用。同一类中的本地调用无法以这种方式拦截; + * 在这种运行时情况下,具有{@link Transactional}注解的方法上的拦截器甚至不会触发。 + * 对于更高级的拦截模式,请考虑将其切换为{@link AdviceMode#ASPECTJ}。 + */ + AdviceMode mode() default AdviceMode.PROXY; + + /** + * 指示在特定连接点应用多个建议时事务顾问的执行顺序。 + *

默认值为{@link Ordered#LOWEST_PRECEDENCE}。 + */ + int order() default Ordered.LOWEST_PRECEDENCE; + +} +``` + +### 六、最佳实践 + +基于注解的应用上下文 `AnnotationConfigApplicationContext`,并加载 `AppConfig` +配置类,然后从该上下文中获取名为 `ScoresService` 的bean,并调用 `ScoresService` 的 `insertScore` +方法,展示了如何在Spring应用中通过注解配置和使用服务类。 + +```java +public class EnableTransactionManagementDemo { + + public static void main(String[] args) { + // 创建基于注解的应用上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从应用上下文中获取ScoresService bean + ScoresService scoresService = context.getBean(ScoresService.class); + // 调用ScoresService的方法 + scoresService.insertScore(); + } +} +``` + +Spring配置类 `AppConfig`,通过注解 `@Configuration`、`@ComponentScan` 和 `@EnableTransactionManagement` +来启用组件扫描和注解驱动的事务管理,并配置了几个 `@Bean`,包括一个 `DataSourceTransactionManager` +来管理事务,一个 `JdbcTemplate` 用于简化JDBC操作,以及一个 `SimpleDriverDataSource` +用于配置数据库连接,连接的数据库是MySQL,指定了连接URL、用户名和密码。 + +```java + +@Configuration +@ComponentScan +@EnableTransactionManagement +public class AppConfig { + + @Bean + public TransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + + @Bean + public SimpleDriverDataSource dataSource() throws SQLException { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + // 初始化数据源 + return new SimpleDriverDataSource(new Driver(), url, username, password); + } +} +``` + +实现了 `ScoresService` 接口,使用 `@Service` 注解将其标记为Spring管理的服务组件,并通过 `@Autowired` 注入 `JdbcTemplate` +;在 `insertScore` 方法中,通过 `@Transactional` 注解启用事务管理,方法生成一个随机分数并插入到数据库中,并打印受影响的行数,同时包含一个被注释掉的异常模拟代码,用于测试事务回滚机制。 + +```java + +@Service +public class ScoresServiceImpl implements ScoresService { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Override + @Transactional + public void insertScore() { + long id = System.currentTimeMillis(); + int score = new Random().nextInt(100); + // 向数据库中插入随机生成的分数 + int row = jdbcTemplate.update("insert into scores(id,score) values(?,?)", id, score); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 打印影响行数 + System.out.println("scores row = " + row); + } +} +``` + +### 七、源码分析 + +在`org.springframework.transaction.annotation.TransactionManagementConfigurationSelector` +类中,继承自 `AdviceModeImportSelector`,根据 `@EnableTransactionManagement` 注解的 `mode` +属性值选择合适的事务管理配置类。如果 `mode` 是 `PROXY`,则返回 `AutoProxyRegistrar` +和 `ProxyTransactionManagementConfiguration` 类名;如果是 `ASPECTJ` +,则根据类路径中是否存在 `javax.transaction.Transactional` 类来决定返回 JTA 事务切面配置类名或非 JTA +事务切面配置类名。这样可以灵活地根据不同的事务管理模式选择合适的实现来配置 Spring 应用的事务管理。 + +```java +/** + * 根据导入的 {@code @Configuration} 类上 {@link EnableTransactionManagement#mode} 的值 + * 选择应使用的 {@link AbstractTransactionManagementConfiguration} 实现。 + * + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.1 + * @see EnableTransactionManagement + * @see ProxyTransactionManagementConfiguration + * @see TransactionManagementConfigUtils#TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME + * @see TransactionManagementConfigUtils#JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME + */ +public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector { + + /** + * 根据事务管理模式返回相应的配置类。 + * + * @param adviceMode 事务管理模式,可以是 PROXY 或 ASPECTJ + * @return 包含事务管理配置类名的数组 + */ + @Override + protected String[] selectImports(AdviceMode adviceMode) { + switch (adviceMode) { + case PROXY: + // 如果是 PROXY 模式,返回 AutoProxyRegistrar 和 ProxyTransactionManagementConfiguration 类名 + return new String[]{AutoProxyRegistrar.class.getName(), + ProxyTransactionManagementConfiguration.class.getName()}; + case ASPECTJ: + // 如果是 ASPECTJ 模式,返回确定的事务切面配置类名 + return new String[]{determineTransactionAspectClass()}; + default: + // 如果模式不是 PROXY 或 ASPECTJ,返回 null + return null; + } + } + + /** + * 确定要使用的事务切面配置类。 + * + * @return JTA 或非 JTA 事务切面配置类名 + */ + private String determineTransactionAspectClass() { + // 检查类路径中是否存在 javax.transaction.Transactional + return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ? + // 如果存在,返回 JTA 事务切面配置类名 + TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME : + // 如果不存在,返回非 JTA 事务切面配置类名 + TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME); + } +} +``` + +在`org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration`类中,用于注册启用基于代理的注解驱动事务管理所需的 +Spring 基础设施 bean。它包括注册事务通知的 `BeanFactoryTransactionAttributeSourceAdvisor` +、事务属性源的 `AnnotationTransactionAttributeSource` 和事务拦截器的 `TransactionInterceptor`。这些 bean 的注册使得 Spring +能够拦截带有 `@Transactional` 注解的方法调用,并应用事务管理功能。 + +```java +/** + * {@code @Configuration} 类,注册了启用基于代理的注解驱动事务管理所需的Spring基础设施bean。 + * + * @author Chris Beams + * @author Sebastien Deleuze + * @since 3.1 + * @see EnableTransactionManagement + * @see TransactionManagementConfigurationSelector + */ +@Configuration(proxyBeanMethods = false) +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) +public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { + + /** + * 注册事务通知的Bean。 + * + * @param transactionAttributeSource 事务属性源 + * @param transactionInterceptor 事务拦截器 + * @return BeanFactoryTransactionAttributeSourceAdvisor 实例 + */ + @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor( + TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) { + + BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); + advisor.setTransactionAttributeSource(transactionAttributeSource); + advisor.setAdvice(transactionInterceptor); + if (this.enableTx != null) { + advisor.setOrder(this.enableTx.getNumber("order")); + } + return advisor; + } + + /** + * 注册事务属性源的Bean。 + * + * @return AnnotationTransactionAttributeSource 实例 + */ + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public TransactionAttributeSource transactionAttributeSource() { + return new AnnotationTransactionAttributeSource(); + } + + /** + * 注册事务拦截器的Bean。 + * + * @param transactionAttributeSource 事务属性源 + * @return TransactionInterceptor 实例 + */ + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) { + TransactionInterceptor interceptor = new TransactionInterceptor(); + interceptor.setTransactionAttributeSource(transactionAttributeSource); + if (this.txManager != null) { + interceptor.setTransactionManager(this.txManager); + } + return interceptor; + } + +} +``` + +在`org.springframework.context.annotation.AutoProxyRegistrar`类中,实现了 `ImportBeanDefinitionRegistrar` +接口,用于根据 `@Enable*` 注解的 `mode` 和 `proxyTargetClass` +属性设置适当的自动代理创建器,并将其注册到当前的 `BeanDefinitionRegistry` 中。该类在导入的 `@Configuration` +类上查找具有正确 `mode` 和 `proxyTargetClass` 属性的最近的注解,并根据其设置注册和配置自动代理创建器。 + +```java +/** + * 根据 {@code @Enable*} 注解的 {@code mode} 和 {@code proxyTargetClass} 属性设置适当的 + * 自动代理创建器 (Auto Proxy Creator, APC),并将其注册到当前的 {@link BeanDefinitionRegistry} 中。 + * + * 该类通过在导入的 {@code @Configuration} 类上查找具有 {@code mode} 和 {@code proxyTargetClass} + * 属性的最近的注解来工作。如果 {@code mode} 设置为 {@code PROXY},则注册 APC;如果 + * {@code proxyTargetClass} 设置为 {@code true},则强制 APC 使用子类(CGLIB)代理。 + * + * 多个 {@code @Enable*} 注解公开了 {@code mode} 和 {@code proxyTargetClass} 属性。重要的是 + * 注意,大多数这些功能最终会共享一个 {@linkplain AopConfigUtils#AUTO_PROXY_CREATOR_BEAN_NAME 单个 APC}。 + * 因此,此实现不关心确切地找到哪个注解,只要它公开正确的 {@code mode} 和 {@code proxyTargetClass} + * 属性,就可以注册和配置 APC。 + * + * @作者 Chris Beams + * @自 3.1 + * @见 org.springframework.cache.annotation.EnableCaching + * @见 org.springframework.transaction.annotation.EnableTransactionManagement + */ +public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar { + + private final Log logger = LogFactory.getLog(getClass()); + + /** + * 根据导入的类的元数据和Bean定义注册表,注册、升级和配置标准的自动代理创建器(APC)。 + * 通过在导入的 {@code @Configuration} 类上找到最近的带有 {@code mode} 和 {@code proxyTargetClass} + * 属性的注解来工作。如果 {@code mode} 设置为 {@code PROXY},则注册 APC;如果 + * {@code proxyTargetClass} 设置为 {@code true},则强制 APC 使用子类(CGLIB)代理。 + *

+ * 多个 {@code @Enable*} 注解公开了 {@code mode} 和 {@code proxyTargetClass} 属性。重要的是 + * 注意,大多数这些功能最终会共享一个 {@linkplain AopConfigUtils#AUTO_PROXY_CREATOR_BEAN_NAME 单个 APC}。 + * 因此,此实现不关心确切地找到哪个注解,只要它公开正确的 {@code mode} 和 {@code proxyTargetClass} + * 属性,就可以注册和配置 APC。 + * + * @param importingClassMetadata 导入类的元数据 + * @param registry Bean定义注册表 + */ + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + // 是否找到候选注解 + boolean candidateFound = false; + // 获取导入类的所有注解类型 + Set annTypes = importingClassMetadata.getAnnotationTypes(); + // 遍历所有注解类型 + for (String annType : annTypes) { + // 获取注解的属性 + AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType); + // 如果属性为空,则继续下一个循环 + if (candidate == null) { + continue; + } + // 获取 mode 和 proxyTargetClass 属性 + Object mode = candidate.get("mode"); + Object proxyTargetClass = candidate.get("proxyTargetClass"); + // 如果 mode 和 proxyTargetClass 属性不为空,并且它们的类型分别是 AdviceMode 和 Boolean + if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() && + Boolean.class == proxyTargetClass.getClass()) { + candidateFound = true; + // 如果 mode 是 AdviceMode.PROXY + if (mode == AdviceMode.PROXY) { + // 注册 APC + AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); + // 如果 proxyTargetClass 是 true + if ((Boolean) proxyTargetClass) { + // 强制 APC 使用类代理 + AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); + return; + } + } + } + } + // 如果未找到候选注解,并且 logger 是启用的 + if (!candidateFound && logger.isInfoEnabled()) { + String name = getClass().getSimpleName(); + logger.info(String.format("%s was imported but no annotations were found " + + "having both 'mode' and 'proxyTargetClass' attributes of type " + + "AdviceMode and boolean respectively. This means that auto proxy " + + "creator registration and configuration may not have occurred as " + + "intended, and components may not be proxied as expected. Check to " + + "ensure that %s has been @Import'ed on the same class where these " + + "annotations are declared; otherwise remove the import of %s " + + "altogether.", name, name, name)); + } + } + +} +``` \ No newline at end of file diff --git a/spring-transaction/spring-transaction-enableTransactionManagement/pom.xml b/spring-transaction/spring-transaction-enableTransactionManagement/pom.xml new file mode 100644 index 0000000..63c3b29 --- /dev/null +++ b/spring-transaction/spring-transaction-enableTransactionManagement/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-enableTransactionManagement + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/AppConfig.java b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/AppConfig.java new file mode 100644 index 0000000..41c054c --- /dev/null +++ b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/AppConfig.java @@ -0,0 +1,42 @@ +package com.xcs.spring; + +import com.mysql.jdbc.Driver; +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.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.transaction.TransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; +import java.sql.SQLException; + +@Configuration +@ComponentScan +@EnableTransactionManagement +public class AppConfig { + + @Bean + public TransactionManager transactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + + @Bean + public SimpleDriverDataSource dataSource() throws SQLException { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + // 初始化数据源 + return new SimpleDriverDataSource(new Driver(), url, username, password); + } +} diff --git a/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/EnableTransactionManagementDemo.java b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/EnableTransactionManagementDemo.java new file mode 100644 index 0000000..b33e70e --- /dev/null +++ b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/EnableTransactionManagementDemo.java @@ -0,0 +1,15 @@ +package com.xcs.spring; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class EnableTransactionManagementDemo { + + public static void main(String[] args) { + // 创建基于注解的应用上下文 + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + // 从应用上下文中获取ScoresService bean + ScoresService scoresService = context.getBean(ScoresService.class); + // 调用ScoresService的方法 + scoresService.insertScore(); + } +} diff --git a/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/ScoresService.java b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/ScoresService.java new file mode 100644 index 0000000..128b1fc --- /dev/null +++ b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/ScoresService.java @@ -0,0 +1,5 @@ +package com.xcs.spring; + +public interface ScoresService { + void insertScore(); +} diff --git a/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/ScoresServiceImpl.java b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/ScoresServiceImpl.java new file mode 100644 index 0000000..1055879 --- /dev/null +++ b/spring-transaction/spring-transaction-enableTransactionManagement/src/main/java/com/xcs/spring/ScoresServiceImpl.java @@ -0,0 +1,28 @@ +package com.xcs.spring; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Random; + +@Service +public class ScoresServiceImpl implements ScoresService { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Override + @Transactional + public void insertScore() { + long id = System.currentTimeMillis(); + int score = new Random().nextInt(100); + // 向数据库中插入随机生成的分数 + int row = jdbcTemplate.update("insert into scores(id,score) values(?,?)", id, score); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 打印影响行数 + System.out.println("scores row = " + row); + } +} diff --git a/spring-transaction/spring-transaction-jdbcTemplate/README.md b/spring-transaction/spring-transaction-jdbcTemplate/README.md new file mode 100644 index 0000000..9fcd4c5 --- /dev/null +++ b/spring-transaction/spring-transaction-jdbcTemplate/README.md @@ -0,0 +1,271 @@ +## JdbcTemplate + +- [JdbcTemplate](#jdbctemplate) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、最佳实践](#四最佳实践) + - [四、源码分析](#四源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +JdbcTemplate是Spring Framework中的一个关键类,用于简化JDBC编程,提供了简洁的方法执行SQL查询、更新和批处理操作,同时处理了JDBC资源的获取、释放和异常处理,使得数据库操作更加简单、高效和安全。 + +### 三、主要功能 + +1. **简化的JDBC操作** + + + JdbcTemplate封装了JDBC的复杂性,提供了简洁的方法来执行SQL查询、更新等操作,无需手动管理连接、语句和结果集。 + +2. **异常处理** + + + JdbcTemplate自动捕获并转换JDBC异常为Spring的DataAccessException,使异常处理更加便捷,无需编写繁琐的异常处理代码。 + +3. **参数化查询** + + + 支持使用占位符进行参数化查询,防止SQL注入攻击,并且可以简化SQL语句的构建和维护。 + +4. **批处理操作** + + + 支持批处理操作,可以一次性执行多个SQL语句,提高数据库操作的效率。 + +5. **回调机制** + + + 提供了回调机制,可以在执行数据库操作前后插入自定义逻辑,如日志记录、性能监控等。 + +6. **支持ORM框架** + + + 可以与Spring的ORM框架(如Spring Data JPA、Spring Data JDBC)结合使用,提供更高层次的数据库访问抽象。 + +### 四、最佳实践 + +使用JdbcTemplate执行SQL查询操作。首先,通过配置数据库连接信息创建了一个`SimpleDriverDataSource` +对象来管理数据源,并创建了一个`DataSourceTransactionManager`对象用于事务管理。然后,通过这些对象创建了一个`JdbcTemplate` +实例。接着,使用`query`方法执行了一个查询操作,将查询结果映射为`Scores`对象的列表,最后打印输出了查询结果。 + +```java +public class JdbcTemplateDemo { + + public static void main(String[] args) throws Exception { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 DataSourceTransactionManager 对象,用于管理事务 + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + + List scoresList = jdbcTemplate.query("select * from scores ", new RowMapper() { + @Override + public Scores mapRow(ResultSet rs, int rowNum) throws SQLException { + Scores scores = new Scores(); + scores.setId(rs.getLong("id")); + scores.setScore(rs.getLong("score")); + return scores; + } + }); + + scoresList.forEach(System.out::println); + } +} +``` + +`Scores`类是一个简单的Java Bean。 + +```java +public class Scores { + + private long id; + + private long score; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getScore() { + return score; + } + + public void setScore(long score) { + this.score = score; + } + + @Override + public String toString() { + return "Scores{" + + "id=" + id + + ", score=" + score + + '}'; + } +} +``` + +运行结果,从数据库查询出的分数记录。 + +```java +Scores { + id = 1715586394553, score = 40 +} + +Scores { + id = 1715587289293, score = 90 +} + +Scores { + id = 1715588070751, score = 20 +} + +Scores { + id = 1715668592566, score = 25 +} +``` + +### 四、源码分析 + +在`org.springframework.jdbc.core.JdbcTemplate#query(java.lang.String, org.springframework.jdbc.core.RowMapper)` +方法中,用于执行SQL查询并将结果映射为对象列表。它接受SQL查询语句和一个RowMapper接口实现作为参数,并将查询结果通过RowMapperResultSetExtractor转换为对象列表后返回。 + +```java +/** + * 执行给定的 SQL 查询,并将结果映射为对象列表。 + * + * @param sql SQL查询语句 + * @param rowMapper 用于将每行结果映射为对象的 RowMapper 接口实现 + * @param 返回结果的类型 + * @return 查询结果的对象列表 + * @throws DataAccessException 如果查询失败或转换结果时发生异常 + */ +@Override +public List query(String sql, RowMapper rowMapper) throws DataAccessException { + // 调用重载的 query 方法,将结果通过 RowMapperResultSetExtractor 转换为对象列表 + return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper))); +} +``` + +在`org.springframework.jdbc.core.JdbcTemplate#query(java.lang.String, org.springframework.jdbc.core.ResultSetExtractor)` +方法中,执行SQL查询并使用ResultSetExtractor提取结果。它接受SQL查询语句和一个ResultSetExtractor接口实现作为参数,通过Statement对象执行SQL查询并将结果集交给ResultSetExtractor进行处理,最终返回ResultSetExtractor提取的结果。 + +```java +/** + * 执行给定的 SQL 查询,并使用指定的 ResultSetExtractor 提取结果。 + * + * @param sql SQL查询语句 + * @param rse 用于提取结果的 ResultSetExtractor 接口实现 + * @param 返回结果的类型 + * @return 查询结果,如果没有结果则返回 null + * @throws DataAccessException 如果查询失败或提取结果时发生异常 + */ +@Nullable +@Override +public T query(final String sql, final ResultSetExtractor rse) throws DataAccessException { + // 确保 SQL 查询语句和 ResultSetExtractor 不为空 + Assert.notNull(sql, "SQL must not be null"); + Assert.notNull(rse, "ResultSetExtractor must not be null"); + + // 如果开启了调试模式,则记录SQL查询语句 + if (logger.isDebugEnabled()) { + logger.debug("Executing SQL query [" + sql + "]"); + } + + /** + * 内部类,用于执行查询操作的回调。 + */ + class QueryStatementCallback implements StatementCallback, SqlProvider { + /** + * 在 Statement 中执行查询并提取结果。 + */ + @Override + @Nullable + public T doInStatement(Statement stmt) throws SQLException { + ResultSet rs = null; + try { + // 执行查询语句,并获取结果集 + rs = stmt.executeQuery(sql); + // 使用 ResultSetExtractor 提取结果 + return rse.extractData(rs); + } finally { + // 关闭结果集 + JdbcUtils.closeResultSet(rs); + } + } + + /** + * 获取查询的 SQL 语句。 + */ + @Override + public String getSql() { + return sql; + } + } + + // 执行查询,并返回结果 + return execute(new QueryStatementCallback(), true); +} +``` + +在`org.springframework.jdbc.core.JdbcTemplate#execute(org.springframework.jdbc.core.StatementCallback, boolean)` +方法中,执行数据库操作回调,并根据需要关闭资源。它接受一个数据库操作回调对象和一个布尔值参数,布尔值参数指示是否在执行完操作后关闭资源。在方法内部,首先获取数据库连接,然后创建Statement对象并应用设置。接着执行数据库操作回调,并处理操作过程中的警告信息。如果操作过程中发生了SQLException,会及时释放连接并抛出DataAccessException。最后,在finally块中根据需要关闭Statement和连接资源。 + +```java +/** + * 执行给定的数据库操作回调,并根据需要关闭资源。 + * + * @param action 数据库操作回调对象 + * @param closeResources 是否关闭资源的标志,如果为 true,则在执行完操作后关闭资源,否则不关闭 + * @param 返回结果的类型 + * @return 执行操作的结果 + * @throws DataAccessException 如果执行操作失败 + */ +@Nullable +private T execute(StatementCallback action, boolean closeResources) throws DataAccessException { + // 确保回调对象不为空 + Assert.notNull(action, "Callback object must not be null"); + + // 获取连接 + Connection con = DataSourceUtils.getConnection(obtainDataSource()); + Statement stmt = null; + try { + // 创建 Statement + stmt = con.createStatement(); + // 应用 Statement 的设置 + applyStatementSettings(stmt); + // 执行数据库操作 + T result = action.doInStatement(stmt); + // 处理警告信息 + handleWarnings(stmt); + return result; + } catch (SQLException ex) { + // 在可能发生连接池死锁的情况下,尽早释放连接以避免死锁 + String sql = getSql(action); + JdbcUtils.closeStatement(stmt); + stmt = null; + DataSourceUtils.releaseConnection(con, getDataSource()); + con = null; + // 转换异常并抛出 + throw translateException("StatementCallback", sql, ex); + } finally { + // 如果需要关闭资源,则在 finally 中关闭 Statement 和连接 + if (closeResources) { + JdbcUtils.closeStatement(stmt); + DataSourceUtils.releaseConnection(con, getDataSource()); + } + } +} +``` + diff --git a/spring-transaction/spring-transaction-jdbcTemplate/pom.xml b/spring-transaction/spring-transaction-jdbcTemplate/pom.xml new file mode 100644 index 0000000..af5d461 --- /dev/null +++ b/spring-transaction/spring-transaction-jdbcTemplate/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-jdbcTemplate + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-jdbcTemplate/src/main/java/com/xcs/spring/JdbcTemplateDemo.java b/spring-transaction/spring-transaction-jdbcTemplate/src/main/java/com/xcs/spring/JdbcTemplateDemo.java new file mode 100644 index 0000000..d2c4cdc --- /dev/null +++ b/spring-transaction/spring-transaction-jdbcTemplate/src/main/java/com/xcs/spring/JdbcTemplateDemo.java @@ -0,0 +1,33 @@ +package com.xcs.spring; + +import com.mysql.jdbc.Driver; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; + +import java.util.List; + +public class JdbcTemplateDemo { + + public static void main(String[] args) throws Exception { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + + List scoresList = jdbcTemplate.query("select * from scores ", (rs, rowNum) -> { + Scores scores = new Scores(); + scores.setId(rs.getLong("id")); + scores.setScore(rs.getLong("score")); + return scores; + }); + + scoresList.forEach(System.out::println); + } +} diff --git a/spring-transaction/spring-transaction-jdbcTemplate/src/main/java/com/xcs/spring/Scores.java b/spring-transaction/spring-transaction-jdbcTemplate/src/main/java/com/xcs/spring/Scores.java new file mode 100644 index 0000000..cf8c677 --- /dev/null +++ b/spring-transaction/spring-transaction-jdbcTemplate/src/main/java/com/xcs/spring/Scores.java @@ -0,0 +1,32 @@ +package com.xcs.spring; + +public class Scores { + + private long id; + + private long score; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getScore() { + return score; + } + + public void setScore(long score) { + this.score = score; + } + + @Override + public String toString() { + return "Scores{" + + "id=" + id + + ", score=" + score + + '}'; + } +} diff --git a/spring-transaction/spring-transaction-platformTransactionManager/README.md b/spring-transaction/spring-transaction-platformTransactionManager/README.md new file mode 100644 index 0000000..712bae7 --- /dev/null +++ b/spring-transaction/spring-transaction-platformTransactionManager/README.md @@ -0,0 +1,865 @@ +## PlatformTransactionManager + +- [PlatformTransactionManager](#platformtransactionmanager) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + - [八、源码分析](#八源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`PlatformTransactionManager` 接口是 Spring +框架中负责管理事务的核心接口,定义了统一的事务管理方法,包括事务的启动、提交、回滚和获取当前事务状态等,为不同的数据访问技术提供了统一的事务管理接口,便于在不同技术之间无缝切换。 + +### 三、主要功能 + +1. **开始事务** + + + `PlatformTransactionManager` 允许我们通过 `getTransaction(TransactionDefinition definition)` + 方法开始一个新的事务或获取一个已存在的事务。`TransactionDefinition` 对象定义了事务的属性,如传播行为、隔离级别、超时设置和只读标志等。 + +2. **提交事务** + + + 如果事务中的所有操作都成功完成,我们可以通过 `commit(TransactionStatus status)` 方法来提交事务。这将使事务中的所有更改永久化。 + +3. **回滚事务** + + + 如果在事务中发生任何异常或错误,我们可以通过 `rollback(TransactionStatus status)` + 方法来回滚事务。这将撤销事务中的所有更改,使数据库回到事务开始前的状态。 + +4. **获取事务状态** + + + `TransactionStatus` 对象提供了关于当前事务状态的信息,如是否是新事务、是否已完成、是否已回滚等。这些信息可以用于在事务处理过程中进行条件逻辑判断。 + +### 四、接口源码 + +`PlatformTransactionManager` 接口是 Spring 命令式事务基础架构的核心接口。它提供了三个主要功能`getTransaction()` +用于获取当前活动事务或创建新事务,`commit()` 用于提交给定的事务,而 `rollback()` +用于回滚给定的事务。这些功能使得应用程序能够有效地管理事务,确保数据的一致性和完整性。 + +```java +/** + * 这是 Spring 命令式事务基础架构中的中心接口。 + * 应用程序可以直接使用它,但主要用途不是作为一个 API + * 通常,应用程序将使用 TransactionTemplate 或通过 AOP 实现的声明式事务界定。 + * + *

对于实现者来说,建议从提供的 AbstractPlatformTransactionManager 类派生, + * 该类预先实现了定义的传播行为并处理了事务同步处理。子类必须实现底层事务的特定状态的模板方法, + * 例如begin、suspend、resume、commit。 + * + *

这个策略接口的默认实现是 JtaTransactionManager 和 DataSourceTransactionManager, + * 它们可以作为其他事务策略的实现指南。 + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 16.05.2003 + * @see org.springframework.transaction.support.TransactionTemplate + * @see org.springframework.transaction.interceptor.TransactionInterceptor + * @see org.springframework.transaction.ReactiveTransactionManager + */ +public interface PlatformTransactionManager extends TransactionManager { + + /** + * 根据指定的传播行为返回当前活动事务或创建一个新事务。 + *

请注意,诸如隔离级别或超时之类的参数仅在创建新事务时应用, + * 因此在参与活动事务时会被忽略。 + *

此外,并非所有的事务定义设置都会被所有的事务管理器支持 + * 当遇到不支持的设置时,适当的事务管理器实现应该抛出异常。 + *

以上规则的一个例外是只读标志, + * 如果不支持显式的只读模式,则应该忽略它。从本质上讲, + * 只读标志只是对潜在优化的一个提示。 + * @param definition TransactionDefinition 实例(对于默认值可以为 null), + * 描述传播行为、隔离级别、超时等。 + * @return 表示新事务或当前事务的事务状态对象 + * @throws TransactionException 如果出现查找、创建或系统错误 + * @throws IllegalTransactionStateException 如果给定的事务定义无法执行 + * (例如,如果当前活动事务与指定的传播行为冲突) + * @see TransactionDefinition#getPropagationBehavior + * @see TransactionDefinition#getIsolationLevel + * @see TransactionDefinition#getTimeout + * @see TransactionDefinition#isReadOnly + */ + TransactionStatus getTransaction(@Nullable TransactionDefinition definition) + throws TransactionException; + + /** + * 根据其状态提交给定的事务。如果事务已被程序标记为仅回滚,请执行回滚。 + *

如果事务不是新的,则省略提交以正确参与周围的事务。 + * 如果先前的事务已被暂停以创建一个新事务,则在提交新事务后恢复先前的事务。 + *

请注意,当提交调用完成时,无论是正常还是抛出异常,事务都必须完全完成和清理。 + * 在这种情况下,不应该期望回滚调用。 + *

如果此方法引发除 TransactionException 之外的异常, + * 则一些提交之前的错误导致提交尝试失败。例如, + * 在提交之前可能会尝试将更改刷新到数据库中, + * 由于 resulting DataAccessException 导致事务失败。在这种情况下,原始异常将传播到此 commit 方法的调用者。 + * @param status 由 getTransaction 方法返回的对象 + * @throws UnexpectedRollbackException 如果事务协调器启动了意外的回滚 + * @throws HeuristicCompletionException 如果事务协调器在事务协调器的一侧做出了启发式决策导致的事务失败 + * @throws TransactionSystemException 在提交或系统错误时(通常是由于基本资源故障引起) + * @throws IllegalTransactionStateException 如果给定的事务已经完成(已提交或已回滚) + * @see TransactionStatus#setRollbackOnly + */ + void commit(TransactionStatus status) throws TransactionException; + + /** + * 执行给定事务的回滚。 + *

如果事务不是新的,则仅将其标记为回滚,以便正确参与周围的事务。 + * 如果先前的事务已被暂停以创建一个新事务,则在回滚新事务后恢复先前的事务。 + *

如果提交引发异常,则不要调用事务回滚。 + * 当提交返回时,事务已经完成和清理,即使在提交异常的情况下也是如此。 + * 因此,在提交失败后调用回滚将导致 IllegalTransactionStateException。 + * @param status 由 getTransaction 方法返回的对象 + * @throws TransactionSystemException 在回滚或系统错误时(通常是由于基本资源故障引起) + * @throws IllegalTransactionStateException 如果给定的事务已经完成(已提交或已回滚) + */ + void rollback(TransactionStatus status) throws TransactionException; + +} +``` + +### 五、主要实现 + +1. **DataSourceTransactionManager** + + + 用于基于 JDBC 的事务管理。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AbstractPlatformTransactionManager +class DataSourceTransactionManager +class InitializingBean { +<> + +} +class PlatformTransactionManager { +<> + +} +class ResourceTransactionManager { +<> + +} +class TransactionManager { +<> + +} + +AbstractPlatformTransactionManager ..> PlatformTransactionManager +DataSourceTransactionManager --> AbstractPlatformTransactionManager +DataSourceTransactionManager ..> InitializingBean +DataSourceTransactionManager ..> ResourceTransactionManager +PlatformTransactionManager --> TransactionManager +ResourceTransactionManager --> PlatformTransactionManager +~~~ + +### 七、最佳实践 + +使用Spring的事务管理器(`PlatformTransactionManager`)和JDBC模板(`JdbcTemplate` +)在Java应用中进行数据库操作。代码首先设置数据库连接信息,并创建数据源和事务管理器。然后,通过`JdbcTemplate` +执行SQL插入操作,将随机生成的分数插入数据库。在执行插入操作时,事务被启动,并在操作成功时提交。如果出现异常,则回滚事务,确保数据一致性。最后,打印操作影响的行数。 + +```java +public class PlatformTransactionManagerDemo { + + private static PlatformTransactionManager transactionManager; + private static JdbcTemplate jdbcTemplate; + + public static void main(String[] args) throws SQLException { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 DataSourceTransactionManager 对象,用于管理事务 + transactionManager = new DataSourceTransactionManager(dataSource); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + jdbcTemplate = new JdbcTemplate(dataSource); + + insertScore(); + } + + private static void insertScore() { + // 开启一个新的事务,返回事务状态对象 + TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition()); + try { + long id = System.currentTimeMillis(); + int score = new Random().nextInt(100); + // 向数据库中插入随机生成的分数 + int row = jdbcTemplate.update("insert into scores(id,score) values(?,?)", id, score); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 提交事务 + transactionManager.commit(transactionStatus); + // 打印影响行数 + System.out.println("scores row = " + row); + } catch (Exception e) { + // 出现异常时回滚事务 + transactionManager.rollback(transactionStatus); + e.printStackTrace(); + } + } +} +``` + +### 八、源码分析 + +**开启事务** + +在`org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction` +方法中,实现了 `PlatformTransactionManager` 接口的 `getTransaction()` +方法,处理了事务的传播行为。根据传播行为的不同,通过调用 `doGetTransaction()`、`isExistingTransaction()` 和 `doBegin()` +等方法来获取、检查现有事务,并开始新的事务。如果存在现有事务,则根据传播行为确定如何处理;如果不存在现有事务,则根据传播行为决定如何继续。在创建新事务时,可能会挂起已存在的事务,并在出现异常时恢复挂起的资源。如果传播行为指定了 " +mandatory" +,但未找到现有事务,则抛出异常。若指定的超时时间无效,则抛出超时异常。最后,根据传播行为返回相应的事务状态对象,可能是一个 " +empty" 事务,没有实际的事务,但可能存在同步。 + +```java +/** + * 此实现处理传播行为。委托给 doGetTransaction、isExistingTransaction 和 doBegin。 + * @see #doGetTransaction + * @see #isExistingTransaction + * @see #doBegin + */ +@Override +public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) + throws TransactionException { + + // 如果没有给定事务定义,则使用默认值。 + TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults()); + + // 获取事务对象 + Object transaction = doGetTransaction(); + // 是否启用调试日志 + boolean debugEnabled = logger.isDebugEnabled(); + + if (isExistingTransaction(transaction)) { + // 找到现有事务 -> 检查传播行为以确定如何操作。 + return handleExistingTransaction(def, transaction, debugEnabled); + } + + // 检查新事务的定义设置。 + if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) { + throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout()); + } + + // 未找到现有事务 -> 检查传播行为以确定如何继续。 + if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) { + throw new IllegalTransactionStateException( + "No existing transaction found for transaction marked with propagation 'mandatory'"); + } else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || + def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || + def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { + // 挂起已存在的事务(如果有),然后创建新事务。 + SuspendedResourcesHolder suspendedResources = suspend(null); + if (debugEnabled) { + logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def); + } + try { + // 开始新事务 + return startTransaction(def, transaction, debugEnabled, suspendedResources); + } catch (RuntimeException | Error ex) { + // 如果在开始新事务时出现异常,则恢复挂起的资源并抛出异常。 + resume(null, suspendedResources); + throw ex; + } + } else { + // 创建“空”事务:没有实际的事务,但可能存在同步。 + if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) { + logger.warn("Custom isolation level specified but no actual transaction initiated; " + + "isolation level will effectively be ignored: " + def); + } + // 返回准备好的事务状态,没有实际事务,但可能存在同步。 + boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); + return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null); + } +} +``` + +在`org.springframework.jdbc.datasource.DataSourceTransactionManager#doGetTransaction` +方法中,首先创建了一个 `DataSourceTransactionObject` +对象,用于管理数据源事务。然后根据是否允许嵌套事务设置了保存点的允许状态。接着通过 `TransactionSynchronizationManager.getResource()` +方法获取当前线程绑定的数据源连接持有者对象,并将其设置到事务对象中。最后返回该事务对象。 + +```java + +@Override +protected Object doGetTransaction() { + // 创建 DataSourceTransactionObject 对象,用于管理数据源事务 + DataSourceTransactionObject txObject = new DataSourceTransactionObject(); + // 设置是否允许设置保存点 + txObject.setSavepointAllowed(isNestedTransactionAllowed()); + // 获取当前线程绑定的数据源的连接持有者 + ConnectionHolder conHolder = + (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource()); + // 将连接持有者设置到事务对象中 + txObject.setConnectionHolder(conHolder, false); + return txObject; +} +``` + +在`org.springframework.jdbc.datasource.DataSourceTransactionManager#isExistingTransaction` +方法中,用于检查给定的事务对象是否表示一个已存在的活动事务。它首先将传入的事务对象强制转换为 `DataSourceTransactionObject` +类型,然后检查该事务对象是否具有连接持有者,并且该连接持有者中的事务是否处于活动状态。最后返回一个布尔值,表示是否存在活动事务。 + +```java + +@Override +protected boolean isExistingTransaction(Object transaction) { + // 将事务对象强制转换为 DataSourceTransactionObject 类型 + DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; + // 检查事务对象是否具有连接持有者,并且连接持有者中的事务是否处于活动状态 + return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive()); +} +``` + +在`org.springframework.transaction.support.AbstractPlatformTransactionManager#handleExistingTransaction` +方法中,根据事务定义中的传播行为不同,它可能会挂起当前事务并创建一个新的事务,也可能会创建一个嵌套事务,或者参与已存在的事务。最终,根据处理结果创建并返回一个新的 +TransactionStatus 对象,用于表示已存在的事务。 + +```java +/** + * 处理已存在的事务,为其创建一个 TransactionStatus 对象。 + * + * @param definition 事务定义对象 + * @param transaction 事务对象 + * @param debugEnabled 是否启用调试日志 + * @return 一个包含有关现有事务的 TransactionStatus 对象 + * @throws TransactionException 如果在处理现有事务时发生错误 + */ +private TransactionStatus handleExistingTransaction( + TransactionDefinition definition, Object transaction, boolean debugEnabled) + throws TransactionException { + + // 如果传播行为是 PROPAGATION_NEVER,则不允许存在现有事务 + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) { + throw new IllegalTransactionStateException( + "Existing transaction found for transaction marked with propagation 'never'"); + } + + // 如果传播行为是 PROPAGATION_NOT_SUPPORTED,则挂起当前事务 + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { + if (debugEnabled) { + logger.debug("Suspending current transaction"); + } + // 挂起当前事务并获取挂起的资源 + Object suspendedResources = suspend(transaction); + // 判断是否需要新的事务同步 + boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); + // 准备事务状态并返回 + return prepareTransactionStatus( + definition, null, false, newSynchronization, debugEnabled, suspendedResources); + } + + // 如果传播行为是 PROPAGATION_REQUIRES_NEW,则挂起当前事务并创建一个新事务 + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) { + if (debugEnabled) { + logger.debug("Suspending current transaction, creating new transaction with name [" + + definition.getName() + "]"); + } + // 挂起当前事务并获取挂起的资源 + SuspendedResourcesHolder suspendedResources = suspend(transaction); + try { + // 启动新的事务 + return startTransaction(definition, transaction, debugEnabled, suspendedResources); + } catch (RuntimeException | Error beginEx) { + // 开始事务时发生异常,恢复挂起的资源,并将异常抛出 + resumeAfterBeginException(transaction, suspendedResources, beginEx); + throw beginEx; + } + } + + // 如果传播行为是 PROPAGATION_NESTED,则创建一个嵌套事务 + if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { + // 检查是否允许嵌套事务 + if (!isNestedTransactionAllowed()) { + throw new NestedTransactionNotSupportedException( + "Transaction manager does not allow nested transactions by default - " + + "specify 'nestedTransactionAllowed' property with value 'true'"); + } + if (debugEnabled) { + logger.debug("Creating nested transaction with name [" + definition.getName() + "]"); + } + // 如果使用保存点进行嵌套事务,则在现有 Spring 管理的事务中创建保存点 + if (useSavepointForNestedTransaction()) { + // 通过 TransactionStatus 实现的 SavepointManager API 在现有的 Spring 管理的事务中创建保存点 + // 通常使用 JDBC 3.0 保存点,永远不会激活 Spring 同步。 + DefaultTransactionStatus status = + prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null); + status.createAndHoldSavepoint(); + return status; + } else { + // 通过嵌套的 begin 和 commit/rollback 调用创建嵌套事务 + // 通常仅用于 JTA:如果存在预先存在的 JTA 事务,则此处可能会激活 Spring 同步。 + return startTransaction(definition, transaction, debugEnabled, null); + } + } + + // Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED. + // 如果传播行为是 PROPAGATION_SUPPORTS 或 PROPAGATION_REQUIRED,则参与现有事务 + if (debugEnabled) { + logger.debug("Participating in existing transaction"); + } + // 如果启用了验证现有事务,则检查定义是否与现有事务兼容 + if (isValidateExistingTransaction()) { + if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { + // 检查隔离级别是否与现有事务兼容 + Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); + if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) { + Constants isoConstants = DefaultTransactionDefinition.constants; + throw new IllegalTransactionStateException("Participating transaction with definition [" + + definition + "] specifies isolation level which is incompatible with existing transaction: " + + (currentIsolationLevel != null ? + isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) : + "(unknown)")); + } + } + // 检查只读属性是否与现有事务兼容 + if (!definition.isReadOnly()) { + if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { + throw new IllegalTransactionStateException("Participating transaction with definition [" + + definition + "] is not marked as read-only but existing transaction is"); + } + } + } + // 根据情况创建新的事务同步 + boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null); +} +``` + +在`org.springframework.transaction.support.AbstractPlatformTransactionManager#startTransaction` +方法中,首先检查是否应该启用事务同步,然后创建一个新的事务状态对象。接着调用 `doBegin` 方法执行事务的开始操作,并准备同步操作。最后返回创建的事务状态对象。 + +```java +/** + * 开始一个新的事务。 + */ +private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction, + boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) { + + // 判断是否要启用事务同步 + boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); + // 创建一个新的事务状态对象 + DefaultTransactionStatus status = newTransactionStatus( + definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); + // 执行事务开始操作 + doBegin(transaction, definition); + // 准备同步操作 + prepareSynchronization(status, definition); + return status; +} +``` + +在`org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin` +方法中,首先尝试从数据源获取一个数据库连接,并在获取连接后将其存储在事务对象中。然后,它根据事务定义中的属性对连接进行一系列配置,例如设置隔离级别和只读属性。如果连接是自动提交的,则将其切换为手动提交以确保事务的一致性。接着,它准备事务连接,并将连接标记为活跃的事务。最后,如果是新的连接持有者,则将连接持有者绑定到当前线程,以便在事务期间管理连接的生命周期。如果在这个过程中发生任何错误,它将释放连接资源,并抛出一个表示无法创建事务的异常。 + +```java + +@Override +protected void doBegin(Object transaction, TransactionDefinition definition) { + // 将事务对象转换为 DataSourceTransactionObject + DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; + Connection con = null; + + try { + // 如果当前事务对象没有连接持有者,或者连接持有者已与事务同步,则获取新的连接 + if (!txObject.hasConnectionHolder() || + txObject.getConnectionHolder().isSynchronizedWithTransaction()) { + // 获取数据源并从中获取连接 + Connection newCon = obtainDataSource().getConnection(); + // 如果日志记录级别为DEBUG,则打印获取到的连接信息 + if (logger.isDebugEnabled()) { + logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction"); + } + // 设置连接持有者为新获取的连接,并指示需要与事务同步 + txObject.setConnectionHolder(new ConnectionHolder(newCon), true); + } + + // 将连接的同步标志设置为true + txObject.getConnectionHolder().setSynchronizedWithTransaction(true); + // 获取当前连接 + con = txObject.getConnectionHolder().getConnection(); + + // 准备连接的事务属性,并记录之前的隔离级别 + Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); + txObject.setPreviousIsolationLevel(previousIsolationLevel); + // 设置只读属性 + txObject.setReadOnly(definition.isReadOnly()); + + // 如果连接是自动提交的,则切换为手动提交 + if (con.getAutoCommit()) { + // 标记需要恢复原来的自动提交状态 + txObject.setMustRestoreAutoCommit(true); + // 如果日志记录级别为DEBUG,则打印切换自动提交到手动提交的信息 + if (logger.isDebugEnabled()) { + logger.debug("Switching JDBC Connection [" + con + "] to manual commit"); + } + // 设置为手动提交 + con.setAutoCommit(false); + } + + // 准备事务连接,并将连接标记为活跃的事务 + prepareTransactionalConnection(con, definition); + txObject.getConnectionHolder().setTransactionActive(true); + + // 确定事务的超时时间,并设置给连接持有者 + int timeout = determineTimeout(definition); + if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { + txObject.getConnectionHolder().setTimeoutInSeconds(timeout); + } + + // 如果是新的连接持有者,则将连接持有者绑定到线程 + if (txObject.isNewConnectionHolder()) { + TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); + } + } catch (Throwable ex) { + // 如果是新的连接持有者,则释放连接,并将连接持有者设置为null + if (txObject.isNewConnectionHolder()) { + DataSourceUtils.releaseConnection(con, obtainDataSource()); + txObject.setConnectionHolder(null, false); + } + // 抛出无法创建事务异常 + throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex); + } +} +``` + +在`org.springframework.jdbc.datasource.DataSourceTransactionManager#prepareTransactionalConnection` +方法中,如果设置了“enforceReadOnly”标志为true,并且事务定义指示为只读事务,则会执行一个“SET TRANSACTION READ ONLY”语句。 + +```java +/** + * 在事务开始后准备事务性 {@code Connection}。 + *

如果 {@link #setEnforceReadOnly "enforceReadOnly"} 标志设置为 {@code true}, + * 并且事务定义指示为只读事务,那么默认实现将执行一个 "SET TRANSACTION READ ONLY" 语句。 + *

"SET TRANSACTION READ ONLY" 语句可被 Oracle、MySQL 和 Postgres 理解,并且可能适用于其他数据库。 + * 如果您希望调整此处理方式,请相应地重写此方法。 + * @param con 事务性 JDBC 连接 + * @param definition 当前事务定义 + * @throws SQLException 如果 JDBC API 抛出异常 + * @since 4.3.7 + * @see #setEnforceReadOnly + */ +protected void prepareTransactionalConnection(Connection con, TransactionDefinition definition) + throws SQLException { + + if (isEnforceReadOnly() && definition.isReadOnly()) { + try (Statement stmt = con.createStatement()) { + stmt.executeUpdate("SET TRANSACTION READ ONLY"); + } + } +} +``` + +**提交事务** + +在`org.springframework.transaction.support.AbstractPlatformTransactionManager#commit` +方法中,检查事务状态是否已完成,如果已完成则抛出非法事务状态异常。然后,检查事务状态是否为本地回滚,如果是,则执行回滚操作。接着,如果全局事务标记为回滚,但事务代码请求提交,则执行回滚操作。最后,如果以上情况都不满足,则执行事务的提交操作。 + +```java +/** + * 此提交的实现处理参与现有事务和编程回滚请求。委托给{@code isRollbackOnly}、{@code doCommit}和{@code rollback}。 + * 如果事务已经完成,则抛出异常。 + * 如果事务状态为本地回滚,则根据情况执行回滚操作。 + * 如果不应仅在全局回滚的情况下提交事务,并且事务状态为全局回滚,则根据情况执行回滚操作。 + * 否则,执行提交操作。 + * @param status 事务状态对象 + * @throws TransactionException 如果提交过程中发生事务异常 + * @see org.springframework.transaction.TransactionStatus#isRollbackOnly() + * @see #doCommit + * @see #rollback + */ +@Override +public final void commit(TransactionStatus status) throws TransactionException { + // 如果事务已经完成,则抛出异常 + if (status.isCompleted()) { + throw new IllegalTransactionStateException( + "Transaction is already completed - do not call commit or rollback more than once per transaction"); + } + + DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; + // 如果事务状态为本地回滚,则根据情况执行回滚操作 + if (defStatus.isLocalRollbackOnly()) { + if (defStatus.isDebug()) { + logger.debug("Transactional code has requested rollback"); + } + processRollback(defStatus, false); + return; + } + + // 如果不应仅在全局回滚的情况下提交事务,并且事务状态为全局回滚,则根据情况执行回滚操作 + if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) { + if (defStatus.isDebug()) { + logger.debug("Global transaction is marked as rollback-only but transactional code requested commit"); + } + processRollback(defStatus, true); + return; + } + + // 否则,执行提交操作 + processCommit(defStatus); +} +``` + +在`org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit` +方法中,检查并应用了回滚标志,然后执行相应的提交逻辑。在提交的过程中,会触发各种回调方法,如提交前、提交后等,以便在事务提交的不同阶段执行特定的逻辑。如果发生了意外回滚或提交失败,它会相应地处理异常情况,并执行相应的回滚操作。最终,无论提交是否成功,都会执行清理操作以确保事务状态正确。 + +```java +/** + * 处理实际的提交操作。 + * 已经检查并应用了回滚标志。 + * + * @param status 表示事务的对象 + * @throws TransactionException 如果提交失败 + */ +private void processCommit(DefaultTransactionStatus status) throws TransactionException { + try { + boolean beforeCompletionInvoked = false; + + try { + boolean unexpectedRollback = false; + // 为提交做准备 + prepareForCommit(status); + // 触发提交前的回调 + triggerBeforeCommit(status); + // 触发事务完成前的回调 + triggerBeforeCompletion(status); + beforeCompletionInvoked = true; + + // 如果存在保存点 + if (status.hasSavepoint()) { + if (status.isDebug()) { + // 释放事务保存点 + logger.debug("Releasing transaction savepoint"); + } + // 判断是否全局回滚 + unexpectedRollback = status.isGlobalRollbackOnly(); + // 释放持有的保存点 + status.releaseHeldSavepoint(); + } else if (status.isNewTransaction()) { + // 如果是新事务 + if (status.isDebug()) { + // 开始事务提交 + logger.debug("Initiating transaction commit"); + } + // 判断是否全局回滚 + unexpectedRollback = status.isGlobalRollbackOnly(); + // 执行提交 + doCommit(status); + } else if (isFailEarlyOnGlobalRollbackOnly()) { + // 如果全局回滚 + // 判断是否全局回滚 + unexpectedRollback = status.isGlobalRollbackOnly(); + } + + // 如果存在意外回滚,但仍未从提交中获得相应的异常,则抛出 UnexpectedRollbackException 异常 + if (unexpectedRollback) { + throw new UnexpectedRollbackException( + "Transaction silently rolled back because it has been marked as rollback-only"); + } + } catch (UnexpectedRollbackException ex) { + // 只能由 doCommit 导致 + triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); + throw ex; + } catch (TransactionException ex) { + // 只能由 doCommit 导致 + if (isRollbackOnCommitFailure()) { + doRollbackOnCommitException(status, ex); + } else { + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + } + throw ex; + } catch (RuntimeException | Error ex) { + if (!beforeCompletionInvoked) { + triggerBeforeCompletion(status); + } + doRollbackOnCommitException(status, ex); + throw ex; + } + + // 触发 afterCommit 回调,在那里抛出的异常被传播给调用者,但事务仍被视为已提交。 + try { + triggerAfterCommit(status); + } finally { + triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); + } + + } finally { + // 完成后的清理 + cleanupAfterCompletion(status); + } +} +``` + +在`org.springframework.jdbc.datasource.DataSourceTransactionManager#doCommit` +方法中,通过事务状态对象获取数据源事务对象,然后从事务对象中获取数据库连接,接着尝试提交数据库事务,如果提交过程中发生 SQL +异常,则将其转换为 Spring 事务异常并抛出。 + +```java + +@Override +protected void doCommit(DefaultTransactionStatus status) { + // 强制转换为 DataSourceTransactionObject 对象,获取事务相关信息 + DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); + // 从事务对象中获取数据库连接 + Connection con = txObject.getConnectionHolder().getConnection(); + // 如果是调试模式,则记录调试信息 + if (status.isDebug()) { + logger.debug("Committing JDBC transaction on Connection [" + con + "]"); + } + try { + // 提交数据库事务 + con.commit(); + } catch (SQLException ex) { + // 发生 SQL 异常,将其转换为 Spring 事务异常并抛出 + throw translateException("JDBC commit", ex); + } +} +``` + +**回滚事务** + +在`org.springframework.transaction.support.AbstractPlatformTransactionManager#rollback` +方法中,,用于处理现有事务的回滚操作。它委托给 `processRollback` 方法来执行回滚,并通过检查事务状态来确保不会多次调用提交或回滚操作。 + +```java +/** + * 该回滚操作的实现处理参与现有事务。委托给 {@code doRollback} 和 {@code doSetRollbackOnly}。 + * + * @see #doRollback + * @see #doSetRollbackOnly + */ +@Override +public final void rollback(TransactionStatus status) throws TransactionException { + // 如果事务已经完成,则抛出异常 + if (status.isCompleted()) { + throw new IllegalTransactionStateException( + "Transaction is already completed - do not call commit or rollback more than once per transaction"); + } + + // 将事务状态转换为默认事务状态 + DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; + // 执行回滚操作 + processRollback(defStatus, false); +} +``` + +在`org.springframework.transaction.support.AbstractPlatformTransactionManager#processRollback` +方法中,检查事务的完成标志,然后根据事务的状态执行相应的回滚操作。如果存在保存点,则回滚到该保存点;如果是新事务,则执行初始化的事务回滚操作;如果参与了较大的事务,则根据条件进行相应的处理。在执行过程中,会根据情况触发相应的事务同步操作,并根据全局回滚标记判断是否引发意外回滚异常。最后,无论是否发生异常,都会执行完成后的清理操作。 + +```java +/** + * 处理实际的回滚操作。 + * 已经检查过事务完成标志。 + * @param status 表示事务的对象 + * @param unexpected 是否意外回滚 + * @throws TransactionException 如果回滚失败 + */ +private void processRollback(DefaultTransactionStatus status, boolean unexpected) { + try { + boolean unexpectedRollback = unexpected; + + try { + // 触发完成前操作 + triggerBeforeCompletion(status); + + // 回滚到保存点 + if (status.hasSavepoint()) { + if (status.isDebug()) { + logger.debug("Rolling back transaction to savepoint"); + } + status.rollbackToHeldSavepoint(); + } + // 初始化事务回滚 + else if (status.isNewTransaction()) { + if (status.isDebug()) { + logger.debug("Initiating transaction rollback"); + } + doRollback(status); + } else { + // 参与较大的事务 + if (status.hasTransaction()) { + // 如果是本地回滚,或者全局回滚失败 + if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) { + if (status.isDebug()) { + logger.debug("Participating transaction failed - marking existing transaction as rollback-only"); + } + // 设置事务为仅回滚 + doSetRollbackOnly(status); + } else { + if (status.isDebug()) { + logger.debug("Participating transaction failed - letting transaction originator decide on rollback"); + } + } + } else { + logger.debug("Should roll back transaction but cannot - no transaction available"); + } + // 如果不是全局仅回滚,则不会在此处考虑意外回滚 + if (!isFailEarlyOnGlobalRollbackOnly()) { + unexpectedRollback = false; + } + } + } catch (RuntimeException | Error ex) { + // 触发完成后操作,状态为未知 + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + throw ex; + } + + // 触发完成后操作,状态为已回滚 + triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); + + // 如果存在全局回滚标记,则引发UnexpectedRollbackException + if (unexpectedRollback) { + throw new UnexpectedRollbackException( + "Transaction rolled back because it has been marked as rollback-only"); + } + } finally { + // 完成后清理 + cleanupAfterCompletion(status); + } +} +``` + +在`org.springframework.jdbc.datasource.DataSourceTransactionManager#doRollback` +方法中,从事务状态中获取数据源事务对象,然后从该对象中获取连接对象。接着,它尝试执行连接对象的回滚操作。 + +```java +/** + * 执行回滚操作。 + * @param status 表示事务的对象 + */ +@Override +protected void doRollback(DefaultTransactionStatus status) { + // 获取事务数据源对象 + DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); + // 获取连接对象 + Connection con = txObject.getConnectionHolder().getConnection(); + // 如果启用了调试模式,则记录回滚日志 + if (status.isDebug()) { + logger.debug("Rolling back JDBC transaction on Connection [" + con + "]"); + } + try { + // 执行回滚操作 + con.rollback(); + } catch (SQLException ex) { + // 抛出数据库异常 + throw translateException("JDBC rollback", ex); + } +} +``` \ No newline at end of file diff --git a/spring-transaction/spring-transaction-platformTransactionManager/pom.xml b/spring-transaction/spring-transaction-platformTransactionManager/pom.xml new file mode 100644 index 0000000..3b7b4fa --- /dev/null +++ b/spring-transaction/spring-transaction-platformTransactionManager/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-platformTransactionManager + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-platformTransactionManager/src/main/java/com/xcs/spring/PlatformTransactionManagerDemo.java b/spring-transaction/spring-transaction-platformTransactionManager/src/main/java/com/xcs/spring/PlatformTransactionManagerDemo.java new file mode 100644 index 0000000..0ff5578 --- /dev/null +++ b/spring-transaction/spring-transaction-platformTransactionManager/src/main/java/com/xcs/spring/PlatformTransactionManagerDemo.java @@ -0,0 +1,84 @@ +package com.xcs.spring; + +import com.mysql.jdbc.Driver; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; + +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.util.Random; + +public class PlatformTransactionManagerDemo { + + private static PlatformTransactionManager transactionManager; + private static JdbcTemplate jdbcTemplate; + + public static void main(String[] args) throws SQLException { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 DataSourceTransactionManager 对象,用于管理事务 + transactionManager = new DataSourceTransactionManager(dataSource); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + jdbcTemplate = new JdbcTemplate(dataSource); + + insertScore(); + } + + private static void insertScore() { + // 事务定义 + DefaultTransactionDefinition definition = new DefaultTransactionDefinition(DefaultTransactionDefinition.PROPAGATION_REQUIRED); + // 开启一个新的事务,返回事务状态对象 + TransactionStatus transactionStatus = transactionManager.getTransaction(definition); + try { + long id = System.currentTimeMillis(); + int score = new Random().nextInt(100); + // 向数据库中插入随机生成的分数 + int row = jdbcTemplate.update("insert into scores(id,score) values(?,?)", id, score); + insertScoreLog(id); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 提交事务 + transactionManager.commit(transactionStatus); + // 打印影响行数 + System.out.println("scores row = " + row); + } catch (Exception e) { + // 出现异常时回滚事务 + transactionManager.rollback(transactionStatus); + e.printStackTrace(); + } + } + + private static void insertScoreLog(long scoreId) { + // 事务定义 + DefaultTransactionDefinition definition = new DefaultTransactionDefinition(DefaultTransactionDefinition.PROPAGATION_REQUIRED); + // 开启一个新的事务,返回事务状态对象 + TransactionStatus transactionStatus = transactionManager.getTransaction(definition); + try { + long id = System.currentTimeMillis(); + LocalDateTime createTime = LocalDateTime.now(); + // 向数据库中插入随机生成的分数 + int row = jdbcTemplate.update("insert into scores_log(id,score_id,create_time) values(?,?,?)", id, scoreId, createTime); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 提交事务 + transactionManager.commit(transactionStatus); + // 打印影响行数 + System.out.println("scores_log row = " + row); + } catch (Exception e) { + // 出现异常时回滚事务 + transactionManager.rollback(transactionStatus); + e.printStackTrace(); + } + } +} diff --git a/spring-transaction/spring-transaction-springTransactionAnnotationParser/README.md b/spring-transaction/spring-transaction-springTransactionAnnotationParser/README.md new file mode 100644 index 0000000..4c5d6c1 --- /dev/null +++ b/spring-transaction/spring-transaction-springTransactionAnnotationParser/README.md @@ -0,0 +1,250 @@ +## TransactionAnnotationParser + +- [TransactionAnnotationParser](#transactionannotationparser) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + - [八、源码分析](#八源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`TransactionAnnotationParser` 接口是 Spring Framework 中的一个接口,用于定义解析事务相关注解的标准方式,我们可以通过实现该接口来自定义解析特定事务注解的逻辑,并将其转换为 +Spring 内部的事务配置对象,以实现更灵活的事务管理。 + +### 三、主要功能 + +1. **解析事务注解** + + + 该接口定义了解析事务相关注解的方法,我们可以通过实现该方法来提取注解中的属性信息。 + +2. **支持多种注解** + + + 允许实现类支持多种事务相关的注解,不仅限于 `@Transactional`,这使得接口更加灵活和通用。 + +3. **自定义扩展** + + + 根据自己的需求实现该接口,并自定义解析逻辑,以满足特定场景下的事务管理需求,从而扩展 Spring Framework 的事务管理功能。 + +### 四、接口源码 + +用于解析已知的事务注解类型。它包括一个默认方法用于确定给定类是否是事务注解的候选类,以及一个方法用于解析给定方法或类上的事务注解并将其转换为 +Spring 框架的事务属性对象。该接口的实现类可用于支持特定的事务注解类型,如 Spring 的 `@Transactional`、JTA 1.2 +的 `javax.transaction.Transactional` 或 EJB3 的 `javax.ejb.TransactionAttribute`。 + +```java +/** + * 已知事务注解类型解析的策略接口。 + * {@link AnnotationTransactionAttributeSource} 委托给此类解析器,以支持特定的注解类型,例如 Spring 的 {@link Transactional}、JTA 1.2 的 {@link javax.transaction.Transactional} 或 EJB3 的 {@link javax.ejb.TransactionAttribute}。 + * + * @author Juergen Hoeller + * @since 2.5 + * @see AnnotationTransactionAttributeSource + * @see SpringTransactionAnnotationParser + * @see Ejb3TransactionAnnotationParser + * @see JtaTransactionAnnotationParser + */ +public interface TransactionAnnotationParser { + + /** + * 确定给定类是否是此 {@code TransactionAnnotationParser} 的注解格式中的事务属性候选类。 + *

如果此方法返回 {@code false},则给定类上的方法将不会被遍历用于 {@code #parseTransactionAnnotation} 内省。 + * 返回 {@code false} 是针对未受影响的类的优化,而 {@code true} 意味着类需要对给定类上的每个方法进行完整的内省。 + * @param targetClass 要内省的类 + * @return 如果已知该类在类或方法级别上没有事务注解,则返回 {@code false};否则返回 {@code true}。默认实现返回 {@code true},导致常规内省。 + * @since 5.2 + */ + default boolean isCandidateClass(Class targetClass) { + return true; + } + + /** + * 根据此解析器理解的注解类型,解析给定方法或类的事务属性。 + *

本质上,这将已知的事务注解解析为 Spring 的元数据属性类。如果方法/类不是事务性的,则返回 {@code null}。 + * @param element 被注解的方法或类 + * @return 配置的事务属性,如果没有找到则返回 {@code null} + * @see AnnotationTransactionAttributeSource#determineTransactionAttribute + */ + @Nullable + TransactionAttribute parseTransactionAnnotation(AnnotatedElement element); + +} +``` + +### 五、主要实现 + +1. **SpringTransactionAnnotationParser** + + + 用于解析 Spring Framework 中的 `@Transactional` 注解,它能够将 `@Transactional` 注解中的属性信息提取出来,并转换为 + Spring 内部的事务配置对象。在 Spring 应用中,通常使用 `@Transactional` 注解声明事务,因此这个解析器是非常常用的,它使得事务管理更加便捷和灵活。 + +2. **Ejb3TransactionAnnotationParser** + + + 用于解析 EJB3 规范中的 `javax.ejb.TransactionAttribute` 注解。EJB3 是 Java EE + 规范中的一部分,用于开发企业级应用程序。`Ejb3TransactionAnnotationParser` 负责解析 `javax.ejb.TransactionAttribute` + 注解,并将其转换为 Spring 内部的事务配置对象,这使得 Spring 能够与 EJB3 技术集成,实现统一的事务管理。 + +3. **JtaTransactionAnnotationParser** + + + 用于解析 JTA(Java Transaction API)规范中定义的 `javax.transaction.Transactional` 注解。JTA 是 Java 平台的一部分,提供了一套 + API 用于管理分布式事务。`JtaTransactionAnnotationParser` 负责解析 `javax.transaction.Transactional` 注解,并将其转换为 + Spring 内部的事务配置对象,这使得 Spring 能够与分布式事务相关的技术集成,实现全面的事务管理支持。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class Ejb3TransactionAnnotationParser +class JtaTransactionAnnotationParser +class SpringTransactionAnnotationParser +class TransactionAnnotationParser { +<> + +} + +Ejb3TransactionAnnotationParser ..> TransactionAnnotationParser +JtaTransactionAnnotationParser ..> TransactionAnnotationParser +SpringTransactionAnnotationParser ..> TransactionAnnotationParser +~~~ + +### 七、最佳实践 + +使用 `SpringTransactionAnnotationParser` +类来解析方法上的事务注解,并将其转换为事务属性对象。在这个示例中,通过反射获取了 `ScoresServiceImpl` 类中的 `insertScore` +方法,然后通过 `SpringTransactionAnnotationParser` 解析该方法上的事务注解,最后将解析结果输出到控制台。 + +```java +public class SpringTransactionAnnotationParserDemo { + + public static void main(String[] args) throws NoSuchMethodException { + // 获取 ScoresServiceImpl 类中的 insertScore 方法 + Method insertScoreMethod = ScoresServiceImpl.class.getMethod("insertScore"); + // 创建 SpringTransactionAnnotationParser 实例 + SpringTransactionAnnotationParser parser = new SpringTransactionAnnotationParser(); + // 解析 insertScore 方法上的事务注解,并转换为事务属性对象 + TransactionAttribute transactionAttribute = parser.parseTransactionAnnotation(insertScoreMethod); + // 输出事务属性对象 + System.out.println(transactionAttribute); + } +} +``` + +`ScoresServiceImpl` 类实现了 `ScoresService` 接口,其中的 `insertScore` 方法被 `@Transactional` +注解修饰,声明了一个事务。该事务的特性包括:只读(readOnly = true),遇到任何异常都会回滚(rollbackFor = +Exception.class),事务隔离级别为可重复读(isolation = Isolation.REPEATABLE_READ),超时时间为 30 秒(timeout = +30),以及标签为 "tx1" 和 "tx2"。 + +```java +public class ScoresServiceImpl implements ScoresService { + + @Override + @Transactional( + readOnly = true, + rollbackFor = Exception.class, + isolation = Isolation.REPEATABLE_READ, + timeout = 30, + label = {"tx1", "tx2"} + ) + public void insertScore() { + // TODO + } +} +``` + +运行结果,事务的传播行为为 `PROPAGATION_REQUIRED`,隔离级别为 `ISOLATION_REPEATABLE_READ`,超时时间为 30 +秒,只读模式开启,标签为 "tx1" 和 "tx2",同时,事务会回滚遇到 `java.lang.Exception` 及其子类的异常。 + +```java +PROPAGATION_REQUIRED,ISOLATION_REPEATABLE_READ,timeout_30,readOnly; [tx1,tx2],-java.lang.Exception +``` + +### 八、源码分析 + +在`org.springframework.transaction.annotation.SpringTransactionAnnotationParser#parseTransactionAnnotation(java.lang.reflect.AnnotatedElement)` +方法中,首先通过 `AnnotatedElementUtils` 查找并获取元素上合并的 `@Transactional` +注解的属性信息,如果找到了注解,则调用另一个方法解析事务注解并返回事务属性对象,如果未找到注解,则返回 null。 + +```java + +@Override +@Nullable +public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) { + // 查找并获取元素上合并的 @Transactional 注解的属性信息 + AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes( + element, Transactional.class, false, false); + // 如果属性信息不为空,则解析事务注解并返回事务属性对象 + if (attributes != null) { + return parseTransactionAnnotation(attributes); + } + // 如果属性信息为空,则返回 null + else { + return null; + } +} +``` + +在`org.springframework.transaction.annotation.SpringTransactionAnnotationParser#parseTransactionAnnotation(org.springframework.core.annotation.AnnotationAttributes)` +方法中,用于解析给定的注解属性并将其转换为事务属性对象。它根据注解属性中的信息设置事务的传播行为、隔离级别、超时时间、只读属性、限定符、标签和回滚规则等。最后,它返回一个基于规则的事务属性对象。 + +```java +protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) { + // 创建一个基于规则的事务属性对象 + RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute(); + + // 设置事务传播行为 + Propagation propagation = attributes.getEnum("propagation"); + rbta.setPropagationBehavior(propagation.value()); + // 设置事务隔离级别 + Isolation isolation = attributes.getEnum("isolation"); + rbta.setIsolationLevel(isolation.value()); + + // 设置事务超时时间 + rbta.setTimeout(attributes.getNumber("timeout").intValue()); + // 设置事务超时时间字符串 + String timeoutString = attributes.getString("timeoutString"); + // 校验是否同时设置了超时时间和超时时间字符串 + Assert.isTrue(!StringUtils.hasText(timeoutString) || rbta.getTimeout() < 0, + "Specify 'timeout' or 'timeoutString', not both"); + rbta.setTimeoutString(timeoutString); + + // 设置事务是否为只读模式 + rbta.setReadOnly(attributes.getBoolean("readOnly")); + // 设置事务限定符 + rbta.setQualifier(attributes.getString("value")); + // 设置事务标签 + rbta.setLabels(Arrays.asList(attributes.getStringArray("label"))); + + // 解析回滚规则 + List rollbackRules = new ArrayList<>(); + // 解析需要回滚的异常类 + for (Class rbRule : attributes.getClassArray("rollbackFor")) { + rollbackRules.add(new RollbackRuleAttribute(rbRule)); + } + // 解析需要回滚的异常类名 + for (String rbRule : attributes.getStringArray("rollbackForClassName")) { + rollbackRules.add(new RollbackRuleAttribute(rbRule)); + } + // 解析不需要回滚的异常类 + for (Class rbRule : attributes.getClassArray("noRollbackFor")) { + rollbackRules.add(new NoRollbackRuleAttribute(rbRule)); + } + // 解析不需要回滚的异常类名 + for (String rbRule : attributes.getStringArray("noRollbackForClassName")) { + rollbackRules.add(new NoRollbackRuleAttribute(rbRule)); + } + // 设置事务的回滚规则 + rbta.setRollbackRules(rollbackRules); + + // 返回解析后的事务属性对象 + return rbta; +} +``` \ No newline at end of file diff --git a/spring-transaction/spring-transaction-springTransactionAnnotationParser/pom.xml b/spring-transaction/spring-transaction-springTransactionAnnotationParser/pom.xml new file mode 100644 index 0000000..d82915f --- /dev/null +++ b/spring-transaction/spring-transaction-springTransactionAnnotationParser/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-springTransactionAnnotationParser + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/ScoresService.java b/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/ScoresService.java new file mode 100644 index 0000000..128b1fc --- /dev/null +++ b/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/ScoresService.java @@ -0,0 +1,5 @@ +package com.xcs.spring; + +public interface ScoresService { + void insertScore(); +} diff --git a/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/ScoresServiceImpl.java b/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/ScoresServiceImpl.java new file mode 100644 index 0000000..027e441 --- /dev/null +++ b/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/ScoresServiceImpl.java @@ -0,0 +1,19 @@ +package com.xcs.spring; + +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; + +public class ScoresServiceImpl implements ScoresService { + + @Override + @Transactional( + readOnly = true, + rollbackFor = Exception.class, + isolation = Isolation.REPEATABLE_READ, + timeout = 30, + label = {"tx1", "tx2"} + ) + public void insertScore() { + // TODO + } +} diff --git a/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/SpringTransactionAnnotationParserDemo.java b/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/SpringTransactionAnnotationParserDemo.java new file mode 100644 index 0000000..bbe9ee1 --- /dev/null +++ b/spring-transaction/spring-transaction-springTransactionAnnotationParser/src/main/java/com/xcs/spring/SpringTransactionAnnotationParserDemo.java @@ -0,0 +1,20 @@ +package com.xcs.spring; + +import org.springframework.transaction.annotation.SpringTransactionAnnotationParser; +import org.springframework.transaction.interceptor.TransactionAttribute; + +import java.lang.reflect.Method; + +public class SpringTransactionAnnotationParserDemo { + + public static void main(String[] args) throws NoSuchMethodException { + // 获取 ScoresServiceImpl 类中的 insertScore 方法 + Method insertScoreMethod = ScoresServiceImpl.class.getMethod("insertScore"); + // 创建 SpringTransactionAnnotationParser 实例 + SpringTransactionAnnotationParser parser = new SpringTransactionAnnotationParser(); + // 解析 insertScore 方法上的事务注解,并转换为事务属性对象 + TransactionAttribute transactionAttribute = parser.parseTransactionAnnotation(insertScoreMethod); + // 输出事务属性对象 + System.out.println(transactionAttribute); + } +} diff --git a/spring-transaction/spring-transaction-transactionAttributeSource/README.md b/spring-transaction/spring-transaction-transactionAttributeSource/README.md new file mode 100644 index 0000000..260e6a0 --- /dev/null +++ b/spring-transaction/spring-transaction-transactionAttributeSource/README.md @@ -0,0 +1,331 @@ +## TransactionAttributeSource + +- [TransactionAttributeSource](#TransactionAttributeSource) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、最佳实践](#六最佳实践) + - [七、源码分析](#七源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`TransactionAttributeSource` 接口是 Spring Framework +中的关键接口,用于提供事务管理的配置信息,通过分析给定的方法和目标类,确定事务的属性,例如传播行为、隔离级别等,为声明式事务提供了灵活性和可定制性。 + +### 三、主要功能 + +1. **提供事务属性** + + + 根据给定的方法和目标类,确定事务的属性,包括传播行为、隔离级别、超时时间、只读状态等。 + +2. **可扩展性** + + + Spring 框架提供了多种实现 `TransactionAttributeSource` + 接口的类,如 `NameMatchTransactionAttributeSource`、`AnnotationTransactionAttributeSource` + 等,以支持不同的解析策略,例如基于方法名的匹配、基于注解的配置等。 + +### 四、接口源码 + +`TransactionAttributeSource` 接口,主要是由 `TransactionInterceptor` +用于元数据检索的策略接口。该接口的实现知道如何获取事务属性,可以从配置、源级别的元数据属性或其他任何地方获取。它包含了两个方法,一个用于确定给定类是否是事务属性的候选类,另一个用于返回给定方法的事务属性。 + +```java +/** + * {@code TransactionAttributeSource} 接口是由 {@link TransactionInterceptor} 用于元数据检索的策略接口。 + * 实现类知道如何获取事务属性,无论是从配置、源级别的元数据属性(例如 Java 5 注解)还是其他任何地方。 + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 15.04.2003 + * @see TransactionInterceptor#setTransactionAttributeSource + * @see TransactionProxyFactoryBean#setTransactionAttributeSource + * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource + */ +public interface TransactionAttributeSource { + + /** + * 确定给定的类是否是此 {@code TransactionAttributeSource} 的元数据格式中事务属性的候选类。 + *

如果此方法返回 {@code false},则不会遍历给定类的方法以进行 {@link #getTransactionAttribute} 内省。 + * 返回 {@code false} 因此是对非受影响类的优化,而 {@code true} 则意味着必须针对给定类的每个方法进行完全内省。 + * @param targetClass 要内省的类 + * @return 如果类已知在类级别或方法级别没有事务属性,则返回 {@code false};否则返回 {@code true}。 + * 默认实现返回 {@code true},导致常规内省。 + * @since 5.2 + */ + default boolean isCandidateClass(Class targetClass) { + return true; + } + + /** + * 返回给定方法的事务属性,如果方法不是事务性的,则返回 {@code null}。 + * @param method 要内省的方法 + * @param targetClass 目标类(可能为 {@code null},在这种情况下,必须使用方法的声明类) + * @return 匹配的事务属性,如果未找到则返回 {@code null} + */ + @Nullable + TransactionAttribute getTransactionAttribute(Method method, @Nullable Class targetClass); + +} +``` + +### 五、主要实现 + +1. **AnnotationTransactionAttributeSource** + + + 用于解析基于注解的事务配置信息的实现类。它能够解析类级别和方法级别的 `@Transactional` + 注解,将注解中定义的事务属性转换为 `TransactionAttribute` 对象。 + +2. **CompositeTransactionAttributeSource** + + + 将多个 `TransactionAttributeSource` 组合在一起。通过组合多个 `TransactionAttributeSource` + 对象,可以实现多种事务属性解析策略的混合使用,提高了灵活性和定制性。 + +3. **MatchAlwaysTransactionAttributeSource** + + + 简单的 `TransactionAttributeSource` + 实现,它始终返回相同的事务属性。通常用于简单的场景或者作为其他复杂 `TransactionAttributeSource` 实现的默认备选项。 + +4. **MethodMapTransactionAttributeSource** + + + 基于方法名匹配的 `TransactionAttributeSource` 实现。它通过配置一个方法名到事务属性的映射表,根据方法名来确定相应的事务属性。 + +5. **NameMatchTransactionAttributeSource** + + + 根据方法名进行匹配的 `TransactionAttributeSource` 实现。它能够根据配置的方法名模式,匹配目标方法并返回相应的事务属性。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class AbstractFallbackTransactionAttributeSource +class AnnotationTransactionAttributeSource +class CompositeTransactionAttributeSource +class MatchAlwaysTransactionAttributeSource +class MethodMapTransactionAttributeSource +class NameMatchTransactionAttributeSource +class TransactionAttributeSource { +<> + +} + +AbstractFallbackTransactionAttributeSource ..> TransactionAttributeSource +AnnotationTransactionAttributeSource --> AbstractFallbackTransactionAttributeSource +CompositeTransactionAttributeSource ..> TransactionAttributeSource +MatchAlwaysTransactionAttributeSource ..> TransactionAttributeSource +MethodMapTransactionAttributeSource ..> TransactionAttributeSource +NameMatchTransactionAttributeSource ..> TransactionAttributeSource +~~~ + +### 六、最佳实践 + +使用 `AnnotationTransactionAttributeSource` 类来解析基于注解的事务配置信息。通过获取 `ScoresServiceImpl` +类中的 `insertScore` 方法,然后利用 `AnnotationTransactionAttributeSource` +对象来解析该方法的事务属性,最后将解析结果输出到控制台。这样可以帮助我们了解特定方法的事务配置情况,以便进行调试和优化。 + +```java +public class TransactionAttributeSourceDemo { + + public static void main(String[] args) throws NoSuchMethodException { + // 获取 ScoresServiceImpl 类中的 insertScore 方法 + Method insertScoreMethod = ScoresServiceImpl.class.getMethod("insertScore"); + // 创建一个基于注解的事务属性源对象 + TransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource(); + // 解析 insertScore 方法的事务属性 + TransactionAttribute transactionAttribute = transactionAttributeSource.getTransactionAttribute(insertScoreMethod, ScoresServiceImpl.class); + // 输出事务属性 + System.out.println(transactionAttribute); + } +} +``` + +`ScoresServiceImpl` 类实现了 `ScoresService` 接口,其中的 `insertScore` 方法被 `@Transactional` +注解修饰,声明了一个事务。该事务的特性包括只读(readOnly = true),遇到任何异常都会回滚(rollbackFor = +Exception.class),事务隔离级别为可重复读(isolation = Isolation.REPEATABLE_READ),超时时间为 30 秒(timeout = +30),以及标签为 "tx1" 和 "tx2"。 + +```java +public class ScoresServiceImpl implements ScoresService { + + @Override + @Transactional( + readOnly = true, + rollbackFor = Exception.class, + isolation = Isolation.REPEATABLE_READ, + timeout = 30, + label = {"tx1", "tx2"} + ) + public void insertScore() { + // TODO + } +} +``` + +运行结果,事务的传播行为为 `PROPAGATION_REQUIRED`,隔离级别为 `ISOLATION_REPEATABLE_READ`,超时时间为 30 +秒,只读模式开启,标签为 "tx1" 和 "tx2",同时,事务会回滚遇到 `java.lang.Exception` 及其子类的异常。 + +```java +PROPAGATION_REQUIRED,ISOLATION_REPEATABLE_READ,timeout_30,readOnly; [tx1,tx2],-java.lang.Exception +``` + +### 七、源码分析 + +在`org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource#getTransactionAttribute` +方法中,用于确定方法调用的事务属性。如果在方法级别找不到事务属性,则默认使用类级别的事务属性。首先,它检查是否有缓存的事务属性值,如果有则直接返回缓存值,否则计算方法的事务属性并将其放入缓存。在计算事务属性时,会根据方法的限定名来标识方法,如果事务属性是 `DefaultTransactionAttribute` +类型,则设置描述符和解析属性字符串。最后,根据日志级别输出添加事务方法及其属性的日志,并将计算得到的事务属性放入缓存。 + +```java +/** + * 确定此方法调用的事务属性。 + *

如果未找到方法属性,则默认为类的事务属性。 + * @param method 当前调用的方法(永远不会为 {@code null}) + * @param targetClass 此调用的目标类(可能为 {@code null}) + * @return 此方法的 TransactionAttribute,如果方法不是事务性的,则返回 {@code null} + */ +@Override +@Nullable +public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class targetClass) { + // 如果方法是 Object 类的方法,直接返回 null,因为这些方法不应该是事务性的。 + if (method.getDeclaringClass() == Object.class) { + return null; + } + + // 首先,查看是否有缓存值。 + Object cacheKey = getCacheKey(method, targetClass); + TransactionAttribute cached = this.attributeCache.get(cacheKey); + if (cached != null) { + // 值将是一个规范值,表示没有事务属性,或者是一个实际的事务属性。 + if (cached == NULL_TRANSACTION_ATTRIBUTE) { + return null; + } + else { + return cached; + } + } + else { + // 我们需要计算它。 + TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass); + // 将其放入缓存。 + if (txAttr == null) { + this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE); + } + else { + // 获取方法的限定名,用于在日志中标识方法。 + String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass); + // 如果事务属性是 DefaultTransactionAttribute 类型,设置描述符和解析属性字符串。 + if (txAttr instanceof DefaultTransactionAttribute) { + DefaultTransactionAttribute dta = (DefaultTransactionAttribute) txAttr; + dta.setDescriptor(methodIdentification); + dta.resolveAttributeStrings(this.embeddedValueResolver); + } + // 如果日志级别为 TRACE,输出添加事务方法及其属性的日志。 + if (logger.isTraceEnabled()) { + logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr); + } + this.attributeCache.put(cacheKey, txAttr); + } + return txAttr; + } +} +``` + +在`org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource#computeTransactionAttribute` +方法中,首先检查方法是否是公共方法,并根据情况从目标类或原始方法中查找事务属性。如果找到了事务属性,则返回该属性;否则返回 +null。 + +```java +/** + * 与 {@link #getTransactionAttribute} 具有相同的签名,但不缓存结果。 + * {@link #getTransactionAttribute} 实际上是此方法的缓存装饰器。 + *

从 4.1.8 版本开始,此方法可以被重写。 + * @since 4.1.8 + * @see #getTransactionAttribute + */ +@Nullable +protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class targetClass) { + // 如果只允许公共方法,并且方法不是公共的,则不允许。 + if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { + return null; + } + + // 方法可能在接口上,但我们需要从目标类获取属性。 + // 如果目标类为 null,则方法不会改变。 + Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); + + // 首先尝试目标类中的方法。 + TransactionAttribute txAttr = findTransactionAttribute(specificMethod); + if (txAttr != null) { + return txAttr; + } + + // 其次尝试目标类上的事务属性。 + txAttr = findTransactionAttribute(specificMethod.getDeclaringClass()); + if (txAttr != null && ClassUtils.isUserLevelMethod(method)) { + return txAttr; + } + + if (specificMethod != method) { + // 回退到原始方法。 + txAttr = findTransactionAttribute(method); + if (txAttr != null) { + return txAttr; + } + // 最后回退到原始方法的类。 + txAttr = findTransactionAttribute(method.getDeclaringClass()); + if (txAttr != null && ClassUtils.isUserLevelMethod(method)) { + return txAttr; + } + } + + return null; +} +``` + +在`org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#findTransactionAttribute(java.lang.reflect.Method)` +方法中,调用了 `determineTransactionAttribute` 方法来确定给定方法的事务属性,并将其返回。如果无法确定事务属性,则返回 null。 + +```java +@Override +@Nullable +protected TransactionAttribute findTransactionAttribute(Method method) { + return determineTransactionAttribute(method); +} +``` + +在`org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#determineTransactionAttribute` +方法中,用于确定给定方法或类的事务属性。它通过配置的 `TransactionAnnotationParsers` 来解析已知注解,并将其转换为 Spring +的元数据属性类。如果找不到事务属性,则返回 null。该方法可以被重写以支持携带事务元数据的自定义注解。 + +```java +/** + * 确定给定方法或类的事务属性。 + *

此实现委托给配置的 {@link TransactionAnnotationParser TransactionAnnotationParsers}, + * 用于将已知的注解解析为 Spring 的元数据属性类。 + * 如果不是事务性的,则返回 {@code null}。 + *

可以重写此方法以支持携带事务元数据的自定义注解。 + * @param element 带有注解的方法或类 + * @return 配置的事务属性,如果找不到则返回 {@code null} + */ +@Nullable +protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) { + // 遍历所有的 TransactionAnnotationParser 实例 + for (TransactionAnnotationParser parser : this.annotationParsers) { + // 解析注解,获取事务属性 + TransactionAttribute attr = parser.parseTransactionAnnotation(element); + // 如果找到事务属性,则返回 + if (attr != null) { + return attr; + } + } + // 如果未找到事务属性,则返回 null + return null; +} +``` + diff --git a/spring-transaction/spring-transaction-transactionAttributeSource/pom.xml b/spring-transaction/spring-transaction-transactionAttributeSource/pom.xml new file mode 100644 index 0000000..67f821a --- /dev/null +++ b/spring-transaction/spring-transaction-transactionAttributeSource/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-transactionAttributeSource + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/ScoresService.java b/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/ScoresService.java new file mode 100644 index 0000000..128b1fc --- /dev/null +++ b/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/ScoresService.java @@ -0,0 +1,5 @@ +package com.xcs.spring; + +public interface ScoresService { + void insertScore(); +} diff --git a/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/ScoresServiceImpl.java b/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/ScoresServiceImpl.java new file mode 100644 index 0000000..027e441 --- /dev/null +++ b/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/ScoresServiceImpl.java @@ -0,0 +1,19 @@ +package com.xcs.spring; + +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; + +public class ScoresServiceImpl implements ScoresService { + + @Override + @Transactional( + readOnly = true, + rollbackFor = Exception.class, + isolation = Isolation.REPEATABLE_READ, + timeout = 30, + label = {"tx1", "tx2"} + ) + public void insertScore() { + // TODO + } +} diff --git a/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/TransactionAttributeSourceDemo.java b/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/TransactionAttributeSourceDemo.java new file mode 100644 index 0000000..5eaf0ff --- /dev/null +++ b/spring-transaction/spring-transaction-transactionAttributeSource/src/main/java/com/xcs/spring/TransactionAttributeSourceDemo.java @@ -0,0 +1,21 @@ +package com.xcs.spring; + +import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource; +import org.springframework.transaction.interceptor.TransactionAttribute; +import org.springframework.transaction.interceptor.TransactionAttributeSource; + +import java.lang.reflect.Method; + +public class TransactionAttributeSourceDemo { + + public static void main(String[] args) throws NoSuchMethodException { + // 获取 ScoresServiceImpl 类中的 insertScore 方法 + Method insertScoreMethod = ScoresServiceImpl.class.getMethod("insertScore"); + // 创建一个基于注解的事务属性源对象 + TransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource(); + // 解析 insertScore 方法的事务属性 + TransactionAttribute transactionAttribute = transactionAttributeSource.getTransactionAttribute(insertScoreMethod, ScoresServiceImpl.class); + // 输出事务属性 + System.out.println(transactionAttribute); + } +} diff --git a/spring-transaction/spring-transaction-transactionDefinition/README.md b/spring-transaction/spring-transaction-transactionDefinition/README.md new file mode 100644 index 0000000..eb1ae17 --- /dev/null +++ b/spring-transaction/spring-transaction-transactionDefinition/README.md @@ -0,0 +1,345 @@ +## TransactionDefinition + +- [TransactionDefinition](#transactiondefinition) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、接口源码](#四接口源码) + - [五、主要实现](#五主要实现) + - [六、类关系图](#六类关系图) + - [七、最佳实践](#七最佳实践) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https//juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https//github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`TransactionDefinition` 接口是 Spring 框架中用于定义事务属性的接口,它包含了事务的传播行为、隔离级别、超时时间和只读状态等属性,可以通过配置这些属性来灵活控制应用程序中的数据库事务行为。 + +### 三、主要功能 + +1. **定义事务传播行为** + + + 定义了事务的传播方式,即当一个方法被调用时,它应该如何处理现有的事务。例如,是加入已有的事务还是创建一个新的事务。 + +2. **指定事务隔离级别** + + + 定义了事务的隔离级别,即事务操作之间的隔离程度。不同的隔离级别可以解决不同的并发问题,如脏读、不可重复读和幻读等。 + +3. **设置事务超时时间** + + + 指定了事务的超时时间,即事务在多长时间内必须完成。如果事务在指定的时间内没有完成,则会被自动回滚。 + +4. **配置事务只读属性** + + + 指定了事务是否只读。只读事务可以优化数据库性能,因为数据库可以跳过一些读取锁的操作。 + +### 四、接口源码 + +`TransactionDefinition`接口,用于定义符合 Spring 规范的事务属性。它基于与 EJB CMT +属性类似的传播行为定义。该接口包括事务传播行为、隔离级别、超时设置和只读标志等属性。它提供了默认值和静态构建器方法,以方便创建事务定义对象。 + +```java +/** + * 接口定义了符合 Spring 规范的事务属性。 + * 基于与 EJB CMT 属性类似的传播行为定义。 + * + *

注意,隔离级别和超时设置仅在实际启动新事务时才会应用。 + * 由于只有 {@link #PROPAGATION_REQUIRED}、{@link #PROPAGATION_REQUIRES_NEW} 和 {@link #PROPAGATION_NESTED} 可能会引起这种情况, + * 因此在其他情况下指定这些设置通常是没有意义的。 + * 此外,注意并非所有的事务管理器都支持这些高级功能,因此在给定非默认值时可能会抛出相应的异常。 + * + *

{@link #isReadOnly() 只读标志} 适用于任何事务上下文,无论是由实际资源事务支持还是在资源级别非事务性地操作。 + * 在后一种情况下,该标志仅适用于应用程序中的受管资源,例如 Hibernate 的 {@code Session}。 + * + * @author Juergen Hoeller + * @since 08.05.2003 + * @see PlatformTransactionManager#getTransaction(TransactionDefinition) + * @see org.springframework.transaction.support.DefaultTransactionDefinition + * @see org.springframework.transaction.interceptor.TransactionAttribute + */ +public interface TransactionDefinition { + + /** + * 支持当前事务;如果不存在则创建一个新事务。 + * 类似于具有相同名称的 EJB 事务属性。 + *

这通常是事务定义的默认设置, + * 通常定义了事务同步范围。 + */ + int PROPAGATION_REQUIRED = 0; + + /** + * 支持当前事务;如果不存在则以非事务方式执行。 + * 类似于具有相同名称的 EJB 事务属性。 + *

注意:对于具有事务同步的事务管理器, + * {@code PROPAGATION_SUPPORTS} 与没有事务稍有不同, + * 因为它定义了可能应用于的事务范围同步。 + * 因此,同一资源(JDBC {@code Connection}、Hibernate {@code Session} 等)将用于整个指定的范围。 + * 注意,确切的行为取决于事务管理器的实际同步配置! + *

通常情况下,谨慎使用 {@code PROPAGATION_SUPPORTS}! + * 特别是,在 {@code PROPAGATION_SUPPORTS} 范围内不要依赖 {@code PROPAGATION_REQUIRED} 或 {@code PROPAGATION_REQUIRES_NEW} + * (这可能会导致运行时同步冲突)。 + * 如果无法避免此类嵌套,请确保适当配置事务管理器(通常切换到“在实际事务上同步”)。 + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#SYNCHRONIZATION_ON_ACTUAL_TRANSACTION + */ + int PROPAGATION_SUPPORTS = 1; + + /** + * 支持当前事务;如果不存在则抛出异常。 + * 类似于具有相同名称的 EJB 事务属性。 + *

在 {@code PROPAGATION_MANDATORY} 范围内的事务同步始终由周围的事务驱动。 + */ + int PROPAGATION_MANDATORY = 2; + + /** + * 创建一个新事务,如果存在则挂起当前事务。 + * 类似于具有相同名称的 EJB 事务属性。 + *

注意:实际的事务挂起不会在所有事务管理器上自动工作。 + * 这尤其适用于 {@link org.springframework.transaction.jta.JtaTransactionManager}, + * 它要求将 {@code javax.transaction.TransactionManager} 提供给它(在标准 Java EE 中是特定于服务器的)。 + *

{@code PROPAGATION_REQUIRES_NEW} 范围总是定义自己的事务同步。 + * 现有的同步将被暂停和恢复。 + * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + */ + int PROPAGATION_REQUIRES_NEW = 3; + + /** + * 不支持当前事务;而总是以非事务方式执行。 + * 类似于具有相同名称的 EJB 事务属性。 + *

注意:实际的事务挂起不会在所有事务管理器上自动工作。 + * 这尤其适用于 {@link org.springframework.transaction.jta.JtaTransactionManager}, + * 它要求将 {@code javax.transaction.TransactionManager} 提供给它(在标准 Java EE 中是特定于服务器的)。 + *

请注意,在 {@code PROPAGATION_NOT_SUPPORTED} 范围内不可用事务同步。 + * 现有同步将被暂停和恢复。 + * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + */ + int PROPAGATION_NOT_SUPPORTED = 4; + + /** + * 不支持当前事务;如果存在则抛出异常。 + * 类似于具有相同名称的 EJB 事务属性。 + *

请注意,在 {@code PROPAGATION_NEVER} 范围内不可用事务同步。 + */ + int PROPAGATION_NEVER = 5; + + /** + * 在存在当前事务时执行嵌套事务,否则与 {@link #PROPAGATION_REQUIRED} 行为相同。 + * 在 EJB 中没有类似的功能。 + *

注意:实际创建嵌套事务只能在特定的事务管理器上工作。 + * 默认情况下,这仅适用于 JDBC {@link org.springframework.jdbc.datasource.DataSourceTransactionManager} + * 在使用 JDBC 3.0 驱动程序时。 + * 一些 JTA 提供程序可能也支持嵌套事务。 + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager + */ + int PROPAGATION_NESTED = 6; + + /** + * 使用底层数据存储的默认隔离级别。 + * 所有其他级别都对应于 JDBC 隔离级别。 + * @see java.sql.Connection + */ + int ISOLATION_DEFAULT = -1; + + /** + * 表示允许发生脏读、不可重复读和幻读。 + *

该级别允许一个事务修改的行在另一个事务提交之前被另一个事务读取(“脏读”)。 + * 如果其中任何更改被回滚,第二个事务将检索到无效的行。 + * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED + */ + int ISOLATION_READ_UNCOMMITTED = 1; + + /** + * 表示防止脏读;可以发生不可重复读和幻读。 + *

该级别仅禁止事务读取一个包含未提交更改的行。 + * @see java.sql.Connection#TRANSACTION_READ_COMMITTED + */ + int ISOLATION_READ_COMMITTED = 2; + + /** + * 表示防止脏读和不可重复读;可以发生幻读。 + *

该级别禁止事务读取一个包含未提交更改的行,同时禁止以下情况的发生: + * 一个事务读取一行,第二个事务修改该行,第一个事务重新读取该行,第二次得到的值与第一次不同(“不可重复读”)。 + * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ + */ + int ISOLATION_REPEATABLE_READ = 4; + + /** + * 表示防止脏读、不可重复读和幻读。 + *

该级别包括 {@link #ISOLATION_REPEATABLE_READ} 中的禁止,同时进一步禁止以下情况的发生: + * 一个事务读取满足 {@code WHERE} 条件的所有行,第二个事务插入满足该 {@code WHERE} 条件的行, + * 第一个事务为相同的条件重新读取,第二次读取中检索到额外的“幻行”。 + * @see java.sql.Connection#TRANSACTION_SERIALIZABLE + */ + int ISOLATION_SERIALIZABLE = 8; + + /** + * 使用底层事务系统的默认超时时间,如果不支持超时,则为无。 + */ + int TIMEOUT_DEFAULT = -1; + + /** + * 返回传播行为。 + *

必须返回 {@link TransactionDefinition} 接口上定义的 {@code PROPAGATION_XXX} 常量之一。 + *

默认值为 {@link #PROPAGATION_REQUIRED}。 + * @return 传播行为 + * @see #PROPAGATION_REQUIRED + * @see org.springframework.transaction.support.TransactionSynchronizationManager#isActualTransactionActive() + */ + default int getPropagationBehavior() { + return PROPAGATION_REQUIRED; + } + + /** + * 返回隔离级别。 + *

必须返回 {@link TransactionDefinition} 接口上定义的 {@code ISOLATION_XXX} 常量之一。 + * 这些常量设计用于与 {@link java.sql.Connection} 上的相同常量的值匹配。 + *

专门用于与 {@link #PROPAGATION_REQUIRED} 或 {@link #PROPAGATION_REQUIRES_NEW} 一起使用, + * 因为它仅适用于新启动的事务。 + * 如果我们希望在参与具有不同隔离级别的现有事务时拒绝隔离级别声明,请考虑在事务管理器上切换 "validateExistingTransactions" 标志为 "true"。 + *

默认值为 {@link #ISOLATION_DEFAULT}。 + * 请注意,不支持自定义隔离级别的事务管理器将在给定除 {@link #ISOLATION_DEFAULT} 之外的任何级别时抛出异常。 + * @return 隔离级别 + * @see #ISOLATION_DEFAULT + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setValidateExistingTransaction + */ + default int getIsolationLevel() { + return ISOLATION_DEFAULT; + } + + /** + * 返回事务超时时间。 + *

必须返回以秒为单位的数字,或者 {@link #TIMEOUT_DEFAULT}。 + *

专门用于与 {@link #PROPAGATION_REQUIRED} 或 {@link #PROPAGATION_REQUIRES_NEW} 一起使用, + * 因为它仅适用于新启动的事务。 + *

请注意,不支持超时的事务管理器将在给定除 {@link #TIMEOUT_DEFAULT} 之外的任何超时时抛出异常。 + *

默认值为 {@link #TIMEOUT_DEFAULT}。 + * @return 事务超时时间 + */ + default int getTimeout() { + return TIMEOUT_DEFAULT; + } + + /** + * 返回是否优化为只读事务。 + *

只读标志适用于任何事务上下文,无论是由实际资源事务({@link #PROPAGATION_REQUIRED}/ + * {@link #PROPAGATION_REQUIRES_NEW})支持,还是在资源级别非事务性地操作({@link #PROPAGATION_SUPPORTS})。 + * 在后一种情况下,该标志仅适用于应用程序中的受管资源,例如 Hibernate 的 {@code Session}。 + *

这只是对实际事务子系统的提示; + * 它不一定会导致写入访问尝试失败。 + * 不能解释只读提示的事务管理器在要求只读事务时不会抛出异常。 + * @return 如果事务被优化为只读,则为 {@code true}(默认为 {@code false}) + * @see org.springframework.transaction.support.TransactionSynchronization#beforeCommit(boolean) + * @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly() + */ + default boolean isReadOnly() { + return false; + } + + /** + * 返回此事务的名称。可以为 {@code null}。 + *

如果适用(例如,WebLogic),则将用作要显示在事务监视器中的事务名称。 + *

在 Spring 的声明式事务中,暴露的名称将是 {@code fully-qualified class name + "." + method name}(默认)。 + * @return 此事务的名称(默认为 {@code null}) + * @see org.springframework.transaction.interceptor.TransactionAspectSupport + * @see org.springframework.transaction.support.TransactionSynchronizationManager#getCurrentTransactionName() + */ + @Nullable + default String getName() { + return null; + } + + /** + * 返回具有默认值的不可修改的 {@code TransactionDefinition}。 + *

为了定制目的,可以使用可修改的 {@link org.springframework.transaction.support.DefaultTransactionDefinition}。 + * @since 5.2 + */ + static TransactionDefinition withDefaults() { + return StaticTransactionDefinition.INSTANCE; + } +} +``` + +### 五、主要实现 + +1. **DefaultTransactionDefinition** + + - Spring + 框架中定义事务基本属性的默认实现类。它允许我们指定事务的传播行为、隔离级别、超时设置和只读标志等。通过提供默认值,它简化了事务属性的设置,并提供了静态方法 `withDefaults()` + ,可用于创建具有默认属性的事务定义对象。 + +2. **DefaultTransactionAttribute** + + - Spring + 框架中定义事务属性的默认实现类。它封装了事务的传播行为、隔离级别、超时设置和只读标志等属性,并提供了操作方法和属性获取方法。作为 `TransactionAttribute` + 接口的默认实现,它可以轻松设置和获取方法或类级别的事务属性。 + +### 六、类关系图 + +~~~mermaid +classDiagram +direction BT +class DefaultTransactionAttribute +class DefaultTransactionDefinition +class TransactionAttribute { +<> + +} +class TransactionDefinition { +<> + +} + +DefaultTransactionAttribute --> DefaultTransactionDefinition +DefaultTransactionAttribute ..> TransactionAttribute +DefaultTransactionDefinition ..> TransactionDefinition +TransactionAttribute --> TransactionDefinition +~~~ + +### 七、最佳实践 + +使用`DefaultTransactionDefinition`类来定义事务属性,并设置传播行为、隔离级别、事务超时时间、是否只读和事务名称等属性,然后打印出它们的值。 + +```java +public class TransactionDefinitionDemo { + + public static void main(String[] args) { + // 创建一个 DefaultTransactionDefinition 实例 + DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition(); + + // 设置传播行为为PROPAGATION_REQUIRES_NEW + transactionDefinition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW); + System.out.println("Propagation Behavior: " + transactionDefinition.getPropagationBehavior()); + + // 设置隔离级别为ISOLATION_REPEATABLE_READ + transactionDefinition.setIsolationLevel(DefaultTransactionDefinition.ISOLATION_REPEATABLE_READ); + System.out.println("Isolation Level: " + transactionDefinition.getIsolationLevel()); + + // 设置事务超时时间为30秒 + transactionDefinition.setTimeout(30); + System.out.println("Timeout: " + transactionDefinition.getTimeout()); + + // 设置事务为只读 + transactionDefinition.setReadOnly(true); + System.out.println("Is Read Only: " + transactionDefinition.isReadOnly()); + + // 设置事务名称为"DemoTransaction" + transactionDefinition.setName("DemoTransaction"); + System.out.println("Transaction Name: " + transactionDefinition.getName()); + } +} +``` + +运行结果,使用 `DefaultTransactionDefinition` 类设置的事务属性:传播行为为 `PROPAGATION_REQUIRES_NEW` +,隔离级别为 `ISOLATION_REPEATABLE_READ`,超时时间为30秒,事务被设置为只读,并且事务名称为 "DemoTransaction"。 + +```java +Propagation Behavior:3 +Isolation Level:4 +Timeout:30 +Is Read +Only:true +Transaction Name:DemoTransaction +``` \ No newline at end of file diff --git a/spring-transaction/spring-transaction-transactionDefinition/pom.xml b/spring-transaction/spring-transaction-transactionDefinition/pom.xml new file mode 100644 index 0000000..3d2466d --- /dev/null +++ b/spring-transaction/spring-transaction-transactionDefinition/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-transactionDefinition + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-transactionDefinition/src/main/java/com/xcs/spring/TransactionDefinitionDemo.java b/spring-transaction/spring-transaction-transactionDefinition/src/main/java/com/xcs/spring/TransactionDefinitionDemo.java new file mode 100644 index 0000000..0ed0179 --- /dev/null +++ b/spring-transaction/spring-transaction-transactionDefinition/src/main/java/com/xcs/spring/TransactionDefinitionDemo.java @@ -0,0 +1,31 @@ +package com.xcs.spring; + +import org.springframework.transaction.support.DefaultTransactionDefinition; + +public class TransactionDefinitionDemo { + + public static void main(String[] args) { + // 创建一个 DefaultTransactionDefinition 实例 + DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition(); + + // 设置传播行为为PROPAGATION_REQUIRES_NEW + transactionDefinition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW); + System.out.println("Propagation Behavior: " + transactionDefinition.getPropagationBehavior()); + + // 设置隔离级别为ISOLATION_REPEATABLE_READ + transactionDefinition.setIsolationLevel(DefaultTransactionDefinition.ISOLATION_REPEATABLE_READ); + System.out.println("Isolation Level: " + transactionDefinition.getIsolationLevel()); + + // 设置事务超时时间为30秒 + transactionDefinition.setTimeout(30); + System.out.println("Timeout: " + transactionDefinition.getTimeout()); + + // 设置事务为只读 + transactionDefinition.setReadOnly(true); + System.out.println("Is Read Only: " + transactionDefinition.isReadOnly()); + + // 设置事务名称为"DemoTransaction" + transactionDefinition.setName("DemoTransaction"); + System.out.println("Transaction Name: " + transactionDefinition.getName()); + } +} diff --git a/spring-transaction/spring-transaction-transactionInterceptor/README.md b/spring-transaction/spring-transaction-transactionInterceptor/README.md new file mode 100644 index 0000000..099cd2d --- /dev/null +++ b/spring-transaction/spring-transaction-transactionInterceptor/README.md @@ -0,0 +1,375 @@ +## TransactionInterceptor + +- [TransactionInterceptor](#transactioninterceptor) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、最佳实践](#四最佳实践) + - [五、源码分析](#五源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`TransactionInterceptor` 类是 Spring +框架中的一个核心组件,用于实现声明式事务管理。它通过拦截方法调用,根据事务属性(如传播行为、隔离级别等)来控制事务的开始、提交和回滚,确保在方法执行过程中事务的一致性和完整性。 + +### 三、主要功能 + +1. **获取事务属性** + + + 从方法或类的元数据中获取事务属性(如传播行为、隔离级别等)。 + +2. **事务管理器决策** + + + 根据事务属性和当前的事务上下文(如是否存在活动事务)来决定是创建一个新事务、加入现有事务还是不需要事务。 + +3. **事务控制** + + + 通过 `TransactionManager` 来控制事务的开始、提交和回滚。 + +4. **异常处理** + + + 在方法执行过程中,如果捕获到异常,根据事务属性配置来决定是否回滚事务。 + +### 四、最佳实践 + +通过 `SimpleDriverDataSource` 创建了数据库连接池,然后使用 `DataSourceTransactionManager` +进行事务管理。通过 `JdbcTemplate` 执行 SQL 语句,并使用 `AnnotationTransactionAttributeSource` 和 `TransactionInterceptor` +来定义事务的属性和拦截方法调用,以确保方法在事务中执行。最后,通过 `ProxyFactory` 创建代理对象,并调用代理对象的方法,使方法的执行受到声明式事务的控制。 + +```java +public class TransactionInterceptorDemo { + + public static void main(String[] args) throws SQLException { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 DataSourceTransactionManager 对象,用于管理事务 + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + // 创建 AnnotationTransactionAttributeSource 对象,用于获取事务属性 + AnnotationTransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource(); + + // 创建 TransactionInterceptor 对象,用于拦截方法调用并管理事务 + TransactionInterceptor transactionInterceptor = new TransactionInterceptor(); + transactionInterceptor.setTransactionAttributeSource(transactionAttributeSource); + transactionInterceptor.setTransactionManager(transactionManager); + + // 创建 BeanFactoryTransactionAttributeSourceAdvisor 对象,用于配置事务拦截器 + BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); + advisor.setTransactionAttributeSource(transactionAttributeSource); + advisor.setAdvice(transactionInterceptor); + + // 创建 ProxyFactory 对象,用于创建代理对象 + ProxyFactory proxyFactory = new ProxyFactory(); + proxyFactory.addAdvisor(advisor); + proxyFactory.setTarget(new ScoresServiceImpl(jdbcTemplate)); + + // 获取代理对象,并调用其方法 + ScoresService scoresService = (ScoresService) proxyFactory.getProxy(); + scoresService.insertScore(); + } +} +``` + +### 五、源码分析 + +在`org.springframework.transaction.interceptor.TransactionInterceptor#invoke`方法中, `TransactionInterceptor` +类中实现了`MethodInterceptor`的 `invoke` 方法,它是 Spring AOP +在事务管理方面的核心实现。通过拦截方法调用并根据事务配置信息,确保在方法执行前开启事务、方法执行后根据结果提交或回滚事务,从而保证数据操作的一致性和完整性。 + +```java + +@Override +@Nullable +public Object invoke(MethodInvocation invocation) throws Throwable { + // 确定目标类:可能为 {@code null}。 + // TransactionAttributeSource 应该传递目标类和方法,方法可能来自接口。 + Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); + + // 适配到 TransactionAspectSupport 的 invokeWithinTransaction 方法... + return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() { + + @Override + @Nullable + public Object proceedWithInvocation() throws Throwable { + return invocation.proceed(); + } + + @Override + public Object getTarget() { + return invocation.getThis(); + } + + @Override + public Object[] getArguments() { + return invocation.getArguments(); + } + }); +} +``` + +在`org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction` +方法中,根据方法的事务属性确定是否需要事务管理,然后选择合适的事务管理器执行事务操作,包括事务的开始、提交和回滚。如果方法执行过程中抛出异常,则根据事务状态进行事务回滚,并重新抛出异常;在方法正常返回时根据配置的回滚规则设置回滚标志,并提交事务。 + +```java +/** + * 在基于环绕通知的子类中的通用委托,委托给该类上的几个其他模板方法。 + * 能够处理 {@link CallbackPreferringPlatformTransactionManager}、常规 {@link PlatformTransactionManager} 实现, + * 以及用于响应式返回类型的 {@link ReactiveTransactionManager} 实现。 + * + * @param method 被调用的方法 + * @param targetClass 我们正在调用方法的目标类 + * @param invocation 用于进行目标调用的回调 + * @return 方法的返回值(如果有) + * @throws Throwable 从目标调用中传播的异常 + */ +@Nullable +protected Object invokeWithinTransaction(Method method, @Nullable Class targetClass, + final InvocationCallback invocation) throws Throwable { + + // 如果事务属性为 null,则方法是非事务性的。 + TransactionAttributeSource tas = getTransactionAttributeSource(); + final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); + final TransactionManager tm = determineTransactionManager(txAttr); + + // 如果存在响应式适配器并且 tm 是 ReactiveTransactionManager 类型,则... + if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) { + // ... [代码部分省略以简化] + } + + // 将 tm 转换为 PlatformTransactionManager + PlatformTransactionManager ptm = asPlatformTransactionManager(tm); + final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); + + // 如果 txAttr 为 null 或者 ptm 不是 CallbackPreferringPlatformTransactionManager 类型 + if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) { + // 使用 getTransaction 和 commit/rollback 调用进行标准事务划分。 + TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification); + + Object retVal; + try { + // 这是一个环绕通知:调用链中的下一个拦截器。 + // 这通常会导致目标对象被调用。 + retVal = invocation.proceedWithInvocation(); + } catch (Throwable ex) { + // 目标调用异常 + completeTransactionAfterThrowing(txInfo, ex); + throw ex; + } finally { + cleanupTransactionInfo(txInfo); + } + + // 如果 retVal 不为 null 且存在 vavrPresent 并且 retVal 是 Vavr Try 类型 + if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) { + // 根据回滚规则在 Vavr 失败时设置仅回滚... + TransactionStatus status = txInfo.getTransactionStatus(); + if (status != null && txAttr != null) { + retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status); + } + } + + // 在方法正常返回后提交事务 + commitTransactionAfterReturning(txInfo); + return retVal; + } else { + // ... [代码部分省略以简化] + } +} +``` + +在`org.springframework.transaction.interceptor.TransactionAspectSupport#createTransactionIfNecessary` +方法中,如果事务属性存在且未指定事务名称,则使用方法标识作为事务名称。然后,如果事务属性和事务管理器都存在,则通过事务管理器获取事务状态;如果没有配置事务管理器,则记录调试日志表示跳过该事务连接点。最后,通过调用 `prepareTransactionInfo` +方法,准备并返回包含事务信息的 `TransactionInfo` 对象,无论是否创建了事务都返回该对象。 + +```java +/** + * 根据给定的 TransactionAttribute 创建一个事务(如果有必要)。 + *

允许调用者通过 TransactionAttributeSource 执行自定义的 TransactionAttribute 查找。 + * + * @param tm 事务管理器(可能为 {@code null}) + * @param txAttr 事务属性(可能为 {@code null}) + * @param joinpointIdentification 完全限定的方法名(用于监控和日志记录) + * @return 一个 TransactionInfo 对象,不论是否创建了事务。 + * 可以使用 TransactionInfo 的 {@code hasTransaction()} 方法来判断是否创建了事务。 + * @see #getTransactionAttributeSource() + */ +@SuppressWarnings("serial") +protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, + @Nullable TransactionAttribute txAttr, final String joinpointIdentification) { + + // 如果事务属性不为 null 且事务名称为 null,则使用方法标识作为事务名称。 + if (txAttr != null && txAttr.getName() == null) { + txAttr = new DelegatingTransactionAttribute(txAttr) { + @Override + public String getName() { + return joinpointIdentification; + } + }; + } + + TransactionStatus status = null; + if (txAttr != null) { + if (tm != null) { + // 如果事务属性不为 null 且事务管理器不为 null,则通过事务管理器获取事务状态。 + status = tm.getTransaction(txAttr); + } else { + // 如果事务管理器为 null,记录调试日志表示跳过事务连接点。 + if (logger.isDebugEnabled()) { + logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + + "] because no transaction manager has been configured"); + } + } + } + // 准备并返回 TransactionInfo 对象。 + return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); +} +``` + +在`org.springframework.transaction.interceptor.TransactionAspectSupport#prepareTransactionInfo` +方法中,通过给定的事务属性和状态对象创建并准备一个 `TransactionInfo` 对象。如果事务属性不为 +null,表示该方法需要事务,并在调试日志中记录获取事务的信息,并通过 `newTransactionStatus` 方法设置事务状态;如果事务属性为 +null,表示该方法不需要事务,仅创建 `TransactionInfo` +对象以维护线程局部变量的完整性。无论是否创建新事务,总是将 `TransactionInfo` 绑定到线程,以确保 `TransactionInfo` 堆栈被正确管理。 + +```java +/** + * 为给定的事务属性和状态对象准备一个 TransactionInfo。 + * @param tm 事务管理器(可能为 {@code null}) + * @param txAttr 事务属性(可能为 {@code null}) + * @param joinpointIdentification 完全限定的方法名(用于监控和日志记录) + * @param status 当前事务的 TransactionStatus + * @return 准备好的 TransactionInfo 对象 + */ +protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm, + @Nullable TransactionAttribute txAttr, String joinpointIdentification, + @Nullable TransactionStatus status) { + + // 创建一个 TransactionInfo 对象 + TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification); + if (txAttr != null) { + // 如果事务属性不为 null,表示该方法需要事务 + if (logger.isTraceEnabled()) { + logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]"); + } + // 事务管理器会在已经存在不兼容事务时标记错误 + txInfo.newTransactionStatus(status); + } else { + // 如果事务属性为 null,表示该方法不需要事务,仅创建 TransactionInfo 以维护线程局部变量的完整性 + if (logger.isTraceEnabled()) { + logger.trace("No need to create transaction for [" + joinpointIdentification + + "]: This method is not transactional."); + } + } + + // 我们总是将 TransactionInfo 绑定到线程,即使我们没有在这里创建新事务。 + // 这保证了即使没有通过这个切面创建事务,TransactionInfo 堆栈也会被正确管理。 + txInfo.bindToThread(); + return txInfo; +} +``` + +在`org.springframework.transaction.interceptor.TransactionAspectSupport.TransactionInfo#bindToThread` +方法中,将当前的 `TransactionInfo` 对象绑定到线程上下文中。在绑定之前,会保存当前线程上的旧的 `TransactionInfo` +对象,以便在事务完成后恢复之前保存的旧的 `TransactionInfo` 对象。 + +```java +/** + * 将当前的 TransactionInfo 对象绑定到线程上下文中。 + * 在绑定之前会保存当前线程上的旧的 TransactionInfo 对象, + * 在事务完成后会恢复之前保存的旧的 TransactionInfo 对象。 + */ +private void bindToThread() { + // 暴露当前的 TransactionStatus,并在事务完成后恢复任何现有的 TransactionStatus。 + this.oldTransactionInfo = transactionInfoHolder.get(); + transactionInfoHolder.set(this); +} +``` + +在`org.springframework.transaction.interceptor.TransactionAspectSupport#commitTransactionAfterReturning` +方法中,通过 `txInfo` 参数获取当前事务的信息,包括事务状态和事务管理器,并调用事务管理器的 `commit` 方法来提交事务。 + +```java +/** + * 在方法成功完成调用后执行,但不会在处理异常后执行。 + * 如果没有创建事务,则什么也不做。 + * + * @param txInfo 当前事务的信息 + */ +protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) { + // 如果 txInfo 不为 null 且事务状态不为 null,则执行事务提交 + if (txInfo != null && txInfo.getTransactionStatus() != null) { + // 如果启用了跟踪日志,则记录完成事务的信息 + if (logger.isTraceEnabled()) { + logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]"); + } + // 调用事务管理器的 commit 方法来提交事务 + txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); + } +} +``` + +在`org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing` +方法中,根据事务属性的配置完成事务的提交或回滚操作。如果事务属性要求在异常时回滚事务,则会调用事务管理器的回滚方法;否则会调用事务管理器的提交方法。 + +```java +/** + * 处理可抛出的异常,完成事务。 + * 根据配置可能会提交或回滚事务。 + * + * @param txInfo 当前事务的信息 + * @param ex 遇到的可抛出异常 + */ +protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) { + // 如果 txInfo 不为 null 且事务状态不为 null,则执行事务完成操作 + if (txInfo != null && txInfo.getTransactionStatus() != null) { + // 如果启用了跟踪日志,则记录在异常后完成事务的信息和异常信息 + if (logger.isTraceEnabled()) { + logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + + "] after exception: " + ex); + } + // 如果事务属性不为 null,并且根据事务属性需要在异常时回滚事务 + if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) { + try { + // 使用事务管理器回滚事务 + txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); + } catch (TransactionSystemException ex2) { + // 如果回滚过程中发生异常,则记录错误信息并抛出异常 + logger.error("Application exception overridden by rollback exception", ex); + ex2.initApplicationException(ex); + throw ex2; + } catch (RuntimeException | Error ex2) { + // 如果回滚过程中发生运行时异常或错误,则记录错误信息并抛出异常 + logger.error("Application exception overridden by rollback exception", ex); + throw ex2; + } + } else { + // 如果不需要在异常时回滚事务,则继续提交事务 + // 如果事务状态为 RollbackOnly,则最终仍然会回滚事务 + try { + txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); + } catch (TransactionSystemException ex2) { + // 如果提交过程中发生异常,则记录错误信息并抛出异常 + logger.error("Application exception overridden by commit exception", ex); + ex2.initApplicationException(ex); + throw ex2; + } catch (RuntimeException | Error ex2) { + // 如果提交过程中发生运行时异常或错误,则记录错误信息并抛出异常 + logger.error("Application exception overridden by commit exception", ex); + throw ex2; + } + } + } +} +``` \ No newline at end of file diff --git a/spring-transaction/spring-transaction-transactionInterceptor/pom.xml b/spring-transaction/spring-transaction-transactionInterceptor/pom.xml new file mode 100644 index 0000000..be3fa0b --- /dev/null +++ b/spring-transaction/spring-transaction-transactionInterceptor/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-transactionInterceptor + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/ScoresService.java b/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/ScoresService.java new file mode 100644 index 0000000..b83fe41 --- /dev/null +++ b/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/ScoresService.java @@ -0,0 +1,5 @@ +package com.xcs.spring; + +public interface ScoresService { + void insertScore() throws Exception; +} diff --git a/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/ScoresServiceImpl.java b/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/ScoresServiceImpl.java new file mode 100644 index 0000000..cee98eb --- /dev/null +++ b/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/ScoresServiceImpl.java @@ -0,0 +1,30 @@ +package com.xcs.spring; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Random; + +public class ScoresServiceImpl implements ScoresService { + + private JdbcTemplate jdbcTemplate; + + public ScoresServiceImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + @Transactional + public void insertScore() throws Exception { + // 主键Id + long id = System.currentTimeMillis(); + // 分数 + int score = new Random().nextInt(100); + // 保存数据 + int row = jdbcTemplate.update("insert into scores(id,score) values(?,?)", id, score); + // 打印影响行数 + System.out.println("scores row = " + row); + // 模拟异常 + // throw new Exception("测试异常"); + } +} diff --git a/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/TransactionInterceptorDemo.java b/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/TransactionInterceptorDemo.java new file mode 100644 index 0000000..2bbc5ad --- /dev/null +++ b/spring-transaction/spring-transaction-transactionInterceptor/src/main/java/com/xcs/spring/TransactionInterceptorDemo.java @@ -0,0 +1,50 @@ +package com.xcs.spring; + +import com.mysql.jdbc.Driver; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource; +import org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor; +import org.springframework.transaction.interceptor.TransactionInterceptor; + +public class TransactionInterceptorDemo { + + public static void main(String[] args) throws Exception { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 DataSourceTransactionManager 对象,用于管理事务 + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + // 创建 AnnotationTransactionAttributeSource 对象,用于获取事务属性 + AnnotationTransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource(); + + // 创建 TransactionInterceptor 对象,用于拦截方法调用并管理事务 + TransactionInterceptor transactionInterceptor = new TransactionInterceptor(); + transactionInterceptor.setTransactionAttributeSource(transactionAttributeSource); + transactionInterceptor.setTransactionManager(transactionManager); + + // 创建 BeanFactoryTransactionAttributeSourceAdvisor 对象,用于配置事务拦截器 + BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); + advisor.setTransactionAttributeSource(transactionAttributeSource); + advisor.setAdvice(transactionInterceptor); + + // 创建 ProxyFactory 对象,用于创建代理对象 + ProxyFactory proxyFactory = new ProxyFactory(); + proxyFactory.addAdvisor(advisor); + proxyFactory.setTarget(new ScoresServiceImpl(jdbcTemplate)); + + // 获取代理对象,并调用其方法 + ScoresService scoresService = (ScoresService) proxyFactory.getProxy(); + scoresService.insertScore(); + } +} diff --git a/spring-transaction/spring-transaction-transactionTemplate/README.md b/spring-transaction/spring-transaction-transactionTemplate/README.md new file mode 100644 index 0000000..18dfcad --- /dev/null +++ b/spring-transaction/spring-transaction-transactionTemplate/README.md @@ -0,0 +1,164 @@ +## TransactionTemplate + +- [TransactionTemplate](#transactiontemplate) + - [一、基本信息](#一基本信息) + - [二、基本描述](#二基本描述) + - [三、主要功能](#三主要功能) + - [四、最佳实践](#四最佳实践) + - [五、源码分析](#五源码分析) + +### 一、基本信息 + +✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址 +** - [github](https://github.com/xuchengsheng/spring-reading) + +### 二、基本描述 + +`TransactionTemplate` 是 Spring Framework 提供的工具类,用于在代码中以编程方式管理事务。它简化了事务的启动、提交/回滚以及异常处理,同时允许灵活配置事务属性,并提供了回调机制以执行特定操作。 + +### 三、主要功能 + +1. **事务的启动和提交/回滚** + + + 允许我们以编程方式启动事务,并在需要时提交或回滚事务。这种方式使得我们可以在代码的特定部分明确定义事务的边界,而不必依赖于容器管理。 + +2. **异常处理** + + + 提供了对异常的处理机制,我们可以通过配置指定在发生异常时应该执行的操作,比如回滚事务。 + +3. **事务属性的灵活配置** + + + 我们可以使用 `TransactionTemplate` 配置各种事务属性,如隔离级别、传播行为等。这使得我们可以针对不同的场景灵活地配置事务行为。 + +4. **回调机制** + + + 允许我们定义回调接口,通过这些回调接口,我们可以在事务的不同阶段执行特定的操作。这为更复杂的事务场景提供了更大的灵活性。 + +### 四、最佳实践 + +使用 `TransactionTemplate` 来管理事务。它首先创建了一个数据库连接,并通过 `DataSourceTransactionManager` +实例化了 `TransactionTemplate`。在 `TransactionTemplate` 的 `execute` +方法中,定义了一个事务回调接口,在该接口的 `doInTransaction` 方法中执行了数据库操作。通过这种方式,可以确保操作要么全部成功提交,要么全部回滚,从而保证数据的一致性和完整性。 + +```java +public class TransactionTemplateDemo { + + public static void main(String[] args) throws SQLException { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 DataSourceTransactionManager 对象,用于管理事务 + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + // 创建事务模板 + TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); + + Boolean insertSuccess = transactionTemplate.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + // 主键Id + long id = System.currentTimeMillis(); + // 分数 + int score = new Random().nextInt(100); + // 保存数据 + int row = jdbcTemplate.update("insert into scores(id,score) values(?,?)", id, score); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 我们也可以使用setRollbackOnly来回滚 + // status.setRollbackOnly(); + // 返回是否新增成功 + return row >= 1; + } + }); + System.out.println("新增scores表数据:" + insertSuccess); + } +} +``` + +运行结果,数据库操作成功完成并成功提交了事务。 + +```java +新增scores表数据:true +``` + +### 五、源码分析 + +在`org.springframework.transaction.support.TransactionTemplate#execute`方法中,首先确保了 `PlatformTransactionManager` +已经设置,然后根据事务管理器的类型选择合适的执行方式。如果事务管理器是 `CallbackPreferringPlatformTransactionManager` +的实例,就会调用其 `execute` 方法来执行事务。否则,它将获取事务状态,执行事务回调操作,并在操作过程中处理可能的异常。最后,无论成功还是失败,都会提交事务。 + +```java + +@Override +@Nullable +public T execute(TransactionCallback action) throws TransactionException { + // 断言确保已设置PlatformTransactionManager + Assert.state(this.transactionManager != null, "No PlatformTransactionManager set"); + + // 如果事务管理器是CallbackPreferringPlatformTransactionManager的实例 + if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { + // 使用CallbackPreferringPlatformTransactionManager执行事务 + return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); + } else { + // 获取事务状态 + TransactionStatus status = this.transactionManager.getTransaction(this); + T result; + try { + // 执行事务回调操作 + result = action.doInTransaction(status); + } catch (RuntimeException | Error ex) { + // 事务中的代码抛出应用程序异常 -> 回滚事务 + rollbackOnException(status, ex); + throw ex; + } catch (Throwable ex) { + // 事务中的代码抛出意外异常 -> 回滚事务 + rollbackOnException(status, ex); + throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); + } + // 提交事务 + this.transactionManager.commit(status); + return result; + } +} +``` + +在`org.springframework.transaction.support.TransactionTemplate#rollbackOnException` +方法中,首先确保已设置了 `PlatformTransactionManager` +,然后记录调试日志以表示在应用异常时启动事务回滚。接着尝试执行事务回滚操作,如果发生回滚异常,则记录错误日志,并将原始异常初始化为回滚异常的应用程序异常。最后,如果发生运行时异常或错误,则将其重新抛出。 + +```java +/** + * 在应用异常时执行回滚,正确处理回滚异常。 + * @param status 表示事务的对象 + * @param ex 抛出的应用程序异常或错误 + * @throws TransactionException 如果发生回滚错误 + */ +private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException { + // 断言确保已设置PlatformTransactionManager + Assert.state(this.transactionManager != null, "No PlatformTransactionManager set"); + + // 打印调试日志,表示在应用异常时启动事务回滚 + logger.debug("Initiating transaction rollback on application exception", ex); + try { + // 执行事务回滚 + this.transactionManager.rollback(status); + } catch (TransactionSystemException ex2) { + // 打印错误日志,表示应用异常被回滚异常覆盖 + logger.error("Application exception overridden by rollback exception", ex); + // 初始化应用程序异常 + ex2.initApplicationException(ex); + throw ex2; + } catch (RuntimeException | Error ex2) { + // 打印错误日志,表示应用异常被回滚异常覆盖 + logger.error("Application exception overridden by rollback exception", ex); + throw ex2; + } +} +``` \ No newline at end of file diff --git a/spring-transaction/spring-transaction-transactionTemplate/pom.xml b/spring-transaction/spring-transaction-transactionTemplate/pom.xml new file mode 100644 index 0000000..82a7cfb --- /dev/null +++ b/spring-transaction/spring-transaction-transactionTemplate/pom.xml @@ -0,0 +1,14 @@ + + + + com.xcs.spring + spring-transaction + 0.0.1-SNAPSHOT + + + 4.0.0 + spring-transaction-transactionTemplate + + \ No newline at end of file diff --git a/spring-transaction/spring-transaction-transactionTemplate/src/main/java/com/xcs/spring/TransactionTemplateDemo.java b/spring-transaction/spring-transaction-transactionTemplate/src/main/java/com/xcs/spring/TransactionTemplateDemo.java new file mode 100644 index 0000000..271821c --- /dev/null +++ b/spring-transaction/spring-transaction-transactionTemplate/src/main/java/com/xcs/spring/TransactionTemplateDemo.java @@ -0,0 +1,52 @@ +package com.xcs.spring; + +import com.mysql.jdbc.Driver; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; + +import java.sql.SQLException; +import java.util.Random; + +public class TransactionTemplateDemo { + + public static void main(String[] args) throws SQLException { + // 数据库连接 URL,格式为 jdbc:数据库驱动名称://主机地址:端口号/数据库名称 + String url = "jdbc:mysql://localhost:3306/spring-reading"; + // 数据库用户名 + String username = "root"; + // 数据库密码 + String password = "123456"; + + // 创建 SimpleDriverDataSource 对象,用于管理数据源 + SimpleDriverDataSource dataSource = new SimpleDriverDataSource(new Driver(), url, username, password); + // 创建 DataSourceTransactionManager 对象,用于管理事务 + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); + // 创建 JdbcTemplate 对象,用于执行 SQL 语句 + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + // 创建事务模板 + TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); + + Boolean insertSuccess = transactionTemplate.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + // 主键Id + long id = System.currentTimeMillis(); + // 分数 + int score = new Random().nextInt(100); + // 保存数据 + int row = jdbcTemplate.update("insert into scores(id,score) values(?,?)", id, score); + // 模拟异常,用于测试事务回滚 + // int i = 1 / 0; + // 我们也可以使用setRollbackOnly来回滚 + // status.setRollbackOnly(); + // 返回是否新增成功 + return row >= 1; + } + }); + System.out.println("新增scores表数据:" + insertSuccess); + } +} diff --git a/spring-transaction/sql/test.sql b/spring-transaction/sql/test.sql new file mode 100644 index 0000000..7e6a28e --- /dev/null +++ b/spring-transaction/sql/test.sql @@ -0,0 +1,20 @@ +CREATE TABLE `scores` +( + `id` bigint NOT NULL, + `score` decimal(5, 2) NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB + CHARACTER SET = utf8mb4 + COLLATE = utf8mb4_bin + ROW_FORMAT = DYNAMIC; + +CREATE TABLE `scores_log` +( + `id` bigint NOT NULL, + `score_id` bigint NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB + CHARACTER SET = utf8mb4 + COLLATE = utf8mb4_bin + ROW_FORMAT = DYNAMIC;