1、映射
1.1、自定义方法
若mapstruct满足不了我们的需求,也可以自己写转换方法。
1.1.1、抽象类
@Mapper
public abstract class TestMapper {
public TestBO testToBO(TestPO testPO) {
TestBO testBO = new TestBO();
testBO.setName(testPO.getName() + "BO");
return testBO;
}
public abstract List<TestBO> testToBOS(List<TestPO> testPOS);
}
// 编译生成的子类
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2020-10-28T16:51:57+0800",
comments = "version: 1.4.1.Final, compiler: javac, environment: Java 1.8.0_251 (Oracle Corporation)"
)
public class TestMapperImpl extends TestMapper {
@Override
public List<TestBO> testToBOS(List<TestPO> testPOS) {
if ( testPOS == null ) {
return null;
}
List<TestBO> list = new ArrayList<TestBO>( testPOS.size() );
for ( TestPO testPO : testPOS ) {
list.add( testToBO( testPO ) );
}
return list;
}
}
1.1.2、默认方法
java8以上可以在接口中定义默认方法
@Mapper
public interface TestMapper {
default TestBO testToBO(TestPO testPO) {
TestBO testBO = new TestBO();
testBO.setName(testPO.getName() + "BO");
return testBO;
}
List<TestBO> testToBOS(List<TestPO> testPOS);
}
public class TestMapperImpl implements TestMapper {
@Override
public List<TestBO> testToBOS(List<TestPO> testPOS) {
if ( testPOS == null ) {
return null;
}
List<TestBO> list = new ArrayList<TestBO>( testPOS.size() );
for ( TestPO testPO : testPOS ) {
list.add( testToBO( testPO ) );
}
return list;
}
}
结果
1.2、多个对象映射成一个
@Mapper
public interface TestMapper {
@Mapping(source = "testPO.id", target = "id")
TestMixBO testToBO(TestPO testPO, TestTwoPO testTwoPO);
}
public class TestMapperImpl implements TestMapper {
@Override
public TestMixBO testToBO(TestPO testPO, TestTwoPO testTwoPO) {
if ( testPO == null && testTwoPO == null ) {
return null;
}
TestMixBO testMixBO = new TestMixBO();
if ( testPO != null ) {
testMixBO.setId( testPO.getId() );
testMixBO.setName( testPO.getName() );
testMixBO.setPrice( testPO.getPrice() );
testMixBO.setCreteTime( testPO.getCreteTime() );
}
if ( testTwoPO != null ) {
testMixBO.setTitle( testTwoPO.getTitle() );
}
return testMixBO;
}
}
// 单元测试
TestPO testPO = new TestPO();
testPO.setId(1L);
testPO.setName("haru");
testPO.setCreteTime(new Date(System.currentTimeMillis()));
TestTwoPO testTwoPO = new TestTwoPO();
testTwoPO.setId(2L);
testTwoPO.setTitle("测试");
testTwoPO.setTotalPrice(10.2F);
TestMapper mapper = Mappers.getMapper(TestMapper.class);
TestMixBO testMixBO = mapper.testToBO(testPO, testTwoPO);
System.out.println(testMixBO);
结果
@Mapping用于指定映射的字段。
还可以将非对象源映射到目标。
@Mapper
public interface TestMapper {
@Mapping(target = "discount", source = "totalPrice")
TestMixBO testToBO(TestPO testPO, Float totalPrice);
}
public class TestMapperImpl implements TestMapper {
@Override
public TestMixBO testToBO(TestPO testPO, Float totalPrice) {
if ( testPO == null && totalPrice == null ) {
return null;
}
TestMixBO testMixBO = new TestMixBO();
if ( testPO != null ) {
testMixBO.setId( testPO.getId() );
testMixBO.setName( testPO.getName() );
testMixBO.setPrice( testPO.getPrice() );
testMixBO.setCreteTime( testPO.getCreteTime() );
}
if ( totalPrice != null ) {
testMixBO.setDiscount( totalPrice );
}
return testMixBO;
}
}
TestPO testPO = new TestPO();
testPO.setId(1L);
testPO.setName("haru");
testPO.setCreteTime(new Date(System.currentTimeMillis()));
TestMapper mapper = Mappers.getMapper(TestMapper.class);
TestMixBO testMixBO = mapper.testToBO(testPO, 2.4F);
System.out.println(testMixBO);
结果
若有同名字段,要使用@Mapping(source = "", target = "")指定目标字段。
1.3、嵌套对象映射
1.3.1、属性对象字段全部映射
可以将target设为".",source所对应的属性对象字段会全部映射到target中同名字段上。
@Mapper
public interface TestMapper {
@Mapping(source = "test", target = ".")
TestMixBO testToBO(TestThreePO testPO);
}
public class TestMapperImpl implements TestMapper {
@Override
public TestMixBO testToBO(TestThreePO testPO) {
if ( testPO == null ) {
return null;
}
TestMixBO testMixBO = new TestMixBO();
testMixBO.setId( testPOTestPOId( testPO ) );TestThreePO
testMixBO.setName( testPOTestPOName( testPO ) );
testMixBO.setPrice( testPOTestPOPrice( testPO ) );
testMixBO.setCreteTime( testPOTestPOCreteTime( testPO ) );
return testMixBO;
}
private Long testPOTestPOId(TestThreePO testThreePO) {
if ( testThreePO == null ) {
return null;
}
TestPO testPO = testThreePO.getTestPO();
if ( testPO == null ) {
return null;
}
Long id = testPO.getId();
if ( id == null ) {
return null;
}
return id;
}
private String testPOTestPOName(TestThreePO testThreePO) {
if ( testThreePO == null ) {
return null;
}
TestPO testPO = testThreePO.getTestPO();
if ( testPO == null ) {
return null;
}
String name = testPO.getName();
if ( name == null ) {
return null;
}
return name;
}
private BigDecimal testPOTestPOPrice(TestThreePO testThreePO) {
if ( testThreePO == null ) {
return null;
}
TestPO testPO = testThreePO.getTestPO();
if ( testPO == null ) {
return null;
}
BigDecimal price = testPO.getPrice();
if ( price == null ) {
return null;
}
return price;
}
private Date testPOTestPOCreteTime(TestThreePO testThreePO) {
if ( testThreePO == null ) {
return null;
}
TestPO testPO = testThreePO.getTestPO();
if ( testPO == null ) {
return null;
}
Date creteTime = testPO.getCreteTime();
if ( creteTime == null ) {
return null;
}
return creteTime;
}
}
TestPO testPO = new TestPO();
testPO.setId(1L);
testPO.setName("haru");
testPO.setCreteTime(new Date(System.currentTimeMillis()));
TestThreePO testThreePO = new TestThreePO();
testThreePO.setTest(testPO);
TestMapper mapper = Mappers.getMapper(TestMapper.class);
TestMixBO testMixBO = mapper.testToBO(testThreePO);
System.out.println(testMixBO);
结果
1.3.2、指定字段映射
@Mapper
public interface TestMapper {
@Mapping(source = "test.id", target = "test.idFive")
@Mapping(target = "test.name", ignore = true)
@Mapping(target = "totalPrice", expression = "java(testThreePO.getTotalPrice().toString() + \"元\")")
@Mapping(target = "test.title", constant = "无名")
TestFourBO toTestBO(TestThreePO testThreePO);
}
@Component
public class TestMapperImpl implements TestMapper {
@Override
public TestFourBO toTestBO(TestThreePO testThreePO) {
if ( testThreePO == null ) {
return null;
}
TestFourBO testFourBO = new TestFourBO();
testFourBO.setTest( testPOToTestFiveBO( testThreePO.getTest() ) );
testFourBO.setTotalPrice( testThreePO.getTotalPrice().toString() + "元" );
return testFourBO;
}
protected TestFiveBO testPOToTestFiveBO(TestPO testPO) {
if ( testPO == null ) {
return null;
}
TestFiveBO testFiveBO = new TestFiveBO();
testFiveBO.setIdFive( testPO.getId() );
if ( testPO.getPrice() != null ) {
testFiveBO.setPrice( testPO.getPrice().toString() );
}
testFiveBO.setCreteTime( testPO.getCreteTime() );
testFiveBO.setTitle( "无名" );
return testFiveBO;
}
}
指定test.id映射到test.idFive,忽略了test.name,@Mapping#expression可以编写java代码或引用一个方法,@Mapping#constant可以设置常亮,其他属性自动映射。
尽量为每个对象映射编写单独的mapper接口,利于复用,减少重复代码。
1.4、Builder创建目标对象
支持builder方式创建对象。
@Data
@Builder
@ToString
public class TestMixBO {
private Long id;
private String name;
private BigDecimal price;
private Date creteTime;
private String title;
private Float discount;
}
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2020-10-28T20:08:04+0800",
comments = "version: 1.4.1.Final, compiler: javac, environment: Java 1.8.0_251 (Oracle Corporation)"
)
public class TestMapperImpl implements TestMapper {
@Override
public TestMixBO testToBO(TestThreePO testPO) {
if ( testPO == null ) {
return null;
}
// builder创建
TestMixBOBuilder testMixBO = TestMixBO.builder();
testMixBO.id( testPOTestPOId( testPO ) );
testMixBO.name( testPOTestPOName( testPO ) );
testMixBO.price( testPOTestPOPrice( testPO ) );
testMixBO.creteTime( testPOTestPOCreteTime( testPO ) );
return testMixBO.build();
}
private Long testPOTestPOId(TestThreePO testThreePO) {
if ( testThreePO == null ) {
return null;
}
TestPO testPO = testThreePO.getTestPO();
if ( testPO == null ) {
return null;
}
Long id = testPO.getId();
if ( id == null ) {
return null;
}
return id;
}
private String testPOTestPOName(TestThreePO testThreePO) {
if ( testThreePO == null ) {
return null;
}
TestPO testPO = testThreePO.getTestPO();
if ( testPO == null ) {
return null;
}
String name = testPO.getName();
if ( name == null ) {
return null;
}
return name;
}
private BigDecimal testPOTestPOPrice(TestThreePO testThreePO) {
if ( testThreePO == null ) {
return null;
}
TestPO testPO = testThreePO.getTestPO();
if ( testPO == null ) {
return null;
}
BigDecimal price = testPO.getPrice();
if ( price == null ) {
return null;
}
return price;
}
private Date testPOTestPOCreteTime(TestThreePO testThreePO) {
if ( testThreePO == null ) {
return null;
}
TestPO testPO = testThreePO.getTestPO();
if ( testPO == null ) {
return null;
}
Date creteTime = testPO.getCreteTime();
if ( creteTime == null ) {
return null;
}
return creteTime;
}
}
结果
1.5、构造方法创建目标对象
@Data
@ToString
@AllArgsConstructor
public class TestMixBO {
private Long id;
private String name;
private BigDecimal price;
private Date creteTime;
private String title;
private Float discount;
}
@Mapper
public interface TestMapper {
TestMixBO testToBO(TestPO testPO);
}
public class TestMapperImpl implements TestMapper {
@Override
public TestMixBO testToBO(TestPO testPO) {
if ( testPO == null ) {
return null;
}
Long id = null;
String name = null;
BigDecimal price = null;
Date creteTime = null;
id = testPO.getId();
name = testPO.getName();
price = testPO.getPrice();
creteTime = testPO.getCreteTime();
String title = null;
Float discount = null;
// 构造方法创建
TestMixBO testMixBO = new TestMixBO( id, name, price, creteTime, title, discount );
return testMixBO;
}
}
TestPO testPO = new TestPO();
testPO.setId(1L);
testPO.setName("haru");
testPO.setCreteTime(new Date(System.currentTimeMillis()));
TestMapper mapper = Mappers.getMapper(TestMapper.class);
TestMixBO testMixBO = mapper.testToBO(testPO);
System.out.println(testMixBO);
结果
1.6、更新数据源
@MappingTarget注解的参数是被更新的数据源对象。
@Mapper
public interface TestMapper {
void updateBO(TestPO testPO, @MappingTarget TestBO testBO);
}
@Component
public class TestMapperImpl implements TestMapper {
@Override
public void updateBO(TestPO testPO, TestBO testBO) {
if ( testPO == null ) {
return;
}
testBO.setId( testPO.getId() );
testBO.setName( testPO.getName() );
if ( testPO.getPrice() != null ) {
testBO.setPrice( testPO.getPrice().toString() );
}
else {
testBO.setPrice( null );
}
if ( testPO.getCreteTime() != null ) {
testBO.setCreteTime( new SimpleDateFormat().format( testPO.getCreteTime() ) );
}
else {
testBO.setCreteTime( null );
}
}
}
TestBO testBO = new TestBO();
testBO.setId(1L);
TestPO testPO = new TestPO();
testPO.setName("XX");
testPO.setId(2L);
testPO.setCreteTime(new Date(System.currentTimeMillis()));
testMapper.updateBO(testPO, testBO);
System.out.println(testBO);
结果
更新时也可以使用@Mapping。
@Data
@ToString
public class TestPO {
private Long id;
private String name;
private BigDecimal price;
private Date creteTime;
}
@Data
@ToString
public class TestBO {
private Long id;
private String name;
private String price;
private String nickname;
}
@Mapper
public interface TestMapper {
@Mapping(target = "id", ignore = true)
@Mapping(target = "nickname", source = "name")
void toBOS(TestPO testPO, @MappingTarget TestBO testBO);
}
@Component
public class TestMapperImpl implements TestMapper {
@Override
public void toBOS(TestPO testPO, TestBO testBO) {
if ( testPO == null ) {
return;
}
testBO.setNickname( testPO.getName() );
testBO.setName( testPO.getName() );
if ( testPO.getPrice() != null ) {
testBO.setPrice( testPO.getPrice().toString() );
}
else {
testBO.setPrice( null );
}
}
}
可以看到testBO的id被忽略没有更新,nickname会被更新为testPO的name。
1.7、*映射属性字段
注意在@Mapping#target、source设置的属性的含义。mapstruct并不是按照设置的属性找到字段,在找此字段的getter/setter方法的,而是直接通过get/set前缀 + 属性值找到相应的方法。
public class TestThreeBO {
private TestBO test;
public TestBO getTestG() {
return test;
}
public void setTestS(TestBO test) {
this.test = test;
}
}
@Mapper
public interface TestMapper {
@Mapping(target = "test", source = "testPO")
TestThreeBO toBO(TestThreePO testThreePO);
@Mapping(target = "testPO", source = "test")
TestThreePO toPO(TestThreeBO testThreeBO);
}
这里修改了TestThreeBO#test的getter/setter方法名,映射属性值还是用test。
编译报错,这里提示了直接提示“testG”的属性名。
@Mapper
public interface TestMapper {
@Mapping(target = "testS", source = "testPO")
TestThreeBO toBO(TestThreePO testThreePO);
@Mapping(target = "testPO", source = "testG")
TestThreePO toPO(TestThreeBO testThreeBO);
}
@Component
public class TestMapperImpl implements TestMapper {
@Override
public TestThreeBO toBO(TestThreePO testThreePO) {
if ( testThreePO == null ) {
return null;
}
TestThreeBO testThreeBO = new TestThreeBO();
testThreeBO.setTestS( testPOToTestBO( testThreePO.getTestPO() ) );
return testThreeBO;
}
@Override
public TestThreePO toPO(TestThreeBO testThreeBO) {
if ( testThreeBO == null ) {
return null;
}
TestThreePO testThreePO = new TestThreePO();
try {
testThreePO.setTestPO( testBOToTestPO( testThreeBO.getTestG() ) );
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
return testThreePO;
}
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 TestPO testBOToTestPO(TestBO testBO) throws ParseException {
if ( testBO == null ) {
return null;
}
TestPO testPO = new TestPO();
testPO.setId( testBO.getId() );
testPO.setName( testBO.getName() );
if ( testBO.getPrice() != null ) {
testPO.setPrice( new BigDecimal( testBO.getPrice() ) );
}
if ( testBO.getCreateTime() != null ) {
testPO.setCreateTime( new SimpleDateFormat().parse( testBO.getCreateTime() ) );
}
return testPO;
}
}
再修改属性值,可以得到正确的实现类。
使用IDEA可以装上MapStruct support插件,可以通过属性值调到相应的getter/setter方法。
源码分析会在后续介绍。