问题描述
项目中涉及充值、扣费逻辑,由于习惯,所以所有的业务逻辑都是通过Mysql存储过程来控制的,所以很自然的将Mysql设置为手动提交(全局的),然后存储过程中根据返回码决定是提交还是回滚。但是今天出现一个存储过程返回错误码,但是部分事务提交了,研究了半天,发现并没有任务问题,手动调用存储过程是返回错误码,但是不会部分提交事务。于是决定找下问题,不找不知道,一找吓一跳。
(先说明下,项目未使用Spring的事务管理,而是通过存储过程手动控制事务,所以此处与Spring的事务管理无任何关系。)
由于经过测试排除了数据库层面的问题,那么问题只能出现在程序层了。
问题发现
项目使用的Spring配置的JBoss的JNDI数据源,JBoss也比较老(是很老),通过使用Spring执行select @@autocommit;发现autocommit竟然是1(自动提交事务),但是明明已经在数据库层将autocommit设置为0(手动提交事务)了,很显然Connection中主动设置了当前会话的autocommit。
问题深究
由于Connection来自DataSource,而Spring中对Connection并未做任何的封装,同时通过Debug发现Spring获得的Connection是org.jboss.resource.adapter.jdbc.WrappedConnection类,看来是JBoss的JNDI数据源的Connection没错了。没找到源码,所以只好用jd反编辑查看了,发现里面有个jdbcAutoCommit,默认是true,WrappedConnection是实现java.sql.Connection的,所以提供了setAutoCommit(),但是由于能力有限无法找到JBoss初始化JNDI数据源的代码,也没找到任何可配置该参数的地方,所以没整了。
问题解决
既然写这篇文章,总归有个解决方案,不管好还是不好,总不能留着打自己脸呀。
解决方案就是发现JBoss配置数据源时有一个参数:new-connection-sql,通过注释发现该参数是在每次创建一个新的Connection时调用的,目的可能是用来测试或者其他的,有了这个参数我们就可以在所有通过该Connection的请求之前设置当前Connection的autoCommit=0,所以这个参数的值设置为set autocommit=0就可以解决啦。
同时发现有些数据源页不提供自动提交事务配置,默认还都是true(不知道是自己没找对还是真的没有,如C3P0)。
以上所述当然是简单叙述,实际过程复杂坎坷,不宜观看。