在本文中,我们将详细介绍@Transactional注释在哪里?的各个方面,并为您提供关于@transactional注解的相关解答,同时,我们也将为您带来关于@Transactional注解属于哪里
在本文中,我们将详细介绍@Transactional注释在哪里?的各个方面,并为您提供关于@transactional注解的相关解答,同时,我们也将为您带来关于@Transactional 注解属于哪里?、@Transactional方法调用没有@Transactional注释的另一个方法?、@transactional注解下失效、@Transactional注解加不加 rollbackFor = Exception.class 的区别和@Transactional 注解失效的3种原因及解决办法的有用知识。
本文目录一览:- @Transactional注释在哪里?(@transactional注解)
- @Transactional 注解属于哪里?
- @Transactional方法调用没有@Transactional注释的另一个方法?
- @transactional注解下失效
- @Transactional注解加不加 rollbackFor = Exception.class 的区别和@Transactional 注解失效的3种原因及解决办法
@Transactional注释在哪里?(@transactional注解)
你应该将放置@Transactional
在DAO
类和/或它们的方法中,还是更好地注释使用DAO对象调用的Service类?还是对两个“层”都进行注释是否有意义?
答案1
小编典典我认为交易属于服务层。这是了解工作单元和用例的人。如果你将多个DAO注入到一个服务中,而这些DAO需要在单个事务中一起工作,那么这是正确的答案。
答案2
小编典典总的来说,我同意其他人的看法,即交易通常在服务级别上开始(当然,这取决于你所需的粒度)。
但是,与此同时,我也开始添加@Transactional(propagation = Propagation.MANDATORY)
到我的DAO层(以及其他不允许启动事务但需要现有事务的层),因为在你忘记在调用方中开始事务的地方,错误检测要容易得多(例如服务)。如果你的DAO带有强制传播注释,你将获得一个异常,指出在调用该方法时没有活动的事务。
我还有一个集成测试,在该测试中,我检查所有bean(bean后处理器)是否有此注释,如果@Transactional
在不属于服务层的bean中存在非强制传播的注释,则失败。这样,我确保我们不会在错误的层上启动事务。
@Transactional 注解属于哪里?
您应该将它们@Transactional
放在DAO
类和/或它们的方法中,还是更好地注释使用 DAO
对象调用的服务类?或者注释两个“层”是否有意义?
答案1
小编典典我认为事务属于服务层。它了解工作单元和用例。如果您将多个 DAO 注入到需要在单个事务中协同工作的服务中,那么这是正确的答案。
@Transactional方法调用没有@Transactional注释的另一个方法?
我在Service类中看到了一种被标记为的方法@Transactional
,但是它也在同一类中调用了其他未标为的方法@Transactional
。
这是否意味着对单独方法的调用导致应用程序打开与DB的单独连接或暂停父事务等?
不带任何注释的方法的默认行为是什么,而另一个带有@Transactional
注释的方法调用该方法的默认行为是什么?
@transactional注解下失效
这几天在项目里面发现我使用@Transactional注解事务之后,抛了异常居然不回滚。后来终于找到了原因。
如果你也出现了这种情况,可以从下面开始排查。
一、特性
先来了解一下@Transactional注解事务的特性吧,可以更好排查问题
1、service类标签(一般不建议在接口上)上添加@Transactional,可以将整个类纳入spring事务管理,在每个业务方法执行时都会开启一个事务,不过这些事务采用相同的管理方式。
2、@Transactional 注解只能应用到 public 可见度的方法上。 如果应用在protected、private或者 package可见度的方法上,也不会报错,不过事务设置不会起作用。
3、默认情况下,Spring会对unchecked异常进行事务回滚;如果是checked异常则不回滚。
辣么什么是checked异常,什么是unchecked异常
java里面将派生于Error或者RuntimeException(比如空指针,1/0)的异常称为unchecked异常,其他继承自java.lang.Exception得异常统称为Checked Exception,如IOException、TimeoutException等
辣么再通俗一点:你写代码出现的空指针等异常,会被回滚,文件读写,网络出问题,spring就没法回滚了。然后我教大家怎么记这个,因为很多同学容易弄混,你写代码的时候有些IOException我们的编译器是能够检测到的,说以叫checked异常,你写代码的时候空指针等死检测不到的,所以叫unchecked异常。这样是不是好记一些啦
4、只读事务:
@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
只读标志只在事务启动时应用,否则即使配置也会被忽略。
启动事务会增加线程开销,数据库因共享读取而锁定(具体跟数据库类型和事务隔离级别有关)。通常情况下,仅是读取数据时,不必设置只读事务而增加额外的系统开销。
二:事务传播模式
Propagation枚举了多种事务传播模式,部分列举如下:
-
1、REQUIRED(默认模式):业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务。
-
2、NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。
-
3、REQUIRESNEW:不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。
-
4、 MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。
-
5、SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。
-
6、NEVER:该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行。
-
7、NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。
上面引用至事务传播模式
三:解决Transactional注解不回滚
1、检查你方法是不是public的
2、你的异常类型是不是unchecked异常
如果我想check异常也想回滚怎么办,注解上面写明异常类型即可
@Transactional(rollbackFor=Exception.class)
类似的还有norollbackFor,自定义不回滚的异常
3、数据库引擎要支持事务,如果是MySQL,注意表要使用支持事务的引擎,比如innodb,如果是myisam,事务是不起作用的
4、是否开启了对注解的解析
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-/>
5、spring是否扫描到你这个包,如下是扫描到org.test下面的包
<context:component-scan base-package="org.test" ></context:component-scan>
6、检查是不是同一个类中的方法调用(如a方法调用同一个类中的b方法)
7、异常是不是被你catch住了
@Transactional注解加不加 rollbackFor = Exception.class 的区别和@Transactional 注解失效的3种原因及解决办法
目录
1、首先我在Mysql中准备了一条数据
2、简单粗暴的开始测试了
2.1 我们的目的是需要把delflag修改为0 简单的准备一下sql
2.2、我们先来测试一下@Transactional 代码如下 大家都知道2/0必会抛出异常
2.3、执行测试 i=1说明更新成功 别着急咱们继续断点往下面走
2.4、果然不出所料 执行到第54行的时候报错了 出现了java.lang.ArithmeticException: /by zero
2.5、细心的同学会发现ArithmeticException这个异常类是继承了RuntimeException的
2.6、我们在点进去RuntimeException这个类里面一探究竟 我们发现RuntimeException又是继承Exception的
2.7、这个时候我们去看一下数据库的值到底有没有修改成功 很显然数据是被回滚了 并没有修改成0
3、下面我们在试试@Transactional不能过滚的异常 代码如下
3.1 用try catch来捕获异常
3.2、ok直接 抛出的异常是我们指定的java.lang.Exception异常 我们去看看数据库
3.3、数据库被更新成0了 说明@Transactional并不能回滚Exception异常
4.Transactional失效场景介绍
4.1 第一种
4.2 第二种
4.3 第三种
5.@Transactional注解不起作用原理分析
第一种
第二种
第三种
1、首先我在MysqL中准备了一条数据
2、简单粗暴的开始测试了
2.1 我们的目的是需要把delflag修改为0 简单的准备一下sql
<update id="test">
UPDATE tbl_users set delflag='0' where account='admin'
</update>
2.2、我们先来测试一下@Transactional
代码如下 大家都知道2/0必会抛出异常
@Override
@Transactional
public Ret test(){
int i = articleMapper.test();
int a = 2/0;
if(i > 0){
ResultUtil.success();
}
return ResultUtil.error();
}
2.3、执行测试 i=1说明更新成功 别着急咱们继续断点往下面走
2.4、果然不出所料 执行到第54行的时候报错了 出现了java.lang.ArithmeticException: /by zero
2.5、细心的同学会发现ArithmeticException
这个异常类是继承了RuntimeException
的
而@Transactional
默认回滚的的异常就是RuntimeException
2.6、我们在点进去RuntimeException
这个类里面一探究竟 我们发现RuntimeException
又是继承Exception
的
而所有的异常类基本都是继承RuntimeException
包括刚才上面的java.lang.ArithmeticException
异常
所以只要是RuntimeException
和RuntimeException
下面的子类抛出的异常 @Transactional
都可以回滚的
2.7、这个时候我们去看一下数据库的值到底有没有修改成功 很显然数据是被回滚了 并没有修改成0
3、下面我们在试试@Transactional
不能过滚的异常 代码如下
3.1 用try catch
来捕获异常
我们直接先用try catch
来捕获异常 然后在catch里面自定义抛出Exception
异常
@Override
@Transactional
public Ret test() throws Exception {
int i = articleMapper.test();
try {
int a = 2 / 0;
} catch (Exception e) {
throw new Exception();
}
if (i > 0) {
ResultUtil.success();
}
return ResultUtil.error();
}
3.2、ok直接 抛出的异常是我们指定的java.lang.Exception
异常 我们去看看数据库
3.3、数据库被更新成0了 说明@Transactional
并不能回滚Exception异常
总结一下:
@Transactional
只能回滚RuntimeException
和RuntimeException
下面的子类抛出的异常 不能回滚Exception
异常
如果需要支持回滚Exception
异常请用@Transactional(rollbackFor = Exception.class)
这里如果是增删改的时候我建议大家都使用@Transactional(rollbackFor = Exception.class)
4.Transactional失效场景介绍
4.1 第一种
Transactional注解标注方法修饰符为非public时,@Transactional
注解将会不起作用。例如以下代码。
定义一个错误的@Transactional
标注实现,修饰一个默认访问符的方法
/**
* @author zhoujy
**/
@Component
public class TestServiceImpl {
@Resource
TestMapper testMapper;
@Transactional
void insertTestWrongModifier() {
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}
}
在同一个包内,新建调用对象,进行访问。
@Component
public class InvokcationService {
@Resource
private TestServiceImpl testService;
public void invokeInsertTestWrongModifier(){
//调用@Transactional标注的默认访问符方法
testService.insertTestWrongModifier();
}
}
测试用例
@RunWith(springrunner.class)
@SpringBoottest
public class DemoApplicationTests {
@Resource
InvokcationService invokcationService;
@Test
public void testInvoke(){
invokcationService.invokeInsertTestWrongModifier();
}
}
以上的访问方式,导致事务没开启,因此在方法抛出异常时,testMapper.insert(new Test(10,20,30));
操作不会进行回滚。如果TestServiceImpl#insertTestWrongModifier
方法改为public的话将会正常开启事务,testMapper.insert(new Test(10,20,30));
将会进行回滚。
4.2 第二种
在类内部调用调用类内部@Transactional
标注的方法。这种情况下也会导致事务不开启。示例代码如下。
设置一个内部调用
/**
* @author zhoujy
**/
@Component
public class TestServiceImpl implements TestService {
@Resource
TestMapper testMapper;
@Transactional
public void insertTestInnerInvoke() {
//正常public修饰符的事务方法
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}
public void testInnerInvoke(){
//类内部调用@Transactional标注的方法。
insertTestInnerInvoke();
}
}
测试用例。
@RunWith(springrunner.class)
@SpringBoottest
public class DemoApplicationTests {
@Resource
TestServiceImpl testService;
/**
* 测试内部调用@Transactional标注方法
*/
@Test
public void testInnerInvoke(){
//测试外部调用事务方法是否正常
//testService.insertTestInnerInvoke();
//测试内部调用事务方法是否正常
testService.testInnerInvoke();
}
}
上面就是使用的测试代码,运行测试知道,外部调用事务方法能够征程开启事务,testMapper.insert(new Test(10,20,30))
操作将会被回滚;
然后运行另外一个测试用例,调用一个方法在类内部调用内部被@Transactional
标注的事务方法,运行结果是事务不会正常开启,testMapper.insert(new Test(10,20,30))
操作将会保存到数据库不会进行回滚。推荐:Java面试练题宝典
4.3 第三种
事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚。示例代码如下。
/**
* @author zhoujy
**/
@Component
public class TestServiceImpl implements TestService {
@Resource
TestMapper testMapper;
@Transactional
public void insertTestCatchException() {
try {
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
//运行期间抛异常
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}catch (Exception e){
System.out.println("i catch exception");
}
}
}
测试用例代码如下。
@RunWith(springrunner.class)
@SpringBoottest
public class DemoApplicationTests {
@Resource
TestServiceImpl testService;
@Test
public void testCatchException(){
testService.insertTestCatchException();
}
}
运行测试用例发现,虽然抛出异常,但是异常被捕捉了,没有抛出到方法 外, testMapper.insert(new Test(210,20,30))
操作并没有回滚。
以上三种就是@Transactional
注解不起作用,@Transactional
注解失效的主要原因。下面结合spring中对于@Transactional
的注解实现源码分析为何导致@Transactional
注解不起作用。
5.@Transactional注解不起作用原理分析
5.1 第一种
@Transactional
注解标注方法修饰符为非public时,@Transactional
注解将会不起作用。这里分析 的原因是,@Transactional
是基于动态代理实现的,@Transactional
注解实现原理中分析了实现方法,在bean初始化过程中,对含有@Transactional
标注的bean实例创建代理对象,这里就存在一个spring扫描@Transactional
注解信息的过程,不幸的是源码中体现,标注@Transactional
的方法如果修饰符不是public,那么就默认方法的@Transactional
信息为空,那么将不会对bean进行代理对象创建或者不会对方法进行代理调用
@Transactional
注解实现原理中,介绍了如何判定一个bean是否创建代理对象,大概逻辑是。根据spring创建好一个aop切点beanfactoryTransactionAttributeSourceAdvisor
实例,遍历当前bean的class的方法对象,判断方法上面的注解信息是否包含@Transactional
,如果bean任何一个方法包含@Transactional
注解信息,那么就是适配这个beanfactoryTransactionAttributeSourceAdvisor
切点。则需要创建代理对象,然后代理逻辑为我们管理事务开闭逻辑。
spring源码中,在拦截bean的创建过程,寻找bean适配的切点时,运用到下面的方法,目的就是寻找方法上面的@Transactional
信息,如果有,就表示切点beanfactoryTransactionAttributeSourceAdvisor
能够应用(canApply)到bean中,
AopUtils#canApply(org.springframework.aop.pointcut, java.lang.class<?>, boolean)
public static boolean canApply(pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
Assert.notNull(pc, "pointcut must not be null");
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}
MethodMatcher methodMatcher = pc.getmethodMatcher();
if (methodMatcher == MethodMatcher.TRUE) {
// No need to iterate the methods if we're matching any method anyway...
return true;
}
IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
}
//遍历class的方法对象
Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
classes.add(targetClass);
for (Class<?> clazz : classes) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method method : methods) {
if ((introductionAwareMethodMatcher != null &&
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
//适配查询方法上的@Transactional注解信息
methodMatcher.matches(method, targetClass)) {
return true;
}
}
}
return false;
}
我们可以在上面的方法打断点,一步一步调试跟踪代码,最终上面的代码还会调用如下方法来判断。在下面的方法上断点,回头看看方法调用堆栈也是不错的方式跟踪。推荐:Java面试练题宝典
AbstractFallbackTransactionAttributeSource#getTransactionAttribute
-
AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
// Don't allow no-public methods as required.
//非public 方法,返回@Transactional信息一律是null
if (allowPublicmethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
//后面省略.......
}
不创建代理对象
所以,如果所有方法上的修饰符都是非public的时候,那么将不会创建代理对象。以一开始的测试代码为例,如果正常的修饰符的testService是下面图片中的,经过cglib创建的代理对象。
如果class中的方法都是非public的那么将不是代理对象。
不进行代理调用
考虑一种情况,如下面代码所示。两个方法都被@Transactional
注解标注,但是一个有public修饰符一个没有,那么这种情况我们可以预见的话,一定会创建代理对象,因为至少有一个public修饰符的@Transactional
注解标注方法。
创建了代理对象,insertTestWrongModifier
就会开启事务吗?答案是不会。
/**
* @author zhoujy
**/
@Component
public class TestServiceImpl implements TestService {
@Resource
TestMapper testMapper;
@Override
@Transactional
public void inserttest() {
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}
@Transactional
void insertTestWrongModifier() {
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}
}
原因是在动态代理对象进行代理逻辑调用时,在cglib创建的代理对象的拦截函数中cglibAopProxy.DynamicAdvisedInterceptor#intercept
,有一个逻辑如下,目的是获取当前被代理对象的当前需要执行的method适配的aop逻辑。
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
而针对@Transactional注解查找aop逻辑过程,相似地,也是执行一次
AbstractFallbackTransactionAttributeSource#getTransactionAttribute
-
AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
也就是说还需要找一个方法上的@Transactional
注解信息,没有的话就不执行代理@Transactional
对应的代理逻辑,直接执行方法。没有了@Transactional
注解代理逻辑,就无法开启事务,这也是上一篇已经讲到的。
5.2 第二种
在类内部调用调用类内部@Transactional
标注的方法。这种情况下也会导致事务不开启。
经过对第一种的详细分析,对这种情况为何不开启事务管理,原因应该也能猜到;
既然事务管理是基于动态代理对象的代理逻辑实现的,那么如果在类内部调用类内部的事务方法,这个调用事务方法的过程并不是通过代理对象来调用的,而是直接通过this对象来调用方法,绕过的代理对象,肯定就是没有代理逻辑了。
其实我们可以这样玩,内部调用也能实现开启事务,代码如下。
/**
* @author zhoujy
**/
@Component
public class TestServiceImpl implements TestService {
@Resource
TestMapper testMapper;
@Resource
TestServiceImpl testServiceImpl;
@Transactional
public void insertTestInnerInvoke() {
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}
public void testInnerInvoke(){
//内部调用事务方法
testServiceImpl.insertTestInnerInvoke();
}
}
上面就是使用了代理对象进行事务调用,所以能够开启事务管理,但是实际操作中,没人会闲的蛋疼这样子玩~
5.3 第三种
事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚。
这种的话,可能我们比较常见,问题就出在代理逻辑中,我们先看看源码里卖弄动态代理逻辑是如何为我们管理事务的。
TransactionAspectSupport#invokeWithinTransaction
代码如下。
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
//开启事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
//反射调用业务方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
//异常时,在catch逻辑中回滚事务
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
//提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
//....................
}
}
所以看了上面的代码就一目了然了,事务想要回滚,必须能够在这里捕捉到异常才行,如果异常中途被捕捉掉,那么事务将不会回滚。
我们今天的关于@Transactional注释在哪里?和@transactional注解的分享已经告一段落,感谢您的关注,如果您想了解更多关于@Transactional 注解属于哪里?、@Transactional方法调用没有@Transactional注释的另一个方法?、@transactional注解下失效、@Transactional注解加不加 rollbackFor = Exception.class 的区别和@Transactional 注解失效的3种原因及解决办法的相关信息,请在本站查询。
本文标签: