设为首页 收藏本站
查看: 1398|回复: 0

[经验分享] mybatis + spring + PostgreSQL使用中的问题一例

[复制链接]

尚未签到

发表于 2016-11-20 10:31:59 | 显示全部楼层 |阅读模式
     在项目中需要调用PostgreSQL中的函数。在客户端pgAdmin中执行正常,但是在java中使用mybatis方式调用却无法正常运行,数据没有变化,但是有函数执行后的结果输出。这是什么问题导致?
      客户端执行:

select balance_check.unsettle_move( 38,'实时分账','2014-06-04'::date,'2014-06-04 20:38:08'::timestamp,'手工平账');
   result:  
     "success"
      代码:
        1.  ITransSettleService    

public interface ITransSettleService {
boolean callUnsettleMove(Integer supplierId, String supplierType, String confirmDate, String transTime,
String opType);
}
           2. TransSettleServiceImpl 

@Service("transSettleService")
public class TransSettleServiceImpl implements ITransSettleService {
@Autowired
private TransSettleDAO transSettleDAO;
public boolean callUnsettleMove(Integer supplierId, String supplierType, String confirmDate, String transTime,
String opType) {
String result = transSettleDAO.callUnsettleMove(supplierId, supplierType, confirmDate, transTime, opType);
return "success".equalsIgnoreCase(result);
}
}
           3. TransSettleDAO 

  @Repository
public interface TransSettleDAO {
public String callUnsettleMove(@Param("supplierId") Integer supplierId, @Param("supplierType") String supplierType,
@Param("confirmDate") String confirmDate, @Param("transTime") String transTime,
@Param("opType") String opType);
}
             4. mybatis xml文件: TransSettleMapper.xml


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="......TransSettleDAO">
<select id="callUnsettleMove" parameterType="map" statementType="PREPARED" resultType="java.lang.String">
select balance_check.unsettle_move( #
{supplierId},#{supplierType},#{confirmDate}::date,#{transTime}:: timestamp with time zone,#{opType});
</select>
</mapper>
 






问题排查阶段:

      1. 增加 pg log:pg开启日志,函数中增加日志。(RAISE log 
        发现程序执行的函数都已经执行过了, 但是 回滚了。
     
    [searcher b2c_product [unknown] 192.168.168.175 2014-06-05 11:48:10.905 CST 23556 538fe87a.5c04 6 0]LOG: execute <unnamed>: select balance_check.unsettle_move( $1,$2,$3::date,$4:: timestamp with time zone,$5) 
  [searcher b2c_product [unknown] 192.168.168.175 2014-06-05 11:48:10.905 CST 23556 538fe87a.5c04 7 0]DETAIL: parameters: $1 = '1630', $2 = '实时分账', $3 = '2014-06-04', $4 = '2014-06-05 11:48:10', $5 = '手工平账'
            .....
  [searcher b2c_product [unknown] 192.168.168.175 2014-06-05 11:48:10.928 CST 23556 538fe87a.5c04 16 820922046]LOG: execute S_2: ROLLBACK
  






      2. 为什么程序执行的就回滚了?而客户端手工执行的却没有问题?检查xml的数据源配置后,发现了问题。
        

<bean id="dataSource-vis">
<property name="driverClassName" value="org.postgresql.Driver" />
<property name="url" value="${conn.url}" />
<property name="username" value="${conn.user}" />
<property name="password" value="${conn.pwd}" />
......        
<property name="defaultAutoCommit" value="false" />
</bean>
 
             既然默认是不会自动提交的,那么就在service中增加@Transactional,使之成为一个事务,因为事务是忽略上面这个属性的,并且如果不报异常就会提交事务的。


解决:

          在service的实现类中增加@Transaction注解标注为spring的事务。
         TransSettleServiceImpl 

  @Service("transSettleService")
public class TransSettleServiceImpl implements ITransSettleService {
@Autowired
private TransSettleDAO transSettleDAO;
@Transactional
public boolean callUnsettleMove(Integer supplierId, String supplierType, String confirmDate, String transTime,
String opType) {
String result = transSettleDAO.callUnsettleMove(supplierId, supplierType, confirmDate, transTime, opType);
return "success".equalsIgnoreCase(result);
}
}
       结果正常了。


新问题:        后来发现有些saveXXX的service中,并没有增加@Transaction,也同样正常保存了。这是为什么呢?上面的解决办法并没有合理的解释这个问题。        问题汇总如下:


Service方法@Transaction标注是否是select语句结果
saveXXXService正常保存
selectXXXService回滚
selectXXXServiceTrans正常


源码跟踪与学习:

          


1. 没有Transaction标注的 selectXXXXService 和 saveXXXService 方法 剖析。

         使用的是org.mybatis.spring.SqlSessionFactoryBean。并且没有指定transactionFactory。所 以默认的transactionFactory是SpringManagedTransactionFactory。
             

mybatis-spring-1.0.2-sources.jar!/org/mybatis/spring/SqlSessionFactoryBean.java:361

     if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory(this.dataSource);
}
           顾名思义transactionFactory就是为了生产Transaction的工厂,所以 org.mybatis.spring.transaction.SpringManagedTransactionFactory#newTransaction() 方法,返回一个SpringManagedTransaction实例。
          
   

mybatis-spring-1.0.2-sources.jar!/org/mybatis/spring/transaction/SpringManagedTransactionFactory.java:45


   public Transaction newTransaction(Connection conn, boolean autoCommit) {
return new SpringManagedTransaction(conn, this.dataSource);
}
 
          在创建SpringManagedTransaction实例的时候,会调用 org.springframework.jdbc.datasource.DataSourceUtils#isConnectionTransactional() 方法,检查当前jdbc Connection是否是有事务,并且是否被spring所管理。
           

  mybatis-spring-1.0.2-sources.jar!/org/mybatis/spring/transaction/SpringManagedTransaction.java#SpringManagedTransaction:70


this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.unwrappedConnection, dataSource);
if (this.logger.isDebugEnabled()) {
this.logger.debug(
"JDBC Connection ["
+ this.connection
+ "] will"
+ (this.isConnectionTransactional?" ":" not ")
+ "be managed by Spring");
}
     
      通过debug跟踪执行的没有标记 为事务的 selectXXXXService 和 saveXXXService 方法,发现 isConnectionTransactional 只均为false。也就是说当前的Connection并没有被spring所管理,而是由SpringManagedTransaction类自己管 理,或者说是由mybatis管理。
