spring事务管理
在了解事务的特点之后,会发现事务其实学问很多,但是疑惑的就是一般使用springboot在开发项目的使用,我们几乎自己很少写事务相关的东西,这是因为spring帮我们做了事务的管理,但是spring的事务管理可以帮助我们做到最基础的事务管理,如果我们的业务越来越复杂,出现多数据源的场景的时候或者出现需要精细化事务管理的时候,我们要是要自己进行处理。
其实spring在处理事务的时候采用了事务传播行为来管理事务,关于事务本身的介绍可以参考事务隔离级别和事务并发问题介绍。接下来就主要看一下spring的事务传播行为,事务传播行为是spring为了管理事务提出的概念,并非是数据库的事务概念这点需要清楚。事务传播行为的解释示例。
spring事务传播行为
传播行为 | 描述 |
---|---|
PROPAGATION_REQUIRED | 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。默认值 |
PROPAGATION_REQUIRES_NEW | 创建一个新的事务,如果当前存在事务,则把当前事务挂起 |
PROPAGATION_SUPPORTS | 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式运行,如果当前存在事务,则把当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式运行,如果当前存在事务,则抛出异常 |
PROPAGATION_MANDATORY | 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED |
spring事务
在早期使用springmvc的时候,也是采用AOP的方式进行事务处理,但都是基于xml的方式,这种方式需要太多配置工作量大,并且在springboot应用之后,可以更好的使用注解来处理,因此xml的方式就不在使用了。 xml配置代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
">
<!-- 加载资源文件 其中包含变量信息,必须在Spring配置文件的最前面加载,即第一个加载-->
<context:property-placeholder location="classpath:mysql.properties" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan">
<list>
<value>com.troyqu.entity</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
</props>
</property>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.user}" />
<property name="password" value="${jdbc.pass}" />
</bean>
<!-- 配置Hibernate事务管理器 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!--配置事务管理器为transactionManager -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="get*" propagation="REQUIRED" />
<tx:method name="*" read-only="true" />
</tx:attributes>
</tx:advice>
<aop:config expose-proxy="true">
<!-- 配置切点 -->
<aop:pointcut id="txPointcut" expression="execution(* com.troyqu.service..*.*(..))" />
<!-- 配置切点和advice -->
<aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice"/>
</aop:config>
</beans>
基于aop的spring事务
在springboot中我们也可以使用自定义注解结合AOP来实现事务管理,AOP的方式会比XML的方式更加简单和节省代码量,这里的AOP配置不是指使用@Transactional的声明式注解。这里指的是结合@Aspect的AOP方式。我们可以看一下关键代码。
数据库连接类MyConnection用来管理数据库连接,使用ThreadLocal来管理数据库连接同一个线程中可以不需要创建多个数据库连接。
package com.troyqu.aop;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
@Component
public class MyConnection {
@Autowired
DataSourceTransactionManager dataSourceTransactionManager;
ThreadLocal<Connection> connection = new NamedThreadLocal<>("MyConnection");
public Connection getConnection(){
Connection conn = connection.get();
if (conn != null) {
return conn;
}
try {
conn = dataSourceTransactionManager.getDataSource().getConnection();
connection.set(conn);
return conn;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
public void closeConnection(){
Connection conn = connection.get();
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
connection.remove();
}
}
connection.remove();
}
}
自定义注解作为事务声明切点
public @interface MyTransactional {
}
创建事务处理AOP类TransactionAop,用来处理标注了@MyTransactional的方法
package com.troyqu.aop;
import lombok.val;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
@Aspect
@Component
public class TransactionAop {
@Autowired
MyConnection myConnection;
@Around("@annotation(com.anchnet.smartops.cmp.aop.MyTransactional)")
public Object txAround(ProceedingJoinPoint pjp) throws Throwable {
Connection conn = myConnection.getConnection();
Object output = null;
try {
beginTransaction(conn);
output = pjp.proceed();
commitTransaction(conn);
} catch (Throwable e) {
rollBackTransaction(conn);
System.out.println("Error happened inside TX");
e.printStackTrace();
}finally {
myConnection.closeConnection();
return output;
}
}
private void beginTransaction(Connection conn){
try {
conn.setAutoCommit(false);
} catch (SQLException e) {
System.out.println("Start TX failed");
e.printStackTrace();
}
}
private void commitTransaction(Connection conn){
try {
conn.commit();
} catch (SQLException e) {
System.out.println("Commit failed");
e.printStackTrace();
myConnection.closeConnection();
}
}
private void rollBackTransaction(Connection conn){
try {
conn.rollback();
} catch (SQLException e) {
System.out.println("Rollback failed");
e.printStackTrace();
myConnection.closeConnection();
}
}
}
声明式事务
目前可以使用更为简单的声明式事务来进行事务处理,只要开启了@EnableTransactionManagement 就可以使用声明式事务,spring-boot-autoconfigure中会引入相关Bean我们只需要使用就可以,开启事务支持只需要在对应的方法和类名上添加@Transactional就可以使用。@Transactional可以设置很多属性,包括事务管理器(transactionManager),事务传播级别(propagation)等。当我们使用单数据元的时候事务管理器可以选择默认的事务管理器,但是如果我们使用多数据源的时候,就需要管理好对应的事务管理器。
出现异常回滚事务
@Transactional使用,可以指定回滚的策略,例如出现下面代码当抛出Exception异常的时候进行回滚。
多事务管理器指定对应的事务管理器
分别给method1和method2指定不同的事务管理器。
@Transactional(value="txManager1")
public void method1(Connection conn){
System.out.println("this is method1");
}
@Transactional(value="txManager2")
public void method2(Connection conn){
System.out.println("this is method2");
}
接下来举几个例子看下具体场景下事务的处理,这里为了简化代码都只是输出output模拟真正的数据库操作。 method1和method2中的数据库操作都会被回滚,因为method1和method2再同一个@Transactional声明的事务中。
@Transactional
public void tx(){
method1();
method2();
}
public void method1(){
System.out.println("this is method1");
int i = 10 / 0;
}
public void method2(){
System.out.println("this is method2");
}
那么上面的场景因为method1和method2再同一个事务中,那么我们再method1和method2上面也添加上@Transactional注解这样不就各自都有独立的声明了,那么同样的场景method2的业务就可以正常操作了呢?这样也是不可以的因为spring事务默认的传播机制是: TransactionDefinition.PROPAGATION_REQUIRED如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
@Transactional
public void tx(){
method1();
method2();
}
@Transactional
public void method1(){
System.out.println("this is method1");
int i = 10 / 0;
}
@Transactional
public void method2(){
System.out.println("this is method2");
}
难道再使用@Transacal之后就没发办法做到事务隔离了吗?肯定不是的我们可以给method1和method2指定需要的事务传播机制就可以,给method1和method2配置Propagation.REQUIRES_NEW的传播机制后,那么即使再最外侧方法有@Transactional注解,但是Propagation.REQUIRES_NEW会给method1和method2各自创建一个新的事务,这时候method1会回滚,method2会正常处理。
@Transactional
public void tx(){
method1();
method2();
}
@Transactional(propagation= Propagation.REQUIRES_NEW)
public void method1(){
System.out.println("this is method1");
int i = 10 / 0;
}
@Transactional(propagation= Propagation.REQUIRES_NEW)
public void method2(){
System.out.println("this is method2");
}
到这里我们对于事务的控制已经有大致的了解,关于事务的具体控制一定是要结合我们的业务来灵活使用的,如果遇到多数据源的情况我们也需要将事务管理器考虑在内。
声明式事务@Transactional失效场景
- 有异常需要抛出,如果catch异常后没有进行throw那么不会进行事务回滚,因为异常已经被捕获所以对于@Transactional而言方法最终无异常抛出;
@Transactional(rollbackFor = Exception.class)
@Override
public void unbind(Integer crmId) {
try {
checkIf(!UserUtils.isOpsUser(), AmsCode.USER_NOT_OPS);
val crmCustomer = customerMapper.selectByPrimaryKey(crmId);
checkIf(crmCustomer == null, AmsCode.CRM_ACCOUNT_NOT_FOUND);
checkIf(crmCustomer.getStatus() != null && !CrmCustomerStatus.bound.name().equalsIgnoreCase(crmCustomer.getStatus()), AmsCode.CRM_CUSTOMER_NOT_BIND_ACCOUNT);
val account = accountMapper.selectOne(new AmsAccount().setCrmId(crmId));
if (account != null) {
account.setCrmId(null);
accountMapper.updateByPrimaryKey(account);
}
customerMapper.updateByPrimaryKey(crmCustomer.setStatus(CrmCustomerStatus.unbound.name()));
} catch (Exception e) {
//不会进行异常回滚,因为异常被捕获没有再抛出
log.error("error happened {}", e);
}
}
以上代码中如果我们既要记录错误日志,也要处理事务那么可以在catch中再throw异常出来,关于catch的范围我们也要注意,如果多个业务操作的话要根据需要对独立的业务处理添加try-catch。
public class TestService {
@Autowired
TestServiceB b;
@Autowired
TestServiceC c;
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
public void operator(){
log.info("process test service");
try {
b.insertB();
c.insertC();
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
public class TestService {
@Autowired
TestServiceB b;
@Autowired
TestServiceC c;
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
public void operator1(){
log.info("process test service");
try {
b.insertB();
} catch (Exception e) {
log.error(e.getMessage());
}
try {
c.insertC();
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
- 方法必须是public修饰符,如果不是public修饰也不会报错,只是注解不会生效(因为动态代理jdk动态代理处理接口不包含非public方法,cglib处理public方法);
- 通过this调用的方法注解是不生效的,通过this在当前类的内部进行调用不会创建新的代理,因此注解不会生效;
总结
- springboot通过自动装配的方式提供了事务管理的相关依赖,但是如果我们业务场景复杂并且遇到多数据源场景,那么需要结合实际业务来灵活使用。
- 使用声明式事务@Transactional的时候,可以通过配置事务管理器(value)和事务传播行为(propagation)的属性来控制满足自己的业务场景。