mybatis+postgresql insert, update or delete returning *问题

Posted 普通网友

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis+postgresql insert, update or delete returning *问题相关的知识,希望对你有一定的参考价值。

原创作品,出自 “晓风残月xj” 博客,欢迎转载,转载时请务必注明出处(http://blog.csdn.net/xiaofengcanyuexj)。

由于各种原因,可能存在诸多不足,欢迎斧正!

       最近DBA说数据库DB log插入insert语句时返回returning *,占用网络带宽,希望优化掉。其实本没有时间查看mybatis源码的,今天看了下,造成returning *的原因和解决方案如下,希望可以帮助解决相同的问题。

       先盗图一张,说明mybatis的执行时调用顺序,原图出处 ,在此表示感谢:


配置:mybatis+postgresql.version 9.4-1201-jdbc4


1、下面是SimpleStatementHandler的update方法:

在MappedStatement中,有如下方法:

   public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) 
            this.mappedStatement.configuration = configuration;
            this.mappedStatement.id = id;
            this.mappedStatement.sqlSource = sqlSource;
            this.mappedStatement.statementType = StatementType.PREPARED;
            this.mappedStatement.parameterMap = (new org.apache.ibatis.mapping.ParameterMap.Builder(configuration, "defaultParameterMap", (Class)null, new ArrayList())).build();
            this.mappedStatement.resultMaps = new ArrayList();
            this.mappedStatement.timeout = configuration.getDefaultStatementTimeout();
            this.mappedStatement.sqlCommandType = sqlCommandType;
            //当useGeneratedKeys="true"且标签为INSERT时,使用keyGenerator=Jdbc3KeyGenerator,后面在某些条件满足时会导致重写sql;否则keyGenerator=NoKeyGenerator
            this.mappedStatement.keyGenerator = (KeyGenerator)(configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)?new Jdbc3KeyGenerator():new NoKeyGenerator());
            String logId = id;
            if(configuration.getLogPrefix() != null) 
                logId = configuration.getLogPrefix() + id;
            

            this.mappedStatement.statementLog = LogFactory.getLog(logId);
            this.mappedStatement.lang = configuration.getDefaultScriptingLanuageInstance();
        
      如上注释所描述的,当useGeneratedKeys="true"且标签为INSERT时,使用keyGenerator=Jdbc3KeyGenerator,后面在某些条件满足时会导致重写sql;否则keyGenerator=NoKeyGenerator。

 public int update(Statement statement) throws SQLException 
        String sql = this.boundSql.getSql();
        Object parameterObject = this.boundSql.getParameterObject();
        KeyGenerator keyGenerator = this.mappedStatement.getKeyGenerator();
        int rows;
        //(keyGenerator instanceof Jdbc3KeyGenerator)满足时,重写sql,加上returning *
        if(keyGenerator instanceof Jdbc3KeyGenerator) 
            //点进去,会发现会重写sql,具体语句: sql = addReturning(connection, sql, new String[]"*", false);
            statement.execute(sql, 1);
            rows = statement.getUpdateCount();
            keyGenerator.processAfter(this.executor, this.mappedStatement, statement, parameterObject);
         
        //(keyGenerator instanceof SelectKeyGenerator)满足时,会在执行原sql后,执行processAfter选择keyProperty属性
        else if(keyGenerator instanceof SelectKeyGenerator) 
            statement.execute(sql);
            rows = statement.getUpdateCount();
            keyGenerator.processAfter(this.executor, this.mappedStatement, statement, parameterObject);
         
        //否则,只执行原sql
        else 
            statement.execute(sql);
            rows = statement.getUpdateCount();
        

        return rows;
    
    Jdbc3KeyGenerator对应的statement是AbstractJdbc3Statement,上面方法中对应statement.execute(sql, 1)的方法如下:

  /**
     * Executes the given SQL statement, which may return multiple results,
     * and signals the driver that any
     * auto-generated keys should be made available
     * for retrieval.  The driver will ignore this signal if the SQL statement
     * is not an <code>INSERT</code> statement.
     * <P>
     * In some (uncommon) situations, a single SQL statement may return
     * multiple result sets and/or update counts.  Normally you can ignore
     * this unless you are (1) executing a stored procedure that you know may
     * return multiple results or (2) you are dynamically executing an
     * unknown SQL string.
     * <P>
     * The <code>execute</code> method executes an SQL statement and indicates the
     * form of the first result.  You must then use the methods
     * <code>getResultSet</code> or <code>getUpdateCount</code>
     * to retrieve the result, and <code>getMoreResults</code> to
     * move to any subsequent result(s).
     *
     * @param sql any SQL statement
     * @param autoGeneratedKeys a constant indicating whether auto-generated
     *    keys should be made available for retrieval using the method
     *    <code>getGeneratedKeys</code>; one of the following constants:
     *    <code>Statement.RETURN_GENERATED_KEYS</code> or
     *    <code>Statement.NO_GENERATED_KEYS</code>
     * @return <code>true</code> if the first result is a <code>ResultSet</code>
     *     object; <code>false</code> if it is an update count or there are
     *     no results
     * @exception SQLException if a database access error occurs
     * @see #getResultSet
     * @see #getUpdateCount
     * @see #getMoreResults
     * @see #getGeneratedKeys
     *
     * @since 1.4
     */
    public boolean execute(String sql, int autoGeneratedKeys) throws SQLException
    
        if (autoGeneratedKeys == Statement.NO_GENERATED_KEYS)
            return execute(sql);

        sql = addReturning(connection, sql, new String[]"*", false);
        wantsGeneratedKeysOnce = true;

        return execute(sql);
    

