Spring 数据库事务管理
Spring 数据库事务管理器的设计
Spring中数据库事务是通过PlatformTransactionManager进行管理的,像jdbcTemplate它自身是不能支持事务的,而能够支持事务的是 org.springframework.transaction.support.TransactionTemplate模版,它是Spring所提供的事务管理器的模版。
- 事务的创建、提交和回滚是通过PlatformTransactionManager接口来完成的。
- 当事务产生异常时会回滚事务,在默认的实现中所有的异常都会回滚。我们可以通过配置去修改在某些异常发生时回滚或者不回滚事务。
- 当无异常时,会提交事务。
同时事务管理器也有多个,常用的有DataSourceTransactionManager,它继承抽象事务管理器AbstractPlatformTransactionManager,而AbstractPlatformTransactionManager又实现了PlatformTransactionManager。
其中PlatformTransactionManager接口中就三个方法:
- 获取事务的状态
TransactionSattus getTransaction(TransactionDefinition definition) throws TransactionException; - 提交事务
void commit(TransactionStatus status) throws TransactionException; - 回滚事务
void rollback(TransactionStatus status) throws TransactionException;
配置事务管理器
MyBatis框架,用的最多的事务管理器是DataSourceTransactionManager
(org.springframework.jdbc.datasource.DataSourceTransactionManager),如果使用的持久框架Hibernate,那么你就要用到spring-orm包org.springframework.orm.hibernate4.HibernateTransactionManager了。
在appliationContext.xml中配置如下:
- xsi:schemaLocation头部标签里面加上相关的 tx连接
- dataSource数据库连接池相关配置
- 配置数据源事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
解释:
- 先引入XML的命名空间,然后定义了数据库连接池,于是使用了DataSourceTransactionManager去定义数据库事务管理器,并且注入了数据库连接池。这样Spring就知道你已经将数据库事务委托给事务管理器 transactionManager管理了。
- 通过了解jdbcTemplate,数据库资源的产出和释放如果没有委托给数据库管理器,那么就由jdbcTemplate管理,但是委托给事务管理器管理后,jdbcTemplate就不用了。
在Spring中可以使用声明式事务或者编程式事务,编程式事务这里不再描述。
声明式事务又可以分为XML配置和注解事务,但XML方式也已经不常用了,目前主流的是用注解@Transaction。
声明式事务
编程式事务是一种约定型的事务,在大部分情况下,当使用数据库事务时,大部分的场景是在代码中发生了异常时,需要回滚事务,而不发生异常时则是提交事务,从而保证数据库的一致性。声明式事务就是,当你的业务方法不发生异常(或者发生异常,但该异常也被配置信息允许提交事务)时,Spring就会让事务管理器提交事务,而发生异常(并且该异常不被你的配置信息所允许提交事务)时,则让事务管理器回滚事务。
Transaction 的配置
配置项 | 含义 | 备注 |
---|---|---|
value | 定义事务管理器 | 它是Spring Ioc里的一个Bean id,这个Bean需要实现接口 |
transactionManager | 同上 | 同上 |
isolation | 隔离级别 | 这是一个数据库在多个事务同时存在的概念,默认取数据库默认隔离级别 |
propagation | 传播行为 | 传播行为是方法之间调用的问题。默认值Propagation.REQUIRED |
timeout | 超时时间 | 单位为秒,当超时时,会引发异常,默认会导致事务回滚 |
readOnly | 是否开启只读事务 | 默认值 false |
rollbackFor | 回滚事务的异常类定义 | 也就是只有当方法产生所定义异常时,才回滚事务,否则就提交事务 |
rollbackForClassName | 回滚事务的异常类名定义 | 同rollbackFor,只是使用类名称定义 |
noRollbackFor | 当产生哪些异常不回滚事务 | 当产生所定义的异常时,Spring将继续提交事务 |
noRollbackForClassName | 同noRollbackFor | 同noRollback,只是使用类的名称定义 |
备注:
这些属性将会被Spring放到事务定义类TransactionFefinition中,事务声明器的配置内容也是以这些为主了。
注意,使用声明式事务需要配置注解驱动,只需要在上述的applicationContext.xml基础上加上:
<!-- 启用对事务注解的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
就可以使用@Transaction配置事务了。
声明式事务的约定流程
@Transaction注解可以使用在方法或者类上面,在Spring Ioc容器初始化时,Spring会读入这个注解或者XML配置的事务信息,并且保存到一个事务定义类里面(TransactionDefinition接口的子类),以备将来使用。当运行时会让Spring拦截注解标注的某一个方法或者类的所有方法。
流程:
1、首先,Spring通过事务管理器(PlatformTransactionManager的子类)创建事务,与此同时会把事务定义中的隔离级别、超时时间等属性
根据配置内容往事务上设置。
2、然后,启动开发者提供的业务代码,我们知道Spring会通过反射的方式调度开发者的业务代码,但是反射的结果可能是正常返回或者产生异常返回,
那么给他的约定是只要发生异常,并且符合事务定义类回滚条件的,Spring就会将数据库事务回滚,否则将数据库事务提交。
选择隔离级别和传播行为
其出发点在于两点:性能和数据一致性
选择隔离级别
- 在系统应用中,不但要考虑数据库的数据的一致性,而且要考虑系统的性能。
从脏读到序列化,系统性能直线下降。因此设置搞的级别,比如序列化,会严重压制并发,从而引发大洋的线程挂起,知道获得锁才能进一步操作,而回复时有需要大量的等待时间。在大部分场景下,企业会选择 读/写提交的方式设置事务。这样既有助于提高并发,又压制了脏读,但是对于数据一致性问题并没有很好的解决。 - 总之,隔离级别需要根据并发的大小和性能来做出决定,对于并发不大又要保证数据安全性的可以使用序列化的隔离界别,这样能够保证数据库在多事务环境中的一致性。
//设置方法为 读/写提交的隔离级别
@Override
@Transactional(propagation=Propagation.REQUIRED,
isolation=Isolation.READ_COMMITTED)
public void saveAnalyst(Analyst analyst) {
analystMapper.insertSelective(analyst);
}
//设置方法为序列化的隔离级别
@Override
@Transactional(propagation=Propagation.REQUIRED,
isolation=Isolation.SERIALIZABLE)
public void saveAnalyst(Analyst analyst) {
analystMapper.insertSelective(analyst);
}
@Transaction注解隔离级别默认为Isolcation.DEFAULT,其含义是默认的,随着数据库的默认值的变化而变化。因为数据库不同,隔离级别的支持也不一样。比如,MySQL可以支持4种隔离级别,而默认的是可重复读的隔离级别。而Oracle只能支持 读/写提交和序列化两种隔离级别,默认为 读/写提交。
传播行为
传播行为是指方法之间的调用事务策略的问题。
一个方法调度另外一个方法时,可以对事务的特性进行传播配置,我们称为传播行为。
在Spring中传播行为的类型是通过一个枚举类型去定义的,这个枚举是org.springframework.transaction.annotation.Propagation
传播行为 | 含义 | 备注 |
---|---|---|
REQUIRED | 当方法调用时,如果不存在当前事务,那么就创建 事务;如果之前的方法已经存在事务了,那么就沿用之前的事务。 | 这是Spring默认的传播行为 |
SUPPROTS | 当方法调用时,如果不存在当前事务,那么不启用事务;如果存在当前事务,那么就沿用当前事务。 | - |
MANDATORY | 方法必须要在事务内运行 | 如果不存在当前事务,那么就抛出异常 |
REQUIRES_NEW | 无论是否存在当前事务,方法都会在新的事务中运行 | 事务管理器会打开新的事务运行该方法 |
NOT_SUPPORTED | 不支持事务,如果不存在当前事务也不会创建事务;如果存在当前事务,则挂起它,直到该方法结束后才恢复当前事务 | 适用于那些不需要事务的SQL |
NEVER | 不支持事务,只有在没有事务的环境中才能运行它 | 如果方法存在当前事务,则抛出异常 |
NESTED | 嵌套事务,也就是调用方法如果抛出异常只会回滚自己内部执行的SQL,而不回滚主方法的SQL。 | 它的实现存在两种情况,如果当前数据库支持存点,那么它就会在当前事务上使用保存点技术;如果发生异常则将异常方法内部执行的SQL回滚到保存点上,而不是全部回滚,否则就等同于REQUIRES_NEW创建新的事务运行方法代码 |
@Transaction自调用失效问题
有时候配置了注解@Transaction,但是它会失效,这里需要注意一些细节问题。
注解@Transaction的底层实现是Spring AOP技术,而Spring AOP技术使用的是动态代理。这意味着对于静态(static)方法和非public方法,注解@Transaction是失效的。
自调用失效问题。就是一个类的一个方法调用自身另外一个方法的过程。
典型的事务注解用法错误问题
- 错误使用Service,在Controller的一个方法中同时调用多次相同方法
原因:会出现一个业务在两个事务中完成 - 过长时间占用事务
把一些跟数据库事务无关的业务拿出来,不放到事务中 - 错误捕捉异常
有时候我们会在service业务中加 try…catch…语句,所以Spring在数据库事务所约定的流程中再也得不到任何异常信息了,此时Spring就会提交事务。
所以我们要自行抛出异常,让Spring事务管理流程获取异常,进行事务管理:
throw new RuntimeException(ex)
上一篇: python读取xml文件
下一篇: webAPI案例之十分钟搞懂js原生轮播