三问Spring事务:解决什么问题,mysql基础教程视频

TransactionStatus事务对象可被传入到commit方法或rollback方法中,完成事务的提交或回滚。

下面我们通过一个具体实现来理解TransactionStatus的作用。以commit方法为例,如何通过TransactionStatus完成事务的提交。AbstractPlatformTransactionManagerPlatformTransactionManager接口的的实现,作为模板类,其commit实现如下:

public final void commit(TransactionStatus status) throws TransactionException {

// 1.检查事务是否已完成

if (status.isCompleted()) {

throw new IllegalTransactionStateException(

“Transaction is already completed - do not call commit or rollback more than once per transaction”);

}

// 2.检查事务是否需要回滚(局部事务回滚)

DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;

if (defStatus.isLocalRollbackOnly()) {

if (defStatus.isDebug()) {

logger.debug(“Transactional code has requested rollback”);

}

processRollback(defStatus, false);

return;

}

// 3.检查事务是否需要回滚(全局事务回滚)

if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {

if (defStatus.isDebug()) {

logger.debug(“Global transaction is marked as rollback-only but transactional code requested commit”);

}

processRollback(defStatus, true);

return;

}

// 4.提交事务

processCommit(defStatus);

}

commit模板方法中定义了事务提交的基本逻辑,通过查看status的事务状态来决定抛出异常还是回滚,或是提交。其中的processRollbackprocessCommit方法也是模板方法,进一步定义了回滚、提交的逻辑。以processCommit方法为例,具体的提交操作将由抽象方法doCommit完成。

protected abstract void doCommit(DefaultTransactionStatus status) throws TransactionException;

doCommit的实现取决于具体的数据访问技术。我们看下JDBC相应的具体实现类DataSourceTransactionManager中的doCommit实现。

protected void doCommit(DefaultTransactionStatus status) {

// 获取status中的事务对象

DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();

// 通过事务对象获得数据库连接对象

Connection con = txObject.getConnectionHolder().getConnection();

if (status.isDebug()) {

logger.debug(“Committing JDBC transaction on Connection [” + con + “]”);

}

try {

// 执行commit

con.commit();

}

catch (SQLException ex) {

throw new TransactionSystemException(“Could not commit JDBC transaction”, ex);

}

}

commitprocessCommit方法中我们根据入参的TransactionStatus提供的事务状态来决定事务行为,而在doCommit中需要执行事务提交时将会通过TransactionStatus中的事务对象来获得数据库连接对象,再执行最后的commit操作。通过这个示例我们可以理解TransactionStatus所提供的事务状态和事务对象的作用。

下面是用Spring事务API改写后的事务管理代码:

// 获得事务管理器

PlatformTransactionManager txManager = getPlatformTransactionManager();

DefaultTransactionDefinition def = new DefaultTransactionDefinition();

// 指定事务元信息

def.setName(“SomeTxName”);

def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

// 获得事务

TransactionStatus status = txManager.getTransaction(def);

try {

// 业务处理

}

catch (MyException ex) {

// 捕获异常,回滚事务

txManager.rollback(status);

throw ex;

}

// 提交事务

txManager.commit(status);

无论是使用JDBC、Hibernate还是MyBatis,我们只需要传给txManager相应的具体实现就可以在多种数据访问技术中切换。

小结:Spring事务通过PlatformTransactionManagerTransactionDefinitionTransactionStatus接口统一事务管理API,并结合策略模式和模板方法决定具体实现。

Spring事务API代码还有个特点有没有发现,SQLException不见了。下面来看Spring事务是如何解决大量的异常处理代码。

2.2 大量的异常处理代码

为什么使用JDBC的代码中会需要写这么多的异常处理代码。这是因为Connection的每个方法都会抛出SQLException,而SQLException又是检查异常,这就强制我们在使用其方法时必须进行异常处理。那Spring事务是如何解决该问题的。我们看下doCommit方法:

protected void doCommit(DefaultT

《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》

【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享

ransactionStatus status) {

DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();

Connection con = txObject.getConnectionHolder().getConnection();

if (status.isDebug()) {

logger.debug(“Committing JDBC transaction on Connection [” + con + “]”);

}

try {

con.commit();

}

catch (SQLException ex) {

// 异常转换

throw new TransactionSystemException(“Could not commit JDBC transaction”, ex);

}

}

Connectioncommit方法会抛出检查异常SQLException,在catch代码块中SQLException将被转换成TransactionSystemException抛出,而TransactionSystemException是一个非检查异常。通过将检查异常转换成非检查异常,让我们能够自行决定是否捕获异常,不强制进行异常处理。

Spring事务中几乎为数据库的所有错误都定义了相应的异常,统一了JDBC、Hibernate、MyBatis等不同异常API。这有助于我们在处理异常时使用统一的异常API接口,无需关心具体的数据访问技术。

小结:Spring事务通过异常转换避免强制异常处理。

2.3 业务处理代码与事务管理代码混杂

在2.1节中给出了使用Spring事务API的写法,即编程式事务管理,但仍未解决“业务处理代码与事务管理代码混杂”的问题。这时候就可以利用Spring AOP将事务管理代码这一横切关注点从代码中剥离出来,即声明式事务管理。以注解方式为例,通过为方法标注@Transaction注解,将为该方法提供事务管理。其原理如下图所示:

image

声明式事务原理

Spring事务会为@Transaction标注的方法的类生成AOP增强的动态代理类对象,并且在调用目标方法的拦截链中加入TransactionInterceptor进行环绕增加,实现事务管理。

下面我们看下TransactionInterceptor中的具体实现,其invoke方法中将调用invokeWithinTransaction方法进行事务管理,如下所示:

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)

throws Throwable {

// 查询目标方法事务属性、确定事务管理器、构造连接点标识(用于确认事务名称)

final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);

final PlatformTransactionManager tm = determineTransactionManager(txAttr);

final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {

// 创建事务

TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

Object retVal = null;

try {

// 通过回调执行目标方法

retVal = invocation.proceedWithInvocation();

}

catch (Throwable ex) {

// 目标方法执行抛出异常,根据异常类型执行事务提交或者回滚操作

completeTransactionAfterThrowing(txInfo, ex);

throw ex;

}

finally {

// 清理当前线程事务信息

cleanupTransactionInfo(txInfo);

}

// 目标方法执行成功,提交事务

commitTransactionAfterReturning(txInfo);

return retVal;

} else {

// 带回调的事务执行处理,一般用于编程式事务

// …

}

}

在调用目标方法前后加入了创建事务、处理异常、提交事务等操作。这让我们不必编写事务管理代码,只需通过@Transaction的属性指定事务相关元信息。

小结:Spring事务通过AOP提供声明式事务将业务处理代码和事务管理代码分离。

3. 存在什么问题


Spring事务为了我们解决了第一节中列出的三个问题,但同时也会带来些新的问题。

3.1 非public方法失效

@Transactional只有标注在public级别的方法上才能生效,对于非public方法将不会生效。这是由于Spring AOP不支持对private、protect方法进行拦截。从原理上来说,动态代理是通过接口实现,所以自然不能支持private和protect方法的。而CGLIB是通过继承实现,其实是可以支持protect方法的拦截的,但Spring AOP中并不支持这样使用,笔者猜测做此限制是出于代理方法应是public的考虑,以及为了保持CGLIB和动态代理的一致。如果需要对protect或private方法拦截则建议使用AspectJ。

3.2 自调用失效

当通过在Bean的内部方法直接调用带有@Transactional的方法时,@Transactional将失效,例如:

public void saveAB(A a, B b)

{

saveA(a);

saveB(b);

}

@Transactional

public void saveA(A a)

{

dao.saveA(a);

}

@Transactional

public void saveB(B b)

{

dao.saveB(b);

}

在saveAB中调用saveA和saveB方法,两者的@Transactional都将失效。这是因为Spring事务的实现基于代理类,当在内部直接调用方法时,将不会经过代理对象,而是直接调用目标对象的方法,无法被TransactionInterceptor拦截处理。解决办法:

(1)ApplicationContextAware

通过ApplicationContextAware注入的上下文获得代理对象。

public void saveAB(A a, B b)

{

Test self = (Test) applicationContext.getBean(“Test”);

self.saveA(a);

self.saveB(b);

}

(2)AopContext

通过AopContext获得代理对象。

public void saveAB(A a, B b)

{

Test self = (Test)AopContext.currentProxy();

self.saveA(a);

self.saveB(b);

}

(3)@Autowired

通过@Autowired注解注入代理对象。

@Component

public class Test {

@Autowired

Test self;

public void saveAB(A a, B b)

{

self.saveA(a);

self.saveB(b);

}

// …

}

(4)拆分

将saveA、saveB方法拆分到另一个类中。

public void saveAB(A a, B b)

{

txOperate.saveA(a);

txOperate.saveB(b);

}

上述两个问题都是由于Spring事务的实现方式的限制导致的问题。下面再看两个由于使用不当容易犯错的两个问题。

3.3 检查异常默认不回滚

在默认情况下,抛出非检查异常会触发回滚,而检查异常不会。

根据invokeWithinTransaction方法,我们可以知道异常处理逻辑在completeTransactionAfterThrowing方法中,其实现如下:

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {

if (txInfo != null && txInfo.getTransactionStatus() != null) {

if (logger.isTraceEnabled()) {

logger.trace(“Completing transaction for [” + txInfo.getJoinpointIdentification() +

"] after exception: " + ex);

}

if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {

try {

// 异常类型为回滚异常,执行事务回滚

txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());

}

catch (TransactionSystemException ex2) {

logger.error(“Application exception overridden by rollback exception”, ex);

ex2.initApplicationException(ex);

throw ex2;

}

catch (RuntimeException | Error ex2) {

logger.error(“Application exception overridden by rollback exception”, ex);

throw ex2;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值