AbstractJdbc3Statement会重写sq,添加RETURNING *,AbstractJdbc3Statement的派生类也会未覆盖的话也会重写。


 (keyGenerator instanceof SelectKeyGenerator)满足时,会在执行原sql后,执行processAfter选择keyProperty属性,对应方法源码如下:

  public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) 
        if(!this.executeBefore) 
            this.processGeneratedKeys(executor, ms, parameter);
        

    

    private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) 
        try 
            //成立条件之一:this.keyStatement.getKeyProperties() != null,即对应配置keyProperty不等于null
            if(parameter != null && this.keyStatement != null && this.keyStatement.getKeyProperties() != null) 
                String[] e = this.keyStatement.getKeyProperties();
                Configuration configuration = ms.getConfiguration();
                MetaObject metaParam = configuration.newMetaObject(parameter);
                if(e != null) 
                    Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
                    //默默的就执行查询,比较耗费性能
                    List values = keyExecutor.query(this.keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
                    if(values.size() == 0) 
                        throw new ExecutorException("SelectKey returned no data.");
                    

                    if(values.size() > 1) 
                        throw new ExecutorException("SelectKey returned more than one value.");
                    

                    MetaObject metaResult = configuration.newMetaObject(values.get(0));
                    if(e.length == 1) 
                        if(metaResult.hasGetter(e[0])) 
                            this.setValue(metaParam, e[0], metaResult.getValue(e[0]));
                         else 
                            this.setValue(metaParam, e[0], values.get(0));
                        
                     else 
                        this.handleMultipleProperties(e, metaParam, metaResult);
                    
                
            

         catch (ExecutorException var10) 
            throw var10;
         catch (Exception var11) 
            throw new ExecutorException("Error selecting key or setting result to parameter object. Cause: " + var11, var11);
        
    
 上述主要针对如下配置文件才会选择的SelectKeyGenerator

<insert id="add" parameterType="EStudent">
  //返回当前插入记录的主键值
  <selectKey resultType="long" keyProperty="id" order="AFTER">
    select @@IDENTITY as id
  </selectKey>
  insert into TStudent(name, age) values(#name, #age)
</insert>


2、在StatementHandler为PreparedStatementHandle时

 protected Statement instantiateStatement(Connection connection) throws SQLException 
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) 
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) 
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
       else 
        return connection.prepareStatement(sql, keyColumnNames);
      
     else if (mappedStatement.getResultSetType() != null) 
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
     else 
      return connection.prepareStatement(sql);
    
  

 public PreparedStatement prepareStatement(String sql, String columnNames[])
    throws SQLException
    
        if (columnNames != null && columnNames.length != 0)
            sql = AbstractJdbc3Statement.addReturning(this, sql, columnNames, true);

        PreparedStatement ps = prepareStatement(sql);

        if (columnNames != null && columnNames.length != 0)
            ((AbstractJdbc3Statement)ps).wantsGeneratedKeysAlways = true;

        return ps;
    
 public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
    throws SQLException
    
        checkClosed();
        if (autoGeneratedKeys != Statement.NO_GENERATED_KEYS)
            sql = AbstractJdbc3Statement.addReturning(this, sql, new String[]"*", false);

        PreparedStatement ps = prepareStatement(sql);

        if (autoGeneratedKeys != Statement.NO_GENERATED_KEYS)
            ((AbstractJdbc3Statement)ps).wantsGeneratedKeysAlways = true;

        return ps;
    
 
    针对PreparedStatementHandle,如果只想返回id,useGeneratedKeys="true" keyColumn="id" keyProperty="id"既可以。

    综上所述,基本postgresql jdbc返回的都是*,即插入的完整数据。这对于网络带宽和磁盘io都有损耗,可以说是pg jdbc的bug吧,指定的属性keyProperty只是在返回的完整数据中选择出来的,并不是只返回keyProperty字段。所以说啊,要返回指定字段首先要控制不让mybatis自动返回,然后在sql语句后面添加returnkeyProperty

    只要删除db机器上的insertreturning *,下面任选都可以一种 :

1、在StatementHandler为SimpleStatementHandler的前提下,任何一种都行。

     1.1、将<insert>标签改成<update>或者其他的

     1.2、useGeneratedKeys="false",强制不返回(默认就是false)

2、在StatementHandler为CallableStatementHandler不会走到重写sql这一步,所以不会出现returning *问题。

3、在StatementHandler为PreparedStatementHandle时,useGeneratedKeys="true" keyColumn="id" keyProperty="id"这三个就可以了

   路漫漫其修远兮,很多时候感觉想法比较幼稚。首先东西比较简单,其次工作也比较忙,还好周末可以抽时间处理这个。由于相关知识积累有限,欢迎大家提意见斧正,在此表示感谢!后续版本会持续更新…



与50位技术专家面对面 20年技术见证,附赠技术全景图

以上是关于mybatis+postgresql insert, update or delete returning *问题的主要内容,如果未能解决你的问题,请参考以下文章

postgresql----INSERT

mysql_insert_id 替代 postgresql

在mybatis中insert语句必须插入表中的全部字段吗??

获取 PostgreSQL 中受 INSERT 或 UPDATE 影响的记录数

mybatis insert 插入为啥会返回空值

mybatis batch insert