在项目中需要调用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 {@Autowiredprivate 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

  @Repositorypublic 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 {@Autowiredprivate TransSettleDAO transSettleDAO;@Transactionalpublic 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:138public void commit() {commit(false);}mybatis-3.0.6-sources.jar!/org/apache/ibatis/session/defaults/DefaultSqlSession.java#commit:142public 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:203private boolean isCommitOrRollbackRequired(boolean force) { //两个测试,该方法的返回值不同。return (!autoCommit && dirty) || force; //主要是 dirty不同。}mybatis-3.0.6-sources.jar!/org/apache/ibatis/executor/BaseExecutor.java#commit:172public 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:118public 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 AbstractPlatformTransactionManagerimplements 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来管理。或者使用编程式事务来处理了。

mybatis + spring + PostgreSQL使用中的问题一例相关推荐

  1. springmvc+mybatis+spring+redis

    只作参考,以防忘记使用! mybatis的配置文件: <?xml version="1.0" encoding="UTF-8" ?> <!DO ...

  2. mybatis spring springMVC

    unit10-mybatis框架 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ZIyKRxA-1611577268155)(第二阶段讲义04.assets/a0a2 ...

  3. SSM(Mybatis + Spring + Spring MVC)框架整合详细步骤(附jar包和项目下载,免费的)

    本例从0开始逐一整合SSM的步骤,实现基础的CRUD 开发环境:Eclipse4.6 + jdk1.8 + Tomcat8 + MySQL SSM所需jar包和项目下载路径在文章最后 1.数据库 创建 ...

  4. hadoop mysql mybatis_MyBatis简介与配置MyBatis+Spring+MySql

    MyBatis 是一个可以自定义SQL.存储过程和高级映射的持久层框架.MyBatis 摒除了大部分的JDBC代码.手工设置参数和结果集重获.MyBatis 只使用简单的XML 和注解来配置和映射基本 ...

  5. springMvc+mybatis+spring 整合 包涵整合activiti 基于maven

    2019独角兽企业重金招聘Python工程师标准>>> 最近自己独立弄一个activiti项目,写一下整合过程: 环境:jdk1.7 tomcat7.0 maven3.5  ecli ...

  6. Mybatis+Spring SqlSessionTemplate注入学习--1

    1.我们创建一个maven工程,创建的时候选择packageing 为war形式,也就是一个web工程 2.工程创建好之后,我们在pom.xml中引入相关的jar,要想使用Spring+mybatis ...

  7. MyBatis+Spring整合

    不多说,直接上案例! 1. 建立数据库+表 CREATE TABLE s_user(user_id INT AUTO_INCREMENT PRIMARY KEY,user_name VARCHAR(3 ...

  8. idae中spring mvc解决问题application context not configured for this file于spring框架使用中的原因

    idae中spring mvc解决问题application context not configured for this file于spring框架使用中的原因 参考文章: (1)idae中spr ...

  9. org.mybatis.spring.MyBatisSystemException异常及处理

    org.mybatis.spring.MyBatisSystemException异常及处理 参考文章: (1)org.mybatis.spring.MyBatisSystemException异常及 ...

  10. 成功解决 org.mybatis.spring.MyBatisSystemException问题!!

    成功解决 org.mybatis.spring.MyBatisSystemException问题!! 参考文章: (1)成功解决 org.mybatis.spring.MyBatisSystemExc ...

最新文章

  1. Shell脚本读取命令行参数
  2. 深度使用魅族16T后的评价(本人魅友,绝对客观公正,不要盲目的为手机厂商辩护,想想从当初到现在,魅族正在一步步背离自己的信仰,有问题,解决问题才能有更好的发展)
  3. 如何root安卓手机_安卓Root+卡开机画面救砖教程丨以一加手机为例
  4. Launchy – 快速调出你的程序
  5. OpenGL调用GPU(七)
  6. Java笔记:Statement和PreparedStatement的区别
  7. CRC32的C语言源代码
  8. AI 工程师职业指南
  9. 如何做到四位验证码更简洁的生成?
  10. js获取当前日期前12月后12月日期
  11. Chrome浏览器安装 React Developer Tools和Redux DevTools插件
  12. iptables实战演练
  13. 带宽的定义标准是什么
  14. python自动买股票_用python可以做哪些有趣的事--我:选股票
  15. 计算机应用2010历年真题,全国2010年0月自学考试00051《管理系统中计算机应用》历年真题.doc...
  16. vis 和黄色边框斗智斗勇
  17. 北塔网管软件BTSO2.5安装过程记录
  18. java dozer 官网,Dozer-JavaBean映射工具
  19. c语言计算跳水分数,C语言判断五位选手跳水名次
  20. CPA十三--借款费用资本化的条件(转载)

热门文章

  1. 游戏测试一工作原理及测试流程
  2. python提取url的顶级域名及域名后缀
  3. 主数据及其管理MDM
  4. Python随机生成6位数密码
  5. 【PCB】贴片电容详解
  6. 支付宝小程序使用 icontfont字体图标
  7. 谷歌学术Google Scholar超实用干货(拒绝翻墙/插件)
  8. java png图片转换成jpg_Java实现将png格式图片转换成jpg格式图片的方法【测试可用】...
  9. hdjs---laravel 框架使用hdjs 实现多图上传功能
  10. java的学习内容,附高频面试题合集