新闻中心
@Autowired
private DataSourceTransactionManager txManager;
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);// 事物隔离级别,开启新事务
TransactionStatus status = txManager.getTransaction(def); // 获得事务状态
在使用Spring声明式事务时,不需要手动的开启事务和关闭事务,但是对于一些场景则需要开发人员手动的提交事务,比如说一个操作中需要处理大量的数据库更改,可以将大量的数据库更改分批的提交,又比如一次事务中一类的操作的失败并不需要对其他类操作进行事务回滚,就可以将此类的事务先进行提交,这样就需要手动的获取Spring管理的Transaction来提交事务。
ContextLoaderListener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,
启动容器时,就会默认执行它实现的方法。至于ApplicationContext.xml这个配置文件部署在哪,如何配置多个xml文件,书上都没怎么详细说明。现在的方法就是查看它的API文档。在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。看看它的API说明
//通过ContextLoader获取WebApplicationContext
}
//状态刷新
transactionStatus.flush();
//事物提交
}catch(Exception e){
//异常回滚
扫一扫,分享内容
打赏作者
daolin1
你的鼓励将是我创作的最大动力
您的余额不足,请更换扫码支付或
抵扣说明:
1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。
Springboot内部提供的事务管理器是根据autoconfigure来进行决定的。
比如当使用jpa的时候,也就是pom中加入了spring-boot-starter-data-jpa这个starter之后(之前我们分析过springboot的自动化配置原理)。
Springboot会构造一个JpaTransactionManager这个事务管理器。
而当我们使用spring-boot-starter-jdbc的时候,构造的事务管理器则是DataSourceTransactionManager。
这2个事务管理器都实现了spring中提供的PlatformTransactionManager接口,这个接口是spring的事务核心接口。
这个核心接口有以下这几个常用的实现策略:
HibernateTransactionManager
DataSourceTransactionManager
JtaTransactionManager
JpaTransactionManager
spring-boot-starter-data-jpa这个starter会触发HibernateJpaAutoConfiguration这个自动化配置类,HibernateJpaAutoConfiguration继承了JpaBaseConfiguration基础类。
在JpaBaseConfiguration中构造了事务管理器:
@Bean
@ConditionalOnMissingBean(PlatformTransactionManager.
)
PlatformTransactionManager transactionManager() {
JpaTransactionManager();
}
spring-boot-starter-jdbc会触发DataSourceTransactionManagerAutoConfiguration这个自动化配置类,也会构造事务管理器:
@Bean
@ConditionalOnMissingBean(PlatformTransactionManager.
)
@ConditionalOnBean(DataSource.
)
DataSourceTransactionManager transactionManager() {
DataSourceTransactionManager(
.dataSource);
}
Spring的事务管理器PlatformTransactionManager接口中定义了3个方法:
// 基于事务的传播特性,返回一个已经存在的事务或者创建一个新的事务
TransactionStatus getTransaction(TransactionDefinition definition)
TransactionException;
// 提交事务
commit(TransactionStatus status)
TransactionException;
// 回滚事务
rollback(TransactionStatus status)
TransactionException;
其中TransactionDefinition接口表示跟spring兼容的事务属性,比如传播行为、隔离级别、超时时间、是否只读等属性。
DefaultTransactionDefinition类是一个默认的TransactionDefinition实现,它的传播行为是PROPAGATION_REQUIRED(如果当前没事务,则创建一个,否则加入到当前事务中),隔离级别是数据库默认级别。
TransactionStatus接口表示事务的状态,比如事务是否是一个刚构造的事务、事务是否已经完成等状态。
下面这段代码就是传统事务的常见写法:
transaction.begin();
{
...
transaction.commit();
}
(Exception e) {
...
transaction.rollback();
}
{
}
由于spring的事务操作被封装到了PlatformTransactionManager接口中,commit和rollback方法对应接口中的方法,begin方法在getTransaction方法中会被调用。
细心的读者发现文章前面构造事务管理器的时候都会加上这段注解:
@ConditionalOnMissingBean(PlatformTransactionManager.
)
也就是说如果我们手动配置了事务管理器,Springboot就不会再为我们自动配置事务管理器。
如果要使用多个事务管理器的话,那么需要手动配置多个:
@Configuration
DatabaseConfiguration {
@Bean
PlatformTransactionManager transactionManager1(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager(entityManagerFactory);
}
@Bean
PlatformTransactionManager transactionManager2(DataSource dataSource) {
DataSourceTransactionManager(dataSource);
}
}
然后使用Transactional注解的时候需要声明是哪个事务管理器:
@Transactional(value="transactionManager1")
save() {
doSave();
}
Spring给我们提供了一个TransactionManagementConfigurer接口,该接口只有一个方法返回PlatformTransactionManager。其中返回的PlatformTransactionManager就表示这是默认的事务处理器,这样在Transactional注解上就不需要声明是使用哪个事务管理器了。
有很多情况我们同一个方法需要多次提交事务:这个时候就不能使用Transactional注解,需要自动提交事务,因为我们项目整合的是jpa
刚开始注入的是JpaTransactionManager,发现无法注入,改为PlatformTransactionManager即可;
扫一扫,分享内容
打赏作者
csy_system
你的鼓励将是我创作的最大动力
您的余额不足,请更换扫码支付或
抵扣说明:
1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。
好记忆不如烂笔头 ,还是记下点东西比较好。
事务还是一个比较好的东东,有了这个,我们在做流程性的东西的时候,就会很好,很nice。
现在看看 SpringMVC 如何实现的,详细请看代码:
@Autowired
DataSourceTransactionManager
;
DefaultTransactionDefinition
=
DefaultTransactionDefinition();
.setPropagationBehavior(TransactionDefinition.
);
TransactionStatus
=
.getTransaction(
);
"transactionManager
"org.springframework.jdbc.datasource.DataSourceTransactionManager"
"dataSource"
"dataSource"
bean
aop:config
"execution(public * com.*.service.impl.*Impl.*(..))"
"pointcut"
advice-ref
pointcut-ref
aop:config
"txAdvice"
"transactionManager"
tx:attributes
name
propagation
read-only
name
propagation
read-only
name
propagation
read-only
"save*"
"REQUIRED"
"add*"
"REQUIRED"
"create*"
"REQUIRED"
"delete*"
"REQUIRED"
"del*"
"REQUIRED"
"remove*"
"REQUIRED"
"modify*"
"REQUIRED"
"update*"
"REQUIRED"
"clear*"
"REQUIRED"
tx:attributes
tx:advice
扫一扫,分享内容
打赏作者
supingemail
你的鼓励将是我创作的最大动力
您的余额不足,请更换扫码支付或
抵扣说明:
1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。
1.其实最开始不知道怎么写,后来想想,想要把一件事情,用“人话”讲明白,就需要从最简单的开始,再逐步加入难度。
2.为什么,非要写代码呢?有两点:第一:验证自己的思想和逻辑,第二:锻炼自己的编码能力。
3.搭建项目的时候,遇到来AOP失效的问题。解决方法在:
4.为什么要自己实现,而不用框架呢?只有这样,才能真正领悟框架的底层和精髓。
先讲一下场景吧,我要删除数据库中的一条记录,那么删除的结果有两种,一种是删除成功,提交事务,另外一种是执行删除的过程中发生异常,回滚事务。
但是,在实际开发过程中,我们从来不直接操作数据库,而是通过ORM框架来操作或者Java相关框架来干这个活!
今天也不例外,我们用springboot + jdbcTemplate来实现这个操作的过程。
这段代码,我想大家都认识,并且也经常写,但是,用的不是jdbcTemplate,而是调用mybatis。效果一样,为来快速实现,我们用jdbcTemplate实现。
@Transactional中的rollbackFor必须加上,否则回滚不生效。
如果,你不信,你可以自己写代码试试
答案是肯定的,那需要怎么做呢?
既然,我们要控制事务的提交,那么我们就需要拿到sql链接,也就是connection.
那么,connection从哪里获取呢?当然,是从DataSource中获取了。
在Springboot中,因为配置了DataSource,所以,DataSource替我们去获取了数据库的connection。如果,没有DataSource,我们怎么获取connection呢?这个应该是一个最基础的了,就是通过JDBC去获取connection。在最初学习Java的时候,我们不用任何ORM框架操作数据库,就是用JDBC实现的。
其实,市面上的ORM框架也都是封装了JDBC的功能,并实现了sql解析、参数映射、结果映射等功能,为了就是简化开发人员的工作量,提高开发人员的工作效率。
但是,这也导致越来越多的开发人员,根本不知道JDBC的存在了。
只是会用了工具,不知道工具是怎么做的。一旦,工具出了问题,根本不知道怎么修理。
第一个,是通过Springboot的jdbcTemplate来实现事务的操作,我只要懂得API就可以了。
第二个,想看看框架的执行流程是怎么样子的,所以,我来手动控制了事务的提交和回滚。
第三个,分析第二段代码,我发现几个我一直很疑惑的问题:
这种思想和动态代理是不是很像,我们要做的就是给业务类的方法增强。增强的部分就是获取数据库链接,实现事务的提交和回滚。
在使用Spring开发的过程中,我们可以用Spring的aop来实现这个功能。
代码并不多,我讲一下我的整个思路和关键类
答案是,必须在一个connection中执行,才能进行事务的操作。
这个就用到了ThreadLocal,其中的知识点是线程封闭,这里就不展开说了,大家自己去了解一下吧!
你们觉得,注解的原理是什么?我觉得,@Transactional只是注解,真正调用的地方是获取注解中的相关配置属性,然后在Connection提交事务的时候来做业务判断而已。如何获取,在Spring中最经典的就是通过AOP+反射。
看源码速记方法,类名#方法名–操作
一般看源码的时候,真正操作的地方,都是以doxxxx开头的。
DataSourceUtils#getConnection–获取链接
DataSourceUtils#doGetConnection–获取链接操作,从TransactionSynchronizationManager中获取
TransactionSynchronizationManager#getResource–获取资源
TransactionSynchronizationManager#doGetResource–从resources获取资源值,resources是一个ThreadLocal
TransactionAttributeSourceAdvisor#transactionInterceptor
TransactionAttributeSourcePointcut#TransactionAttributeSourceClassFilter#matches
AnnotationTransactionAttributeSource#isCandidateClass–解析并设置属性
SpringTransactionAnnotationParser#isCandidateClass–解析注解策略
通过上面几个简单的类,我们就实现了事务的控制。其实,实现没那么复杂,重要的思想。通过阅读源码,我们知道了事务注解,是通过AOP来完成的。是不是和我实现的思想是一样的?
扫一扫,分享内容
打赏作者
JavaUpgrade
你的鼓励将是我创作的最大动力
您的余额不足,请更换扫码支付或
抵扣说明:
1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。
今天在工作中遇到了一个spring事务的问题: 在service方法内需要通过线程去执行添加用户积分和用户积分日志的情况,试了下通过@Transactional声明式事务不起作用,只能使用手动事务去控制
因此写了这篇博客,记录一下这个情况,希望能帮助到大家
一、事务的重要性,相信在实际开发过程中,都有很深的了解了。但是存在一个问题我们经常在开发的时候一般情况下都是用的注解的方式来进行事务的控制,说白了基于spring的7种事务控制方式来进行事务的之间的协调。
二、spring的7中事务传播行为
三、数据库四大特性和MySQL事务的隔离级别
1)四大特性
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
2)隔离级别
a、脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。(读取未提交的数据)
b、不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。(边读边写)
c、幻读指两个事务同时发生,两个事务修改数据,读到的数据不是自己开始修改的数据。幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体。(同时写,同时读)
3)数据库事务级别,默认使用Repeatable read级别,列表级别从下往上级别越低。查看级别
四、扯远了,写上面spring事务和数据库事务隔离级别,主要的目的就是了解事务之间存在的传递关系,这样在控制的时候,spring会通过事务与事务之间关系,来达到回滚或者提交的效果。
五、如果在没有办法使用注解的时候(比如多线程等),就要使用手动的方式来做事务管理了,这也就是编程式的事务管理。
1)首先加入注解,这就是spring的jdbc框架中提供的事务管理方式
2)看一下源码(DataSourceTransactionManagerAutoConfiguration.class、TransactionTemplate.class)
[
](javascript:void(0)??
[
](javascript:void(0)??
备注:有兴趣可以了解一下
的写法和原理。
注意,这里的所有事务传播方式包括处理,都需要自己手动去处理。
3)编写方式
说明:这里开发事务过后,返回一个事务状态,这个状态记录了东西,用来控制事务的管理,当然,多个事务之间的控制需要人为控制。
4)编程式的事务控制经量少用,因为控制程度上面来说spring的方式还是来的更加不错,编程式的方式,更多用于在需要事务的时候,没有办法加入事务,才采取手动控制事务的方式。
使用示例 :
spring 方法加了@Transactional,我想循环一次就提交,怎么搞,开启了事务默认应该是执行完了才全部提交
配置的事务就是在这个方法执行结束后提交,当然我这样说也不全面,应该说,事务在开启他的那个方法结束后就提交。你这个问题就是你数据库没有学好了,在一个事务空间(就是在一个事务里面)查询到的数据是这个事务空间里面的数据(换句话就是说你在这个事务里面添加的数据,在这个事务本身里面是能够看到的,只是其他事务不能看到这个数据而已)。
报告相同问题?
一、事务的重要性,相信在实际开发过程中,都有很深的了解了。但是存在一个问题我们经常在开发的时候一般情况下都是用的注解的方式来进行事务的控制,说白了基于spring的7种事务控制方式来进行事务的之间的协调。
二、spring的7中事务传播行为
三、数据库四大特性和MySQL事务的隔离级别
1)四大特性
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
2)隔离级别
a、脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。(读取未提交的数据)
b、不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。(边读边写)
c、幻读指两个事务同时发生,两个事务修改数据,读到的数据不是自己开始修改的数据。幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体。(同时写,同时读)
3)数据库事务级别,默认使用Repeatable read级别,列表级别从下往上级别越低。查看级别
四、扯远了,写上面spring事务和数据库事务隔离级别,主要的目的就是了解事务之间存在的传递关系,这样在控制的时候,spring会通过事务与事务之间关系,来达到回滚或者提交的效果。
五、如果在没有办法使用注解的时候(比如多线程等),就要使用手动的方式来做事务管理了,这也就是
。
1)首先加入注解,这就是spring的jdbc框架中提供的事务管理方式
2)看一下源码(DataSourceTransactionManagerAutoConfiguration.class、TransactionTemplate.class)
备注:有兴趣可以了解一下
的写法和原理。
注意,这里的所有事务传播方式包括处理,都需要自己手动去处理。
3)编写方式
说明:这里开发事务过后,返回一个事务状态,这个状态记录了东西,用来控制事务的管理,当然,多个事务之间的控制需要人为控制。
4)编程式的事务控制经量少用,因为控制程度上面来说spring的方式还是来的更加不错,编程式的方式,更多用于在需要事务的时候,没有办法加入事务,才采取手动控制事务的方式。
student接口
student实现类
user接口
user实现类
接口:
接口实现类
当执行以上saveEntity()代码时,因StudentDaoImpl 插入语句的一个错误,会导致事务不一致,user表成功插入一条记录,student表没有。
为了使事务一致,在SpringBoot项目中,我们只需要在saveEntity()上添加@Transactional注解,,对@Transactional的注解可以查看
一节。
这里为了适应更多的异常,我们提升了事务捕获异常的范围:@Transactional(rollbackFor = Exception.class)
有时我们需要捕获一些错误信息,又需要进行事务回滚,这时我们就需要用到Spring提供的事务切面支持类TransactionAspectSupport。
手动回滚事务一定要加上@Transactional,不然会报以下错误:
想想也是,不开启事务,何来手动回滚,所以@Transactional必不可少。
使用Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); 设置回滚点。
使用TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint); 回滚到savePoint。
springboot 开启事务以及手动提交事务,可以在服务类上加上两个注解。
手动开启事务
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
手动提交事务
dataSourceTransactionManager.commit(transactionStatus);//提交
手动回滚事务
dataSourceTransactionManager.rollback(transactionStatus);//最好是放在catch 里面,防止程序异常而事务一直卡在哪里未提交
默认spring事务只在发生未被捕获的 RuntimeException 时才回滚。
spring aop 异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚,默认情况下aop只捕获 RuntimeException 的异常,但可以通过配置来捕获特定的异常并回滚。
换句话说在service的方法中不使用try catch 或者在catch中最后加上throw new RuntimeException (),这样程序异常时才能被aop捕获进而回滚。
解决方案:
方案1:例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw new RuntimeException()语句,以便让aop捕获异常再去回滚,并且在service上层(webservice客户端,view层action)要继续捕获这个异常并处理。
方案2:在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常。
1. 默认地,如果使用的数据源不是SpringBoot的默认配置(即是由自己定义的配置信息,自己解析创建的数据源),则需要手动创建事务管理器,因为SpringBoot无法识别配置信息,无法完成自动注入。
2. SpringBoot1.x需要在启动类上添加@EnableTransactionManagement,SpringBoot2.x则不需要。
写这篇文章的初衷呢就是最近遇到了一个spring事务的大坑.与其说是坑,还不如说是自己事务这块儿太薄弱导致的(自嘲下).
项目环境 sprinigboot
下面开始问题描述,发生的过程有点长,想直接看方案的直接跳过哦~;
最近在做项目中有个业务是每天定时更新xx的数据,某条记录更新中数据出错,不影响整体数据,只需记录下来并回滚当条记录所关联的表数据; 好啊,这个简单,接到任务后,楼主我三下五除二就写完了,由于这个业务还是有些麻烦,我就在一个service里拆成了两个方法去执行,一个
是查询数据与验证组装数据,另外一个
更新这条数据所对应的表(
);由于这个数据是循环更新,所以我想的是,一条数据更新失败直接回滚此条数据就是,不会影响其他数据,其他的照常更新,所以我就在
上加了事务,
没有加; 以为很完美,自测一下正常,ok通过,再测试一下报错情况,是否回滚,一测,没回滚,懵圈儿?.以为代码写错了,改了几处地方,再测了几次,均没回滚.这下是真难受了.
好啦,写到这里,相信各位看官心里肯定在嘲讽老弟了,spring的传播机制都没搞明白(/难受);
下面开始一步步分析解决问题:
首先我们来看下spring事务的传播机制及原因分析;
spring默认的是PROPAGATION_REQUIRED机制,如果
标注了注解
是完全没问题的,执行的时候传播给
,因为
开启了事务,线程内的connection的属性autoCommit=false,并且执行到
时,事务传播依然是生效的,得到的还是
的connection,autoCommit还是为false,所以事务生效;反之,如果方法A没有注解
时,是不受事务管理的,autoCommit=true,那么传播给方法B的也为true,执行完自动提交,即使B标注了
所以以上就是为什么我在
的
里去调用
的
而没有事务滚回的原因;
看到这里,有的看官可能在想,你在
上标个注解不就完了吗?为什么非要标注在
上?
由于我这里是循环更新数据,调用一次
就更新一次数据,涉及到几张表,需要执行几条update sql, 一条数据更新失败不影响所有数据,所以说一条数据更新执行完毕后就提交一次事务,如果标注在
上,要所有的都执行完毕了才提交事务,这样子是有问题滴.
下边先上下代码:
从上图可以看到,如果方法B中User更新出错后需要回滚RedPacket数据,所以User更新失败就抛出了继承自RuntimeException的自定义异常,并且在调用方把这个异常catch到重新抛出,触发事务回滚,但是并没有执行;
下面是解决方案:
1.把方法B抽离到另外一个XXService中去,并且在这个Service中注入XXService,使用XXService调用方法B;
显然,这种方式一点也不优雅,且要产生很多冗余文件,看起来很烦,实际开发中也几乎没人这么做吧?.反正我不建议采用此方案;
2.通过在方法内部获得当前类代理对象的方式,通过代理对象调用方法B
上面说了:
所以我们就使用代理对象来调用,就会触发事务;
综上解决方案,我觉得第二种方式简直方便到炸. 那怎么获取代理对象呢? 这里提供两种方式:
1.使用 ApplicationContext 上下文对象获取该对象;
2.使用 AopContext.currentProxy() 获取代理对象,但是需要配置exposeProxy=true
我这里使用的是第二种解决方案,具体操作如下:
springboot启动类加上注解:@EnableAspectJAutoProxy(exposeProxy = true)
方法内部获取代理对象调用方法
完了后再测试,数据顺利回滚,至此,问题得到解决!
都是事务这块儿基础太差的错啊~~希望各位遇到这种问题的兄弟些都好好的去研究研究spring这块儿,好了不说了,我也该去深造了!
扫一扫,分享内容
打赏作者
西风一任秋
你的鼓励将是我创作的最大动力
您的余额不足,请更换扫码支付或
抵扣说明:
1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。