Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error occurred when I try to "INSERT INTO ~ ON DUPLICATE KEY UPDATE" with useGeneratedKeys. #1523

Closed
ApplePedlar opened this issue Apr 16, 2019 · 14 comments
Assignees
Labels
enhancement Improve a feature or add a new feature
Milestone

Comments

@ApplePedlar
Copy link

ApplePedlar commented Apr 16, 2019

MyBatis version

3.5.1

Database vendor and version

MySQL 5.6

Test case or example project

Table scheme

CREATE TABLE `Animal` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

model (sample.Animal)

@lombok.Data
@lombok.NoArgsConstructor
@lombok.AllArgsConstructor
public class Animal {
    private long id;
    private String name;
}

mapper interface (sample.AnimalMapper)

public interface AnimalMapper {
    Animal get(long id);
    int insertUpdate(Animal animal);
}

mapper xml (sample/AnimalMapper.xml)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.AnimalMapper">
  <select id="get" resultType="Animal">
    SELECT * FROM Animal WHERE id = #{id}
  </select>

  <insert id="insertUpdate" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO Animal
    SET
    id = #{id},
    name = #{name}
    ON DUPLICATE KEY UPDATE
    name = #{name}
  </insert>
</mapper>

test case (sample.AnimalMapperTest)

public class AnimalMapperTest extends AbstractTestBase {
    @Test
    public void test() {
        Animal animal = new Animal(0, "wolf");
        AnimalMapper mapper = inject(AnimalMapper.class);
        mapper.insertUpdate(animal);
        assertThat(animal.getId(), not(is(0L)));
        Animal animalFromDb = mapper.get(animal.getId());
        assertThat(animalFromDb.getId(), is(animal.getId()));
        assertThat(animalFromDb.getName(), is(animal.getName()));

        animal.setName("dog");
        mapper.insertUpdate(animal);
        animalFromDb = mapper.get(animal.getId());
        assertThat(animalFromDb.getId(), is(animal.getId()));
        assertThat(animalFromDb.getName(), is(animal.getName()));
    }
}

Steps to reproduce

run test.

Expected result

Name changes from wolf to dog.

Actual result

Error occurred.

org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting result to parameter object. Cause: java.util.NoSuchElementException
### The error may exist in sample/AnimalMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: INSERT INTO Animal     SET     id = ?,     name = ?     ON DUPLICATE KEY UPDATE     name = ?
### Cause: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting result to parameter object. Cause: java.util.NoSuchElementException

	at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:199)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:184)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.apache.ibatis.session.SqlSessionManager$SqlSessionInterceptor.invoke(SqlSessionManager.java:357)
	at com.sun.proxy.$Proxy54.insert(Unknown Source)
	at org.apache.ibatis.session.SqlSessionManager.insert(SqlSessionManager.java:236)
	at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:62)
	at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:58)
	at com.sun.proxy.$Proxy65.insertUpdate(Unknown Source)
	at sample.AnimalMapperTest.test(AnimalMapperTest.java:23)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
	at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
	at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
	at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
	at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
	at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
	at org.junit.rules.RunRules.evaluate(RunRules.java:20)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
	at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
	at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
	at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
	at org.junit.rules.RunRules.evaluate(RunRules.java:20)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting result to parameter object. Cause: java.util.NoSuchElementException
	at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.processBatch(Jdbc3KeyGenerator.java:81)
	at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.processAfter(Jdbc3KeyGenerator.java:64)
	at org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler.java:51)
	at org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler.java:74)
	at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:50)
	at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)
	at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)
	at sun.reflect.GeneratedMethodAccessor45.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.apache.ibatis.plugin.Invocation.proceed(Invocation.java:49)
	... 46 more
Caused by: java.util.NoSuchElementException
	at java.util.AbstractList$Itr.next(AbstractList.java:364)
	at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.assignKeysToParam(Jdbc3KeyGenerator.java:113)
	at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.assignKeys(Jdbc3KeyGenerator.java:97)
	at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.processBatch(Jdbc3KeyGenerator.java:78)
	... 60 more

Version 3.4.6 and 3.5.0 work fine.

Related Issues

@kazuki43zoo
Copy link
Member

Hi @ApplePedlar , I cannot reproduce this. Could you provide a small reproduce executable project via GitHub?

harawata added a commit to harawata/mybatis-issues that referenced this issue Apr 16, 2019
@harawata
Copy link
Member

Me neither (tested with MySQL 5.6.43 and Connector/J 8.0.15).
There might be some non-default config option that triggers the issue.

