⚠️ 再提醒一次:Spring 本身并不实现事务,Spring事务 的本质还是底层数据库对事务的支持。你的程序是否支持事务首先取决于数据库 ,比如使用 MySQL 的话,如果你选择的是 innodb 引擎,那么恭喜你,是可以支持事务的。但是,如果你的 MySQL 数据库使用的是 myisam 引擎的话,那不好意思,Spring从根上就是不支持事务的。
这里再多提一下一个非常重要的知识点:MySQL 怎么保证原子性的?
我们知道如果想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,在 MySQL 中,恢复机制是通过 回滚日志(undo log) 实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中遇到异常的话,我们直接利用 回滚日志 中的信息将数据回滚到修改之前的样子即可!并且,回滚日志会先于数据持久化到磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚之前未完成的事务。
〇、Spring 事务简介
Spring 事务 提供一套抽象的事务管理,并且结合 Spring IoC 和 Spring AOP,简化了应用程序使用数据库事务,通过声明式事务,可以做到对应用程序无侵入的实现事务功能。例如 使用JDBC 操作数据库,想要使用事务的步骤为:
- 获取连接 Connection con = DriverManager.getConnection()
- 开启事务con.setAutoCommit(true/false);
- 执行CRUD
- 提交事务/回滚事务 con.commit() / con.rollback();
- 关闭连接 conn.close();
采用Spring 事务后,只需要 关注第3步的实现,其他的步骤 都是Spring 完成。
Spring事务的本质 其实就是 AOP 和 数据库事务,Spring 将数据库的事务操作提取为切面,通过AOP的方式增强事务方法。
一、Spring 支持两种方式的事务管理
Spring提供了编程式事务和声明式事务两种实现方式。
1.1 编程式事务管理
Spring编程式事务有两种实现方法:1、TransactionTemplate 2、PlatformTransactionManager
对于编程式事务管理,Spring推荐使用TransactionTemplate。
通过 TransactionTemplate或者PlatformTransactionManager 手动管理事务,实际应用中很少使用,现在大多都是用声明式事务通过Spring自动帮我们管理事务,但是编程式事务对于你理解 Spring 事务管理原理有帮助。
1.1.1 TransactionTemplate
采用TransactionTemplate和采用其他Spring模板,如JdbcTempalte和HibernateTemplate来实现事务是一样的,都属于编程式事务。它使用回调方法,把应用程序从处理取得和释放资源中解脱出来。如同其他模板,TransactionTemplate是线程安全的。实例代码如下:
1、无返回值
// 可以自己手动新建,也可以将TransactionTemplate交给Spring容器管理,直接通过@Autowired自动注入得到transactionTemplate对象
TransactionTemplate transactionTemplate = new TransactionTemplate();
// 如果无返回值,就new TransactionCallbackWithoutResult()
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// .... 业务代码
jdbcTemplate.update("update t set v=? where k=?", "n", "k2");
//不需要手动提交
}catch (Exception e){
e.printStackTrace();
//但是需要手动回滚
status.setRollbackOnly();
}
}
});
2、有返回值
// 新建一个TransactionTemplate
TransactionTemplate tt = new TransactionTemplate();
// 如果有返回值,就new TransactionCallback()
Object result = tt.execute(
new TransactionCallback(){
public Object doTransaction(TransactionStatus status){
// 业务逻辑...
updateOperation();
// 返回值
return resultOfUpdateOperation();
}
}
); // 执行execute方法进行事务管理
注: 使用TransactionCallback()可以返回一个值。如果使用TransactionCallbackWithoutResult则没有返回值。
使用TransactionTemplate来实现编程式事务需要自己创建事务,但是不需要自己手动提交,它如果正常执行完会自动提交事务。不过需要自己手动设置回滚,如果不设置回滚事务出现问题是不会回滚的。
1.1.2 PlatformTransactionManager
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA,它们自己去按照接口模板实现自己的事务管理器。示例代码如下:
1、可以自己手动创建TransactionManager对象
// 定义一个某个框架平台的TransactionManager,如JDBC、Hibernate
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
// 设置数据源
dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource());
// 定义事务属性
DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
// 设置传播行为属性
transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
// 获得事务状态,开始事务
TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef);
try {
// 业务逻辑:数据库操作
doSomething();
// 提交事务
dataSourceTransactionManager.commit(status);
} catch (Exception e) {
// 回滚回滚
dataSourceTransactionManager.rollback(status);
}
2、也可以将TransactionManager对象交给Spring去管理,通过@Autowired自动注入获得事务管理器对象
@Autowired
private PlatformTransactionManager transactionManager;
public void testTransaction() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// .... 业务代码
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
}
使用TransactionManager来实现编程式事务与TransactionTemplate有些许不同,它依旧需要自己创建事务,需要自己手动设置回滚。并且它还需要自己需手动提交事务,如果不手动提交,事务是不会自动提交的。
1.2 声明式事务管理
声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
推荐使用声明式事务,因为代码侵入性最小,底层实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)。声明式事务并不需要自己创建事务,提交事务,回滚等操作,Spring会自动替你完成,但是声明式事务的粒度就是方法层面的,而编程式事务的粒度可以更小。
既然是基于AOP实现的,那么使用声明式事务必然需要对事务进行配置,根据不同的配置方法,可以总结出三种实现方法:1、XML实现 2、注解实现 3、混合实现(XML和注解同时使用)
其实我们能发现这三种实现方法和AOP的配置是一样的,Spring事务的底层原理就是通过AOP代理增强代码实现的,在原有代码上增加了事务管理的代码。
1.2.1 xml配置声明式事务
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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
Index of /schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
Index of /schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
Index of /schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 纯xml 我们的业务类也配成xml -->
<!--将业务类交给spring管理-->
<bean id="xmlUserService" class="com.test.transaction.service.XMLUserService">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!--配置jdbcTemplate,来设置数据源,并将jdbcTemplate装配进Spring容器-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--配置数据源,将数据源绑定到jdbcTemplate-->
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
<property name="url" value="jdbc:mysql://localhost:3306/shadow?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC">
</property>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver">
</property>
</bean>
<!--配置事务管理器,将事务管理器绑定上数据源-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--事务通知(设置在什么时候干什么事),将事务管理器作用到指定的方法中-->
<tx:advice transaction-manager="transactionManager" id="txAdvice">
<tx:attributes>
<tx:method name="update"/>
</tx:attributes>
</tx:advice>
<!-- aop的配置,设置切入点,表示对包路径com.test.transaction.service下的所有名为update的方法进行事务管理-->
<aop:config>
<aop:pointcut id="p" expression="execution(*com.test.transaction.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="p"></aop:advisor>
</aop:config>
</beans>
Java代码:
public class XMLUserService {
JdbcTemplate jdbcTemplate;
// 通过xml配置注入jdbcTemplate
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// Spring会自动对update方法进行事务管理
public void update() {
jdbcTemplate.update("update t set v=? where k=?","n","k1");
// 检测到抛出异常就会自动回滚事务
throw new NullPointerException();
}
}
1.2.2 注解配置声明式事务
JavaConfig配置类:
// 将App这个类作为JavaConfig配置类.这个注解如果不加会有问题
@Configuration
// 开启注解支撑
@EnableTransactionManagement
// 扫描这个包下面的注解
@ComponentScan("com.test.transaction")
public class App {
// 将jdbcTemplate对象放到容器当中,用于连接数据库,执行sql语句
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
// 将数据源对象配置到Spring容器中
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setPassword("123456");
dataSource.setUrl("jdbc:mysql://localhost:3306/shadow?
useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC");
dataSource.setUsername("root");
return dataSource;
}
/**
* 声明命事务管理器,将事务管理器装配到Spring容器中
* @return
*/
@Bean
public PlatformTransactionManager transactionManager() {
// 创建事务管理器对象
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
// 设置数据源
dataSourceTransactionManager.setDataSource(dataSource());
return dataSourceTransactionManager;
}
}
使用 @Transactional注解进行事务管理的示例代码如下:
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
B b = new B();
C c = new C();
b.bMethod();
c.cMethod();
}
注解@Transactional的属性
具体位置在:org.springframework.transaction.annotation.Transactional
1.2.3 混合方式
这个就没有标准,看自己的喜好哪些写到xml,哪些写成Java代码。
以下配置代码参考自Spring事务配置的五种方式。根据代理机制的不同,总结了五种Spring事务的配置方式。他们都是可以实现XML和注解混用的。
1、每个Bean都有一个代理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
Index of /schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
Index of /schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- 配置注解扫描 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 配置DAO -->
<bean id="userDaoTarget" class="xxx.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 配置事务代理 -->
<bean id="userDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置事务管理器 -->
<!-- 将事务管理添加到DAO层代码上 -->
<property name="transactionManager" ref="transactionManager" />
<property name="target" ref="userDaoTarget" />
<property name="proxyInterfaces" value="xxx.dao.GeneratorDao" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>
2、所有Bean共享一个代理基类
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
Index of /schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
Index of /schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- 配置数据源 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 配置事务基类 -->
<bean id="transactionBase" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" lazy-init="true" abstract="true">
<!-- 配置事务管理器 -->
<property name="transactionManager" ref="transactionManager" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 配置DAO -->
<bean id="userDaoTarget" class="xxx.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="userDao" parent="transactionBase" >
<property name="target" ref="userDaoTarget" />
</bean>
</beans>
3、使用拦截器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
Index of /schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
Index of /schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- 配置数据源 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 配置事务拦截器 -->
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>*Dao</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
<!-- 配置DAO -->
<bean id="userDao" class="xxx.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>
4、使用tx标签配置的拦截器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
Index of /schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
Index of /schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- 开启注解支持 -->
<context:annotation-config />
<!-- 扫描注解 现在可以不写上面那句,直接写下面的扫描注解标签就可以开启注解支持了-->
<context:component-scan base-package="xxx" />
<!-- 定义数据源 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 定义事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!-- 定义切面 -->
<aop:config>
<aop:pointcut id="interceptorPointCuts" expression="execution(* xxx.dao.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="interceptorPointCuts" />
</aop:config>
</beans>
5、全注解
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
Index of /schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
Index of /schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- 1.配置数据源 -->
<context:annotation-config />
<!-- 2.配置事务管理器 -->
<context:component-scan base-package="xxx" />
<!-- 3.配置事务管理器 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 启用注解方式的声明式事务,如果将这个去掉,将不会创建事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
然后在需要使用事务的类或方法上加上注解@Transactional即可。
1.3 编程式事务和声明式事务的区别
编程式事务每次实现都要单独实现,业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。
二、Spring 事务管理接口介绍
Spring 框架中,事务管理相关最重要的 3 个接口如下:
- PlatformTransactionManager:(平台)事务管理器,Spring 事务策略的核心。
- TransactionDefinition:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
- TransactionStatus:事务运行状态。
我们可以把 PlatformTransactionManager 接口可以被看作是事务上层的管理者,而 TransactionDefinition 和 TransactionStatus 这两个接口可以看作是事务的描述。
PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。
2.1 PlatformTransactionManager:事务管理接口
Spring 并不直接管理事务,而是提供了多种事务管理器 。Spring 事务管理器的接口是:PlatformTransactionManager,它是Spring 事务结构中的核心接口。
通过这个接口,Spring 为各个平台如:JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
PlatformTransactionManager 接口的具体实现如下:
PlatformTransactionManager接口中定义了三个方法:
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface PlatformTransactionManager {
// 获得事务
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
// 提交事务
void commit(TransactionStatus var1) throws TransactionException;
// 回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
通过PlatformTransactionManager的接口 可以看出,Spring 事务的 的三个核心方法,事务开启 ,提交事务,回滚事务,Spring 事务功能的实现 都是围绕这三个方法来实现。
这里多插一嘴。为什么要定义或者说抽象出来PlatformTransactionManager这个接口呢?
主要是因为要将事务管理行为抽象出来,然后不同的平台去实现它,这样我们可以保证提供给外部的行为不变,方便我们扩展。
“为什么我们要用接口?”
《设计模式》(GOF 那本)这本书在很多年前都提到过说要基于接口而非实现编程,你真的知道为什么要基于接口编程么?
纵观开源框架和项目的源码,接口是它们不可或缺的重要组成部分。要理解为什么要用接口,首先要搞懂接口提供了什么功能。我们可以把接口理解为提供了一系列功能列表的约定,接口本身不提供功能,它只定义行为。但是谁要用,就要先实现我,遵守我的约定,然后再自己去实现我定义的要实现的功能。
举个例子,我上个项目有发送短信的需求,为此,我们定了一个接口,接口只有两个方法:
1.发送短信 2.处理发送结果的方法。
刚开始我们用的是阿里云短信服务,然后我们实现这个接口完成了一个阿里云短信的服务。后来,我们突然又换到了别的短信服务平台,我们这个时候只需要再实现这个接口即可。这样保证了我们提供给外部的行为不变。几乎不需要改变什么代码,我们就轻松完成了需求的转变,提高了代码的灵活性和可扩展性。
什么时候用接口?当你要实现的功能模块设计抽象行为的时候,比如发送短信的服务,图床的存储服务等等。
2.1.1 JDBC事务
如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。
2.1.2 Hibernate事务
如果应用程序的持久化是通过Hibernate实习的,那么你需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的<bean>声明:
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。
2.1.3 Java持久化API事务(JPA)
Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。如果你计划使用JPA的话,那你需要使用Spring的JpaTransactionManager来处理事务。你需要在Spring中这样配置JpaTransactionManager:
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合作来构建事务。
2.1.4 Java原生API事务
如果你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),你就需要使用JtaTransactionManager:
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName" value="java:/TransactionManager" />
</bean>
JtaTransactionManager将事务管理的责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象,其中事务成功完成通过UserTransaction.commit()方法提交,事务失败通过UserTransaction.rollback()方法回滚。
2.2 TransactionInfo:事务信息
事务信息对象,包括一个事务所有的信息,持有事务管理器、事务定义对象、目标方法唯一标识、事务状态对象、外层的TransactionInfo,外层的TransactionInfo 用于在应用程序中获取 当前的TransactionInfo 对象。
2.3 TransactionDefinition:事务定义
事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法来得到一个事务,这个方法里面的参数是 TransactionDefinition 类 ,这个类就定义了一些基本的事务属性。
什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。
事务属性包含了 5 个方面:
- 隔离级别
- 传播行为
- 回滚规则
- 是否只读
- 事务超时
TransactionDefinition 接口中定义了 5 个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等。
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
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;
int TIMEOUT_DEFAULT = -1;
// 返回事务的传播行为,默认值为 REQUIRED。
int getPropagationBehavior();
// 返回事务的隔离级别,默认值是 DEFAULT
int getIsolationLevel();
// 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
int getTimeout();
// 返回是否为只读事务,默认值为 false
boolean isReadOnly();
@Nullable
String getName();
}
2.4 TransactionStatus:事务状态
TransactionStatus接口用来记录事务的状态。该接口定义了一组方法,用来获取或判断事务的相应状态信息。
PlatformTransactionManager.getTransaction(…)方法返回一个 TransactionStatus 对象。
TransactionStatus 接口内容如下:
public interface TransactionStatus{
boolean isNewTransaction(); // 是否是新的事务
boolean hasSavepoint(); // 是否有恢复点
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已完成
}
2.5 TransationSynchronization:事务同步回调接口
事务同步回调接口,在事务 周期的各个点 执行回调 方法。比如 挂起 ,继续,提交前后 ,完成前后 。用于 管理 应用程序在事务周期中绑定的资源。在Spring - Mybatis 整合时,正式Mybatis 正式利用了TransationSynchronization同步器,才让Mybatis 的事务管理交给了 Spring 事务来管理。
2.6 TransactionSynchronizationManager:事务同步回调的管理器
在事务运行过程中,需要保存一些状态,比如 数据库连接,
ThreadLocal<Map<Object, Object>> resources - 应用代码随事务的声明周期绑定的对象
ThreadLocal<Set<TransactionSynchronization>> synchronizations-使用的同步器,用于应用扩展
ThreadLocal<String> currentTransactionName-事务的名称
ThreadLocal<Boolean> currentTransactionReadOnly-事务是否是只读
ThreadLocal<Integer> currentTransactionIsolationLevel-事务的隔离级别
ThreadLocal<Boolean> actualTransactionActive-是否实际的开启了事务,如果加入 到 别的事务,就不是实际开启事务。
2.7 SuspendedResourceHolder:挂起的资源持有对象
在挂起一个事务时,用于记录被挂起事务的运行时信息,这些信息就是TransactionSynchronizationManager中记录的事务信息。然后将这些信息 保存在 新的DefaultTransactionStatus对象中,便于内部事务运行结束后,恢复外层事务。
三、事务属性详解
实际业务开发中,大家一般都是使用 @Transactional 注解来开启事务,很多人并不清楚这个参数里面的参数是什么意思,有什么用。为了更好的在项目中使用事务管理,强烈推荐好好阅读一下下面的内容。
3.1 事务传播行为(事务传播机制)
事务传播行为是为了解决业务层方法之间互相调用的事务问题。事务的传播性一般用在事务嵌套 的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
举个例子:我们在 A 类的aMethod()方法中调用了 B 类的 bMethod() 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 bMethod()发生异常需要回滚,如何配置事务传播行为才能让 aMethod()也跟着回滚呢?这个时候就需要事务传播行为的知识了,如果你不知道的话一定要好好看一下。
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.xxx)
public void aMethod {
//do something
b.bMethod();
}
}
@Service
Class B {
@Transactional(propagation = Propagation.xxx)
public void bMethod {
//do something
}
}
在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
......
}
不过,为了方便使用,Spring 相应地定义了一个枚举类:Propagation
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition;
public enum Propagation {
//如果当前存在事务则加入该事务 如果当前没有事务,则创建一个新的事务
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED)
//如果当前存在事务则加入该事务 如果当前没有事务 则以非事务的方式继续运行
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS)
//如果当前存在事务则加入该事务 如果当前没有事务 则抛出异常
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY)
//创建一个新的事务 如果当前存在事务 则把当前事务挂起
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW)
//以非事务方式运行 如果当前存在事务 则把当前事务挂起
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED)
//以非事务方式运行如果当前存在事务则抛出异常
NEVER(TransactionDefinition.PROPAGATION_NEVER)
//如果当前存在事务 则创建一个事务作为当前事务的嵌套事务来运行 如果不存在则创建一个新的事务
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
正确的事务传播行为可能的值如下:
1.TransactionDefinition.PROPAGATION_REQUIRED
REQUIRED 是 Spring 默认的事务传播类型,该传播类型的特点是:当前方法存在事务时,子方法加入该事务。此时父子方法共用一个事务,无论父子方法哪个发生异常回滚,整个事务都回滚。即使父方法捕捉了异常,也是会回滚。而当前方法不存在事务时,子方法新建一个事务。
它是使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:
- 如果外部方法没有开启事务的话,Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
- 如果外部方法开启事务并且被Propagation.REQUIRED修饰的话,所有Propagation.REQUIRED修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。
举个例子:如果我们上面的aMethod()和bMethod()使用的都是PROPAGATION_REQUIRED传播行为的话,两者使用的就是同一个事务,只要其中一个方法回滚,整个事务均回滚。
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
b.bMethod();
}
}
@Service
Class B {
@Transactional(propagation = Propagation.REQUIRED)
public void bMethod {
//do something
}
}
为了验证 REQUIRED 事务传播类型的特点,我们再来做几个测试。
还是上面 methodA 和 methodB 的例子。当 methodA 不开启事务,methodB 开启事务,这时候 methodB 就是独立的事务,而 methodA 并不在事务之中。因此当 methodB 发生异常回滚时,methodA 中的内容就不会被回滚。用如下的代码就可以验证我们所说的。
public void methodA(){
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB();
}
@Transactional
public void methodB(){
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}
最终的结果是:tablea 插入了数据,tableb 没有插入数据,符合了我们的猜想。
当 methodA 开启事务,methodB 也开启事务。按照我们的结论,此时 methodB 会加入 methodA 的事务。此时,我们验证当父子事务分别回滚时,另外一个事务是否会回滚。
我们先验证第一个:当父方法事务回滚时,子方法事务是否会回滚?
@Transactional
public void methodA(){
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB();
throw new RuntimeException();
}
@Transactional
public void methodB(){
tableService.insertTableB(new TableEntity());
}
结果是:talbea 和 tableb 都没有插入数据,即:父事务回滚时,子事务也回滚了。
我们继续验证第二个:当子方法事务回滚时,父方法事务是否会回滚?
@Transactional
public void methodA(){
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB();
}
@Transactional
public void methodB(){
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}
结果是:talbea 和 tableb 都没有插入数据,即:子事务回滚时,父事务也回滚了。
我们继续验证第三个:当字方法事务回滚时,父方法捕捉了异常,父方法事务是否会回滚?
@Transactional
public void methodA() {
tableService.insertTableA(new TableEntity());
try {
transactionServiceB.methodB();
} catch (Exception e) {
System.out.println("methodb occur exp.");
}
}
@Transactional
public void methodB() {
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}
结果是:talbea 和 tableb 都没有插入数据,即:子事务回滚时,父事务也回滚了。所以说,这也进一步验证了我们之前所说的:REQUIRED 传播类型,它是父子方法共用同一个事务的。
2.TransactionDefinition.PROPAGATION_REQUIRES_NEW
REQUIRES_NEW 也是常用的一个传播类型,该传播类型的特点是:无论当前方法是否存在事务,子方法都新建一个事务。此时父子方法的事务时独立的,它们都不会相互影响。但父方法需要注意子方法抛出的异常,避免因子方法抛出异常,而导致父方法回滚。
创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
举个例子:如果我们上面的bMethod()使用PROPAGATION_REQUIRES_NEW事务传播行为修饰,aMethod还是用PROPAGATION_REQUIRED修饰的话。如果aMethod()发生异常回滚,bMethod()不会跟着回滚,因为 bMethod()开启了独立的事务。但是,如果 bMethod()抛出了未被捕获的异常(如果在bMethod方法内try...catch处理了这个异常,那么aMethod就不会检测到这个异常,也就不会回滚了)并且这个异常满足事务回滚规则的话,aMethod()同样也会回滚,因为这个异常被 aMethod()的事务管理机制检测到了。
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
b.bMethod();
}
}
@Service
Class B {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void bMethod {
//do something
}
}
为了验证 REQUIRES_NEW 事务传播类型的特点,我们再来做几个测试。
首先,我们来验证一下:当父方法事务发生异常时,子方法事务是否会回滚?
@Transactional
public void methodA(){
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB();
throw new RuntimeException();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB(){
tableService.insertTableB(new TableEntity());
}
结果是:tablea 没有插入数据,tableb 插入了数据,即:父方法事务回滚了,但子方法事务没回滚。这可以证明父子方法的事务是独立的,不相互影响。
下面,我们来看看:当子方法事务发生异常时,父方法事务是否会回滚?
@Transactional
public void methodA(){
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB(){
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}
结果是:tablea 没有插入了数据,tableb 没有插入数据。
从这个结果来看,貌似是子方法事务回滚,导致父方法事务也回滚了。但我们不是说父子事务都是独立的,不会相互影响么?怎么结果与此相反呢?
其实是因为子方法抛出了异常,而父方法并没有做异常捕捉,此时父方法同时也抛出异常了,于是 Spring 就会将父方法事务也回滚了。如果我们在父方法中捕捉异常,那么父方法的事务就不会回滚了,修改之后的代码如下所示。
@Transactional
public void methodA(){
tableService.insertTableA(new TableEntity());
// 捕捉异常
try {
transactionServiceB.methodB();
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB(){
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}
结果是:tablea 插入了数据,tableb 没有插入数据。这正符合我们刚刚所说的:父子事务是独立的,并不会相互影响。
这其实就是我们上面所说的:父方法需要注意子方法抛出的异常,避免因子方法抛出异常,而导致父方法回滚。因为如果执行过程中发生 RuntimeException 异常和 Error 的话,那么 Spring 事务是会自动回滚的。
3.TransactionDefinition.PROPAGATION_NESTED
NESTED 也是常用的一个传播类型,该方法的特性与 REQUIRED 非常相似,其特性是:当前方法存在事务时,子方法加入在嵌套事务执行。当父方法事务回滚时,子方法事务也跟着回滚。当子方法事务发送回滚时,父事务是否回滚取决于是否捕捉了异常。如果捕捉了异常,那么就不回滚,否则回滚。
可以看到 NESTED 与 REQUIRED 的区别在于:父方法与子方法对于共用事务的描述是不一样的,REQUIRED 说的是共用同一个事务,而 NESTED 说的是在嵌套事务执行。这一个区别的具体体现是:在子方法事务发生异常回滚时,父方法有着不同的反应动作。
对于 REQUIRED 来说,无论父子方法哪个发生异常,全都会回滚。而 REQUIRED 则是:父方法发生异常回滚时,子方法事务会回滚。而子方法事务发送回滚时,父事务是否回滚取决于是否捕捉了异常。
如果当前存在事务,就在嵌套事务内执行;如果当前没有事务,就执行与TransactionDefinition.PROPAGATION_REQUIRED类似的操作。也就是说:
- 在外部方法开启事务的情况下,在内部开启一个新的事务,作为嵌套事务存在。
- 如果外部方法无事务,则单独开启一个事务,与 PROPAGATION_REQUIRED 类似。
这里还是简单举个例子:如果 bMethod() 回滚的话,aMethod()不会回滚(内部事务回滚,外部事务不回滚)。如果 aMethod() 回滚的话,bMethod()会回滚(外部事务回滚,内部事务也回滚)。
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
b.bMethod();
}
}
@Service
Class B {
@Transactional(propagation = Propagation.NESTED)
public void bMethod {
//do something
}
}
为了验证 NESTED 事务传播类型的特点,我们再来做几个测试。
首先,我们来验证一下:当父方法事务发生异常时,子方法事务是否会回滚?
@TransactionalpublicvoidmethodA() {
tableService.insertTableA(newTableEntity());
transactionServiceB.methodB();
thrownewRuntimeException();
}
@Transactional(propagation = Propagation.NESTED)
publicvoidmethodB() {
tableService.insertTableB(newTableEntity());
}
结果是:tablea 和 tableb 都没有插入数据,即:父子方法事务都回滚了。这说明父方法发送异常时,子方法事务会回滚。
接着,我们继续验证一下:当子方法事务发生异常时,如果父方法没有捕捉异常,父方法事务是否会回滚?
@TransactionalpublicvoidmethodA() {
tableService.insertTableA(newTableEntity());
transactionServiceB.methodB();
}
@Transactional(propagation = Propagation.NESTED)
publicvoidmethodB() {
tableService.insertTableB(newTableEntity());
thrownewRuntimeException();
}
结果是:tablea 和 tableb 都没有插入数据,即:父子方法事务都回滚了。这说明子方法发送异常回滚时,如果父方法没有捕捉异常(try...catch异常),那么父方法事务也会回滚。
最后,我们验证一下:当子方法事务发生异常时,如果父方法捕捉了异常,父方法事务是否会回滚?
@TransactionalpublicvoidmethodA() {
tableService.insertTableA(newTableEntity());
try{
transactionServiceB.methodB();
} catch(Exceptione) {
}
}
@Transactional(propagation = Propagation.NESTED)
publicvoidmethodB() {
tableService.insertTableB(newTableEntity());
thrownewRuntimeException();
}
结果是:tablea 插入了数据,tableb 没有插入数据,即:父方法事务没有回滚,子方法事务回滚了。这说明子方法发送异常回滚时,如果父方法捕捉了异常,那么父方法事务就不会回滚。
看到这里,相信大家已经对 REQUIRED、REQUIRES_NEW 和 NESTED 这三个最常用的事务传播类型有了深入的理解了。最后,让我们来总结一下:
事务传播类型 | 特性 |
REQUIRED | 当前方法存在事务时,子方法加入该事务。此时父子方法共用一个事务,无论父子方法哪个发生异常回滚,整个事务都回滚。即使父方法捕捉了异常,也是会回滚。而当前方法不存在事务时,子方法新建一个事务。 |
REQUIRES_NEW | 无论当前方法是否存在事务,子方法都新建一个事务。此时父子方法的事务时独立的,它们都不会相互影响。但父方法需要注意子方法抛出的异常,避免因子方法抛出异常,而导致父方法回滚。 |
NESTED | 当前方法存在事务时,子方法加入在嵌套事务执行。当父方法事务回滚时,子方法事务也跟着回滚。当子方法事务发送回滚时,父事务是否回滚取决于是否捕捉了异常。如果捕捉了异常,那么就不回滚,否则回滚。 |
4.TransactionDefinition.PROPAGATION_MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
这个使用的很少,就不举例子来说了。
若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚,这里不对照案例讲解了,使用的很少。
- TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
3.2 事务隔离级别
事务的隔离级别定义一个事务可能受其他并发务活动活动影响的程度,可以把事务的隔离级别想象为这个事务对于事物处理数据的自私程度。
在一个典型的应用程序中,多个事务同时运行,经常会为了完成他们的工作而操作同一个数据。并发虽然是必需的,但是会导致以下问题:
- 脏读(Dirty read)
脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的。
- 不可重复读(Nonrepeatable read)
不可重复读发生在一个事务执行相同的查询两次或两次以上,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据。不可重复读重点在修改。
- 幻读(Phantom reads)
幻读和不可重复读相似。当一个事务(T1)读取几行记录后,另一个并发事务(T2)插入了一些记录时,幻读就发生了。在后来的查询中,第一个事务(T1)就会发现一些原来没有的额外记录。幻读重点在新增或删除。
在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离经常涉及到锁定在数据库中的记录(甚至有时是锁表)。完全隔离要求事务相互等待来完成工作,会阻碍并发。因此,可以根据业务场景选择不同的隔离级别。
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
public interface TransactionDefinition {
......
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
......
}
和事务传播行为那块一样,为了方便使用,Spring 也相应地定义了一个枚举类:Isolation
public enum Isolation {
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
// 最低级别,只能保证不读取 物理上损害的数据,允许脏读
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
// 只能读到已经提交的数据
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
// 可重复读
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
// 串行化读,读写相互阻塞
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
private final int value;
Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
下面我依次对每一种事务隔离级别进行介绍:
- TransactionDefinition.ISOLATION_DEFAULT :使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别 Oracle 默认采用的 READ_COMMITTED 隔离级别.
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED :最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
- TransactionDefinition.ISOLATION_READ_COMMITTED : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
- TransactionDefinition.ISOLATION_REPEATABLE_READ : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- TransactionDefinition.ISOLATION_SERIALIZABLE : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务的隔离级别示例:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
读取未提交数据(会出现脏读, 不可重复读) 基本不使用
3.3 事务超时属性
为了使一个应用程序很好地执行,它的事务不能运行太长时间。因此,声明式事务的一个特性就是它的超时。
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒,默认值为-1,这表示事务的超时时间取决于底层事务系统或者没有超时时间。
假设事务的运行时间变得格外的长,由于事务可能涉及对数据库的锁定,所以长时间运行的事务会不必要地占用数据库资源。这时就可以声明一个事务在特定秒数后自动回滚,不必等它自己结束。
由于超时时钟在一个事务启动的时候开始的,因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、ROPAGATION_NESTED)的方法来说,声明事务超时才有意义。
事务的超时性示例:
@Transactional(timeout=30)
3.4 事务只读属性
如果一个事务只对数据库执行读操作,那么该数据库就可能利用那个事务的只读特性,采取某些优化措施。通过把一个事务声明为只读,可以给后端数据库一个机会来应用那些它认为合适的优化措施。由于只读的优化措施是在一个事务启动时由后端数据库实施的, 因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、 ROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义。
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface TransactionDefinition {
......
// 返回是否为只读事务,默认值为 false
boolean isReadOnly();
}
对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。
很多人就会疑问了,为什么我一个数据查询操作还要启用事务支持呢?
拿 MySQL 的 innodb 举例子,根据官网 MySQL :: MySQL 5.7 Reference Manual :: 14.7.2.2 autocommit, Commit, and Rollbackopen in new window描述:MySQL 默认对每一个新建立的连接都启用了autocommit模式。在该模式下,每一个发送到 MySQL 服务器的sql语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务,并开启一个新的事务。
但是,如果你给方法加上了Transactional注解的话,这个方法执行的所有sql会被放在一个事务中。如果声明了只读事务的话,数据库就会去优化它的执行,并不会带来其他的什么收益。
如果不加Transactional,每条sql会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值。
分享一下关于事务只读属性,其他人的解答:
- 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持 SQL 执行期间的读一致性;
- 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持
只读示例:
@Transactional(readOnly=true)
该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。
3.5 事务回滚规则
这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行时异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚,但是,在遇到检查型(Checked)异常何IO异常(IOException)时不会回滚。
例如下面的代码执行后,tablea 和 tableb 两个表格,都会插入一条数据,它们是不会回滚的:
@Transactional
public void methodA() throws Exception {
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB();
}
@Transactional
public void methodB() throws Exception {
tableService.insertTableB(new TableEntity());
// 非 RuntimeException
throw new Exception();
}
回滚示例:
指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)
指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。
如果你想要回滚你定义的特定的异常类型的话,可以通过注解设置:
@Transactional(rollbackFor= MyException.class)
也可以通过XML设置:
<tx:method name="update" propagation="REQUIRED"/>
但是比如设置了IO异常就回滚,如果在代码中try...catch处理了这个异常之后,就不会回滚了。
四、Spring事务使用方法论
看完了事务的传播类型,我们对 Spring 事务又有了深刻的理解。
看到这里,你应该也明白:使用事务,不再是简单地使用 @Transaction 注解就可以,还需要根据业务场景,选择合适的传播类型。那么我们再升华一下使用 Spring 事务的方法论。一般来说,使用 Spring 事务的步骤为:
- 根据业务场景,分析要达成的事务效果,确定使用的事务传播类型。
- 在 Service 层使用 @Transaction 注解,配置对应的 propogation 属性。(事务管理一般是对Service层使用的,所以以后编写Service层时一定不要忘了加事务管理,事务管理是系统开发的最基本素质,如果一个程序员在开发的时候不去对Service层做事务管理,那么这个程序员就该被开除了)
下次遇到要使用事务的情况,记得按照这样的步骤去做哦~
@Transactional 介绍
@Transactional 注解 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该注解来覆盖类级别的定义。
虽然@Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。