那么,为什么会有提交和回滚的差别呢?
跟到了SqlSessionInterceptor类,该类实现了InvocationHandler接口。也就是说是被jdk的动态代理所使用的。

mybatis-spring-1.0.2-sources.jar!/org/mybatis/spring/SqlSessionTemplate.SqlSessionInterceptor.java:350


           Object result = method.invoke(sqlSession, args);
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { //两个测试 都是true,正常进入提交。为什么会有不同的?
sqlSession.commit();
}
return result;
   
  两个测试 都是true,正常进入提交。为什么会有不同的?继续跟进到了sqlSession.commit();方法。.

    mybatis-3.0.6-sources.jar!/org/apache/ibatis/session/defaults/DefaultSqlSession.java#commit:138
public void commit() {
commit(false);
}

mybatis-3.0.6-sources.jar!/org/apache/ibatis/session/defaults/DefaultSqlSession.java#commit:142
public void commit(boolean force) {
try {
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
mybatis-3.0.6-sources.jar!/org/apache/ibatis/session/defaults/DefaultSqlSession.java#isCommitOrRollbackRequired:203
private boolean isCommitOrRollbackRequired(boolean force) { //两个测试,该方法的返回值不同。
return (!autoCommit && dirty) || force; //主要是 dirty不同。
}

mybatis-3.0.6-sources.jar!/org/apache/ibatis/executor/BaseExecutor.java#commit:172
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
clearLocalCache();
flushStatements();
if (required) {
transaction.commit();
}
}
   
          由上面的代码可看出事务是否真正提交,是由isCommitOrRollbackRequired方法决定的。而在本例中,关键因素在于dirty属性。那么什么时候会修改这个值呢?
        发现只有update一处有修改dirty=true的语句。进一步追查发现insert,delete,update语句都是会调用该方法来进行数据操纵的。唯独select语句是不会调用该方法的。
         

mybatis-3.0.6-sources.jar!/org/apache/ibatis/session/defaults/DefaultSqlSession.java#update:118
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
public int delete(String statement) {
return update(statement, null);
}
   
          那么现在就可以解释为什么 selectXXXXService 和 saveXXXService 方法, 一个回滚,一个提交了。
  tips: DefaultSqlSession类的命名太不爽了,根本看不出来什么意思。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
小结:

Service方法@Transaction标注是否是select语句结果原因
saveXXXService正常保存mybatis自己管理事务,非select语句做提交操作。
selectXXXService回滚mybatis自己管理事务,select语句不做提交操作
selectXXXServiceTrans正常 
      现在只剩下一个问题了。@Transaction标注的方法如何处理。


2.   @Transaction标注的 selectXXXServiceTrans 方法剖析。

          在上面提到 DataSourceUtils#isConnectionTransactional()方法可以检查当前Connection是否被spring所管理。当有@Transaction的方法调用时,改方法则会返回true。
那么这时,mybatis就不会管理当前事务,则由spring来接管。那么如何判断是否是spring在管理事务呢?

spring-jdbc-3.1.2.RELEASE-sources.jar!/org/springframework/jdbc/datasource/DataSourceUtils.java:240

        public static boolean isConnectionTransactional(Connection con, DataSource dataSource) {
if (dataSource == null) {
return false;
}
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
return (conHolder != null && connectionEquals(conHolder, con));
}
         
          
        TransactionSynchronizationManager类会管理一个 ThreadLocal<Map<Object, Object>> resources=new NamedThreadLocal<Map<Object, Object>>("Transactional resources");对象,
该对象保存了事务的资源信息。如果是spring管理的事务,那么就会注册到该对象中,与当前线程绑定,而没有归到spring管理自然就为null了。
    
    那么什么时候set 到resources的呢?在DataSourceTransactionManager中. 该类是spring的基础类,不多说了。

spring-jdbc-3.1.2.RELEASE-sources.jar!/org/springframework/jdbc/datasource/DataSourceTransactionManager.java#doBegin:233


public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, InitializingBean {
protected void doBegin(Object transaction, TransactionDefinition definition) {
.....
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
.....
// Bind the session holder to the thread.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
}
.....
}
}
   
  


总结:


Service方法@Transaction标注是否是select语句结果原因
saveXXXService正常保存mybatis自己管理事务,非select语句做提交操作。
selectXXXService回滚mybatis自己管理事务,select语句不做提交操作
selectXXXServiceTrans正常spring 管理事务。并且忽略自动提交参数。强行置为false,事务完成commit。
  1. 主要问题在于 PostgreSQL与mybatis在语法上的差异。只能使用mybatis的<select>标签,如果采用存储过程方式的话,还可以使用<update>标签。
    select balance_check.unsettle_move(); 
2. 如果需要使用事务的话 需要增加@Transaction交由spring来管理。或者使用编程式事务来处理了。
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-302816-1-1.html 上篇帖子: PostgreSQL学习手册(PL/pgSQL过程语言) 下篇帖子: PostgreSQL数据表操作手册手册
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表