运行环境
Mybatis 版本:3.5.16
数据库:mysql8.X
场景
使用的Mybatis工具来操作DB数据,插入数据后返回对象的自增长序列ID
执行代码
PO类
public class TestPO{
private int id;
private int a;
private int b;
//省略getset!!!
}
apper接口,这里我用的是注解
public interface TestMapper{
@Insert("insert into game_demo (a,b) values(#{a},#{b})")
@SelectKey(statement = "select last_insert_id()",keyProperty = "id",before = false,resultType = int.class)
int insert(TestVO testPO);
@Insert("<script>insert into game_demo (a,b) <foreach item=\"item\" index=\"index\" collection=\"testPOS\" open=\"values\" separator=\",\" close=\";\" nullable=\"true\"> (#{item.a},#{item.b}) </foreach> </script>")
@Options(useGeneratedKeys = true,keyProperty = "id",useCache = false)
int inserts(@Param("testPOS") List<TestPO> testPOS);
}
try(InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml")){
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
Configuration configuration = sqlSessionFactory.getConfiguration();
TypeAliasRegistry typeAliasRegistry = configuration.getTypeAliasRegistry();
typeAliasRegistry.registerAlias(TestPO.class);
configuration.addMapper(TestMapper.class);
try(SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)){
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
List<TestPO> saves = new ArrayList<>();
saves.add(new TestPO(3,1));
saves.add(new TestPO(4,2));
mapper.inserts(saves);
for (TestPO save : saves) {
System.out.println("id==>"+save.getId());
}
sqlSession.commit();
}
}catch (Exception e){
e.printStackTrace();
}
错误
org.apache.ibatis.exceptions.PersistenceException:
### Error committing transaction. Cause: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting result to parameter object. Cause: java.lang.UnsupportedOperationException
### Cause: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting result to parameter object. Cause: java.lang.UnsupportedOperationException
Cause: java.lang.UnsupportedOperationException 这个看的我很懵逼,通过错误查看源码,找到了CollectionWrapper 抛出的错误点 hasSetter函数
Caused by: java.lang.UnsupportedOperationException
at org.apache.ibatis.reflection.wrapper.CollectionWrapper.hasSetter(CollectionWrapper.java:73)
at org.apache.ibatis.reflection.MetaObject.hasSetter(MetaObject.java:106)
at org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator$KeyAssigner.assign(Jdbc3KeyGenerator.java:259)
public class CollectionWrapper implements ObjectWrapper {
//省略若干!!!
public boolean hasSetter(String name) {
throw new UnsupportedOperationException();
}
public boolean hasGetter(String name) {
throw new UnsupportedOperationException();
}
}
是不是很操蛋,这是什么原因造成的呢!!!
问题排查
1.翻阅官方文档
2.注解补全参数
@Options(useGeneratedKeys = true,keyProperty = "testPOS.id",useCache = false)
通过看源码,即使这里不加testPOS,底层处理上也会自动补全
3.大量测试,阅读源码
通过以上两种方式最终定位在SqlSession上,SqlSession获取时有多个参数可调,来改变内部执行逻辑,上面代码中恰好我使用的是:
openSession(ExecutorType.BATCH)
对于ExecutorType 官方文档是这样描述的,这个枚举类型定义了三个值:
ExecutorType.SIMPLE
:该类型的执行器没有特别的行为。它为每个语句的执行创建一个新的预处理语句。ExecutorType.REUSE
:该类型的执行器会复用预处理语句。ExecutorType.BATCH
:该类型的执行器会批量执行所有更新语句,如果 SELECT 在多个更新中间执行,将在必要时将多条更新语句分隔开来,以方便理解。
而我使用的是ExecutorType.BATCH,这个批量执行器从设计上就不支持ID返回
问题解决
在OpenSession指定以下
ExecutorType之一即可:
ExecutorType.SIMPLE
ExecutorType.REUSE