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"这三个就可以了
路漫漫其修远兮,很多时候感觉想法比较幼稚。首先东西比较简单,其次工作也比较忙,还好周末可以抽时间处理这个。由于相关知识积累有限,欢迎大家提意见斧正,在此表示感谢!后续版本会持续更新…
以上是关于mybatis+postgresql insert, update or delete returning *问题的主要内容,如果未能解决你的问题,请参考以下文章
在mybatis中insert语句必须插入表中的全部字段吗??