推荐一个 Java 实体映射工具 MapStruct
声明:
1、DO(业务实体对象),DTO(数据传输对象)。
2、我的代码中用到了 Lombok ,不了解的可以自行了解一下,了解的忽略这条就好。
在一个成熟的工程中,尤其是现在的分布式系统中,应用与应用之间,还有单独的应用细分模块之后,DO 一般不会让外部依赖,这时候需要在提供对外接口的模块里放 DTO 用于对象传输,也即是 DO 对象对内,DTO对象对外,DTO 可以根据业务需要变更,并不需要映射 DO 的全部属性。
这种 对象与对象之间的互相转换,就需要有一个专门用来解决转换问题的工具,毕竟每一个字段都 get/set 会很麻烦。
MapStruct 就是这样的一个属性映射工具,只需要定义一个 Mapper 接口,MapStruct 就会自动实现这个映射接口,避免了复杂繁琐的映射实现。MapStruct官网地址: http://mapstruct.org/
工程中引入 maven 依赖
<properties>
<mapstruct.version>1.2.0.Final</mapstruct.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>
基本映射
这里定义两个 DO 对象 Person 和 User,其中 user 是 Person 的一个属性 ,一个 DTO 对象 PersonDTO
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Person {
private Long id;
private String name;
private String email;
private Date birthday;
private User user;
}
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
private Integer age;
}
@NoArgsConstructor
@AllArgsConstructor
@Data
public class PersonDTO {
private Long id;
private String name;
/**
* 对应 Person.user.age
*/
private Integer age;
private String email;
/**
* 与 DO 里面的字段名称(birthDay)不一致
*/
private Date birth;
/**
* 对 DO 里面的字段(birthDay)进行拓展,dateFormat 的形式
*/
private String birthDateFormat;
/**
* 对 DO 里面的字段(birthDay)进行拓展,expression 的形式
*/
private String birthExpressionFormat;
}
写一个 Mapper 接口 PersonConverter,其中两个方法,一个是单实体映射,另一个是List映射
若源对象属性与目标对象属性名字一致,会自动映射对应属性,不一样的需要指定,也可以用 format 转成自己想要的类型,也支持表达式的方式,可以看到像 id、name、email这些名词一致的我并没有指定 source-target,而birthday-birth指定了,转换格式的 birthDateFormat 加了dateFormat 或者 birthExpressionFormat 加了 expression,如果某个属性你不想映射,可以加个 ignore=true
@Mapper
public interface PersonConverter {
PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
@Mappings({
@Mapping(source = "birthday", target = "birth"),
@Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
@Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),\"yyyy-MM-dd HH:mm:ss\"))"),
@Mapping(source = "user.age", target = "age"),
@Mapping(target = "email", ignore = true)
})
PersonDTO domain2dto(Person person);
List<PersonDTO> domain2dto(List<Person> people);
}
编译MapStruct之后,手工编译或者启动 IDE 的时候 IDE 也会帮我们编译, 会自动在 target/classes 下生成对应的实现类
手工编译命令
mvn compile
注意!!!下面这个 PersonConverterImpl 是自动生成的,不是自己写的!
public class PersonConverterImpl implements PersonConverter {
public PersonConverterImpl() {
}
public PersonDTO domain2dto(Person person) {
if (person == null) {
return null;
} else {
PersonDTO personDTO = new PersonDTO();
personDTO.setBirth(person.getBirthday());
if (person.getBirthday() != null) {
personDTO.setBirthDateFormat((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(person.getBirthday()));
}
Integer age = this.personUserAge(person);
if (age != null) {
personDTO.setAge(age);
}
personDTO.setId(person.getId());
personDTO.setName(person.getName());
personDTO.setBirthExpressionFormat(DateFormatUtils.format(person.getBirthday(), "yyyy-MM-dd HH:mm:ss"));
return personDTO;
}
}
public List<PersonDTO> domain2dto(List<Person> people) {
if (people == null) {
return null;
} else {
List<PersonDTO> list = new ArrayList(people.size());
Iterator var3 = people.iterator();
while(var3.hasNext()) {
Person person = (Person)var3.next();
list.add(this.domain2dto(person));
}
return list;
}
}
private Integer personUserAge(Person person) {
if (person == null) {
return null;
} else {
User user = person.getUser();
if (user == null) {
return null;
} else {
Integer age = user.getAge();
return age == null ? null : age;
}
}
}
}
写一个单元测试类 PersonConverterTest 测试一下,看看效果
public class PersonConverterTest {
@Test
public void test() {
Person person = new Person(1L,"zhige","zhige.me@gmail.com",new Date(),new User(1));
PersonDTO personDTO = PersonConverter.INSTANCE.domain2dto(person);
assertNotNull(personDTO);
assertEquals(personDTO.getId(), person.getId());
assertEquals(personDTO.getName(), person.getName());
assertEquals(personDTO.getBirth(), person.getBirthday());
String format = DateFormatUtils.format(personDTO.getBirth(), "yyyy-MM-dd HH:mm:ss");
assertEquals(personDTO.getBirthDateFormat(),format);
assertEquals(personDTO.getBirthExpressionFormat(),format);
List<Person> people = new ArrayList<>();
people.add(person);
List<PersonDTO> personDTOs = PersonConverter.INSTANCE.domain2dto(people);
assertNotNull(personDTOs);
}
}
多对一
MapStruct 可以将几种类型的对象映射为另外一种类型,比如将多个 DO 对象转换为 DTO
例子
- 两个 DO 对象 Item 和 Sku,一个 DTO 对象 SkuDTO
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Item {
private Long id;
private String title;
}
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Sku {
private Long id;
private String code;
private Integer price;
}
@NoArgsConstructor
@AllArgsConstructor
@Data
public class SkuDTO {
private Long skuId;
private String skuCode;
private Integer skuPrice;
private Long itemId;
private String itemName;
}
- 创建 ItemConverter(映射)接口,MapStruct 就会自动实现该接口
@Mapper
public interface ItemConverter {
ItemConverter INSTANCE = Mappers.getMapper(ItemConverter.class);
@Mappings({
@Mapping(source = "sku.id",target = "skuId"),
@Mapping(source = "sku.code",target = "skuCode"),
@Mapping(source = "sku.price",target = "skuPrice"),
@Mapping(source = "item.id",target = "itemId"),
@Mapping(source = "item.title",target = "itemName")
})
SkuDTO domain2dto(Item item, Sku sku);
}
- 创建测试类,讲 Item 和 Sku 两个 DO对象,映射成一个 DTO 对象 SkuDTO
public class ItemConverterTest {
@Test
public void test() {
Item item = new Item(1L, "iPhone X");
Sku sku = new Sku(2L, "phone12345", 1000000);
SkuDTO skuDTO = ItemConverter.INSTANCE.domain2dto(item, sku);
assertNotNull(skuDTO);
assertEquals(skuDTO.getSkuId(),sku.getId());
assertEquals(skuDTO.getSkuCode(),sku.getCode());
assertEquals(skuDTO.getSkuPrice(),sku.getPrice());
assertEquals(skuDTO.getItemId(),item.getId());
assertEquals(skuDTO.getItemName(),item.getTitle());
}
}
可以添加自定义方法
// 形式如下
default PersonDTO personToPersonDTO(Person person) {
//hand-written mapping logic
}
// 比如在 PersonConverter 里面加入如下
default Boolean convert2Bool(Integer value) {
if (value == null || value < 1) {
return Boolean.FALSE;
} else {
return Boolean.TRUE;
}
}
default Integer convert2Int(Boolean value) {
if (value == null) {
return null;
}
if (Boolean.TRUE.equals(value)) {
return 1;
}
return 0;
}
// 测试类 PersonConverterTest 加入
assertTrue(PersonConverter.INSTANCE.convert2Bool(1));
assertEquals((int)PersonConverter.INSTANCE.convert2Int(true),1);
#### 如果已经有了接收对象,更新目标对象
// 比如在 PersonConverter 里面加入如下,@InheritConfiguration 用于继承刚才的配置
@InheritConfiguration(name = "domain2dto")
void update(Person person, @MappingTarget PersonDTO personDTO);
// 测试类 PersonConverterTest 加入如下
Person person = new Person(1L,"zhige","zhige.me@gmail.com",new Date(),new User(1));
PersonDTO personDTO = PersonConverter.INSTANCE.domain2dto(person);
assertEquals("zhige", personDTO.getName());
person.setName("xiaozhi");
PersonConverter.INSTANCE.update(person, personDTO);
assertEquals("xiaozhi", personDTO.getName());
Spring 注入的方式
// 刚才一直写的例子是默认的方式
PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
还有一种常用的方式,是和常用的框架 Spring 结合,在 @Mapper 后面加入 componentModel="spring"
@Mapper(componentModel="spring")
public interface PersonConverter {
@Mappings({
@Mapping(source = "birthday", target = "birth"),
@Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
@Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),\"yyyy-MM-dd HH:mm:ss\"))"),
@Mapping(source = "user.age", target = "age"),
@Mapping(target = "email", ignore = true)
})
PersonDTO domain2dto(Person person);
}
这时候测试类改一下,我用的 spring boot 的形式
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BaseTestConfiguration.class)
public class PersonConverterTest {
//这里把转换器装配进来
@Autowired
private PersonConverter personConverter;
@Test
public void test() {
Person person = new Person(1L,"zhige","zhige.me@gmail.com",new Date(),new User(1));
PersonDTO personDTO = personConverter.domain2dto(person);
assertNotNull(personDTO);
assertEquals(personDTO.getId(), person.getId());
assertEquals(personDTO.getName(), person.getName());
assertEquals(personDTO.getBirth(), person.getBirthday());
String format = DateFormatUtils.format(personDTO.getBirth(), "yyyy-MM-dd HH:mm:ss");
assertEquals(personDTO.getBirthDateFormat(),format);
assertEquals(personDTO.getBirthExpressionFormat(),format);
}
}
我 test 路径下加入了一个配置类
@EnableAutoConfiguration
@Configuration
@ComponentScan
public class BaseTestConfiguration {
}
MapStruct 注解的关键词
@Mapper 只有在接口加上这个注解, MapStruct 才会去实现该接口
@Mapper 里有个 componentModel 属性,主要是指定实现类的类型,一般用到两个
default:默认,可以通过 Mappers.getMapper(Class) 方式获取实例对象
spring:在接口的实现类上自动添加注解 @Component,可通过 @Autowired 方式注入
@Mapping:属性映射,若源对象属性与目标对象名字一致,会自动映射对应属性
source:源属性
target:目标属性
dateFormat:String 到 Date 日期之间相互转换,通过 SimpleDateFormat,该值为 SimpleDateFormat 的日期格式
ignore: 忽略这个字段
@Mappings:配置多个@Mapping
@MappingTarget 用于更新已有对象
@InheritConfiguration 用于继承配置
本文只是写了一些常用的比较简单的一些功能,更详细的可以去阅读官方文档: http://mapstruct.org/documentation/stable/reference/html/
</div>
推荐一个 Java 实体映射工具 MapStruct的更多相关文章
- Java实体映射工具MapStruct的使用
官网地址:http://mapstruct.org/ MapStruct 是一个代码生成器,简化了不同的 Java Bean 之间映射的处理,所谓的映射指的就是从一个实体变化成一个实体.例如我们在实际 ...
- 【工具库】Java实体映射工具MapStruct
一.什么是MapStruct? MapStruct是用于代码中JavaBean对象之间的转换,例如DO转换为DTO,DTO转换为VO,或Entity转换为VO等场景,虽然Spring库和 Apache ...
- Java实体映射工具MapStruct使用详解
1.序 通常在后端开发中经常不直接返回实体Entity类,经过处理转换返回前端,前端提交过来的对象也需要经过转换Entity实体才做存储:通常使用的BeanUtils.copyProperties方法 ...
- 在线数据库表(sql语句)生成java实体类工具
相信每个做java开发的读者,都接触过SQL建表语句,尤其是在项目开发初期,因为数据库是项目的基石. 在现代项目开发中,出现了许多ORM框架,通过简单的实体映射,即可实现与数据库的交互,然而我们最初设 ...
- 我写了一个java实体类,implements了Serializable接口,然后我如何让serialversionUID自动生成
写了一个java实体类,implements了Serializable接口,让serialversionUID自动生成方法: 1.点击类旁边的警告符号: 2.选择Add generated seria ...
- 推荐一个web字体转换工具TTF转SVG
推荐一个web字体转换工具:https://www.fontsquirrel.com/tools/webfont-generator
- 推荐一个 Java 里面比较牛逼的公众号!
今天给大家推荐一个牛逼的纯 Java 技术公众号:Java技术栈,作者:栈长. Java程序员.Java爱好者扫码关注吧! 确实牛逼,几十万人关注了,原创文章350+,好友都 3000+ 关注了. 栈 ...
- 文章推荐一个Java程序员跟大家谈谈从业心得
一个Java程序员跟大家谈谈从业心得 2017-10-21 java那些事 java那些事 java那些事 微信号 csh624366188 功能介绍 分享java开发中常用的技术,分享软件开发中各种 ...
- 推荐一个实用的css工具
后台程序员整天在和数据打交道,天天的活就是抱着mysql抠数据,如果让他去写网站的样式,就让人感觉力不从心,所以推荐一个twitter的团队开发的东西,几乎囊括了网站所需的样式,http://www. ...
随机推荐
- AJPFX实例集合嵌套之ArrayList嵌套ArrayList
案例:import java.util.ArrayList;import java.util.Iterator;import com.heima.bean.Person;public class De ...
- JavaWeb ,EL,
WEB 概述: java web 是用java 技术来解决相关web 互联网领域的技术总和 . web 可分为 web服务器和web客户端(浏览器) web 的资源分类: 静态资源: HTM ...
- Git-merge & rebase(变基)
在 Git 中整合来自不同分支的修改主要有两种方法:merge 以及 rebase. merge: merge有两种方法,fast-forward(快速合并)和three-way merge(三方合并 ...
- Redis学习笔记(二)字符串进阶
1.增减操作 字符串可以储存字节串.整数.浮点数三种类型的值,如果值无法被解释为十进制的整数或浮点数,那么对此值进行增减操作会返回错误,如果对一个不存在的或者保存了空串的键进行增减操作,Redis将当 ...
- 最新精品 强势来袭 XP,32/64位Win7,32/64位Win8,32/64位Win10系统【国庆版版】
本系统是10月最新完整版本的Windows10 安装版镜像,Win10正式版,更新了重要补丁,提升应用加载速度,微软和百度今天宣布达成合作,百度成为Win10 Edge浏览器中国默认主页和搜索引擎,系 ...
- 1《数学之美》第1章 文字和语言 vs 数字和信息
1<数学之美>第1章 文字和语言 vs 数字和信息
- js中重载问题
在js中是没有重载的 但是 Arguments对象(可以实现模拟重载的效果) 利用arguments对象的length属性,可以获取函数接收的参数的个数 例如: function add(){ i ...
- Visual Studio 2017部署 webStrom开发的nodejs项目
vs点击文件--新建--项目--JavaScript--Node.js--通过现有Node.js代码 wxxcx为nodejs项目根目录,然后右击整个项目--属性:1.启动目录2.默认打开的链接3.设 ...
- mybatis-5 手写代理
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Select { public St ...
- LinkedHashMap/HashMap(数҉据҉缓҉存҉准҉备҉)
顾名思义LinkedHashMap是比HashMap多了一个链表的结构.与HashMap相比LinkedHashMap维护的是一个具有双重链表的HashMap,LinkedHashMap支持2中排序一 ...