文章目录
SSM单元测试事务生效,实际项目无效
-
问题:
service有一个事务,事务管理器、连接点等配置都有效,且正常。
在Junit单元测试里,是加载的spring的配置文件的扫描包;
在项目运行时,在springmvc里把整个项目的包都扫了。
(因为applicationContext的只能扫描service+mapper,不能扫controller)
(springmvc配置文件,既可以扫controller,下层的也可以扫)然后在Junit单元测试时,service方法事务正常,可以加上事务。但是在项目中,这个事务失效了,日志打印显示,没有加事务。
-
原因:因为spring-mvc是一个子容器,不具有事务功能,而spring-config(applicationContext)是顶级父容器,有事务功能,
如果你在spring-mvc.xml内扫描了处理事务的service类,会导致事务功能失效

图片来自:https://blog.csdn.net/Stephen_mu/article/details/88077942
参考:https://www.cnblogs.com/helldiriver/p/11360540.html
-
解决方法:修改扫描包的配置
注意:
use-default-filters="false",这个一定要有,加这个的意思是,不用默认的filter,用下面自己配置的filter(include-filter)。即扫描的时候要只扫描加Controller注解的。
@ControllerAdvice 处理全局异常,在spring3.2中增加的注解- Springmvc.xml(配置扫描带Controller注解的类)
<context:component-scan base-package="com.stu" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan>- applicationContext.xml(配置不扫描带Controller注解的类)
<context:component-scan base-package="com.stu"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> </context:component-scan>
spring整合springmvc:https://blog.csdn.net/BigDevil_/article/details/104686721
@Transaction注解
https://developer.ibm.com/zh/articles/j-master-spring-transactional-use/
声明式事务的原理
声明式事务的原理是切面,
切面原理是动态代理,
jdk动态代理原理是反射,cglib动态代理原理是修改字节码框架ASM
Spring事务:https://mp.weixin.qq.com/s/Agf66BGdGLylj24IkiLSHA
https://blog.csdn.net/qq_43631716/article/details/122285786
PS:不同版本的源码可能不一致
@Transaction注解失效的原因
-
@Transactional 应用在非 public 修饰的方法上
(因为在Spring AOP 代理时,检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息)
注解到protected、private 修饰的方法上,虽然事务无效,但不会有任何报错 -
@Transactional 注解属性 propagation 设置错误(传播机制)
-
@Transactional 注解属性 rollbackFor 设置错误(触发回滚的异常类型)
-
同一个类中方法调用,导致@Transactional失效
即,在ServcieA中,a方法,调用b方法。b方法的事务不生效。public void A() throws Exception { ... ... this.B(); ... ... } @Transactional() public void B() throws Exception { mapper.insert(); }- 原因:Spring采用动态代理(AOP)实现对bean的管理和切片,它为我们的每个class生成一个代理对象。只有在代理对象之间进行调用时,可以触发切面逻辑。而在同一个class中,方法B调用方法A,调用的是原对象的方法,而不通过代理对象。
- 解决办法:
-
把方法分到两个类中
-
用@Autowired 注入自己 然后在用注入的bean调用自己的方法
(注意:可能会产生循环依赖问题,@Autowired的bean上加@Lazy注解。) -
获取本对象的代理对象,再进行调用。具体操作如:
-
Spring-content.xml上下文中,增加配置:<aop:aspectj-autoproxy expose-proxy=“true”/>
-
在xxxServiceImpl中,用(xxxService)(AopContext.currentProxy()),获取到xxxService的代理类,再调用事务方法,强行经过代理类,激活事务切面。
-
-
-
异常被你的 catch“吃了” 导致@Transactional失效
-
数据库引擎不支持事务
参考:https://www.cnblogs.com/xhq1024/p/13085280.html
参考:https://www.cnblogs.com/ynyhl/p/12066530.html
Spring事务的手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();手动回滚- 抛出运行时异常
throw new RuntimeException(); - 创建自定义异常类,实现RuntimeException接口;(推荐)
@Transactional注解到类上时
如果@Transactional注解到类上,该类里的内部方法调用时,注解同样生效!
ps:spring重写了sql异常,其BadSqlGrammarException异常其实是RuntimeException,sql错了同样会回滚。
@Insert("INSERT INTO table_1(type,name,create_time) VALUES('1','1',now());")
int insertOne();
@Insert("INSERT INTO table_1(type,name,create_time) VALUES('2','2',now());")
int insertTwo();
@Service
@Transactional
public class TestTransactionalServiceImpl implements TestTransactionalService {
@Autowired
private TestDao testDao;
@Override
public void insertOne() {
int i = testDao.insertOne();
System.out.println(i);
// throw new RuntimeException("第1个报错啦!");
}
@Override
public void insertTwo() {
int i = testDao.insertTwo();
System.out.println(i);
// int j = 1/0;
// throw new RuntimeException("第二个报错啦!");
}
@Override
public void testMain() {
this.insertOne();
// int i = 1/0;
this.insertTwo();
// int i = 1/0;
}
}
@Autowired
private TestTransactionalService service;
@Test
public void testTransactional(){
service.testMain();
}
在事务结束后异步执行方法
https://www.csdn.net/tags/MtjaEg3sNjY0MzItYmxvZwO0O0OO0O0O.html
解决方法:https://www.likecs.com/show-77070.html?sc=300
封装注册步骤,使用注解控制(在切面里注册) https://www.jdon.com/57371
整合线程池,实现事务结束后异步调用;(在线程池里注册)https://blog.csdn.net/ly199108171231/article/details/92984574
在对应的事务里注册一个TransactionSynchronization,然后实现对应的方法;
@Transactional
public void method1(){
db.store1();// 伪代码,业务逻辑
db.store2();// 伪代码,业务逻辑
// 注册
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// code for publishing message to kafka
sendMessage();
}
});
}
案例:整合到线程池,参考第二个文档:
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* 等待spring事务提交之后,异步执行任务 的线程池
* @author ZY
* @date 2022/9/28 11:46
*/
@Slf4j
public class TransactionAfterCommitExecutor extends ThreadPoolExecutor {
public TransactionAfterCommitExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
private final ThreadLocal<List<Runnable>> currentTasks = ThreadLocal.withInitial(() -> new ArrayList<>(5));
/**
* 注意:拒绝策略【丢弃最老的任务】
*/
public TransactionAfterCommitExecutor() {
this(
50, 500,
10L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1024),
// guava包里的Builder,如果没有,可自行实现工厂类
new ThreadFactoryBuilder().setNameFormat("transaction-after-commit-call-pool-%d").build(),
new ThreadPoolExecutor.DiscardOldestPolicy());
}
@Override
public void execute(final Runnable runnable) {
// 如果事务同步未启用表示没有开启事务或事务已结束,按正常线程池逻辑处理
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
super.execute(runnable);
} else {
// 把当前线程里所有需要等提交后执行的任务,都存到ThreadLocal里,然后用AfterCompletionTransactionSynchronizationHandler 【统一】执行和清除
List<Runnable> curThreadTaskList = currentTasks.get();
if(curThreadTaskList.isEmpty()){
TransactionSynchronizationManager.registerSynchronization(new AfterCompletionTransactionSynchronizationHandler());
}
curThreadTaskList.add(runnable);
}
}
@NotNull
@Override
public Future<?> submit(final Runnable runnable) {
if(runnable == null) throw new NullPointerException("runnable can not be null ");
// 如果事务同步未启用表示没有开启事务或事务已结束,按正常线程池逻辑处理
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
return super.submit(runnable);
} else {
//如果存在事务则在事务结束后【直接】异步执行,不放到ThreadLocal统一执行
// PS: 这是Runnable不是Callable,即使是submit也不会有返回值
final RunnableFuture<Void> ftask = newTaskFor(runnable, null);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
TransactionAfterCommitExecutor.super.submit(ftask);
}
});
return ftask;
}
}
/*
* beforeCommit:事务提交之前执行
* afterCommit: 事务提交之后执行
* beforeCompletion:事务完成之前执行
* afterCompletion:事务完成之后执行(无论是提交还是回滚,都算完成)
* */
private class AfterCompletionTransactionSynchronizationHandler extends TransactionSynchronizationAdapter {
@Override
public void afterCompletion(int status) {
final List<Runnable> txRunnableList = new ArrayList<>(currentTasks.get());
currentTasks.remove();
if(status == STATUS_COMMITTED){
TransactionAfterCommitExecutor.super.execute(() -> {
for (Runnable runnable : txRunnableList) {
try {
runnable.run();
} catch (Exception e) {
log.error("ex:",e);
}
}
});
}
}
}
}
注入bean:
@Bean(name = "afterCommitTXExecutor")
public ThreadPoolExecutor transactionAfterCommitExecutor(){
return new TransactionAfterCommitExecutor();
}
使用:
public class Test{
@Autowire
@Qualifier("afterCommitTXExecutor")
private ThreadPoolExecutor afterCommitTXExecutor;
@Transactional(rollbackFor = Exception.class)
public void update(String id,String model){
preChck(model);
doUpdate(model);
// . 清redis缓存,刷新本地缓存
afterCommitTXExecutor.execute(() -> refreshRoleCache(id, model));
}
}
@Transactional + 锁 的问题
@Transactional + 锁 可能会导致锁失效。
伪代码:

