在使用Jakarta JAX-RS的REST API项目中,我们使用mapstruct将DTOMap到Hibernate实体,并且我们遇到了父子关系的问题。
我们将两个类称为Parent和Child,第一个类与第二个类相关联。
当调用API方法保存Parent时,我们做两件事:
// find the entity in the database
Parent parent = parentRepo.findById(dto.id());
// update the entity with the dto's values
parentMapper.toDb(dto, parent);
字符串
除了DTO更改了引用的子对象(即父对象不像以前那样链接到同一个子对象,而是链接到另一个子对象)之外,一切正常。然后Hibernate将立即抛出一个Exception,只要Map器尝试调用setId()
方法:
Caused by: org.hibernate.HibernateException: identifier of an instance of ChildEntity was altered from 123 to 456
at org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor.handleWrite(EnhancementAsProxyLazinessInterceptor.java:265)
at org.hibernate.bytecode.enhance.spi.interceptor.AbstractInterceptor.writeObject(AbstractInterceptor.java:157)
at com.acme.ChildEntity.$$_hibernate_write_id(ChildEntity.java)
at com.acme.BaseEntity.setId(BaseEntity.java:44)
at com.acme.ParentMapperImpl.resourceFileDtoToResourceFile(parentMapperImpl.java:272)
at com.acme.ParentMapperImpl.toDb(parentMapperImpl.java:210)
型
Hibernate的字节码不允许我们将id更新为新值,这是有意义的。
问题是mapstruct生成的Map器没有任何方法来区分这两种情况:
- 子对象的属性被更新的情况(id保持不变,但其他属性被更新),在这种情况下,子对象应该保持相同的实体示例,并对其调用一些
setProperty()
- 在这种情况下,更新的是父子关系本身(id不同),在这种情况下,Child应该是数据库先前返回的示例之外的另一个示例
当前,以下Map器声明...
ParentEntity toDb(ParentDto dtoParent, @MappingTarget ParentEntity dbParent);
型
...生成以下Map器实现:
if ( dtoParent.child() != null ) {
if ( dbParent.getChild() == null ) {
dbParent.setChild( new ChildEntity() );
}
// this generated method calls every set() methods available
childDtoToChildDb( dtoParent.child(), dbParent.getChild() );
}
else {
dbParent.setChild( null );
}
型
它缺少类似于:
// check if the child is the same or another one
if (dbParent.getChild().getId().equals(dtoParent.child().id()) { ... }
型
一种可能性是告诉ParentMapper始终使用ChildMapper:
// declaration
@Mapper(... uses = {ChildMapper.class})
// implementation, replacing the whole previous if/else
dbParent.setChild( childMapper.toDb( dtoParent.child() ) );
型
在这种情况下,Map器总是用一个新的子实体替换子实体,但是它挑战我们在之后重新附加实体,并且它似乎倾向于其他警告(Map器总是创建一个新的子实体,而不考虑数据库中的内容)。
我想没有办法处理生成的Map器的所有内容,因为它应该能够在child替换的情况下调用database,但也许有一种方法可以自定义mapstruct或以更智能的方式调用其Map器。也许我们应该让mapstruct只将简单的DTOMap到简单的实体,并自己处理实体到实体/DTO到DTO的关系?
1条答案
按热度按时间b4qexyjb1#
我不知道这是不是更好的方法,但我们找到了一个似乎很成功的方法,如果有人需要,我花几分钟把它写在这里。
基本思想是DTO应该总是更新一个真正的DB实体,只更新它知道的字段,只覆盖Hibernate实体中包含的部分DB状态。这样,我们可以确保实体只在DTO范围内被修改。
否则可能会导致问题,例如一个全新的对象只被部分填充,hVersion过期,问题中提到的id冲突。
要做到这一点,我们有三个部分:
#1Map器的ObjectFactory:
字符串
#2 MapperConfig中的原型定义:
型
#3每个DTO/DB对的几个Map器方法:
型
以下是可以使用这些方法的两种情况:
型
为了显示结果,下面是由mapstruct生成的方法:
型