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

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

因为各类缘由,可能存在诸多不足,欢迎斧正!sql

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

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


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


一、下面是SimpleStatementHandler的update方法:mybatis

在MappedStatement中,有以下方法:app

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的派生类也会未覆盖的话也会重写。less


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

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>


二、在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 *,下面任选均可以一种 :

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

     1.一、将<insert>标签改为<update>或者其余的

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

二、在StatementHandler为CallableStatementHandler不会走到重写sql这一步,因此不会出现returning *问题。

三、在StatementHandler为PreparedStatementHandle时,useGeneratedKeys="true" keyColumn="id" keyProperty="id"这三个就能够了

   路漫漫其修远兮,不少时候感受想法比较幼稚。首先东西比较简单,其次工做也比较忙,还好周末能够抽时间处理这个。因为相关知识积累有限,欢迎你们提意见斧正,在此表示感谢!后续版本会持续更新…