Spring的@Transactional(readOnly=true)注解,对其效果进行测试

时间:2024-05-30 16:08:38

(内容有点乱 之后整理后重新发)

温馨提示:结论在底部,如果没有耐心看证明的话可以直接拉到最底部

 

今天在使用阿里代码规范插件检查代码的时候发现,代码提示有问题。

Spring的@Transactional(readOnly=true)注解,对其效果进行测试

错误提示:注解【Transactional】需要设置rollbackFor属性。

我就想,既然是阿里的插件提示的应该比较权威,那我就把这个属性加上去把。

但是转念一想,如果要加的话该怎么加呢,会不会和项目有冲突,我们项目里所有的service类上面都统一加了

@Transactional(readOnly=true)这个注解。

于是就去网上找,看看有没有联合使用的。

但是找到的是这样的:

        @Transactional(readOnly=false , propagation = Propagation.REQUIRED , rollbackFor = Throwable.class)

也就是说readOnly为true的情况下,rollbackFor可能是不需要加的。

但是阿里代码规范插件说@Transactional必须要加rollbackFor这个啊。

我想我需要搞清楚@Transactional(readOnly=true)这个设置的含义是什么:

搜索上说:

    【@Transactional(readOnly=true)

        从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!(查询中不会出现别人在时间点a之后提交的数据)】注1

但是他还没说只读是否就不需要回滚了,而且我也想试试看是否真如上面说的

于是我就去对代码进行测试:

测试方式是:

在添加@Transactional(readOnly=true)的service中对数据库进行两次相同的查询(一个方法中),两次查询中间对数据库数据进行修改(commit),然后移除@Transactional(readOnly=true)后再进行一次相同的操作

测试方法1:

Spring的@Transactional(readOnly=true)注解,对其效果进行测试

测试方法2:

Spring的@Transactional(readOnly=true)注解,对其效果进行测试

