资讯动态

mybatis-keyproperty-error_illustrated

发布时间:2026/10/15 17:03:15 来源:尧图企业网站定制
MyBatis 报 No setter found for the keyProperty ‘id’useGeneratedKeys 要回填主键DTO 里却没有 id 字段关键词MyBatis、useGeneratedKeys、keyProperty、No setter found for the keyProperty、Jdbc3KeyGenerator、Data一、问题现场insertidinsertDetailparameterTypecom.xxx.dto.DetailDtouseGeneratedKeystruekeyPropertyidINSERT INTO detail (prod_id, prod_name, update_time) VALUES (#{prodId}, #{prodName}, #{updateTime})/insertDatapublicclassDetailDto{privateStringprodId;privateStringprodName;privateDateupdateTime;// id 是数据库自增的所以这里没有 id 字段}一执行 insert 就报Cause: org.apache.ibatis.executor.ExecutorException: No setter found for the keyProperty id in com.xxx.dto.DetailDto.二、原因useGeneratedKeys干的事情是INSERT 执行完之后把数据库生成的主键回填到参数对象的id属性上。而DetailDto里既没有id字段也没有setId方法回填无处落脚于是抛异常。Data只给类里声明过的字段生成 getter/setter不会凭空造字段加Setter也是同样的报错。为什么 SQL 都执行成功了还抛异常因为回填发生在ps.execute()之后// PreparedStatementHandlerpublicintupdate(Statementstatement)throwsSQLException{PreparedStatementps(PreparedStatement)statement;ps.execute();introwsps.getUpdateCount();ObjectparameterObjectboundSql.getParameterObject();KeyGeneratorkeyGeneratormappedStatement.getKeyGenerator();keyGenerator.processAfter(executor,mappedStatement,ps,parameterObject);// 异常从这一行抛出returnrows;}▲ 图 1主键回填链路 —— 回填发生在ps.execute()之后所以 SQL 已经落库*外层没有事务autocommit这一行数据已经落库了但接口给你报错 —— 脏数据的来源之一外层有 Spring 事务异常会触发回滚表里干净但你会因为库里明明没数据却报了个 setter 的错而更加困惑。三、源码MyBatis 怎么判断有没有 setterKeyAssigner.assign()里调metaParam.hasSetter(id)最终落到ReflectorpublicbooleanhasSetter(StringpropertyName){returnsetMethods.containsKey(propertyName);}而setMethods里除了setXxx方法Reflector.addFields()还会把字段本身注册进去沿父类链扫描for(Fieldfield:clazz.getDeclaredFields()){if(!setMethods.containsKey(field.getName())){intmodifiersfield.getModifiers();if(!Modifier.isFinal(modifiers)||!Modifier.isStatic(modifiers)){addSetField(field);// 字段本身被当成可写属性}}}if(clazz.getSuperclass()!null){addFields(clazz.getSuperclass());}所以hasSetter为 true 的条件满足任一即可条件例子有setId方法含父类继承public void setId(Long id)有id字段且非final staticprivate Long id;即使没有任何 setter由此可以反推出这个报错的唯一含义这个类和它所有父类里既没有setId方法也没有id字段。不用再去怀疑 Lombok 配置、IDEA 注解处理器或 jar 版本 —— 就是字段不存在。▲ 图 2hasSetter的两条判定路径满足任一即为 true四、怎么改先判断一件事插入之后你还会不会用到这个新 id不需要 → 删掉两个属性insertidinsertDetailparameterTypecom.xxx.dto.DetailDtoINSERT INTO detail (prod_id, prod_name, update_time) VALUES (#{prodId}, #{prodName}, #{updateTime})/insertSQL 和 DTO 一行都不用动报错立刻消失。代价是以后拿不到自增主键需要时得再查一次库。需要 → 给 DTO 加字段DatapublicclassDetailDto{privateLongid;// 只是接住回填的主键值privateStringprodId;privateStringprodName;privateDateupdateTime;}这里要澄清一个常见误解加id字段不会把 id 插进数据库。SQL 内容只由 XML 里有没有#{id}占位符决定加字段前后 SQL 完全一样INSERTINTOdetail(prod_id,prod_name,update_time)VALUES(?,?,?)keyProperty管的是读回来不是写进去自增依然由数据库负责。如果觉得更新用的 DTO 不该长出一个id字段那就拆成DetailInsertDto带 id用于插入和DetailUpdateDto不带用于按 prodId 更新—— 一个 DTO 承担两种职责才是这个问题的根子。▲ 图 3修复决策树 —— 按插入后是否需要这个新 id分两条路五、三行代码验证MetaClassmcMetaClass.forClass(DetailDto.class);System.out.println(mc.hasSetter(id));// falseSystem.out.println(Arrays.toString(mc.getSetterNames()));// [prodId, prodName, updateTime]getSetterNames()会列出 MyBatis 眼里的全部可写属性一眼就能看出id不在里面比翻 Lombok 配置快得多。六、排查清单遇到这个报错四步走确认报错阶段No setter found for the keyProperty属于结果回填说明 SQL 已经执行过了问题在参数对象上。列出类的可写属性Arrays.toString(MetaClass.forClass(XxxDto.class).getSetterNames())看目标属性在不在列表里。交叉验证字节码javap -p -classpath target/classes com.xxx.XxxDto | grep setId同时确认编译产物是不是最新的。确认自己到底需不需要这个 id不需要就删掉useGeneratedKeyskeyProperty需要就补字段。两者都不影响 INSERT SQL 的写法。▲ 图 4四步排查清单七、几个顺手的提醒useGeneratedKeys只该配在 INSERT 上在update/delete上看到就直接删。多参数或有任一参数带Param时keyProperty必须带参数名前缀如keyPropertydto.id。老版本不报这个错3.4.x 及以前只有一句if (hasSetter(...))判断不满足就静默跳过 —— insert 成功但id一直是null升级到 3.5.x 才变成显式异常。所以同样的代码同事跑着没事八成是版本不同。keyProperty是 MyBatis 少数配了却不做启动校验的属性只有真正执行到那条 insert 才会暴露单测记得覆盖插入分支。▲ 图 5同一份代码MyBatis 3.4.x 静默失效 vs 3.5.x 显式抛异常参考MyBatis 源码org.apache.ibatis.executor.keygen.Jdbc3KeyGeneratorKeyAssigner.assign中抛出该异常MyBatis 源码org.apache.ibatis.executor.statement.PreparedStatementHandlerMyBatis 源码org.apache.ibatis.reflection.ReflectoraddFields/addSetField/hasSetterMyBatis 源码org.apache.ibatis.reflection.MetaClass、org.apache.ibatis.reflection.invoker.SetFieldInvoker

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