Dozer映射清空目标独有字段ID致Hibernate约束异常求助
这个问题我之前也踩过坑,本质是Dozer的默认映射逻辑和JPA实体的持久化需求不匹配导致的,咱们先拆解原因,再给解决办法:
为什么ID会被置为null?
核心原因有两个:
- Dozer对集合的默认处理是「完全替换」:你用
dozerMapper.map(pojo, entity)时,源对象Pojo根本没有ep这个集合字段,Dozer就会直接把Entity里已加载的ep集合覆盖成null,或者创建一个全新的空集合/新的EntityPojo实例——原来从数据库捞出来的EntityPojo的ID自然就丢了。 - 源对象和集合元素的结构不匹配:
Pojo只有name和surname,但EntityPojo多了id字段。Dozer映射时会生成新的EntityPojo对象来匹配Pojo的属性,新对象当然没有原有ID,直接覆盖后就导致ID为null了。
怎么解决?
给你三种实用的方案,按需选择:
1. 自定义Dozer映射规则,保留ID
你可以通过XML或者编程式配置,告诉Dozer别乱处理集合,也别覆盖id字段。
举个XML配置的例子:
<!-- 让Dozer跳过Pojo和Entity之间的ep集合映射 --> <mapping> <class-a>your.package.Pojo</class-a> <class-b>your.package.Entity</class-b> <field-exclude> <a>ep</a> <b>ep</b> </field-exclude> </mapping> <!-- 让Dozer映射Pojo到EntityPojo时,不碰id字段 --> <mapping> <class-a>your.package.Pojo</class-a> <class-b>your.package.EntityPojo</class-b> <field-exclude> <a></a> <!-- Pojo没有id,所以留空 --> <b>id</b> </field-exclude> </mapping>
然后代码里手动处理集合更新:
Entity entity = repo.findByCode("aCode"); // 先映射Entity的非集合属性(如果有的话) dozerMapper.map(pojo, entity); // 手动更新集合里的EntityPojo属性(如果是多个元素就循环处理) if (!entity.getEp().isEmpty()) { EntityPojo existingEp = entity.getEp().get(0); dozerMapper.map(pojo, existingEp); // 只更新name和surname,id保留 } repo.save(entity);
2. 直接手动更新属性,跳过Dozer的全量映射
如果要更新的属性不多,直接手动修改最省心,完全避免Dozer的坑:
Entity entity = repo.findByCode("aCode"); // 遍历集合里的EntityPojo,手动更新name和surname entity.getEp().forEach(ep -> { ep.setName(pojo.getName()); ep.setSurname(pojo.getSurname()); }); repo.save(entity);
3. 用编程式配置替代XML
不想写XML的话,用Dozer的BeanMappingBuilder来配置:
BeanMappingBuilder builder = new BeanMappingBuilder() { @Override protected void configure() { // 排除Pojo到Entity的ep字段映射 mapping(Pojo.class, Entity.class) .exclude("ep"); // 排除EntityPojo的id字段不被覆盖 mapping(Pojo.class, EntityPojo.class) .exclude("id"); } }; // 把配置加到DozerMapper里 dozerMapper.addMapping(builder); // 之后的映射逻辑和方案1一致
划重点
- Dozer默认是覆盖式映射,尤其是集合,它不会帮你合并原有数据,只会直接替换,这点对JPA实体特别致命。
- JPA关联对象的主键(
id)绝对不能丢,否则Hibernate会把它当成新对象处理,要么报约束异常,要么重复插入数据。
内容的提问来源于stack exchange,提问作者Phate
相关产品推荐
相关产品推荐

