如何让Orika在父对象映射时为嵌套对象使用ObjectFactory?
我来帮你搞定这个MapStruct的困扰!首先得先搞清楚为什么你原来的代码不生效:
@Override public void mapBtoA(FooDto b, Foo a, MappingContext context) { a = new Foo(); a.setId(b.getId()); }
这段代码里,a = new Foo()只是重新给方法参数的局部引用赋值了,而Java是值传递,这个改变不会影响父对象里原来持有的Foo引用,所以父对象的子对象还是原来的那个,不会被替换。
下面给你几种可行的解决方案,你可以根据自己的场景选择:
方案一:使用ObjectFactory创建新实例
ObjectFactory是MapStruct用来创建目标对象实例的扩展点,我们可以用它来强制每次都生成新的Foo对象:
- 先定义一个ObjectFactory实现类:
public class FooObjectFactory implements ObjectFactory<Foo> { @Override public Foo create(Class<Foo> type) { // 每次调用都返回新的Foo实例 return new Foo(); } }
- 在你的Mapper接口里指定这个ObjectFactory,同时定义子对象的映射方法:
@Mapper(objectFactory = FooObjectFactory.class) public interface ParentMapper { // 父对象的映射方法,MapStruct会自动处理子对象的创建和映射 Parent parentDtoToParent(ParentDto parentDto); // 子对象的映射方法,MapStruct会用我们的ObjectFactory创建新Foo实例,再填充属性 Foo fooDtoToFoo(FooDto fooDto); }
这样当映射父对象时,MapStruct会调用fooDtoToFoo方法,而这个方法会通过我们的ObjectFactory生成新的Foo实例,最终父对象里的子对象会被替换成这个新实例。
方案二:使用@Mapping的expression直接创建新实例
如果不想额外写ObjectFactory,也可以在父对象的映射方法里,通过expression直接指定创建新的Foo对象,再结合属性更新:
@Mapper public interface ParentMapper { // 先创建新的Foo实例,再调用update方法填充属性 @Mapping(target = "foo", expression = "java(new Foo())") Parent parentDtoToParent(ParentDto parentDto); // 这个方法用来给已有的Foo实例填充属性(这里就是我们刚创建的新实例) void updateFooFromDto(FooDto fooDto, @MappingTarget Foo foo); }
这种方式更直接,不需要额外的工厂类,适合简单场景。
方案三:自定义Converter强制返回新实例
我们可以创建一个FooDto到Foo的Converter,确保每次转换都返回新实例:
- 实现Converter接口:
@Converter public class FooDtoConverter implements Converter<FooDto, Foo> { @Override public Foo convert(FooDto source) { Foo newFoo = new Foo(); newFoo.setId(source.getId()); // 手动映射其他需要的属性 return newFoo; } }
- 在Mapper里引入这个Converter:
@Mapper(uses = FooDtoConverter.class) public interface ParentMapper { Parent parentDtoToParent(ParentDto parentDto); }
当MapStruct映射父对象的foo属性时,会调用这个Converter,自动用新创建的Foo实例替换原来的子对象。
总结
核心思路就是:不要试图在自定义映射方法里重新赋值方法参数的引用(这在Java里不会影响外部对象),而是让MapStruct通过ObjectFactory、Converter或者expression来负责创建新的子对象实例,这样就能实现父对象映射时替换子对象为新引用的需求。
内容的提问来源于stack exchange,提问作者Guerric P