前者得到的结果是:6,6 (根据这个测试结果我可以暂且认为上面的说法是正确的,注1

后者得到的结果也是:6,6

但是为什么删除@Transactional(readOnly=true)注解得到的也是同样的结果呢,会不会是注解可以被继承。

Spring的@Transactional(readOnly=true)注解,对其效果进行测试

于是找到了CrudService进去后果然,有这个注解,同样的在BaseService中也存在这个注解

Spring的@Transactional(readOnly=true)注解,对其效果进行测试

那接下来的问题就是注解是不是可以被继承了

网上搜到的链接地址:https://elf8848.iteye.com/blog/1621392

为防止链接失效原文内容如下

 

----------------------原文分割线  begin----------------------------------------------------------------------------------------------------------------------------------------------------

 

子类可以继承到父类上的注解吗?

-----------------------------------------------------------------

我们知道在编写自定义注解时,可以通过指定@Inherited注解,指明自定义注解是否可以被继承。但实现情况又可细分为多种。

 

 

测试环境如下:

-----------------------------------------------------------------

父类的类上和方法上有自定义的注解--MyAnnotation

子类继承了这个父类,分别:

子类方法,实现了父类上的抽象方法

子类方法,继承了父类上的方法

子类方法,覆盖了父类上的方法

 

 

MyAnnotation自定义注解

-----------------------------------------------------------------

Java代码  Spring的@Transactional(readOnly=true)注解,对其效果进行测试

  1. package test.annotation;  

  2. import java.lang.annotation.Inherited;  

  3. import java.lang.annotation.Retention;  

  4. /** 

  5.  * 自定义注解 

  6.  */  

  7. //@Inherited  //可以被继承  

  8. @Retention(java.lang.annotation.RetentionPolicy.RUNTIME)  //可以通过反射读取注解  

  9. public @interface MyAnnotation {    

  10.     String value();    

  11. }   

 

 

父类

-----------------------------------------------------------------

Java代码  Spring的@Transactional(readOnly=true)注解,对其效果进行测试

  1. package test.annotation;  

  2. @MyAnnotation(value = "类名上的注解")  

  3. public abstract class ParentClass {  

  4.   

  5.     @MyAnnotation(value = "父类的abstractMethod方法")  

  6.     public abstract void abstractMethod();  

  7.   

  8.     @MyAnnotation(value = "父类的doExtends方法")  

  9.     public void doExtends() {  

  10.         System.out.println(" ParentClass doExtends ...");  

  11.     }  

  12.       

  13.     @MyAnnotation(value = "父类的doHandle方法")  

  14.     public void doHandle(){  

  15.         System.out.println(" ParentClass doHandle ...");  

  16.     }  

  17. }  

 

 

子类

-----------------------------------------------------------------

Java代码  Spring的@Transactional(readOnly=true)注解,对其效果进行测试

  1. package test.annotation;  

  2. public class SubClass extends ParentClass{    

  3.     

  4.     //子类实现父类的抽象方法  

  5.     @Override    

  6.     public void abstractMethod() {    

  7.         System.out.println("子类实现父类的abstractMethod抽象方法");    

  8.     }    

  9.       

  10.     //子类继承父类的doExtends方法  

  11.       

  12.     //子类覆盖父类的doHandle方法  

  13.     @Override    

  14.     public void doHandle(){  

  15.         System.out.println("子类覆盖父类的doHandle方法");   

  16.     }  

  17. }   

 

 

测试类

-----------------------------------------------------------------

Java代码  Spring的@Transactional(readOnly=true)注解,对其效果进行测试

  1. package test.annotation;  

  2.   

  3. import java.lang.reflect.Method;  

  4.   

  5. public class MainTest {  

  6.     public static void main(String[] args) throws SecurityException,  

  7.             NoSuchMethodException {  

  8.   

  9.         Class<SubClass> clazz = SubClass.class;  

  10.   

  11.         if (clazz.isAnnotationPresent(MyAnnotation.class)) {  

  12.             MyAnnotation cla = clazz  

  13.                     .getAnnotation(MyAnnotation.class);  

  14.             System.out.println("子类继承到父类类上Annotation,其信息如下:"+cla.value());  

  15.         } else {  

  16.             System.out.println("子类没有继承到父类类上Annotation");  

  17.         }  

  18.   

  19.         // 实现抽象方法测试  

  20.         Method method = clazz.getMethod("abstractMethod"new Class[] {});  

  21.         if (method.isAnnotationPresent(MyAnnotation.class)) {  

  22.             MyAnnotation ma = method  

  23.                     .getAnnotation(MyAnnotation.class);  

  24.             System.out.println("子类实现父类的abstractMethod抽象方法,继承到父类抽象方法中的Annotation,其信息如下:"+ma.value());  

  25.         } else {  

  26.             System.out.println("子类实现父类的abstractMethod抽象方法,没有继承到父类抽象方法中的Annotation");  

  27.         }  

  28.   

  29.         //覆盖测试  

  30.         Method methodOverride = clazz.getMethod("doExtends"new Class[] {});  

  31.         if (methodOverride.isAnnotationPresent(MyAnnotation.class)) {  

  32.             MyAnnotation ma = methodOverride  

  33.                     .getAnnotation(MyAnnotation.class);  

  34.             System.out  

  35.                     .println("子类继承父类的doExtends方法,继承到父类doExtends方法中的Annotation,其信息如下:"+ma.value());  

  36.         } else {  

  37.             System.out.println("子类继承父类的doExtends方法,没有继承到父类doExtends方法中的Annotation");  

  38.         }  

  39.   

  40.         //继承测试  

  41.         Method method3 = clazz.getMethod("doHandle"new Class[] {});  

  42.         if (method3.isAnnotationPresent(MyAnnotation.class)) {  

  43.             MyAnnotation ma = method3  

  44.                     .getAnnotation(MyAnnotation.class);  

  45.             System.out  

  46.                     .println("子类覆盖父类的doHandle方法,继承到父类doHandle方法中的Annotation,其信息如下:"+ma.value());  

  47.         } else {  

  48.             System.out.println("子类覆盖父类的doHandle方法,没有继承到父类doHandle方法中的Annotation");  

  49.         }  

  50.     }  

  51. }  

  

 

编写自定义注解时未写@Inherited的运行结果

-----------------------------------------------------------------

子类没有继承到父类类上Annotation

子类实现父类的abstractMethod抽象方法,没有继承到父类抽象方法中的Annotation

子类继承父类的doExtends方法,继承到父类doExtends方法中的Annotation,其信息如下:父类的doExtends方法

子类覆盖父类的doHandle方法,没有继承到父类doHandle方法中的Annotation

 

 

编写自定义注解时写了@Inherited的运行结果

-----------------------------------------------------------------

子类继承到父类类上Annotation,其信息如下:类名上的注解

子类实现父类的abstractMethod抽象方法,没有继承到父类抽象方法中的Annotation

子类继承父类的doExtends方法,继承到父类doExtends方法中的Annotation,其信息如下:父类的doExtends方法

子类覆盖父类的doHandle方法,没有继承到父类doHandle方法中的Annotation

 

 

结论

-----------------------------------------------------------------

 

父类的类上和方法上有自定义的注解,

子类继承了这个父类,的情况下。

 

 

 

编写自定义注解时未写@Inherited的运行结果:

编写自定义注解时写了@Inherited的运行结果:

子类的类上能否继承到父类的类上的注解?

子类方法,实现了父类上的抽象方法,这个方法能否继承到注解?

子类方法,继承了父类上的方法,这个方法能否继承到注解?

子类方法,覆盖了父类上的方法,这个方法能否继承到注解?

我们知道在编写自定义注解时,可以通过指定@Inherited注解,指明自定义注解是否可以被继承。

通过测试结果来看,@Inherited 只是可控制 对类名上注解是否可以被继承。不能控制方法上的注解是否可以被继承。

 

 

附注

-----------------------------------------------------------------

Spring 实现事务的注解@Transactional 是可以被继承的,

通过查看它的源码可以看到@Inherited。

 

----------------------原文分割线  end----------------------------------------------------------------------------------------------------------------------------------------------------

也就是说注解是可以被继承的,第一次测试和第二次测试结果相同的原因也找到了。

那接下来就看@Transactional(readOnly=true)具体实现的效果如何了,

之前在测试的时候发现,再调用dao方法dao.findMaxFloorId(officeId)的时候,只有在第一调用的时候才会查询数据库,

第二次就不会调用数据库了,猜想spring的@Transactional(readOnly=true)实现效果是否就是,对相同的方法返回同样的结果。

证明:

    测试一:

        于是再写一个dao.findMaxFloorIdB(officeId)方法,该方法与dao.findMaxFloorId(officeId)方法的sql语句相同,唯一的区别是方法名不同。再次进行之前的添加@Transactional(readOnly=true)注解的测试后

        Spring的@Transactional(readOnly=true)注解,对其效果进行测试

 

    得到的结果:6,7 (证明了调用不同方法,即使sql语句相同也会返回不同结果,@Transactional(readOnly=true)只作用到,不会辨别sql语句

测试二:

    将MallFloorService和它的所有父类的@Transactional(readOnly=true)注解都删除,CrudService和BaseService的@Transactional(readOnly=true)注解删除

    测试调用的两次查询方法都为dao.findMaxFloorId(officeId)时

Spring的@Transactional(readOnly=true)注解,对其效果进行测试

Spring的@Transactional(readOnly=true)注解,对其效果进行测试

得到的结果:6,7 (证明了调用相同方法,返回相同结果是由@Transactional(readOnly=true)支持的。经过后面的测试后发现这个结论是有问题的)

 

后面我又做了保存操作的测试:

测试1:

@Transactional(readOnly=true)

public void save (Goods goods){

    dao.insert(goods);

    Integer.parseInt("ssss");//抛出异常用

}

测试结果:1、开启事务2、插入数据后3、抛出异常4、数据回滚未保存

@Transactional(readOnly=false)

public void save (Goods goods){

    dao.insert(goods);

    Integer.parseInt("ssss");//抛出异常用

}

测试结果:1、开启事务2、插入数据后3、抛出异常4、数据回滚未保存

两者效果相同(测试环境是ORACLE数据库)

当时的第一反应是为什么明明设置了只读,却还是能够保存数据呢,二是:为什么readonly=true和false好像没有明显区别

网上搜索后找到了原因:readonly=true对ORACLE数据库而言只是声明了事务,但并没有起到只读事务的作用,仍然可以读写

原文网址:https://juejin.im/post/5b0cc0e751882515387bee85

原文内容:

 

Spring Transcation 只读事务对 mysql 、oracle 的支持

上次在学习项目的过程中发现了一个这样的配置,方法事务上@Transactional(value = "transactionManager",readOnly = true)增加了一个readOnly = true的配置,很好奇这有什么用?原理是什么?下面我将一一说说它是什么以及在mysql、oracle中的区别。

 

事务分为只读事务和读写事务,我们第一反应会想到的含义是只读事务不允许你在一个事务中执行诸如update、delete、insert的操作,你只能执行select操作,但是,其实不是,只读事务并不是必须的,你同样可以在只读事务中执行修改操作,只不过显示声明事务为只读模式,会让相应的数据库对你的事务进行一些优化操作而已。

 

要演示只读事务对 mysql、oracle 的支持,首先要做的就是把例子给搭建起来,下面我将描述相关测试用例:

 

一、声明式配置 Spring 只读事务

Spring 声明式事务分为两种,一种是基于<tx>、<aop:config>标签的事务声明,另外一种是基于@Transacional的注解声明。我们采用第二种方式声明。

 

很显然,既然你要连接到 mysql、oracle 数据库,那么你就需要相应的数据库驱动程序;由于采用 Spring 进行事务管理,那么相应的Spring 包必不可少;当然你还需要一个数据源,告诉相应的驱动程序你的数据从哪里来;最后,我就不贴出代码了,代码太多,直接在github上看即可。

 

代码github地址

 

二、测试 Spring 对 mysql 的支持

mysql 提供对只读事务的支持,事务的默认隔离级别为“可重复读”。

 

编号    是否加事务    是否执行更新操作    事务是否只读    执行结果

1        否            否                            多次执行查询,结果随数据库改变而改变

2        否            是                            多次执行查询,结果随数据库改变而改变

3        是            否                否          多次执行查询,结果不随数据库改变而改变,读视图一致

4        是            是                否          多次执行查询,结果不随数据库改变而改变,读视图一致

5        是            否                是          多次执行查询,结果不随数据库改变而改变,读视图一致

6        是            是                是          执行更会报错,提示 readOnly

三、测试 Spring 对 oracle 的支持

oracle 提供对只读事务的支持,事务的默认隔离级别为“read committed”。

 

编号    是否加事务    是否执行更新操作    事务是否只读    执行结果

1        否            否                            多次执行查询,结果随数据库改变而改变

2        否            是                            多次执行查询,结果随数据库改变而改变

3        是            否                否          多次执行查询,结果随数据库改变而改变

4        是            是                否          多次执行查询,结果随数据库改变而改变

5        是            否                是          多次执行查询,结果随数据库改变而改变

6        是            是                是          结果随数据库改变而改变

有此可见,Spring 设置的readOnly事务属性对oracle来说是无效的。

 

然后。既然ORACLE中只读效果实际是不存在的,我再测试下@Transactional(readOnly=false)对于查询方法的影响

@Transactional(readOnly=false)

public void findMaxSort(){

    Integer a=dao.findMaxSort();

    Integer b=dao.findMaxSort();

}

测试方法同上两次查询中间对数据进行修改

结果:6,6.进行到这里得到的结论是,Spring开启事务后,对于相同查询方法不会再次调用数据库查询

结论:spring的@Transactional注解实现效果:根据不同的参数开启不同的事务,开启事务后,Spring多次调用相同查询方法仅在第一次时调用数据库,并且该注解可被子类继承,在ORCALE数据库中readOnly=true并没有只读效果,该注解下的方法仍然可以做增、删、改操作,readOnly=true和=false对于oracle数据库没有区别

也就是说,现在的项目里写了很多多余的@Transactional(readOnly=true),因为基类BaseService 已经注解了@Transactional(readOnly=true),那它的子类们就不需要再写重复的代码,并且类上的readOnly=true和保存方法上的readOnly=false并没有任何意义,除非项目使用的是MYSQL数据库