章
目
录
聊聊本地事务失效的场景是一道分布式常见面试题,在咱开发程序的过程中,本地事务要是失效了,那麻烦可就大了,尤其是在线上生产环境里,这会导致数据不一致,出现脏数据的问题。所以,很多公司都制定了开发规范,就是为了让大家避开这些坑。今天咱就唠唠本地事务失效的那些常见场景!
事务方法没被Spring管理
有些小伙伴在写代码的时候,可能会遇到这样的情况:明明自己写了事务相关的代码,但就是不生效。这很有可能是事务方法没有被Spring管理。简单来说,就是你调用方法的那个类,没有加@Service
或者@Component
这类注解。要是你直接用new
的方式去创建对象,然后调用方法,那这个类就不会被放进Spring的IOC容器里。Spring的事务代理管理机制管不着它,事务自然就失效了。
比如下面这段代码:
// 没有加@Service或@Component注解的类
class SomeClass {
// 这里假设这个方法需要事务管理
public void someMethod() {
// 业务逻辑代码
}
}
然后在其他地方这么调用:
public class Main {
public static void main(String[] args) {
// 直接new对象调用方法,事务不会生效
SomeClass someClass = new SomeClass();
someClass.someMethod();
}
}
所以啊,大家在写代码的时候,可别忘了给相关的类加上这些注解。
同一个类里方法互相调用
在一个类里面,如果方法A调用方法B,你可能会这么写:this.b()
。这时候,就算你给方法B加了Spring的事务注解,事务也不会生效。为啥呢?这和AOP失效的原理差不多,因为事务注解本质上就是一种AOP实现。在这种情况下,调用过程不会走Spring的代理,事务也就管不住了。
给大家举个例子:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MethodCallService {
// 加了事务注解的方法B
@Transactional
public void methodB() {
// 这里是业务逻辑,比如数据库操作
}
// 方法A调用方法B
public void methodA() {
this.methodB();
}
}
在上面这个例子里,methodA
调用methodB
的时候,methodB
上的事务注解就失效了。
try – catch导致事务失效
在开发中,try - catch
是我们常用的异常处理手段,但要是用不好,也会让事务失效。好多公司都不太建议用try - catch
,为啥呢?因为它会把程序里的一些错误给“藏”起来,就像吃了止疼药,虽然当时不疼了,但病还在。在事务处理里,要是用了try - catch
,事务注解就捕捉不到发生的错误,导致事务没办法回滚,就会出现部分操作成功,部分操作失败的情况。
比如下面这段代码:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class TryCatchService {
@Transactional
public void doSomething() {
try {
// 可能会出错的业务逻辑代码
// 比如数据库插入操作,可能违反唯一约束
// 这里用伪代码表示
insertDataToDatabase();
} catch (Exception e) {
// 这里捕获了异常,但没有抛出,事务无法回滚
System.out.println("捕获到异常:" + e.getMessage());
}
}
}
在这个例子里,doSomething
方法里的事务因为try - catch
的存在,没办法正常回滚了。
多线程调用引发事务失效
开发的时候,我们经常会遇到需要异步处理的业务场景,这时候就会新开线程。但这里有个坑得注意:Spring的事务是和当前线程绑定的,通过ThreadLocal
来保证线程同步。要是你新开了一个线程去处理业务,那这个新线程就有了自己的事务,原来的事务就管不着了,这也会导致事务失效。
比如说:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MultiThreadService {
@Transactional
public void mainTask() {
// 开启新线程处理任务
new Thread(() -> {
// 这里的业务逻辑操作,事务不会按照预期生效
// 假设这是一个数据库更新操作
updateDatabase();
}).start();
}
}
在这个例子里,mainTask
方法开启新线程执行任务时,新线程里的事务就失效了。
用final修饰方法让事务失效
大家都知道,final
修饰的东西不能被修改。可能很少有人会用final
去修饰方法,毕竟代码很少会写完就不再改动了。但要是真这么用了,就得注意事务的问题。因为事务代理需要重写方法来实现事务管理,而final
修饰的方法拒绝被重写,所以事务也就没法生效了。
下面是一个示例:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class FinalMethodService {
// 用final修饰的方法,事务不会生效
@Transactional
public final void finalMethod() {
// 业务逻辑代码
}
}
在这个类里,finalMethod
虽然加了事务注解,但因为被final
修饰,事务不会生效。
以上就是本地事务失效的一些常见场景啦。大家在开发的时候,多注意这些问题,就能少踩不少坑。要是你在实际开发中遇到了类似的问题,或者对这些内容还有疑问,欢迎在评论区留言讨论!咱一起把技术难题给攻克了!