spring支持两种类型的事务管理:
- 编程式事务管理:写在代码里
- PlatformTransactionManager
- TransactionTemplate
- 声明式事务管理:将事务管理从业务代码中分离。
- <tx:advice>标签
- @Transactional
事务的类型
- 数据库分为本地事务跟全局事务
- 本地事务:普通事务,独立一个数据库,能保证在该数据库上操作的ACID
- 分布式事务:设计两个或多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成),分布式事务旨在保证这些本地事务的所有操作的ACID,使事务可以跨越多台数据库
- java事务类型分为JDBC事务和JTA事务
- JDBC事务:即为上面说的数据库事务中的本地事务,通过connection对象控制管理
- JTA事务:
- 按是否通过编程分为声明式事务和编程式事务
- 声明式事务:通过XML配置或者注解实现
- 编程式事务:通过编程代码在业务逻辑时需要时自行实现,粒度更小
TransactionDefinition接口
五个隔离级别
- Transaction.ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,MySQL默认采用的是REPEATABLE_READ隔离级别,Oracle默认采用的是READ_COMMIT级别
- Transaction.ISOLATION_READ_UNCOMMIT:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
- Transaction.ISOLATION_READ_COMMIT:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
- Transaction.ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务修改,可以阻止脏读或不可重复读,但幻读仍有可能发生
- Transaction.ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。
事务传播行为(为了解决业务层方法之间互相调用的事务问题)
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。在TransactionDefinition自定义中包括了下面几个表示传播行为的常量
支持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;
如果当前没有事务,则创建一个新的事务 - TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
- TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
不支持当前事务的情况: - TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起
- TransactionDefinition.PROPAGATION_NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常
其他情况 - TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则创建一个新事务(后一情况与TransactionDefinition.PROPAGATION_REQUIRED一直)
前六种事务传播行为是spring从EJB引入的,而PROPAGATION_NESTED是spring所特有的。
内嵌事务并不是一个独立的事务,它依赖于外部事务存在,只有通过外部事务的提交,才能引起内部事务的提交,内嵌的子事务不能单独提交,外部事务的回滚也会导致嵌套子事务的回滚
事务超时属性(一个事务允许执行的最长时间)
指一个事务所允许执行的最长时间,如果
超过该时间限制但事务还没完成,则自动回滚事务。在TransactionDefinition中以int的值来表示超时时间,单位是秒
事务只读属性(对事务资源是否执行只读操作)
指事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、JMS资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在TransactionDefinition中以boolean类型来表示该事务是否只读
回滚规则(定义事务回顾规则)
这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行时异常时才会回滚,而遇到检查型异常时不会回滚。但是可以声明事务在遇到特定的检查型异常时也回滚。
Spring事务管理接口介绍
- PlatformTransactionManager:(平台)事务管理器
- TransactionDefinition:事务定义信息(上述隔离级别、传播行为、超时、只读、回滚)
- TransactionStatus:事务运行状态
PlatformTransactionManager
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
Spring框架支持事务管理的核心就是事务管理器抽象,对于不同的数据访问框架通过实现策略接口PlatformTransactionManager,从而能支持多种数据访问框架的事务。
PlatformTransactionManager接口定义了三大方法
Public interface PlatformTransactionManager()...{
// Return a currently active transaction or create a new one, according to the specified propagation behavior(根据指定的传播行为,返回当前活动的事务或创建一个新事务。)
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// Commit the given transaction, with regard to its status(使用事务目前的状态提交事务)
Void commit(TransactionStatus status) throws TransactionException;
// Perform a rollback of the given transaction(对执行的事务进行回滚)
Void rollback(TransactionStatus status) throws TransactionException;
}
}
刚刚也说了Spring中PlatformTransactionManager根据不同持久层框架所对应的接口实现类,几个比较常见的如下图所示
编程式事务管理
PlatformTransactionManager
首先配置事务管理器
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${db.jdbcUrl}" />
<property name="user" value="${user}" />
<property name="password" value="${password}" />
<property name="driverClass" value="${db.driverClass}" />
<!--连接池中保留的最小连接数。 -->
<property name="minPoolSize">
<value>5</value>
</property>
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize">
<value>30</value>
</property>
<!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
<property name="initialPoolSize">
<value>10</value>
</property>
<!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime">
<value>60</value>
</property>
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
<property name="acquireIncrement">
<value>5</value>
</property>
<!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。 如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 -->
<property name="maxStatements">
<value>0</value>
</property>
<!--每60秒检查所有连接池中的空闲连接。Default: 0 -->
<property name="idleConnectionTestPeriod">
<value>60</value>
</property>
<!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 -->
<property name="acquireRetryAttempts">
<value>30</value>
</property>
<!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效 保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。Default: false -->
<property name="breakAfterAcquireFailure">
<value>true</value>
</property>
<!--因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的 时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable等方法来提升连接测试的性能。Default: false -->
<property name="testConnectionOnCheckout">
<value>false</value>
</property>
</bean>
<!--DataSourceTransactionManager位于org.springframework.jdbc.datasource包下,数据源事务管理类,提供对单个javax.sql.DataSource数据源的事务管理,主要用于JDBC,Mybatis框架事务管理。 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring-public.xml" })
public class test {
@Resource
private PlatformTransactionManager txManager;
@Resource
private DataSource dataSource;
private static JdbcTemplate jdbcTemplate;
Logger logger=Logger.getLogger(test.class);
private static final String INSERT_SQL = "insert into testtranstation(sd) values(?)";
private static final String COUNT_SQL = "select count(*) from testtranstation";
@Test
public void testdelivery(){
//定义事务隔离级别,传播行为,
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
//事务状态类,通过PlatformTransactionManager的getTransaction方法根据事务定义获取;获取事务状态后,Spring根据传播行为来决定如何开启事务
TransactionStatus status = txManager.getTransaction(def);
jdbcTemplate = new JdbcTemplate(dataSource);
int i = jdbcTemplate.queryForInt(COUNT_SQL);
System.out.println("表中记录总数:"+i);
try {
jdbcTemplate.update(INSERT_SQL, "1");
txManager.commit(status); //提交status中绑定的事务
} catch (RuntimeException e) {
txManager.rollback(status); //回滚
}
i = jdbcTemplate.queryForInt(COUNT_SQL);
System.out.println("表中记录总数:"+i);
}
}
TransactionTemplate
该类继承接口DefaultTransactionDefinition,用于简化事务管理,事务管理由模版类指定,主要通过TransactionCallback回调接口或TransactionCallbackWithoutResult回调接口指定,通过调用模版类的参数类型为TransactionCallback或TransactionCallbackWithoutResult的execute方法来自动享受事务管理
TransactionTemplate模板类使用的回调接口:
- TransactionCallback:通过实现该接口的“T doInTransaction(TransactionStatus status) ”方法来定义需要事务管理的操作代码;
- TransactionCallbackWithoutResult:继承TransactionCallback接口,提供“void doInTransactionWithoutResult(TransactionStatus status)”便利接口用于方便那些不需要返回值的事务操作代码。
@Test
public void testTransactionTemplate(){
jdbcTemplate = new JdbcTemplate(dataSource);
int i = jdbcTemplate.queryForInt(COUNT_SQL);
System.out.println("表中记录总数:"+i);
//构造函数初始化TransactionTemplate
TransactionTemplate template = new TransactionTemplate(txManager);
template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
//重写execute方法实现事务管理
template.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcTemplate.update(INSERT_SQL, "饿死"); //字段sd为int型,所以插入肯定失败报异常,自动回滚,代表TransactionTemplate自动管理事务
}}
);
i = jdbcTemplate.queryForInt(COUNT_SQL);
System.out.println("表中记录总数:"+i);
}
声明式事务管理
编程式事务管理每次实现都要单独实现,代码冗余度高,而声明式事务属于无侵入式,不影响业务逻辑的实现。声明式事务主要有两种使用<tx:advice>标签或@Transactional注解
<tx:advice>标签
配置文件如下
<!--
<tx:advice>定义事务通知,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过<tx:attributes>指定具体需要拦截的方法
<tx:method>拦截方法,其中参数有:
name:方法名称,将匹配的方法注入事务管理,可用通配符
propagation:事务传播行为,
isolation:事务隔离级别定义;默认为“DEFAULT”
timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
read-only:事务只读设置,默认为false,表示不是只读;
rollback-for:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚;
no-rollback-for:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;
-->
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 拦截save开头的方法,事务传播行为为:REQUIRED:必须要有事务, 如果没有就在上下文创建一个 -->
<tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="" read-only="false" no-rollback-for="" rollback-for=""/>
<!-- 支持,如果有就有,没有就没有 -->
<tx:method name="*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!-- 定义切入点,expression为切人点表达式,如下是指定impl包下的所有方法,具体以自身实际要求自定义 -->
<aop:config>
<aop:pointcut expression="execution(* com.kaizhi.*.service.impl.*.*(..))" id="pointcut"/>
<!--<aop:advisor>定义切入点,与通知,把tx与aop的配置关联,才是完整的声明事务配置 -->
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>
注意事项:
- 事务回滚异常只能为RuntimeException,而Checked Exception异常不回滚,捕获异常不抛出也不会回滚,但可以强制事务回滚(配置事务回滚规则)
- 解决“自我调节”而导致的不能设置正确的事务属性问题,可参考
http://www.iteye.com/topic/1122740
@Transactional
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="txManager"/> //开启事务注解
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED),具体参数跟上面tx:method中一样
Spring提供的@Transaction注解事务管理,内部同样是利用环绕通知TransactionInterceptor实现事务的开启及关闭。
使用@Transactional注意点:
- 如果在接口/实现类或方法上都指定了@Transactional竹节,则优先级顺序为方法>实现类>接口
- 建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是因为如果使用CGLIB代理就会失效,因为CGLIB是基于类的代理(继承),而接口上的@Transactional注解是不能继承的;JDK代理则没有问题
编写自定义注解时未写@Inherited的运行结果: | 编写自定义注解时写了@Inherited的运行结果: | |
---|---|---|
子类的类上能否继承到父类的类上的注解? | 否 | 能 |
子类方法,实现了父类上的抽象方法,这个方法能否继承到注解? | 否 | 否 |
子类方法,继承了父类上的方法,这个方法能否继承到注解? | 能 | 能 |
子类方法,覆盖了父类上的方法,这个方法能否继承到注解? | 否 | 否 |
我们知道在编写自定义注解时,可以通过指定@Inherited注解,指明自定义注解是否可以被继承。通过测试结果来看,@Inherited 只是可控制 对类名上注解是否可以被继承。不能控制方法上的注解是否可以被继承。
只能继承父类(普通类,抽象类)的注解, 接口的都不行
并且只能父类的class级别的注解才能继承:@Target(value=
ElementType.Type,
ElementType.TYPE_PARAMETER )
参考:
spring事务隔离级别、传播行为以及spring+mybatis+atomikos实现分布式事务管理
Spring事务管理实现方式之编程式事务与声明式事务详解
Spring事务配置的五种方式 巨全!不看后悔,一看必懂!
一些问题
- 在 XML 里面配置了一个 SqlSessionFactoryBean 后,其究竟做了什么?
- 在 XML 里面配置了一个 MapperScannerConfigurer 后,其究竟做了什么?
- 在执行 Mapper 接口的查询方法后,发生了什么?
- tx:advice/、aop:config 标签如何创建事务切面的?
- 标签添加后为何就可以使用注解式事务了?
- 为什么会报 Transaction rolled back because it has been marked as rollback-only 异常?
- Transactional 注解是否可以加在 private、protected 方法上?
- 事务的传播属性到底有什么用,嵌套事务到底又是怎么一回事?
- 为什么抛出了异常,事务却没有回滚?
- Spring 事务是如何保证线程安全的?