一、Spring框架事务支持模型的优点
全面的事务支持是使用Spring框架最令人信服的原因之一。 Spring Framework为事务管理提供了一个一致的抽象,给我们的开发带来了极大的便利。
Spring允许应用程序开发人员在任何环境中使用【一致的编程模型】。 只需编写一次代码,它就可以从不同环境中的不同事务管理策略中获益。
Spring框架同时提供【声明式】和【编程式】事务管理。 大多数用户更喜欢【声明式事务管理】,这也是我们在大多数情况下所推荐的。
使用声明式模型,开发人员通常【很少或不编写】与事务管理相关的代码,因此,不依赖于Spring
Framework事务API或任何其他事务API,也就是啥也不用写。
二、理解Spring框架的事务抽象
spring事务对事务抽象提现在一下三个类中:PlatformTransactionManager,TransactionDefinition ,TransactionStatus。
1️⃣TransactionManager
TransactionManage主要有一下两个子接口:
org.springframework.transaction.PlatformTransactionManager接口用于为不同平台提供统一抽象的事务管理器,重要。
org.springframework.transaction.ReactiveTransactionManager接口用于响应式事务管理,这个不重要。
下面显示了’ PlatformTransactionManager ’ API的定义:
public interface PlatformTransactionManager extends TransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
任何【PlatformTransactionManager】接口实现类的方法抛出的【TransactionException】是未检查的 (也就是说,它继承了【java.lang.RuntimeException】的类)。 这里边隐藏了一个知识点,我们后续再说。
public abstract class TransactionException extends NestedRuntimeException { public TransactionException(String msg) { super(msg); } public TransactionException(String msg, Throwable cause) { super(msg, cause); } }
任何一个【TransactionManager】的实现通常需要了解它们工作的环境:JDBC、mybatis、Hibernate等等。 下面的示例展示了如何定义一个本地的【PlatformTransactionManager】实现(在本例中,使用纯JDBC)。
你可以通过创建一个类似于下面这样的bean来定义JDBC ’ DataSource ':
username=root password=root url=jdbc:mysql://127.0.0.1:3306/ydlclass?characterEncoding=utf8&serverTimezone=Asia/Shanghai driverName=com.mysql.cj.jdbc.Driver
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:property-placeholder location="jdbc.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${driverName}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> <property name="url" value="${url}"/> </bean> </beans>
DataSourceTransactionManager是PlatformTransactionManager的一个子类,他需要一个数据源进行注入:
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
注意点,在DataSourceTransactionManager源码中有这么一句话,将线程的持有者绑定到线程当中:
// Bind the connection holder to the thread. if (txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); }
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
从这里我们也能大致明白,PlatformTransactionManager的事务是和线程绑定的,事务的获取是从当前线程中获取的。
2️⃣TransactionDefinition
TransactionDefinition 接口指定了当前事务的相关配置,主要配置如下:
Propagation: 通常情况下,事务范围内的所有代码都在该事务中运行。 但是,如果事务方法在 【已经存在事务的上下文中运行】,则可以指定事务的【传播行为】。
Isolation: 该事务与其他事务的工作隔离的程度。 例如,这个事务可以看到其他事务未提交的写入吗? 【隔离级别】
Timeout: 该事务在超时并被底层事务基础设施自动回滚之前运行多长时间。
只读状态: 当代码读取但不修改数据时,可以使用只读事务。 在某些情况下,如使用Hibernate时,只读事务可能是一种有用的优化。
public interface TransactionDefinition { /** * Support a current transaction; create a new one if none exists. */ int PROPAGATION_REQUIRED = 0; /** * Support a current transaction; execute non-transactionally if none exists. */ int PROPAGATION_SUPPORTS = 1; /** * Support a current transaction; throw an exception if no current transaction */ int PROPAGATION_MANDATORY = 2; /** * Create a new transaction, suspending the current transaction if one exists. */ int PROPAGATION_REQUIRES_NEW = 3; /** * Do not support a current transaction; rather always execute non-transactionally. */ int PROPAGATION_NOT_SUPPORTED = 4; /** * Do not support a current transaction; throw an exception if a current transaction */ int PROPAGATION_NEVER = 5; /** * Execute within a nested transaction if a current transaction exists, */ int PROPAGATION_NESTED = 6; int ISOLATION_DEFAULT = -1; int ISOLATION_READ_UNCOMMITTED = 1; int ISOLATION_READ_COMMITTED = 2; int ISOLATION_REPEATABLE_READ = 4; int ISOLATION_SERIALIZABLE = 8; /** * Use the default timeout of the underlying transaction system, * or none if timeouts are not supported. */ int TIMEOUT_DEFAULT = -1; }
这个接口有一个默认实现:
public class DefaultTransactionDefinition implements TransactionDefinition, Serializable { private int propagationBehavior = PROPAGATION_REQUIRED; private int isolationLevel = ISOLATION_DEFAULT; private int timeout = TIMEOUT_DEFAULT; private boolean readOnly = false; //.... }
3️⃣TransactionStatus
TransactionStatus接口为事务代码提供了一种简单的方法来控制事务执行和查询事务状态。下面的例子显示了TransactionStatus接口:
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable { @Override //返回当前事务是否是新的; 否则将参与现有事务,或者可能从一开始就不在实际事务中运行。 boolean isNewTransaction(); boolean hasSavepoint(); @Override // 只设置事务回滚。 这指示事务管理器,事务的唯一可能结果可能是回滚,而不是抛出异常,从而触发回滚。 void setRollbackOnly(); @Override // 返回事务是否被标记为仅回滚(由应用程序或事务基础设施)。 boolean isRollbackOnly(); void flush(); @Override // 返回该事务是否已完成,即是否已提交或回滚。 boolean isCompleted(); }
三、编程式事务管理
Spring Framework提供了两种编程式事务管理的方法:
- 使用TransactionTemplate。
- 使用 TransactionManager。
1️⃣使用 TransactionManager
🍀使用 PlatformTransactionManager
我们可以直接使用【org.springframework.transaction.PlatformTransactionManager】直接管理事务。 为此,通过bean引用将您使用的PlatformTransactionManager的实现传递给您的bean。 然后,通过使用TransactionDefinition和 TransactionStatus对象,您可以发起事务、回滚和提交。 下面的示例显示了如何这样做:
给容器注入对应的事务管理器:
<context:property-placeholder location="jdbc.properties"/> <context:component-scan base-package="com.ydlclass"/> <!-- 注入事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 注入事务管理器 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <!--数据源--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${url}"/> <property name="driverClassName" value="${driverName}"/> <property name="username" value="${user}"/> <property name="password" value="${password}"/> </bean>
注入对应的service:
@Override public void transfer(String from, String to, Integer money) { // 默认的事务配置 DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); // 使用事务管理器进行事务管理 TransactionStatus transaction = transactionManager.getTransaction(definition); try{ // 转账其实是两个语句 String moneyFrom = "update account set money = money - ? where username = ? "; String moneyTo = "update account set money = money + ? where username = ? "; // 从转账的人处扣钱 jdbcTemplate.update(moneyFrom,money,from); int i = 1/0; jdbcTemplate.update(moneyTo,money,to); }catch (RuntimeException exception){ exception.printStackTrace(); // 回滚 transactionManager.rollback(transaction); } // 提交 transactionManager.commit(transaction); }
2️⃣使用TransactionTemplate
【TransactionTemplate】采用了与其他Spring模板相同的方法,比如【JdbcTemplate】。 它使用回调方法将应用程序代码从获取和释放事务性资源的样板程序中解放出来,因为您的代码只关注您想要做的事情,而不是希望将大量的时间浪费在这里。
正如下面的示例所示,使用【TransactionTemplate】绝对会将您与Spring的事务基础设施和api耦合在一起。 编程事务管理是否适合您的开发需求,这是您必须自己做出的决定。
必须在事务上下文中运行并显式使用TransactionTemplate的应用程序代码类似于下一个示例。 您作为一个应用程序开发人员,可以编写一个TransactionCallback实现(通常表示为一个匿名内部类),其中包含您需要在事务上下文中运行的代码。 然后你可以将你的自定义 TransactionCallback的一个实例传递给TransactionTemplate中暴露的 execute(..)方法。 下面的示例显示了如何这样做:
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"/> </bean>
如果没有返回值,你可以在匿名类中使用方便的TransactionCallbackWithoutResult类,如下所示:
@Override public void transfer3(String from, String to, Integer money) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { // 转账其实是两个语句 String moneyFrom = "update account set money = money - ? where username = ? "; String moneyTo = "update account set money = money + ? where username = ? "; // 从转账的人处扣钱 jdbcTemplate.update(moneyFrom, money, from); // int i = 1 / 0; jdbcTemplate.update(moneyTo, money, to); } }); }
四、声明式事务管理
大多数Spring框架用户选择声明式事务管理。 该选项对应用程序代码的影响最小,因此最符合非侵入式轻量级容器的理想。
Spring框架的声明性事务管理是通过Spring面向切面编程(AOP)实现的。 然而,由于事务切面代码随Spring Framework发行版一起提供,并且可以模板的方式使用,所以通常不需要理解AOP概念就可以有效地使用这些代码。
1️⃣理解Spring框架的声明式事务
Spring框架的声明式事务通过AOP代理进行实现,事务的通知是由AOP元数据与事务性元数据的结合产生了一个AOP代理,该代理使用【TransactionInterceptor】结合适当的【TransactionManager】实现来驱动方法调用的事务。
Spring Framework的【TransactionInterceptor】为命令式和响应式编程模型提供了事务管理。 拦截器通过检查方法返回类型来检测所需的事务管理风格。 事务管理风格会影响需要哪个事务管理器。 命令式事务需要【PlatformTransactionManager】,而响应式事务使用【ReactiveTransactionManager 】实现。
【@Transactional 】通常用于【PlatformTransactionManager 】管理的【线程绑定事务】,将事务暴露给当前执行线程中的所有数据访问操作。(注意:这不会传播到方法中新启动的线程)
2️⃣声明式事务实现的示例
<!-- from the file 'context.xml' --> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- the transactional semantics... --> <tx:attributes> <!-- all methods starting with 'get' are read-only --> <tx:method name="get*" read-only="true"/> <!-- other methods use the default transaction settings (see below) --> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- ensure that the above transactional advice runs for any execution of an operation defined by the FooService interface --> <aop:config> <aop:pointcut id="point" expression="within(com.ydlclass.service..*)"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="point"/> </aop:config> </beans>