如果没有事务管理器的话,我们的程序可能是这样:
Connection connection = acquireConnection(); try{ int updated = connection.prepareStatement().executeUpdate(); connection.commit(); }catch (Exception e){ rollback(connection); }finally { releaseConnection(connection); }
也有可能是这样"优雅的事务":
execute(new TxCallback() { @Override public Object doInTx(Connection var1) { //do something... return null; } }); public void execute(TxCallback txCallback){ Connection connection = acquireConnection(); try{ txCallback.doInTx(connection); connection.commit(); }catch (Exception e){ rollback(connection); }finally { releaseConnection(connection); } } # lambda版 execute(connection -> { //do something... return null; });
但是以上两种方式,针对一些复杂的场景是很不方便的。在实际的业务场景中,往往有比较复杂的业务逻辑,代码冗长,逻辑关联复杂,如果一个大操作中有全是这种代码的话我想开发人员可能会疯把。更不用提定制化的隔离级别,以及嵌套/独立事务的处理了。
Spring作为Java最强框架,事务管理也是其核心功能之一。Spring为事务管理提供了统一的抽象,有以下优点:
事务,自然是控制业务的,在一个业务流程内,往往希望保证原子性,要么全成功要么全失败。
所以事务一般是加载 @Service
层,一个Service内调用了多个操作数据库的操作(比如Dao),在Service结束后事务自动提交,如有异常抛出则事务回滚。
这也是Spring事务管理的基本使用原则。
下面贴出具体的使用代码:
在被Spring管理的类头上增加@Transactional注解,即可对该类下的所有方法开启事务管理。事务开启后,方法内的操作无需手动开启/提交/回滚事务,一切交给Spring管理即可。
@Service @Transactional public class TxTestService{ @Autowired private OrderRepo orderRepo; public void submit(Order order){ orderRepo.save(order); } }
也可以只在方法上配置,方法配置的优先级是大于类的
@Service public class TxTestService{ @Autowired private OrderRepo orderRepo; @Transactional public void submit(Order order){ orderRepo.save(order); } }
XML的配置方式较为古老,此处就不贴代码了,如有需要自行搜索
事务隔离级别是数据库最重要的特性之一,他保证了脏读/幻读等问题不会发生。作为一个事务管理框架自然也是支持此配置的,在@Transactional注解中有一个isolation配置,可以很方便的配置各个事务的隔离级别,等同于 connection.setTransactionIsolation()
Isolation { DEFAULT(-1), READ_UNCOMMITTED(1), READ_COMMITTED(2), REPEATABLE_READ(4), SERIALIZABLE(8); }
可能没有接触过Spring的人听到传播行为会奇怪,这是个什么东西。
其实这个传播行为和数据库功能无关,只是事务管理器为了处理复杂业务而设计的一个机制。
比如现在有这样一个调用场景, A Service -> B Service -> C Service
,但是希望A/B在一个事务内,C是一个独立的事务,同时C如果出错,不影响AB所在的事务。
此时,就可以通过传播行为来处理;将C Service的事务配置为 @Transactional(propagation = Propagation.REQUIRES_NEW)
即可
Spring支持以下几种传播行为:
默认策略,优先使用当前事务(及当前线程绑定的事务资源),如果不存在事务,则开启新事务
优先使用当前的事务(及当前线程绑定的事务资源),如果不存在事务,则以无事务方式运行
优先使用当前的事务,如果不存在事务,则抛出异常
创建一个新事务,如果存在当前事务,则挂起(Suspend)
以非事务方式执行,如果当前事务存在,则挂起当前事务。
以非事务方式执行,如果当前事务存在,则抛出异常
@Transactional中有4个配置回滚策略的属性,分为Rollback策略,和NoRollback策略
默认情况下,RuntimeException和Error这两种异常会导致事务回滚,普通的Exception(需要Catch的)异常不会回滚。
配置需要回滚的异常类
# 异常类Class Class<? extends Throwable>[] rollbackFor() default {}; # 异常类ClassName,可以是FullName/SimpleName String[] rollbackForClassName() default {};
针对一些要特殊处理的业务逻辑,比如插一些日志表,或者不重要的业务流程,希望就算出错也不影响事务的提交。
可以通过配置NoRollbackFor来实现,让某些异常不影响事务的状态。
# 异常类Class Class<? extends Throwable>[] noRollbackFor() default {}; # 异常类ClassName,可以是FullName/SimpleName String[] noRollbackForClassName() default {};
设置当时事务的只读标示,等同于 connection.setReadOnly()
有下列代码,入口为test方法,在testTx方法中配置了@Transactional注解,同时在插入数据后抛出RuntimeException异常,但是方法执行后插入的数据并没有回滚,竟然插入成功了
public void test(){ testTx(); } @Transactional public void testTx(){ UrlMappingEntity urlMappingEntity = new UrlMappingEntity(); urlMappingEntity.setUrl("http://www.baidu.com"); urlMappingEntity.setExpireIn(777l); urlMappingEntity.setCreateTime(new Date()); urlMappingRepository.save(urlMappingEntity); if(true){ throw new RuntimeException(); } }
这里不生效的原因是因为入口的方法/类没有增加@Transaction注解,由于Spring的事务管理器也是基于AOP实现的,不管是Cglib(ASM)还是Jdk的动态代理,本质上也都是子类机制;在同类之间的方法调用会直接调用本类代码,不会执行动态代理曾的代码;所以在这个例子中,由于入口方法 test
没有增加代理注解,所以 textTx
方法上增加的事务注解并不会生效
比如在一个事务方法中,开启了子线程操作库,那么此时子线程的事务和主线程事务是不同的。
因为在Spring的事务管理器中,事务相关的资源(连接,session,事务状态之类)都是存放在TransactionSynchronizationManager中的,通过ThreadLocal存放,如果跨线程的话就无法保证一个事务了
# TransactionSynchronizationManager.java private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources"); private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations"); private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name"); private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal<>("Current transaction read-only status"); private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal<>("Current transaction isolation level"); private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal<>("Actual transaction active");
org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only
这个异常是由于在同一个事务内,多个事务方法之间调用,子方法抛出异常,但又被父方法忽略了导致的。
因为子方法抛出了异常,Spring事务管理器会将当前事务标为失败状态,准备进行回滚,可是当子方法执行完毕出栈后,父方法又忽略了此异常,待方法执行完毕后正常提交时,事务管理器会检查回滚状态,若有回滚标示则抛出此异常。具体可以参考 org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit
示例代码:
A -> B # A Service(@Transactional): public void testTx(){ urlMappingRepo.deleteById(98l); try{ txSubService.testSubTx(); }catch (Exception e){ e.printStackTrace(); } } # B Service(@Transactional) public void testSubTx(){ if(true){ throw new RuntimeException(); } }