有些弹簧相关的知识点之前一直没有仔细研究:比如春天的事务,并不是没有使用,也曾经简单的在某些需要事务处理的方法上通过增加事务注解来实现事务功能,仅仅是跟随使用(甚至并未测试过事务的正确性),至于如何在项目中配置事务,如何才能将事务写正确,事务的其它的一些原理性的东西从未花时间研究。最近同事正好抛出了一个问题,借此机会学习了一遍。
<强>问题一:增加了只读的=true的事务中包含写操作,为什么线上运行这段代码是正常的呢?
强>
@ transactional (readOnly=true) 公共整数getUID(字符串,字符串类型){ keyGeneratorDao。插入(关键、类型); keyGeneratorDao。更新(关键、类型); keyGeneratorDao返回。选择(关键、类型); }
我为什么对这个问题感兴趣?
不懂这个只读的参数的含义,之前写@ transactional的注解,那都是使用的默认值,不带显示参数。提出配置了只读的参数后,理论上应该程序报错而实际上没有报的错,想搞清楚为什么。
开始写单元测试:
在单元测试类中写事务函数以及测试方法
@ autowired 私人IkeyGeneratorDao keyGeneratorDao; @ transactional (readOnly=true) 公共整数getId(字符串,字符串类型){ keyGeneratorDao返回。选择(关键、类型); } @ transactional 公共整数getUID(字符串,字符串类型){ keyGeneratorDao。插入(关键、类型); keyGeneratorDao。更新(关键、类型); 返回this.getId(关键、类型); } @Test 公共空间testCreateGuid () { int guid=etUID(" 12345 ", "吉姆"); System.out.println (guid); }
测试结果显示正常,与上面提到的不允许进行写操作的观点相反,于是想起典型的事务生效问题。
挖的第一个坑:如果事务采用的是cglib动态代理,调用的方法与事务方法处在同一个类中事务不生效。
将两个事务事务转移到单独的类中,然后测试,类代码省略,只是将上面两个标记了@ transactional的方法封装在一个单独的类中。
@ autowired 私人KeyGeneratorService keyService; @Test 公共空间testCreateGuid2 () { int guid=this.keyService。getUID(" 12345 ", "吉姆"); System.out.println (guid); }
测试结果显示也是正常,于是想确认下事务到底是否生效,加入异常以测试数据是否回滚,修改代码如下:
@ transactional 公共整数getUID3(字符串,字符串类型){ keyGeneratorDao。插入(关键、类型); Integer.parseInt (" aaa ");//抛出异常 keyGeneratorDao。更新(关键、类型); }
测试结果显示事务回滚正常,可以排除事务环境配置问题。
挖的第二个坑:做测试一定要与原问题代码尽量保持一致,否则会产生其它的不明原因影响判断。通过对比原问题的代码发现我写的测试代码与问题代码有区别,只读的是加在包含有写操作的方法上,而我的是两个方法,只有在读的方法上增加了只读的,于时再次修改代码:
@ transactional (readOnly=true) 公共整数getUID4(字符串,字符串类型){ keyGeneratorDao。插入(关键、类型); keyGeneratorDao。更新(关键、类型); keyGeneratorDao返回。选择(关键、类型); }
测试结果显示运行不正常,提示如下错误:原因:java.sql。SQLException异常:连接是只读的。查询导致数据修改是不允许的,到这的确说明在在加了只读的=true的事务内是不允许写入操作的。为什么这段代码在线上运行是成功的呢,于时查看前端的调用,发现前端调用的并不是直接标识了事务的方法,而是根据不同的具体业务重新包装的方法,比如我们需要生成订单的编号,前端只调用genOrderCode而不调用getUID。
非事务方法在内部调用了本类事务方法,然后非事务方法被外部调用ServicegenOrderCode,是一个非事务方法,内部调用了getUIDgetUID,是一个事务方法
@ autowired 私人IkeyGeneratorDao keyGeneratorDao; @ transactional (readOnly=true) 公共整数getUID(字符串,字符串类型){ keyGeneratorDao。插入(关键、类型); keyGeneratorDao。更新(关键、类型); keyGeneratorDao返回。选择(关键、类型); } orderDate公共字符串genOrderCode(日期) { SimpleDateFormat df=new SimpleDateFormat (“yyMMddHH”); 字符串ticketDate=df.format(向); 整数uid=getUID (ticketDate ORDER_CODE); 返回ticketDate + genRandom2 (uid.toString (), 3、3); }春天事务相关问题解决方案