Here's the project I used (docker required).
https://github.com/harawata/mybatis-issues/tree/master/gh-1523
Try copying relevant settings from your my.cnf to mysql_conf/my.cnf and see if the issue reproduces.

@harawata
Copy link
Member

harawata commented Apr 16, 2019

The description matches this Connector/J bug.
https://bugs.mysql.com/bug.php?id=90688
I'm not sure why the MyBatis test does not reproduce the problem.

UPDATE:
The issue with MySQL Connector/J reproduces only when PreparedStatement#executeUpdate() is used.
MyBatis uses PreparedStatement#execute() and the driver returns only one generated key in this case.

@ApplePedlar
Copy link
Author

ApplePedlar commented Apr 17, 2019

Thank you for creating a test project.

The bug was reproduced using mariadb-connector.
https://github.com/ApplePedlar/mybatis-issues/tree/gh-1523-reproduced


09:55:29.068 [main] DEBUG test.Mapper.insertUpdate - ==>  Preparing: INSERT INTO Animal SET id = ?, name = ? ON DUPLICATE KEY UPDATE name = ? 
09:55:29.134 [main] DEBUG test.Mapper.insertUpdate - ==> Parameters: 0(Long), wolf(String), wolf(String)
09:55:29.137 [main] DEBUG test.Mapper.insertUpdate - <==    Updates: 1
09:55:29.149 [main] DEBUG test.Mapper.get - ==>  Preparing: SELECT * FROM Animal WHERE id = ? 
09:55:29.150 [main] DEBUG test.Mapper.get - ==> Parameters: 5(Long)
09:55:29.220 [main] TRACE test.Mapper.get - <==    Columns: id, name
09:55:29.220 [main] TRACE test.Mapper.get - <==        Row: 5, wolf
09:55:29.222 [main] DEBUG test.Mapper.get - <==      Total: 1
09:55:29.227 [main] DEBUG test.Mapper.insertUpdate - ==>  Preparing: INSERT INTO Animal SET id = ?, name = ? ON DUPLICATE KEY UPDATE name = ? 
09:55:29.228 [main] DEBUG test.Mapper.insertUpdate - ==> Parameters: 5(Long), dog(String), dog(String)
09:55:29.229 [main] DEBUG test.Mapper.insertUpdate - <==    Updates: 2

org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting result to parameter object. Cause: java.util.NoSuchElementException
### The error may exist in test/Mapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: INSERT INTO Animal     SET     id = ?,     name = ?     ON DUPLICATE KEY UPDATE     name = ?
### Cause: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting result to parameter object. Cause: java.util.NoSuchElementException

	at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:199)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:184)
	at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:62)
	at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:58)
	at com.sun.proxy.$Proxy10.insertUpdate(Unknown Source)
	at test.SimpleTest.test(SimpleTest.java:59)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:628)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:117)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:184)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:180)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:127)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1378)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1378)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
	at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
	at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting result to parameter object. Cause: java.util.NoSuchElementException
	at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.processBatch(Jdbc3KeyGenerator.java:81)
	at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.processAfter(Jdbc3KeyGenerator.java:64)
	at org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler.java:51)
	at org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler.java:74)
	at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:50)
	at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)
	at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:197)
	... 56 more
Caused by: java.util.NoSuchElementException
	at java.base/java.util.Arrays$ArrayItr.next(Arrays.java:4430)
	at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.assignKeysToParam(Jdbc3KeyGenerator.java:113)
	at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.assignKeys(Jdbc3KeyGenerator.java:97)
	at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.processBatch(Jdbc3KeyGenerator.java:78)
	... 63 more


Process finished with exit code 255

@harawata
Copy link
Member

Thank you for the update, @ApplePedlar !

I could verify the problem.
The error occurs because java.sql.Statement#getGeneratedKeys() returns two rows even though the statement updated one row.
Although it was silently ignored in previous versions, throwing an exception may be the right behavior in this case. I'll submit a PR that improves the error message later.

As it's the driver's bug, I reported it to MariaDB JIRA.
https://jira.mariadb.org/browse/CONJ-698

@harawata harawata added the enhancement Improve a feature or add a new feature label Apr 20, 2019
@harawata harawata self-assigned this Apr 20, 2019
@harawata harawata added this to the 3.5.2 milestone Apr 20, 2019
@harawata
Copy link
Member

Hi @ApplePedlar ,
I have committed the error handling improvement.
Please try the snapshot and let us know if there is any problem.
Thanks again!

p.s.
Looking at the driver's code, it might need a major change to fix the bug.

