About the Bug...
-
[x] I have checked that this issue has not already been reported.
-
[x] I have confirmed this bug exists on the latest version of MyBatis.
-
[x] I have confirmed this bug reproduces without 3rd party extensions (e.g. mybatis-plus).
Database Version
MySQL 5.7.27
JDBC Driver Version
MySQL Connector/J 5.1.47
Issue Description
MyBatis Version: 3.4.6 → 3.5.6 Database Version and JDK Version is not so important.
After upgrading MyBatis from version [3.4.6]to [3.5.6], batch insert operations that previously worked correctly now throw the exception: "Too many keys are generated. There are only 1 target objects.".
Error Details: - Exception Type: org.apache.ibatis.executor.ExecutorException - Error Message: Too many keys are generated. There are only 1 target objects. You either specified a wrong 'keyProperty' or encountered a driver bug like #1523. - Impact: Batch insert operations fail completely, affecting data persistence functionality.
Steps to Reproduce: 1. Configure MyBatis with batch executor 2. Perform batch insert operation with multiple entities 3. Operation fails with the specified exception
Code Example: ps: this code works well when version is 3.4.6.
repository:
List<XXXDTO> entities;// get some entities
Map<String, Object> param = Maps.newHashMap();
param.put("list", entities);
XXXMapper.batchInsert(param);// use Map as param
mapper:
int batchInsert(Map<String, Object> param);
xml:
<insert id="batchInsert" keyProperty="id" useGeneratedKeys="true" parameterType="com.xxx.XXXDTO">
insert into table_name
<trim prefix="(" suffix=")" suffixOverrides=",">
column,
</trim>
<foreach collection="list" item="item" index="index" separator="," open="values">
<trim prefix="(" suffix=")" suffixOverrides=",">
#{item.columnName,jdbcType=VARCHAR},
</trim>
</foreach>
</insert>
Expected Behavior: - Batch insert should complete successfully - All records should be inserted with proper key generation - Behavior should be consistent with previous version
Actual Behavior: - Exception thrown: "Too many keys are generated. There are only 1 target objects." - No records are inserted - Batch operation fails completely
Key Code Comparison: version 3.5.6:
private static Collection<?> collectionize(Object param) {
if (param instanceof Collection) {
return (Collection<?>) param;
} else if (param instanceof Object[]) {
return Arrays.asList((Object[]) param);
} else {
// when param is Map, enter this method
return Arrays.asList(param);
}
}
version 3.4.6:
private Collection<Object> getParameters(Object parameter) {
Collection<Object> parameters = null;
if (parameter instanceof Collection) {
parameters = (Collection) parameter;
} else if (parameter instanceof Map) {
// when param is Map, enter this method
Map parameterMap = (Map) parameter;
if (parameterMap.containsKey("collection")) {
parameters = (Collection) parameterMap.get("collection");
} else if (parameterMap.containsKey("list")) {
// parse list in Map
parameters = (List) parameterMap.get("list");
} else if (parameterMap.containsKey("array")) {
parameters = Arrays.asList((Object[]) parameterMap.get("array"));
}
}
if (parameters == null) {
parameters = new ArrayList<Object>();
parameters.add(parameter);
}
return parameters;
}
new version seems not support Map as param any longer.
Suggested Fix: Fix param parse logic in Jdbc3KeyGenerator or maybe throw a clearer exception like Map not support as param
for example:
private static Collection<?> collectionize(Object param) {
if (param instanceof Collection) {
return (Collection<?>) param;
} else if (param instanceof Object[]) {
return Arrays.asList((Object[]) param);
} else if (param instanceof Map){
throw new ExecutorException("Map is no longer supported as parameter.");
} else {
return Arrays.asList(param);
}
}
About your report...
-
[x] I did not use images 🖼️ for showing text information (code, error, etc.).
-
[x] I checked the Preview and my report looks awesome! 👍
Comment From: harawata
Hello @NarutoConanKing ,
Previously, the parameter must have a specific name (in your case, for example, it was not allowed to change the entry key to "entities") and it was not ideal, so we fixed it in #1249 . Inevitably, the fix broke some usages that had worked in the older versions. I explained it in the section 2 of the linked PR description.
Anyway, it may be relatively easy to update your mapper.
-
If the list is the only entry in the map, you can just pass the
List
directly instead of putting it in a map.java int insert(List<XXXDTO> list);
1. If the map contains other parameters, specify them as method parameters and change thekeyProperty
fromid
tolist.id
. You can omit@Param
if the-parameters
compiler option is enabled.java int insert(@Param("str")String str, @Param("list")List<XXXDTO> list, @Param("num")Integer num);
Note that Map
parameter IS supported.
It is just not the way you used.
This test demonstrates the supported usage.
Please let me know if you have further questions.
By the way, your mapper statement uses multirow insert which is different from batch insert. Using BATCH executor with multirow insert does not do anything good.