Too many keys are generated. There are only 1 target objects.
记一次由spring boot升级导致的问题解决。
版本信息:
mybatis-spring-boot-starter : 2.1.4
mybatis-spring: 2.0.6
mybatis : 3.5.6
mysql-connector-java: 8.0.22
错误日志:
Caused by: org.apache.ibatis.executor.ExecutorException: Too many keys are generated. There are only 1 target objects. You either specified a wrong 'keyProperty' or encountered a driver bug like #1523.
at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.assignKeysToParam(Jdbc3KeyGenerator.java:121) ~[mybatis-3.5.5.jar:3.5.5]
at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.assignKeys(Jdbc3KeyGenerator.java:104) ~[mybatis-3.5.5.jar:3.5.5]
at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.processBatch(Jdbc3KeyGenerator.java:85) ~[mybatis-3.5.5.jar:3.5.5]
at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.processAfter(Jdbc3KeyGenerator.java:71) ~[mybatis-3.5.5.jar:3.5.5]
at org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler.java:51) ~[mybatis-3.5.5.jar:3.5.5]
at org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler.java:74) ~[mybatis-3.5.5.jar:3.5.5]
at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:50) ~[mybatis-3.5.5.jar:3.5.5]
at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117) ~[mybatis-3.5.5.jar:3.5.5]
at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76) ~[mybatis-3.5.5.jar:3.5.5]
at com.jd.eps.eis.product.core.interceptor.TenantInterceptor.intercept(TenantInterceptor.java:80) ~[eis-core-service-2.0.0-SNAPSHOT.jar:?]
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) ~[mybatis-3.5.5.jar:3.5.5]
at com.sun.proxy.$Proxy184.update(Unknown Source) ~[?:?]
at com.jd.eps.eis.product.core.interceptor.DataPermissionInterceptor.intercept(DataPermissionInterceptor.java:75) ~[eis-core-service-2.0.0-SNAPSHOT.jar:?]
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) ~[mybatis-3.5.5.jar:3.5.5]
at com.sun.proxy.$Proxy184.update(Unknown Source) ~[?:?]
at sun.reflect.GeneratedMethodAccessor277.invoke(Unknown Source) ~[?:?]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_171]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_171]
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:63) ~[mybatis-3.5.5.jar:3.5.5]
at com.sun.proxy.$Proxy184.update(Unknown Source) ~[?:?]
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:197) ~[mybatis-3.5.5.jar:3.5.5]
at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:184) ~[mybatis-3.5.5.jar:3.5.5]
at sun.reflect.GeneratedMethodAccessor416.invoke(Unknown Source) ~[?:?]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_171]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_171]
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:426) ~[mybatis-spring-2.0.5.jar:2.0.5]
... 119 more
跟踪日志发现是在执行批量插入时,使用了useGeneratedKeys=“true”,因为是批量操作,所以返回多个key,但是在设置的时候发现只有一个对象可以设置,因此报错。
org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processBatch
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
final String[] keyProperties = ms.getKeyProperties();
if (keyProperties == null || keyProperties.length == 0) {
return;
}
try (ResultSet rs = stmt.getGeneratedKeys()) {
final ResultSetMetaData rsmd = rs.getMetaData();
final Configuration configuration = ms.getConfiguration();
if (rsmd.getColumnCount() < keyProperties.length) {
// Error?
} else {
assignKeys(configuration, rs, rsmd, keyProperties, parameter);
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
}
}
private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
Object parameter) throws SQLException {
if (parameter instanceof ParamMap || parameter instanceof StrictMap) {
// Multi-param or single param with @Param
assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);
} else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()
&& ((ArrayList<?>) parameter).get(0) instanceof ParamMap) {
// Multi-param or single param with @Param in batch operation
assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, (ArrayList<ParamMap<?>>) parameter);
} else {
// Single param without @Param
assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
}
}
原因一:
自己开发的Mybatis插件对parameter类型做了转换,使原本的MapperMethod.ParamMap类型变成了HashMap类型,导致执行assignKeys方法时走了else的逻辑。
解决方法:
对MapperMethod.ParamMap类型单独处理。
// 如果参数为空,创建空Map
if (parameterObject == null) {
paramMap = new HashMap<>();
} else if (parameterObject instanceof MapperMethod.ParamMap) {
paramMap = new MapperMethod.ParamMap<>();
paramMap.putAll((Map) parameterObject);
} else if (parameterObject instanceof Map) {
// 解决不可变Map的情况
paramMap = new HashMap<>();
paramMap.putAll((Map) parameterObject);
} else {
// do sth.
}
原因二:
指定keyProperty时应包含list的别名信息,如keyProperty=“list.id”。
int insertConfigDetails(@Param("list") List<ConfigDetailDO> configDetailDOS);
<insert id="insertConfigDetails" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="list.id">
</insert>