George5814 pushed a commit to George5814/mybatis-3 that referenced this issue May 14, 2019
* master: (616 commits)
  Fix trivial grammer mistake
  Trivial doc correction
  Add 'defaultNetworkTimeout' to PooledDataSource and UnpooledDataSource
  Update README.md
  Follow-up to mybatis#1517
  Change to use try-with-resources on doc
  New versions available.
  fixes mybatis#1523 Better error message when too many keys are generated
  Minor refactoring.
  Reuse PostgreSQL container without restarting
  Replace postgresql-embedded with Testcontainers
  Added Stack Overflow as a place for questions
  Add `.editorconfig` to maintain consistent coding styles
  [maven-release-plugin] prepare for next development iteration
  [maven-release-plugin] prepare release mybatis-3.5.1
  [ci] Cache maven dependencies.
  Added edge case tests provided by @kdubb
  Add comment for derby
  Revert "Upgrade to derby 10.15.1.3"
  Upgrade to assertj 3.12.2
  ...

# Conflicts:
#	pom.xml
#	src/main/java/org/apache/ibatis/binding/MapperMethod.java
#	src/main/java/org/apache/ibatis/binding/MapperProxyFactory.java
#	src/main/java/org/apache/ibatis/binding/MapperRegistry.java
#	src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java
#	src/main/java/org/apache/ibatis/datasource/unpooled/UnpooledDataSource.java
#	src/main/java/org/apache/ibatis/plugin/Plugin.java
#	src/main/java/org/apache/ibatis/reflection/Reflector.java
#	src/main/java/org/apache/ibatis/session/Configuration.java
#	src/main/java/org/apache/ibatis/session/SqlSession.java
#	src/main/java/org/apache/ibatis/session/TransactionIsolationLevel.java
#	src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java
#	src/main/java/org/apache/ibatis/transaction/Transaction.java
#	src/main/java/org/apache/ibatis/transaction/TransactionFactory.java
#	src/main/java/org/apache/ibatis/transaction/jdbc/JdbcTransaction.java
#	src/main/java/org/apache/ibatis/transaction/managed/ManagedTransaction.java
#	src/main/java/org/apache/ibatis/type/ArrayTypeHandler.java
#	src/main/java/org/apache/ibatis/type/BaseTypeHandler.java
#	src/main/java/org/apache/ibatis/type/SimpleTypeRegistry.java
#	src/main/java/org/apache/ibatis/type/TypeAliasRegistry.java
#	src/main/java/org/apache/ibatis/type/TypeHandlerRegistry.java
@harawata
Copy link
Member

harawata commented Jun 3, 2019

This is just a heads up.

This particular issue (CONJ-698) will be resolved in MariaDB Connector/J 2.5.0.
However, the fix (CONJ-706) may affect getGeneratedKeys() behavior in general.

As a result, with MariaDB Connector/J 2.5.0, useGeneratedKeys will no longer work with a typical multi-row insert statement like the following (i.e. only the first item in the list will have the generated id).

