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

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

阅读更多

   在项目中需要调用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 楼 55930421 2014-06-16  
很好,很详细。标记下,希望系统能复现下这个问题 

相关推荐

    springboot(4) 整合mybatis和hibernate

    在实际项目中,可能需要考虑事务管理,确保在一次操作中使用同一种ORM框架。 在启动项目之前,按照描述中的提示,检查本地Maven仓库设置。如果使用的是默认的Maven Central仓库,可能因为网络问题导致启动失败。...

    mybatis 简单运行demo

    标签中的"springboot mybatis demo"表明这是一个使用SpringBoot集成MyBatis的示例,可以帮助开发者快速理解和实践MyBatis在SpringBoot环境下的应用。 至于"mybatis自动生成工具",这可能是指MyBatis的代码生成器,...

    mybatis plus.pdf

    以Spring Boot项目为例,需要在项目中加入mybatis-plus和mysql等相关依赖。例如,在Maven项目中,加入mysql-connector-java作为MySQL驱动包,并加入MyBatis-Plus驱动包,这些都是通过在pom.xml文件中添加相应的...

    013-MyBatis分页插件-PageHelper1

    2. **配置文件**:在项目中,需要配置Spring的`spring-config.xml`、SpringMVC的`springmvc-config.xml`以及处理中文乱码问题。 **三、使用PageHelper进行分页查询** 1. **测试功能**:在业务逻辑中,通过...

    MyBatis-Plus入门.pdf

    MyBatis-Plus是一种针对MyBatis框架的增强工具,它的出现是为了简化数据库操作的开发流程,提高开发效率。...无论是在大型企业级应用还是小型项目中,MyBatis-Plus都是一个值得推荐使用的数据库操作框架。

    《spring boot 实战》源代码

    Spring Boot是Java生态系统中的一个核心组件,它简化了创建独立的、生产级别的基于Spring的应用程序的过程。这个压缩包包含的是第五版教程的配套示例代码,对于学习和掌握Spring Boot的实际运用具有很高的价值。 源...

    9. 使用JdbcTemplate【从零开始学Spring Boot】

    在本节中,我们将深入探讨如何在Spring Boot项目中使用JdbcTemplate进行数据库操作。JdbcTemplate是Spring框架提供的一种简化数据库访问的工具,它通过提供一套模板方法,使得开发者可以更安全、更方便地执行SQL语句...

    Spring Boot添删改查

    现在,我们可以在Spring Boot的控制器(Controller)中使用这个接口来实现添删改查功能: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind....

    ssm整合配置文件.rar

    在实际开发中,根据描述中的提示,如果需要使用其他数据库(如MySQL、PostgreSQL等),则需要修改`applicationContext.xml`中的数据源配置,以及MyBatis配置中的相关数据库驱动和连接参数。 通过这些配置文件的正确...

    Java学习路线图.pdf

    * Spring/Spring Boot/Spring Security/Spring Data/Spring Data JPA/Spring Data MongoDB/Spring Data Redis 基础:掌握 Spring 生态系统中的各种框架的使用。 阶段七:软技能 * IDE 基础:掌握 Eclipse、...

    校园二手书籍拍卖微信小程序.zip

    2. **微信小程序**:微信小程序是腾讯公司推出的一种轻量级的应用开发平台,无需下载安装即可使用,适用于快速构建服务型应用,如本例中的二手书籍交易。 3. **Java**:项目可能使用Java作为后端开发语言,Spring ...

    基于SpringBoot搭建的一个通用的OA管理系统,以车辆管理为例.zip

    1. **SpringDataJPA**:用于数据访问,简化了与数据库的交互,支持ORM映射,可与各种数据库(如MySQL、PostgreSQL)配合使用。 2. **Thymeleaf**:作为模板引擎,用于动态生成HTML页面,方便前后端分离。 3. **...

    SpringBoot 课程在线学习系统 毕业设计.zip

    4. 持久层:Spring Data JPA 或 MyBatis 可用于数据库操作,简化 CRUD(创建、读取、更新、删除)操作。 5. 安全管理:Spring Security 可用于实现用户认证和授权,保护系统资源。 三、关键功能模块 1. 用户管理:...

    基于SpringBoot的失物招领平台源码数据库.zip

    数据库是存储和管理数据的关键部分,此项目中的数据库可能使用MySQL或者PostgreSQL等关系型数据库,存储失物招领的信息,如物品类型、丢失地点、发布者信息等。开发者需要编写SQL语句进行数据操作,并通过MyBatis将...

    javaWeb物资管理系统项目源码.zip

    - 了解Spring Boot和MyBatis的配置与使用。 - 理解MVC和三层架构,能够设计并实现业务逻辑。 - 熟悉软件工程方法,包括需求分析、设计、编码和测试。 通过研究这个【javaWeb物资管理系统项目源码】,开发者不仅...

    留言版学习交流

    在本例中,虽然压缩包中列出的子文件是"ASP.NET(c#)新手留言本",但根据标题和描述,我们可以推测这可能是与Java留言版系统相关的学习资源或对比示例。ASP.NET是微软的Web开发框架,C#是其常用的语言。尽管技术栈...

    javaWEB中分页

    在Java Web开发中,有许多优秀的分页库,如MyBatis的PageHelper、Spring Data JPA的Pageable接口等。这些库提供了更高级的分页功能,如排序、缓存等,并且简化了分页的代码编写。以MyBatis的PageHelper为例,只需在...

    SuperMarketWMS:Java Spring的WMS项目

    2. **数据库**: 虽未明确指出具体数据库类型,但通常Spring项目会使用MySQL、PostgreSQL等关系型数据库,或者MongoDB等NoSQL数据库来存储业务数据。 3. **IDE(Integrated Development Environment)**: 如Eclipse、...

    基于Java的健康档案管理系统源码.zip

    6. **Spring框架**:Spring是一个广泛使用的Java企业级应用开发框架,提供了依赖注入、AOP(面向切面编程)等功能,对于构建复杂的应用系统非常有帮助。 7. **安全性**:在处理敏感的个人健康信息时,系统需要遵循...

Global site tag (gtag.js) - Google Analytics