原创做品,出自 “晓风残月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; }
综上所述,基本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"这三个就能够了
路漫漫其修远兮,不少时候感受想法比较幼稚。首先东西比较简单,其次工做也比较忙,还好周末能够抽时间处理这个。因为相关知识积累有限,欢迎你们提意见斧正,在此表示感谢!后续版本会持续更新…