<insert id="insertUpdate" useGeneratedKeys="true" keyProperty="id">
  INSERT INTO Animal (name) VALUES
  <foreach var="animal" items="list" separator=",">
    (#{animal.name})
  </foreach>
</insert>

[EDIT]

The behavior change mentioned above was 'corrected' as a regression in MariaDB Connector/J 2.5.1 (CONJ-735).
And then, it stopped working again in 3.0.x.
And in 3.3.2, a new connection property returnMultiValuesGeneratedIds was added. See CONJ-1117.

@harawata
Copy link
Member

harawata commented Oct 3, 2019

MariaDB Java Connector 2.5.0 is released.
https://github.com/MariaDB/mariadb-connector-j/releases/tag/2.5.0
And, with this latest driver, @ApplePedlar 's test passed.

@magicliang
Copy link

magicliang commented Oct 29, 2020

I have a similar issue, which can be reproduced under these conditions:

mysql-connector-java: 5.1.40
mybatis: 3.5.5

<insert id="insertShopInfo" parameterType="com.mycompany.po.ShopInfo"
        useGeneratedKeys="true" keyProperty="id">
    insert into shop_info
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="id != null">
            id,
        </if>
        <if test="poiId != null">
            poi_id,
        </if>
        <if test="bizAccountId != null">
            biz_account_id,
        </if>
        <if test="userType != null">
            user_type,
        </if>
        <if test="pointName != null">
            point_name,
        </if>
        <if test="brandName != null">
            brand_name,
        </if>
        <if test="district != null">
            district,
        </if>
        <if test="brandId != null">
            brand_id,
        </if>
        <if test="typeId != null">
            type_id,
        </if>
        <if test="bareaId != null">
            barea_id,
        </if>
        <if test="locationId != null">
            location_id,
        </if>
        <if test="address != null">
            address,
        </if>
        <if test="phone != null">
            phone,
        </if>
        <if test="mergeId != null">
            merge_id,
        </if>
        <if test="bareaName != null">
            barea_name,
        </if>
        <if test="typeName != null">
            type_name,
        </if>
        <if test="openInfo != null">
            open_info,
        </if>
        <if test="pricePerson != null">
            price_person,
        </if>
        <if test="typeHierarchy != null">
            type_hierarchy,
        </if>
        <if test="isForeign != null">
            is_foreign,
        </if>
        <if test="geo != null">
            geo,
        </if>
        <if test="status != null">
            status,
        </if>
        <if test="createTime != null">
            create_time,
        </if>
        <if test="modifyTime != null">
            modify_time,
        </if>
        <if test="cityLocationId != null">
            city_location_id,
        </if>
        <if test="cityName != null">
            city_name,
        </if>
        <if test="provinceName != null">
            province_name,
        </if>
        <if test="provinceId != null">
            province_id,
        </if>
        <if test="email != null">
            email,
        </if>
        <if test="hasEat != null">
            has_eat,
        </if>
        <if test="dispatchTypes != null">
            dispatch_types,
        </if>
        <if test="poiType != null">
            poi_type,
        </if>
        <if test="locationName != null">
            location_name,
        </if>
        <if test="encryptWay != null">
            encrypt_way,
        </if>
        <if test="phoneToken != null">
            phone_token,
        </if>
        <if test="tokenEncryptedPhone != null">
            token_encrypted_phone,
        </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
        <if test="id != null">
            #{id,jdbcType=BIGINT},
        </if>
        <if test="poiId != null">
            #{poiId,jdbcType=BIGINT},
        </if>
        <if test="bizAccountId != null">
            #{bizAccountId,jdbcType=BIGINT},
        </if>
        <if test="userType != null">
            #{userType,jdbcType=INTEGER},
        </if>
        <if test="pointName != null">
            #{pointName,jdbcType=VARCHAR},
        </if>
        <if test="brandName != null">
            #{brandName,jdbcType=VARCHAR},
        </if>
        <if test="district != null">
            #{district,jdbcType=VARCHAR},
        </if>
        <if test="brandId != null">
            #{brandId,jdbcType=INTEGER},
        </if>
        <if test="typeId != null">
            #{typeId,jdbcType=INTEGER},
        </if>
        <if test="bareaId != null">
            #{bareaId,jdbcType=INTEGER},
        </if>
        <if test="locationId != null">
            #{locationId,jdbcType=INTEGER},
        </if>
        <if test="address != null">
            #{address,jdbcType=VARCHAR},
        </if>
        <if test="phone != null">
            #{phone,jdbcType=VARCHAR},
        </if>
        <if test="mergeId != null">
            #{mergeId,jdbcType=INTEGER},
        </if>
        <if test="bareaName != null">
            #{bareaName,jdbcType=VARCHAR},
        </if>
        <if test="typeName != null">
            #{typeName,jdbcType=VARCHAR},
        </if>
        <if test="openInfo != null">
            #{openInfo,jdbcType=VARCHAR},
        </if>
        <if test="pricePerson != null">
            #{pricePerson,jdbcType=INTEGER},
        </if>
        <if test="typeHierarchy != null">
            #{typeHierarchy,jdbcType=VARCHAR},
        </if>
        <if test="isForeign != null">
            #{isForeign,jdbcType=INTEGER},
        </if>
        <if test="geo != null">
            #{geo,jdbcType=VARCHAR},
        </if>
        <if test="status != null">
            #{status,jdbcType=INTEGER},
        </if>
        <if test="createTime != null">
            #{createTime,jdbcType=TIMESTAMP},
        </if>
        <if test="modifyTime != null">
            #{modifyTime,jdbcType=TIMESTAMP},
        </if>
        <if test="cityLocationId != null">
            #{cityLocationId,jdbcType=INTEGER},
        </if>
        <if test="cityName != null">
            #{cityName,jdbcType=VARCHAR},
        </if>
        <if test="provinceName != null">
            #{provinceName,jdbcType=VARCHAR},
        </if>
        <if test="provinceId != null">
            #{provinceId,jdbcType=INTEGER},
        </if>
        <if test="email != null">
            #{email,jdbcType=VARCHAR},
        </if>
        <if test="hasEat != null">
            #{hasEat,jdbcType=BIT},
        </if>
        <if test="dispatchTypes != null">
            #{dispatchTypes,jdbcType=VARCHAR},
        </if>
        <if test="poiType != null">
            #{poiType,jdbcType=INTEGER},
        </if>
        <if test="locationName != null">
            #{locationName,jdbcType=VARCHAR},
        </if>
        <if test="encryptWay != null">
            #{encryptWay,jdbcType=INTEGER},
        </if>
        <if test="phoneToken != null">
            #{phoneToken,jdbcType=VARCHAR},
        </if>
        <if test="tokenEncryptedPhone != null">
            #{tokenEncryptedPhone,jdbcType=VARCHAR},
        </if>
    </trim>
    ON DUPLICATE KEY UPDATE
    address = VALUES(address),
    point_name = VALUES(point_name),
    location_id = VALUES(location_id),
    phone = VALUES(phone),
    type_name = VALUES(type_name),
    email =VALUES(email),
    has_eat = VALUES(has_eat),
    dispatch_types = VALUES(dispatch_types),
    modify_time = VALUES(modify_time),
    encrypt_Way = VALUES(encrypt_way),
    phone_token = VALUES(phone_token),
    token_encrypted_phone = VALUES(token_encrypted_phone)
</insert>
shopInfoExtMapper.insertShopInfo(shopInfo)

The error stacktrace is:

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)
at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.assignKeys(Jdbc3KeyGenerator.java:104)
at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator.processBatch(Jdbc3KeyGenerator.java:85)
... 56 more

