`
fly0wings
  • 浏览: 34765 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

mybatis + spring + PostgreSQL使用中的问题一例

 
阅读更多

在项目中需要调用PostgreSQL中的函数。在客户端pgAdmin中执行正常,但是在java中使用mybatis方式调用却无法正常运行,数据没有变化,但是有函数执行后的结果输出。这是什么问题导致?

    客户端执行:

 

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

 

 

result:  
     "success"

    代码:
        1.  ITransSettleService    

 

Java代码  收藏代码
  1. public interface ITransSettleService {  
  2.         boolean callUnsettleMove(Integer supplierId, String supplierType, String confirmDate, String transTime,  
  3.            String opType);  
  4.    }  

 

 

        2. TransSettleServiceImpl 

 

Java代码  收藏代码
  1. @Service("transSettleService")  
  2. public class TransSettleServiceImpl implements ITransSettleService {  
  3.     @Autowired  
  4.     private TransSettleDAO transSettleDAO;  
  5.      public boolean callUnsettleMove(Integer supplierId, String supplierType, String confirmDate, String transTime,  
  6.                                     String opType) {  
  7.         String result = transSettleDAO.callUnsettleMove(supplierId, supplierType, confirmDate, transTime, opType);  
  8.         return "success".equalsIgnoreCase(result);  
  9.      }  
  10. }  

 

 

        3. TransSettleDAO 

 

Java代码  收藏代码
  1. @Repository  
  2.   
  3.   public interface TransSettleDAO {  
  4.   
  5.       public String callUnsettleMove(@Param("supplierId") Integer supplierId, @Param("supplierType") String supplierType,  
  6.   
  7.                   @Param("confirmDate") String confirmDate, @Param("transTime") String transTime,  
  8.                   @Param("opType") String opType);  
  9.         }  

 

 

          4. mybatis xml文件: TransSettleMapper.xml

Xml代码  收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  
  3.         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">  
  4. <mapper namespace="......TransSettleDAO">  
  5.     <select id="callUnsettleMove" parameterType="map" statementType="PREPARED" resultType="java.lang.String">  
  6.         select balance_check.unsettle_move( #  
  7. {supplierId},#{supplierType},#{confirmDate}::date,#{transTime}:: timestamp with time zone,#{opType});  
  8.     </select>  
  9. </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的数据源配置后,发现了问题。
        

Xml代码  收藏代码
  1. <bean id="dataSource-vis">  
  2.   <property name="driverClassName" value="org.postgresql.Driver" />  
  3.   <property name="url" value="${conn.url}" />  
  4.   <property name="username" value="${conn.user}" />  
  5.   <property name="password" value="${conn.pwd}" />  
  6.   ......          
  7.    <property name="defaultAutoCommit" value="false" />  
  8. </bean>  

 

 

           既然默认是不会自动提交的,那么就在service中增加@Transactional,使之成为一个事务,因为事务是忽略上面这个属性的,并且如果不报异常就会提交事务的。

解决:

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

 

Java代码  收藏代码
  1. @Service("transSettleService")  
  2.   public class TransSettleServiceImpl implements ITransSettleService {  
  3.       @Autowired  
  4.       private TransSettleDAO transSettleDAO;  
  5.   
  6.       @Transactional  
  7.        public boolean callUnsettleMove(Integer supplierId, String supplierType, String confirmDate, String transTime,  
  8.                                       String opType) {  
  9.           String result = transSettleDAO.callUnsettleMove(supplierId, supplierType, confirmDate, transTime, opType);  
  10.           return "success".equalsIgnoreCase(result);  
  11.        }  
  12.   }  

 

 

    结果正常了。

新问题:        后来发现有些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

 

Java代码  收藏代码
  1. if (this.transactionFactory == null) {  
  2.            this.transactionFactory = new SpringManagedTransactionFactory(this.dataSource);  
  3.        }  

 

 

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

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

 

Java代码  收藏代码
  1. public Transaction newTransaction(Connection conn, boolean autoCommit) {  
  2.          return new SpringManagedTransaction(conn, this.dataSource);  
  3.      }  

 

 

        在创建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

 

Java代码  收藏代码
  1. this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.unwrappedConnection, dataSource);  
  2.            if (this.logger.isDebugEnabled()) {  
  3.                this.logger.debug(  
  4.                        "JDBC Connection ["  
  5.                        + this.connection  
  6.                        + "] will"  
  7.                        + (this.isConnectionTransactional?" ":" not ")  
  8.                        + "be managed by Spring");  
  9.            }  

 

   

    通过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

 

Java代码  收藏代码
  1. Object result = method.invoke(sqlSession, args);  
  2.          if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { //两个测试 都是true,正常进入提交。为什么会有不同的?  
  3.              sqlSession.commit();  
  4.          }  
  5.          return result;  

 

两个测试 都是true,正常进入提交。为什么会有不同的?继续跟进到了sqlSession.commit();方法。.

 

 

Java代码  收藏代码
  1. mybatis-3.0.6-sources.jar!/org/apache/ibatis/session/defaults/DefaultSqlSession.java#commit:138  
  2.   
  3.  public void commit() {  
  4.     commit(false);  
  5.   }  
  6.   
  7.   
  8. mybatis-3.0.6-sources.jar!/org/apache/ibatis/session/defaults/DefaultSqlSession.java#commit:142  
  9.   
  10.   public void commit(boolean force) {  
  11.     try {  
  12.       executor.commit(isCommitOrRollbackRequired(force));  
  13.       dirty = false;  
  14.     } catch (Exception e) {  
  15.       throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);  
  16.     } finally {  
  17.       ErrorContext.instance().reset();  
  18.     }  
  19.   }  
  20.   
  21.     mybatis-3.0.6-sources.jar!/org/apache/ibatis/session/defaults/DefaultSqlSession.java#isCommitOrRollbackRequired:203  
  22.   
  23.   private boolean isCommitOrRollbackRequired(boolean force) { //两个测试,该方法的返回值不同。  
  24.     return (!autoCommit && dirty) || force; //主要是 dirty不同。  
  25.   }  
  26.   
  27.   
  28.     mybatis-3.0.6-sources.jar!/org/apache/ibatis/executor/BaseExecutor.java#commit:172  
  29.   
  30.   public void commit(boolean required) throws SQLException {  
  31.     if (closed) {  
  32.       throw new ExecutorException("Cannot commit, transaction is already closed");  
  33.     }  
  34.     clearLocalCache();  
  35.     flushStatements();  
  36.     if (required) {  
  37.       transaction.commit();  
  38.     }  
  39.   }  

 

        由上面的代码可看出事务是否真正提交,是由isCommitOrRollbackRequired方法决定的。而在本例中,关键因素在于dirty属性。那么什么时候会修改这个值呢?
        发现只有update一处有修改dirty=true的语句。进一步追查发现insert,delete,update语句都是会调用该方法来进行数据操纵的。唯独select语句是不会调用该方法的。
         

 

Java代码  收藏代码
  1. mybatis-3.0.6-sources.jar!/org/apache/ibatis/session/defaults/DefaultSqlSession.java#update:118  
  2.   
  3.         public int update(String statement, Object parameter) {  
  4.   
  5.            try {  
  6.   
  7.              dirty = true;  
  8.   
  9.              MappedStatement ms = configuration.getMappedStatement(statement);  
  10.   
  11.              return executor.update(ms, wrapCollection(parameter));  
  12.   
  13.            } catch (Exception e) {  
  14.   
  15.              throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);  
  16.   
  17.            } finally {  
  18.   
  19.              ErrorContext.instance().reset();  
  20.   
  21.            }  
  22.   
  23.          }  
  24.   
  25.          public int insert(String statement, Object parameter) {  
  26.   
  27.            return update(statement, parameter);  
  28.   
  29.          }  
  30.   
  31.          public int delete(String statement) {  
  32.   
  33.            return update(statement, null);  
  34.   
  35.          }  

 

 

 

 

        那么现在就可以解释为什么 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
Java代码  收藏代码
  1.    public static boolean isConnectionTransactional(Connection con, DataSource dataSource) {  
  2.   
  3.  if (dataSource == null) {  
  4.   return false;  
  5.  }  
  6.  ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);  
  7.  return (conHolder != null && connectionEquals(conHolder, con));  
  8. }  

        

        
        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

 

Java代码  收藏代码
  1. public class DataSourceTransactionManager extends AbstractPlatformTransactionManager  
  2.      implements ResourceTransactionManager, InitializingBean {  
  3.   
  4.                 protected void doBegin(Object transaction, TransactionDefinition definition) {  
  5.                                    .....  
  6.   
  7.             if (con.getAutoCommit()) {  
  8.   
  9.                txObject.setMustRestoreAutoCommit(true);  
  10.   
  11.                if (logger.isDebugEnabled()) {  
  12.   
  13.                 logger.debug("Switching JDBC Connection [" + con + "] to manual commit");  
  14.   
  15.                }  
  16.   
  17.                con.setAutoCommit(false);  
  18.   
  19.               }  
  20.   
  21.                            .....  
  22.   
  23.             // Bind the session holder to the thread.  
  24.   
  25.               if (txObject.isNewConnectionHolder()) {  
  26.   
  27.                TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());  
  28.   
  29.               }  
  30.            .....  
  31.   
  32.        }  
  33.   
  34.    }  

 

        

总结:

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来管理。或者使用编程式事务来处理了。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics