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

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

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

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

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

mybatis+postgresql insert, update or delete returning *问题_第1张图片

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

mybatis+postgresql insert, update or delete returning *问题_第2张图片

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 INSERT statement.
     * 

* 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. *

* The execute method executes an SQL statement and indicates the * form of the first result. You must then use the methods * getResultSet or getUpdateCount * to retrieve the result, and getMoreResults 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 * getGeneratedKeys; one of the following constants: * Statement.RETURN_GENERATED_KEYS or * Statement.NO_GENERATED_KEYS * @return true if the first result is a ResultSet * object; false 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的派生类也会未覆盖的话也会重写。

mybatis+postgresql insert, update or delete returning *问题_第3张图片

 (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


  //返回当前插入记录的主键值
  
    select @@IDENTITY as id
  
  insert into TStudent(name, age) values(#{name}, #{age})


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、将标签改成或者其他的

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

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

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

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



你可能感兴趣的:(数据库)