I know this issue is closed, but this is only lead I can found on Github. I can open a new issue if necessary.

@harawata
Copy link
Member

Hello @magicliang ,
We may need to reproduce the problem.
Please create and share a complete project like this one on your GitHub repo.
And what is the DB version?

@magicliang
Copy link

Hello @magicliang ,
We may need to reproduce the problem.
Please create and share a complete project like this one on your GitHub repo.
And what is the DB version?

Thanks for replying.

Our DB version is 5.7.21-21-log(but it is a percona version, not normal community version).

It is very hard to provide a complete demo to reproduce this problem, because we are using a non-open-sourcing technical stack which uses non-standar jdbc middleware.

After some digging, we think the root cause of this case is insertion with "update on duplicate key" creates 2 affected rows, which generates 2 auto_increment ids. I will try to prove it and make a simple demo later. Let's see what happens then.

@anhao
Copy link

anhao commented Dec 16, 2020

Hello @magicliang ,
We may need to reproduce the problem.
Please create and share a complete project like this one on your GitHub repo.
And what is the DB version?

Thanks for replying.

Our DB version is 5.7.21-21-log(but it is a percona version, not normal community version).

It is very hard to provide a complete demo to reproduce this problem, because we are using a non-open-sourcing technical stack which uses non-standar jdbc middleware.

After some digging, we think the root cause of this case is insertion with "update on duplicate key" creates 2 affected rows, which generates 2 auto_increment ids. I will try to prove it and make a simple demo later. Let's see what happens then.

Hello @magicliang ,
We may need to reproduce the problem.
Please create and share a complete project like this one on your GitHub repo.
And what is the DB version?

Thanks for replying.

Our DB version is 5.7.21-21-log(but it is a percona version, not normal community version).

It is very hard to provide a complete demo to reproduce this problem, because we are using a non-open-sourcing technical stack which uses non-standar jdbc middleware.

After some digging, we think the root cause of this case is insertion with "update on duplicate key" creates 2 affected rows, which generates 2 auto_increment ids. I will try to prove it and make a simple demo later. Let's see what happens then.

I have also encountered this problem. Has this problem been solved?

@harawata
Copy link
Member

Hello @anhao ,
It's unclear which issue you are referring to.
If the issue is reproducible with the latest version of MyBatis (=3.5.6 at the moment), please create a new issue and fill out the bug report form in the template.
Be sure to include 1) the versions of DB, driver and MyBatis you are using and 2) a demo project or steps to reproduce the problem.

@harawata
Copy link
Member

I'm going to lock this issue.

Read my earlier comment explaining the flip-flops in MariaDB Connector/J.
If you still think there is a bug in MyBatis (and not in the driver), please create a new issue and fill in the bug report form in the issue template.

@mybatis mybatis locked as resolved and limited conversation to collaborators Dec 20, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement Improve a feature or add a new feature
Projects
None yet
Development

No branches or pull requests

5 participants