spring事务配置及用法
上一篇文章数据库事务那些事儿介绍了数据库事务的传播机制和隔离级别的概念和应用,如果对此还不熟悉的小伙伴可以点击这里查看。 本文是在数据库事务的基础上讲讲spring事务的配置和用法,相关测试代码已上传至github:https://github.com/star9500/spring-tx-demo
spring事务
-
在spring事务出现之前,基本都是在代码中获取数据库连接、开启事务、CRUD操作、 事务提交或回滚、关闭连接,所有的CRUD操作按照这样的流程封装成模板使用,核心代码大致如下:
//获取连接
Connection conn = DriverManager.getConnection();
try {
//将自动提交设置为false
conn.setAutoCommit(false);
//CRUD操作
insert(...);
update(...);
...
//当操作成功后手动提交
conn.commit();
} catch (Exception e) {
//一旦其中一个操作出错都将回滚,所有操作都不成功
conn.rollback();
e.printStackTrace();
} finally {
//重点:关闭连接
conn.colse();
}
-
传统的jdbc事务控制代码与业务逻辑代码耦合性高,开发人员需要手工控制事务和业务代码开发,效率低并且风险高,而使用spring之后,开发人员无需去关注获取数据库连接、开启事务、 事务提交或回滚、关闭连接这样的操作,把更多的时间和精力放在业务逻辑处理上。
-
实际spring并不直接管理事务,而是提供了多种事务管理器,提供各数据库统一的接口,将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
-
spring事务的特性如下图所示
-
spring事务核心接口
public interface PlatformTransactionManager {
//根据指定的传播行为返回当前活动的事务或创建一个新的事务
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//提交事务
void commit(TransactionStatus var1) throws TransactionException;
//回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
由此可以看到PlatformTransactionManager这个类作为事务管理器的核心接口,只提供获取事务、提交事务、回滚事务的接口,支持事务的框架需要实现这些接口。
-
org.springframework.transaction.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;
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;
//获取事务的传播行为
int getPropagationBehavior();
//获取隔离级别
int getIsolationLevel();
//获取超时时间
int getTimeout();
//是否只读
boolean isReadOnly();
//返回事务名称
@Nullable
String getName();
}
TransactionDefinition接口中定义了事务传播行为和隔离级别的常量和获取方法
-
org.springframework.transaction.TransactionStatus是调用PlatformTransactionManager.getTransaction(@Nullable TransactionDefinition var1)返回的具体事务。
public interface TransactionStatus extends SavepointManager, Flushable {
//是否新事务
boolean isNewTransaction();
//是否有恢复点
boolean hasSavepoint();
//设置为只回滚
void setRollbackOnly();
//是否为只回滚
boolean isRollbackOnly();
//刷新修改的数据持久化到数据库
void flush();
//事务是否已完成
boolean isCompleted();
}
以上就是事务管理的核心接口类,具体的实现需要hibernate、jta等持久化框架实现,不过spring已经支持了对这些框架的整合,通过简单的事务配置即可完成事务管理,并且spring自身提供了jdbc事务的管理。
-
spring提供的常用内置事务管理器
-
DataSourceTransactionManager:位于org.springframework.jdbc.datasource包中,数据源事务管理器,提供对单个javax.sql.DataSource事务管理,用于Spring JDBC抽象框架、iBATIS或MyBatis框架的事务管理。
-
JdoTransactionManager:位于org.springframework.orm.jdo包中,提供对单个javax.jdo.PersistenceManagerFactory事务管理,用于集成JDO框架时的事务管理。
-
JpaTransactionManager:位于org.springframework.orm.jpa包中,提供对单个javax.persistence.EntityManagerFactory事务支持,用于集成JPA实现框架时的事务管理。
-
HibernateTransactionManager:位于org.springframework.orm.hibernate3包中,提供对单个org.hibernate.SessionFactory事务支持,用于集成Hibernate框架时的事务管理;该事务管理器只支持Hibernate3+版本,且Spring3.0+版本只支持Hibernate3.2+版本;
-
JtaTransactionManager:位于org.springframework.transaction.jta包中,提供对分布式事务管理的支持,并将事务管理委托给Java EE应用服务器事务管理器;
spring事务传播行为
当一个方法的执行被纳入到spring事务管理中时,需要指明事务应怎样传播,例如是忽略事务、还是继续在现有事务中运行、还是新开启一个事务等,这将影响事务的回滚和提交。 spring事务定义了7中传播行为:
传播行为 | 含义 |
---|---|
PROPAGATION_MANDATORY | 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 |
PROPAGATION_NESTED | 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样 |
PROPAGATION_NEVER | 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常 |
PROPAGATIONNOTSUPPORTED | 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起 |
PROPAGATION_REQUIRED | 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务 |
PROPAGATIONREQUIREDNEW | 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起 |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行 |
其中PROPAGATION_REQUIRED为默认的传播属性
spring事务隔离级别
spring的隔离级别一定是要数据库锁机制支持的,如果数据库不支持某种隔离级别,那么spring设置了也无效。
隔离级别 | 含义 |
---|---|
ISOLATION_DEFAULT | 默认的隔离级别,使用数据库默认的事务隔离级别 |
ISOLATION_READ_UNCOMMITTED | 读未提交,最低的隔离级别,允许另外一个事务可以看到这个事务未提交的数据,这种隔离级别会产生脏读,不可重复读和幻读 |
ISOLATION_READ_COMMITTED | 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据,解决了脏读,但是会产生不可重复读和幻读(oracle默认的隔离级别) |
ISOLATION_REPEATABLE_READ | 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读(mysql默认的隔离级别) |
ISOLATION_SERIALIZABLE | 代价最高、效率最低但是最可靠的事务隔离级别。事务被处理为顺序执行,解决了所有的事务问题 |
spring事务超时
spring的事务超时不是某个方法执行完成所耗费的时间,而是每执行一次sql就检查一次时间,两次sql执行期间的其他非数据库操作不计入。 spring事务超时 = 事务开始 + 最后一个数据库操作的执行时间(或超时时间)。 例如:如果配置的事务超时时间为3分钟,情况1:在一个事务方法内顺序的有两个sql执行和一个耗时的非数据库操作,如果两个sql执行时间没有超过3分钟,但是最后一个非数据库操作耗时了5分钟(比如http请求或者线程sleep)那么这个事务会提交,不会回滚。 情况2:在一个事务方法内顺序的有一个sql、一个耗时的非数据库操作、另外一个sql,如果两个sql总执行时间没有超过3分钟,但是非数据库操作耗时了5分钟(比如http请求或者线程sleep)那么这个事务会回滚,因为事务开始时到最后一个sql的执行时间和检查时间超过了设置的超时时间,所以事务回滚。
spring事务回滚规则
默认情况下,事务只有在遇到运行期异常(RuntimeException类或其子类)时才会回滚,而在遇到检查型异常(比如IOException,可被try…catch或throw)时不会回滚。 但是也可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。另外也可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。 注意以下几点:
-
如果所有异常事务均回滚,可以将spring默认的回滚时的异常修改为Exception
-
只有非只读事务才能回滚的,只读事务是不会回滚的
-
如果被try…catch了,catch中没有抛出RuntimeException异常,事务不回滚,抛出RuntimeException异常事务才会回滚
-
可以申明事务在遇到特定的检查型异常时回滚,需要配置相应的异常类型,使用rollbackFor或者rollbackForClassName。
-
可以申明事务遇到特定的异常不回滚,使用noRollbackForClassName。
spring事务是否只读
“只读事务”并不是一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。 如果事务设置了只读属性,如果在方法里包含了新增、修改、删除等非查询数据操作,方法会抛异常,spring默认的readOnly为false。
spring事务配置方式
基础配置如下:
<!-- 自动扫描,将带有注解的类纳入spring容器管理 -->
<context:component-scan base-package="com.star95.study.spring"/>
<!-- jdbc配置文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath*:jdbc.properties</value>
</list>
</property>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="username" value="${jdbc.username}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="password" value="${jdbc.password}"/>
<property name="initialSize" value="2"/>
<property name="maxActive" value="5"/>
<property name="maxWait" value="30000"/>
</bean>
<!-- 配置事务管理器 ,管理器需要事务,事务从Connection获得,连接从连接池DataSource获得 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
<property name="defaultTimeout" value="3000"></property>
<!--<property name="nestedTransactionAllowed" value="true"></property>-->
</bean>
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource"/>
</bean>
以下5中配置依赖于基础配置:
-
每个Bean都有一个代理(基于TransactionProxyFactoryBean代理)
<bean id="transactionWithSingleTxProxyBean"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置事务管理器 -->
<property name="transactionManager" ref="txManager"/>
<property name="target" ref="transactionWithSingleTxProxy"/>
<property name="proxyInterfaces" value="com.star95.study.spring.service.UserBalanceService"/>
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
-
所有Bean共享一个代理基类(基于TransactionProxyFactoryBean代理)
<bean id="transactionBase"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
lazy-init="true" abstract="true">
<!-- 配置事务管理器 -->
<property name="transactionManager" ref="txManager"/>
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="transactionWithSharedTxProxyBean" parent="transactionBase">
<property name="target" ref="transactionWithSharedTxProxy"/>
</bean>
-
使用拦截器
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="txManager"/>
<!-- 配置事务属性 -->
<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>*InterceptorService</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
-
使用tx标签配置的拦截器(AOP)
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!--
propagation:
REQUIRED
REQUIRES_NEW
SUPPORTS
NOT_SUPPORTED
MANDATORY
NESTED
NEVER
isolation:
DEFAULT
READ_UNCOMMITTED
READ_COMMITTED
REPEATABLE_READ
SERIALIZABLE
-->
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor advice-ref="txAdvice"
pointcut="execution(* com.star95.study.spring.service.impl.TransactionWithAopImpl.*(..))"/>
</aop:config>
-
全注解(方法或类加@Transactional)
<!--全注解方式需要在spring的配置中启用bean扫描和在对应的类或方法加入@Transactional,spring配置文件中加入事务管理注解驱动即可-->
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
spring事务用法
spring事务的相关配置、传播行为、隔离级别、超时、回滚、只读这些属性均已通过testcase测试,由于测试用例及代码较多,限于篇幅限制,代码已上传至github:https://github.com/star9500/spring-tx-demo,如有需求,欢迎fork,欢迎issues。
注意:本项目里用的是spring5.1.3.RELEASE版本
-
测试用例使用的数据库表结构sql如下:
CREATE TABLE `user_balance` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(64) DEFAULT NULL,
`balance` decimal(18,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-
测试用例说明 测试类里注释比较全,这里只介绍测试用例类的用户,使用时请阅读代码注释
-
事务的几种配置方式的使用见测试用例类:com.star95.study.spring.UserBalanceTransactionTest
-
事务的传播特性测试用例见测试用例类:com.star95.study.spring.PropagationTest
-
隔离级别测试用例类:com.star95.study.spring.IsolationTest
-
事务只读测试用例类:com.star95.study.spring.ReadOnlyTest
-
事务回滚测试用例类:com.star95.study.spring.RollbackTest
-
事务超时测试用例类:com.star95.study.spring.TransactionTimeoutTest
原文始发于微信公众号(小新成长之路):spring事务配置及用法
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/238695.html