新项目试运行,DBA提示生产数据库一个表的事务20分钟都未提交,分析过程如下:
1.查看日志log文件,最近20分钟是否有error日志;
2.发现某表有insert错误日志,初步判断由该表插入异常,并且未做rollback操作;
3.查看代码:该表的操作DAO、Service,事务处理为Spring的声明式事务处理,并控制到Service层;
4.场景还原:
打开Spring的debug日志(查看数据库conn打开、commit、rollback必须启用debug)
模拟插入异常数据
打断点测试。。。。
5.异常日志重现,分析日志发现未有事务的任何处理(默认以来数据库的自动事务提交);
6.问题原因初步判定为:Spring声明式事务代理失败
代码分析:
1. 配置文件
因为事务配置在service层,直接看service的配置:
<!-- 业务事务管理服务 --> <bean id="transactionManager_masopen" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource_masopen"></property> </bean> <!-- 【BIZ业务逻辑层Service事务代理模板 - Tx Proxy Template】 --> <bean id="txProxyService_masopen" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true"> <property name="transactionManager" ref="transactionManager_masopen" /> <property name="proxyTargetClass" value="false" /> <property name="transactionAttributes"> <props> <prop key="get*">PROPAGATION_SUPPORTS,-Throwable,readOnly</prop> <prop key="query*">PROPAGATION_SUPPORTS,-Throwable,readOnly</prop> <prop key="find*">PROPAGATION_SUPPORTS,-Throwable,readOnly</prop> <prop key="count*">PROPAGATION_SUPPORTS,-Throwable,readOnly</prop> <prop key="update*">PROPAGATION_REQUIRED,-Throwable,ISOLATION_READ_COMMITTED</prop> <prop key="insert*">PROPAGATION_REQUIRED,-Throwable,ISOLATION_READ_COMMITTED</prop> <prop key="finish*">PROPAGATION_REQUIRED,-Throwable,ISOLATION_READ_COMMITTED</prop> <prop key="batch*">PROPAGATION_REQUIRED,-Throwable,ISOLATION_READ_COMMITTED</prop> <prop key="*">PROPAGATION_REQUIRED,-Throwable,ISOLATION_READ_COMMITTED</prop> </props> </property> </bean> <!-- 事务控制定义 --> <bean id="orderService_masopen" parent="txProxyService_masopen"> <property name="target" ref="orderService"/> </bean>
解释:
a. 定义了一个处理事务的模板txProxyService_masopen,任何需要做事务处理的service类可直接使用该template
(注意:区别于直接把所有的事务放XX目录下所有类的配置)
-Throwable 就是对rollback的配置,一般也配置为-Exception
-表示抛出该异常时需要回滚
+表示即使抛出该异常事务同样要提交
Throwable就所有exception的父类,只要有异常就会rollback
b.orderService做了代理类orderService_masopen
2. 代码
public interface OrderService { 。。。略。。。。 @Service("orderService") public class OrderServiceImpl implements OrderService { /** * 订单数据库操作DAO */ @Resource private OrderDao orderDao_masopen; 。。。略。。。。
定义了OrderService接口,然后OrderServiceImpl实现了该接口,并通过annotation设置资源name=orderService(上面1里面代理类ref的就是该bean)
3.service接口调用
/** * 订单数据库操作Service */ @Resource private OrderService orderService_masopen; 。。。略。。。。 logger.info("落地日志信息:" + context.getLogs()); int[] flag2 = logService_masopen.batchInsertLog(context.getLogs()); int[] updateFlag = checkBatchUpdateResult(flag2); logger.info("日志记录总数:{},更新成功:{},更新失败:{}", context.getLogs().size(), updateFlag[0], updateFlag[1]);
其他业务类中注入该service,并在业务逻辑中调用(典型的短事务)
问题来了:
正常注入了service
@Resource
private OrderService orderService_masopen;
调用该资源没有事务处理,原因在哪???
4. 单步调试
调用该方法时,查看被代理的service,发现不是被代理的对象:
分析:
接口:OrderService
实现类:OrderServiceImpl
代理类:orderService_masopen
调用注入:
@Resource
private OrderService orderService_masopen;
很明显,问题出在注入该service的地方,通过resource自动注入的OrderService为它的实现类OrderServiceImpl,而不是代理类orderService_masopen
首先:
spring中,声明了2个bean:实现类OrderServiceImpl、代理类orderService_masopen
可是注入时候,只识别了实现类,没有识别代理类
5. 修改方案
在Spring中值声明一个代理类,防止识别错误
a. 第一步:移除实现类OrderServiceImpl的annotation
//@Service("orderService") --移除 public class OrderServiceImpl implements OrderService { 。。。略。。。。
b.第二步:配置文件中不使用ref指定bean,直接写死bean路径,
<!-- 事务控制定义 -->
<bean id="orderService_masopen" parent="txProxyService_masopen">
<property name="target" >
<bean class="com.shengpay.masopen.domain.service.impl.OrderServiceImpl"></bean>
</property>
</bean>
测试并查看日志,成功使用代理类:注意标红的$Proxy12
6. 问题原因总结
为何Spring声明了2个Bean,注入时候偏偏不能够识别代理的bean呢。
错误就出在resource这个annotaion的使用上:
@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入。
@Resource装配顺序
(1). 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常;
(2). 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常;
(3). 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常;
(4). 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
如果仍旧声明两个bean,有仍要使用resource注入,只要简单指定name即可,准确的识别为代理类,代码如下:
/** * 订单数据库操作Service */ @Resource(name="orderService_masopen") private OrderService orderService_masopen;