声明式事务是在切面里实现的,释放锁会在事务提交之前,所以可能会在 线程A锁释放,事务未提交之前,线程B进行了提交前的数据操作。
https://www.cnblogs.com/wk-missQ1/p/16340587.html
MyBatis整合Spring之后,openSession(autoCommit)失效
以下面代码为例
@Test
public void testTrans2(){
try (SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(false)) {
try {
menuConfigDao.testInsert1();
int i = 1 / 0;
menuConfigDao.testInsert2();
sqlSession.commit();
} catch (Exception e) {
log.error("", e);
sqlSession.rollback();
}
}
}
会发现,事务没生效。
原因:
autoCommit字段,是connection级别的,就是说,实际上,autoCommit是由connection决定的。
springboot 默认的数据库连接池是HikariProxyConnection,而它默认的autoCommit属性是true,而不是我们openSession里传的值。
解决:
-
使用spring的DataSourceTransactionManager来手动控制事务。
@Autowired private DataSourceTransactionManager transactionManager; public void method(){ // 获取事务定义 DefaultTransactionDefinition df = new DefaultTransactionDefinition(); // 设置事务隔离级别,开启新的事务 df.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); // 获取事务状态,相当于开启事务 TransactionStatus transactionStatus = transactionManager.getTransaction(df); try { menuConfigDao.testInsert1(); int i = 1 / 0; menuConfigDao.testInsert2(); transactionManager.commit(transactionStatus); } catch (Exception e) { log.error("", e); transactionManager.rollback(transactionStatus); } } -
通过配置数据库连接池的默认autoCommit

&spm=1001.2101.3001.5002&articleId=109777357&d=1&t=3&u=c646f6f081df4a36b1cb0151c23df79e)
333

被折叠的 条评论
为什么被折叠?



