在映射成目标对象时,创建目标对象实例,可以使用@ObjectFactory注解的方法取代调用默认的构造方法创建目标对象,工厂方法可以是无参返回类型是目标类型的方法,无参的工厂方法可以不设置@ObjectFactory注解;或有参返回类型是目标类型的方法,参数可以用@TargetType或@Context注解。
- 为避免返回类型有泛型时的匹配错误,特别是继承List的类的情况下,不建议使用无参的工厂方法。
- 例如protobuf中的repeated·string生成的ProtocolStringList要在CollectionMappingStrategy.ACCESSOR_ONLY策略下使用。
可用来生成默认source。
@Data
public class TestFivePO {
private TestFourPO test;
private TestPO testPO;
}
@Mapper
public class BaseMapper {
@ObjectFactory-------注解
public TestSixBO createSixBO() {
TestSixBO testSixBO = new TestSixBO();
testSixBO.setId(1L);
return testSixBO;
}
}
@Mapper(uses = BaseMapper.class)
public interface TestMapper {
@Mapping(target = "test.id", ignore = true)
TestSevenBO toBOS(TestFivePO testPO);
}
@Component
public class TestMapperImpl implements TestMapper {
@Autowired
private BaseMapper baseMapper;
@Override
public TestSevenBO toBOS(TestFivePO testPO) {
if ( testPO == null ) {
return null;
}
TestSevenBO testSevenBO = new TestSevenBO();
testSevenBO.setTest( testFourPOToTestSixBO( testPO.getTest() ) );
return testSevenBO;
}
protected TestSixBO testFourPOToTestSixBO(TestFourPO testFourPO) {
if ( testFourPO == null ) {
return null;
}
TestSixBO testSixBO = baseMapper.createSixBO();//只用在了这行。
testSixBO.setName( testFourPO.getName() );
testSixBO.setCreateTime( testFourPO.getCreateTime() );
return testSixBO;
}
}
@Mapper
public class BaseMapper {
@ObjectFactory
public T createSixBO(@TargetType Class tClass) throws IllegalAccessException, InstantiationException {
T t = tClass.newInstance();
t.setId(3L);
return t;
}
}
testSevenBO.setTest( testFourPOToTestSixBO( testPO.getTest() ) );
//给@Mapping的属性defaultValue设置一个值,若果源字段为空,那么目标字段就为此默认值
@Mapping(target = "name", source = "name", defaultValue = "noName")
TestBO toBO(TestPO testPO);
@Mapping(target = "name", expression = "java(testPO.getName() + \"BO\")")
@Mapping(target = "totalPrice", expression = "java(testThreePO.getTotalPrice().toString() + \"元\")")
TestBO toBO(TestPO testPO);
还可以设置@Mapping的defaultExpression设置默认表达式,在source的值为null时,调用此默认表达式代码。
@Mapping(target = "name", source = "name", defaultExpression = "java(StringBOUtils.toBOString(testPO.getName()))")
@Mapping(target = "state", expression = "java(net.shukun.universe.core.common.DoctorMapStructUtil.doctorId1(var1.getState()))" )
//这块需要注意包装类型
@Mapping(target = "state", expression = "java(DoctorMapStructUtil.getStateByName(var1.getState().getValue()).getCode())" )
public class StringBOUtils {
public static String toBOString(String poString) {
return poString + "BO";
}
}
//使用 imports
@Mapper(uses = BaseMapper.class, imports = {StringBOUtils.class})
public interface TestMapper {
@Mapping(target = "name", expression = "java(StringBOUtils.toBOString(testPO.getName()))")
TestBO toBO(TestPO testPO);
}
//还可以设置@Mapping的defaultExpression设置默认表达式,在source的值为null时,调用此默认表达式代码
@Mapping(target = "name", source = "name", defaultExpression = "java(StringBOUtils.toBOString(testPO.getName()))")
TestBO toBO(TestPO testPO);
如果映射的结果是父类,又存在不同子类的映射方法,编译会报错,此时需要使用@Mapping或@BeanMapping的resultType指定映射结果的具体类型。
@Mapper
public class BaseMapper {
public TestSixBO createSixBO() {
return new TestSixBO();
}
public TestEightBO createEightBO() {
return new TestEightBO();
}
}
@Mapper(uses = {BaseMapper.class})
public interface TestMapper {
@BeanMapping(resultType = TestEightBO.class)
BaseBO toBO(TestFourPO testFourPO);
}
当映射的source对象为null时可以通过@BeanMapping、@IterableMapping、@MapMapping(优先级最高),@Mappe,@MappingConfig(优先级最低)的nullValueMappingStrategy策略来控制NULL值的映射结果。策略值有:
@IterableMapping:
集合使用了NullValueMappingStrategy.RETURN_DEFAULT策略所以源为NULL时返回return new ArrayList
@Mapper
public interface TestMapper {
@IterableMapping(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT)
List toBO(List testFivePO);
}
@Component
public class TestMapperImpl implements TestMapper {
@Override
public List toBO(List testFivePO) {
if ( testFivePO == null ) {
return new ArrayList();
}
List list = new ArrayList( testFivePO.size() );
for ( TestFivePO testFivePO1 : testFivePO ) {
list.add( testFivePOToTestThreeBO( testFivePO1 ) );
}
return list;
}
protected TestBO testPOToTestBO(TestPO testPO) {
if ( testPO == null ) {
return null;
}
TestBO testBO = new TestBO();
testBO.setId( testPO.getId() );
testBO.setName( testPO.getName() );
if ( testPO.getPrice() != null ) {
testBO.setPrice( testPO.getPrice().toString() );
}
if ( testPO.getCreateTime() != null ) {
testBO.setCreateTime( new SimpleDateFormat().format( testPO.getCreateTime() ) );
}
return testBO;
}
protected TestThreeBO testFivePOToTestThreeBO(TestFivePO testFivePO) {
if ( testFivePO == null ) {
return null;
}
TestThreeBO testThreeBO = new TestThreeBO();
testThreeBO.setTest( testPOToTestBO( testFivePO.getTest() ) );
return testThreeBO;
}
}
@BeanMapping
@Mapper
public interface TestMapper {
@BeanMapping(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT)
@Mapping(target = "id", constant = "0L")
@Mapping(target = "name", expression = "java(testPO.getName() + \"BO\")")
TestBO toBO(TestPO testPO);
}
@Component
public class TestMapperImpl implements TestMapper {
@Override
public TestBO toBO(TestPO testPO) {
TestBO testBO = new TestBO();
if ( testPO != null ) {
if ( testPO.getPrice() != null ) {
testBO.setPrice( testPO.getPrice().toString() );
}
else {
testBO.setPrice( testPO.getPrice() + "BO" );
}
if ( testPO.getCreateTime() != null ) {
testBO.setCreateTime( new SimpleDateFormat().format( testPO.getCreateTime() ) );
}
}
testBO.setId( (long) 0L );
testBO.setName( testPO.getName() + "BO" );
return testBO;
}
}
在使用@MappingTarget更新目标时,可以设置@Mapping、@BeanMapping(优先级最高),@Mapper或@MappingConfig(优先级最底)的nullValuePropertyMappingStrategy属性值,用于设置当源对象属性为null时,如何设置目标属性的值。nullValuePropertyMappingStrategy的值有:
@Mapper
public interface TestMapper {
@Mapping(target = "name", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
@Mapping(target = "price", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void toBO(TestPO testPO, @MappingTarget TestBO testBO);
}
@Component
public class TestMapperImpl implements TestMapper {
@Override
public void toBO(TestPO testPO, TestBO testBO) {
if ( testPO == null ) {
return;
}
if ( testPO.getName() != null ) {
testBO.setName( testPO.getName() );
}
else {
testBO.setName( "" );
}
if ( testPO.getPrice() != null ) {
testBO.setPrice( testPO.getPrice().toString() );
}
testBO.setId( testPO.getId() );
if ( testPO.getCreateTime() != null ) {
testBO.setCreateTime( new SimpleDateFormat().format( testPO.getCreateTime() ) );
}
else {
testBO.setCreateTime( null );
}
}
}
在进行映射时可以通过装饰器模式,为目标对象设置一些,不能由源对象直接生成或者源对象没有的属性。spring注入策略的装饰器映射使用@DecoratedWith注解制定装饰器。装饰器中使用@Qualifier("delegate")指定注入的bean。
@Mapper
@DecoratedWith(TestDecotator.class)
public interface TestMapper {
TestBO toTestBO(TestPO testPO);
}
public abstract class TestDecorator implements TestMapper {
@Autowired
@Qualifier("delegate")
private TestMapper testMapper;
@Override
public TestBO toTestBO(TestPO testPO) {
TestBO testBO = testMapper.toTestBO(testPO);
testBO.setName("1111");//这处应该是个组合关系,用来设计一些特定的不能由源对象直接映射的赋值操作
return testBO;
}
}
================================生成的代码如下================================
class TestMapperImpl extends TestDecorator implements TestMapper {
private final TestMapper delegate;
public TestMapperImpl() {
this( new TestMapperImpl_() );
}
private TestMapperImpl(TestMapperImpl_ delegate) {
this.delegate = delegate;
}
}
class TestMapperImpl_ implements TestMapper {
@Override
public TestBO toTestBO(TestPO testPO) {
if ( testPO == null ) {
return null;
}
TestBO testBO = new TestBO();
testBO.setId( testPO.getId() );
testBO.setName( testPO.getName() );
testBO.setPrice( testPO.getPrice() );
testBO.setCreteTime( testPO.getCreteTime() );
return testBO;
}
}
可以在抽象mapper类、Mapper#uses引入的类或被@Context注解的上下文对象类中设置映射前后的回调方法。@BeforeMapping注解的方法是前置方法,@AfterMapping注解的方法是后置方法。如果前后置方法具有参数,返回的类型能被赋予映射方法的返回类型并且参数都可以用源/目标参数获取,才会调用前后置方法。
前后置方法中用@MappingTarget注解的参数获得的是目标实例,@TargetType获取的是目标类型,@Context可以获取上下文对象,其他的参数被赋予源参数。然后前后置方法不是void的,返回的值不为null的情况下将作为映射方法的返回值。同样,若前后置方法能匹配到多个,将会都调用,可以通过给前置方法添加@Named和映射方法上添加@BeanMapping#qualifiedByName制定要调用的前后置方法;一旦使用了@BeanMapping#qualifiedByName就必须指定自己选用调用的所有前后置方法的@Named名。不带@MappingTarget注解参数的@BeforeMapping会在源参数进行null检查并且构造新的目标bean之前调用;带@MappingTarget注解参数的@BeforeMapping会在构造新的目标bean之后调用;@AfterMapping会在return前最后调用。在使用建造者时,@BeforeMapping、@AfterMapping中要想获取目标对象,@MappingTarget注解的就必须是建造者。
@Mapper
@Named("baseMapper")
public class BaseMapper {
@BeforeMapping
@Named("before")
public void before(BasePO basePO, @MappingTarget BaseBO baseBO) {
System.out.println(basePO);
System.out.println(baseBO);
}
@AfterMapping
public T after(@TargetType Class clazz, @Context ThreadLocalContext threadLocalContext){
System.out.println(threadLocalContext);
T t = null;
try {
t = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return t;
}
}
@Data
public class ThreadLocalContext {
private ThreadLocal
public interface LicenseModelTranslator {
LicenseModelTranslator INSTANCE = Mappers.getMapper(LicenseModelTranslator.class);
@Mapping(target = "activationInfo",ignore = true)
@Mapping(target = "operatorUid", source = "operatorUserId")
@Mapping(target = "createdAt", source = "ctime")
LicenseActivationRecordBo toActivationRecordBo(ActivationRecordEntity var1);
List toActivationRecordBos(List var1);
@AfterMapping
default void setLicenseActivationInfoBo(@MappingTarget LicenseActivationRecordBo bo, ActivationRecordEntity var1) {
List activationInfo = JSONUtil.toList(var1.getActivationInfo(), LicenseActivationInfoBo.class);
bo.setActivationInfo(activationInfo);
}
}
public class Employee {
private String name;
private Employee reportsTo;
private List team;
}
public class EmployeeDto {
private String employeeName;
private EmployeeDto reportsTo;
private List team;
}
@Mapper
public interface EmployeeMapper {
EmployeeMapper MAPPER = Mappers.getMapper( EmployeeMapper.class );
@Mapping(source = "employeeName", target = "name")
Employee toEmployee(EmployeeDto employeeDto, @Context CycleAvoidingMappingContext context);
@InheritInverseConfiguration
EmployeeDto fromEmployee(Employee employee, @Context CycleAvoidingMappingContext context);
}
public class CycleAvoidingMappingContext {
private Map
@Data
public class TestBOS {
private TestBOS testS;
private List list;
}
@Data
public class TestPOS {
private TestPOS testS;
private List list;
}
@Mapper
public class BaseMapper {
@BeforeMapping
public T before(Object source, @TargetType Class targetType) {
T t;
try {
t = targetType.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
return null;
}
return t;
}
}
@Mapper(uses = BaseMapper.class)
public interface TestMapper {
TestBOS toBean(TestPOS testPOS);
}
@InheritConfiguration可以继承@Mapping,@BeanMapping,@IterableMapping的映射规则。@InheritConfiguration注解的方法上,有需要映射的字段,它会搜索有相同配置的映射,找到了直接复用此映射;若找到多个方法上都有满足此映射的配置,需要制定@InheritConfiguration#name的值,制定继承方法的映射。
@Data
@ToString
public class TestPO {
private String name;
}
@Data
@ToString
public class TestBO {
private String nickName;
}
@Data
public class TestTwoBO {
private String realName;
}
@Mapper
public interface TestMapper {
@Mapping(target = "nickName", source = "name", defaultValue = "nickName")
TestBO toTestBO(TestPO testPO);
@Mapping(target = "nickName", source = "name", defaultValue = "realName")
TestBO toTestTwoBO(TestPO testPO);
@InheritConfiguration(name = "toTestTwoBO")
TestBO toTestThreeBO(TestPO testPO); //toTestThreeBO的实现方法在设置nickName时,继承的toTestTwoBO方法的映射配置。
}
当我们定义了一种对象到另一种对象的映射后,可以通过@InheritInverseConfiguration直接进行逆映射。可以看到实现的toTestBO方法中的price->priceString使用的就是toTestPO中priceString->price的逆映射,但是正映射的@Mapping#expression、#defaultExpression、#defaultValue和#constant会被逆映射忽略,就如其中的默认值“-1”在逆映射中不存在;此外某个字段的逆映射可以被ignore,expression或constant覆盖,就如name->nameString;在正映射的createTimeString->createTime上设置了ignore,若此时同时制定了source,则逆映射会继承ignore,不用再重新设置。@InheritConfiguration的优先级高于@InheritInverseConfiguration。
@Data
@ToString
public class TestPO {
private Long id;
private String nameString;
private BigDecimal priceString;
private Date createTimeString;
}
//InheritInverseConfiguration 这个注解是一个相对的操作,必须要有前提此处才会有用,比如下例中必须有TestBO toTestBO(TestPO testPO)才可以,也就是为了少写
//映射代码,如果有多个类似的话,可以用@InheritInverseConfiguration( name = "toTestBO" )指定一个原实现方法名
@Mapper
public interface TestMapper {
@Mapping(target = "price", source = "priceString", defaultValue = "-1")
@Mapping(target = "name", source = "nameString")
@Mapping(target = "createTime", source = "createTimeString", ignore = true)
TestBO toTestBO(TestPO testPO);
@InheritInverseConfiguration
@Mapping(target = "nameString", constant = "namePO")
TestPO toTestBO(TestBO testBO);
}
@Component
public class TestMapperImpl implements TestMapper {
@Override
public TestBO toTestBO(TestPO testPO) {
if ( testPO == null ) {
return null;
}
TestBO testBO = new TestBO();
if ( testPO.getPriceString() != null ) {
testBO.setPrice( testPO.getPriceString().toString() );
}
else {
testBO.setPrice( "-1" );
}
testBO.setName( testPO.getNameString() );
testBO.setId( testPO.getId() );
return testBO;
}
@Override
public TestPO toTestBO(TestBO testBO) {
if ( testBO == null ) {
return null;
}
TestPO testPO = new TestPO();
if ( testBO.getPrice() != null ) {
testPO.setPriceString( new BigDecimal( testBO.getPrice() ) );
}
testPO.setId( testBO.getId() );
testPO.setNameString( "namePO" );
return testPO;
}
}
@MapperConfig注解的接口就是共享配置,可以在@Mapper#config指定共享配置,@MapperConfig中的属性和@Mapper相同,@Mapper中的属性会覆盖@MapperConfig。共享配置中可以设置原型映射,也可以是父类映射,再通过@MapperConfig、@Mapper的mappingInheritanceStrategy就可以实现原型映射的继承。mappingInheritanceStrategy的值有:
以上也都要注意映射类型冲突,冲突同样需要指明name属性。
@Data
@ToString
public class BaseBO {
private Long id;
private String name;
private String createTimeString;
}
@MapperConfig
public interface BaseConfig {
@Mapping(target = "createTimeString", source = "createTime")
BaseBO toTestBaseBO(BasePO basePO);
}
共享配置的原型映射并不会生成单独的实现方法。虽然默认继承策略支持正/逆,但是引入共享配置,且mapper中正映射能继承原型映射的情况下,再设置逆映射方法,就必须制定name属性,否则同样有冲突报错。
@Mapper(config = BaseConfig.class, mappingInheritanceStrategy = MappingInheritanceStrategy.EXPLICIT)
public interface TestMapper {
@InheritConfiguration
void updateBO(TestFourPO testFourPO, @MappingTarget TestSixBO testSixBO);
@InheritInverseConfiguration(name = "toTestBaseBO")
void updatePO(TestSixBO testSixBO, @MappingTarget TestFourPO testFourPO);
}
@Component
public class TestMapperImpl implements TestMapper {
@Override
public void updateBO(TestFourPO testFourPO, TestSixBO testSixBO) {
if ( testFourPO == null ) {
return;
}
if ( testFourPO.getCreateTime() != null ) {
testSixBO.setCreateTimeString( new SimpleDateFormat().format( testFourPO.getCreateTime() ) );
}
else {
testSixBO.setCreateTimeString( null );
}
testSixBO.setId( testFourPO.getId() );
testSixBO.setName( testFourPO.getName() );
}
@Override
public void updatePO(TestSixBO testSixBO, TestFourPO testFourPO) {
if ( testSixBO == null ) {
return;
}
try {
if ( testSixBO.getCreateTimeString() != null ) {
testFourPO.setCreateTime( new SimpleDateFormat().parse( testSixBO.getCreateTimeString() ) );
}
else {
testFourPO.setCreateTime( null );
}
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
testFourPO.setId( testSixBO.getId() );
testFourPO.setName( testSixBO.getName() );
}
}
@Mapper(config = BaseConfig.class, mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG)
public interface TestMapper {
void updateBO(TestFourPO testFourPO, @MappingTarget TestSixBO testSixBO);
}
@Component
public class TestMapperImpl implements TestMapper {
@Override
public void updateBO(TestFourPO testFourPO, TestSixBO testSixBO) {
if ( testFourPO == null ) {
return;
}
if ( testFourPO.getCreateTime() != null ) {
testSixBO.setCreateTimeString( new SimpleDateFormat().format( testFourPO.getCreateTime() ) );
}
else {
testSixBO.setCreateTimeString( null );
}
testSixBO.setId( testFourPO.getId() );
testSixBO.setName( testFourPO.getName() );
}
}
@Mapper(config = BaseConfig.class, mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_ALL_FROM_CONFIG)
public interface TestMapper {
void updatePO(TestSixBO testSixBO, @MappingTarget TestFourPO testFourPO);
}
@Component
public class TestMapperImpl implements TestMapper {
@Override
public void updatePO(TestSixBO testSixBO, TestFourPO testFourPO) {
if ( testSixBO == null ) {
return;
}
try {
if ( testSixBO.getCreateTimeString() != null ) {
testFourPO.setCreateTime( new SimpleDateFormat().parse( testSixBO.getCreateTimeString() ) );
}
else {
testFourPO.setCreateTime( null );
}
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
testFourPO.setId( testSixBO.getId() );
testFourPO.setName( testSixBO.getName() );
}
}
@Mapper(config = BaseConfig.class, mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_ALL_FROM_CONFIG)
public interface TestMapper {
void updateBO(TestFourPO testFourPO, @MappingTarget TestSixBO testSixBO);
void updatePO(TestSixBO testSixBO, @MappingTarget TestFourPO testFourPO);
}
@Component
public class TestMapperImpl implements TestMapper {
@Override
public void updateBO(TestFourPO testFourPO, TestSixBO testSixBO) {
if ( testFourPO == null ) {
return;
}
if ( testFourPO.getCreateTime() != null ) {
testSixBO.setCreateTimeString( new SimpleDateFormat().format( testFourPO.getCreateTime() ) );
}
else {
testSixBO.setCreateTimeString( null );
}
testSixBO.setId( testFourPO.getId() );
testSixBO.setName( testFourPO.getName() );
}
@Override
public void updatePO(TestSixBO testSixBO, TestFourPO testFourPO) {
if ( testSixBO == null ) {
return;
}
try {
if ( testSixBO.getCreateTimeString() != null ) {
testFourPO.setCreateTime( new SimpleDateFormat().parse( testSixBO.getCreateTimeString() ) );
}
else {
testFourPO.setCreateTime( null );
}
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
testFourPO.setId( testSixBO.getId() );
testFourPO.setName( testSixBO.getName() );
}
}