Java实体映射工具MapStruct

简介: Java实体映射工具MapStruct

1 项目中关于Entity、VO、DTO的认识和应用

1、entity 里的每一个字段,与数据库相对应,

2、vo 里的每一个字段,是和你前台 html 页面相对应,

3、dto 这是用来转换从 entity 到 vo,或者从 vo 到 entity 的中间的东西 。(DTO中拥有的字段应该是entity中或者是vo中的一个子集)

举个例子:

你的html页面上有三个字段,name,pass,age

你的数据库表里,有两个字段,name,pass , 注意没有 age。

而你的 vo 里,就应该有下面三个成员变量 ,因为对应 html 页面上三个字段 。


private string name;
private string pass; 
private string age;

这个时候,你的 entity 里,就应该有两个成员变量 ,因为对应数据库表中的 2 个字段 。

private string name;
private string pass;

到了这里,好了,业务经理让你做这样一个业务“年龄大于 20 的才能存入数据库,这个时候,你就要用到 dto 了,


1)你要先从页面上拿到 vo,然后判断 vo 中的 age 是不是大于 20。

2)如果大于 20,就把 vo 中的 name 和 pass 拿出来,放到 dto 中。

3)然后在把 dto 中的 name 和 pass 原封不动的给 entity,然后根据 entity 的值,在传入数据库。


这就是他们三个的区别。

PS: dto 和 entity 里面的字段应该是一样的,dto 只是 entity 到 vo,或者 vo 到 entity 的中间过程,如果没有这个过程,你仍然可以做到增删改查,这是根据具体公司规范来的 。


2 Java 实体映射工具 MapStruct

简单的说就是进行实体类直接的转换,一般如果字段名不匹配只需用mappers进行字段映射,mapper中也可以自己重写方法一般都是远程的调用过来没有对应的字段进行一个数据的组装.

20210309123941883.png



上面这种情况 对象与对象之间的互相转换,就需要有一个专门用来解决转换问题的工具,毕竟每一个字段都 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>

其中实现日期的话默认依赖需要一个commons-lang3的一个包需要导入.


踩坑-Mapstruct使用问题Couldn’t retrieve @Mapper annotation


在切换版本可能有残留,清理一下缓存即可

20210309143226839.png


202103091430520.png


踩坑-Mapstruct再使用中无法生成实现类,要去了解gradle引入依赖的可见性问题.


这个放入依赖是加载插件的

annotationProcessor(“org.mapstruct:mapstruct-processor:1.4.2.Final”)

相当于maven中的插件管理


20210309162511512.png

Java 实体映射工具 MapStruct

声明:
1、DO(业务实体对象),DTO(数据传输对象)。
2、我的代码中用到了 Lombok ,不了解的可以自行了解一下,了解的忽略这条就好。123

在一个成熟的工程中,尤其是现在的分布式系统中,应用与应用之间,还有单独的应用细分模块之后,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 compile12

注意!!!下面这个 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, "phone", 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);12

还有一种常用的方式,是和常用的框架 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);
}11

这时候测试类改一下,我用的 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 用于继承配置1112

本文只是写了一些常用的比较简单的一些功能,更详细的可以去阅读官方文档: http://mapstruct.org/documentation/stable/reference/html/

目录
相关文章
|
8天前
|
人工智能 缓存 监控
使用LangChain4j构建Java AI智能体:让大模型学会使用工具
AI智能体是大模型技术的重要演进方向,它使模型能够主动使用工具、与环境交互,以完成复杂任务。本文详细介绍如何在Java应用中,借助LangChain4j框架构建一个具备工具使用能力的AI智能体。我们将创建一个能够进行数学计算和实时信息查询的智能体,涵盖工具定义、智能体组装、记忆管理以及Spring Boot集成等关键步骤,并展示如何通过简单的对话界面与智能体交互。
158 1
|
4月前
|
Java 数据安全/隐私保护
银行转账虚拟生成器app,银行卡转账截图制作软件,java实现截图生成工具【仅供装逼娱乐用途】
本项目提供了一套基于Java的图片处理教学方案,包含自定义图片生成、图像水印添加及合法电子凭证生成技术示例。
|
4月前
|
Java 数据安全/隐私保护 计算机视觉
银行转账虚拟生成器app,银行卡转账截图制作软件,java实现截图生成工具【仅供装逼娱乐用途】
本内容提供Java生成自定义图片的示例代码,涵盖基础图像创建、文本添加及保存功能,适合学习2D图形编程。包括教学示例图片生成、文本图层处理和数字水印技术实现方案。
|
4月前
|
安全 Java 编译器
JD-GUI,java反编译工具及原理: JavaDecompiler一个Java反编译器
Java Decompiler (JD-GUI) 是一款由 Pavel Kouznetsov 开发的图形化 Java 反编译工具,支持 Windows、Linux 和 Mac Os。它能将 `.class` 文件反编译为 Java 源代码,支持多文件标签浏览、高亮显示,并兼容 Java 5 及以上版本。JD-GUI 支持对整个 Jar 文件进行反编译,可跳转源码,适用于多种 JDK 和编译器。其原理基于将字节码转换为抽象语法树 (AST),再通过反编译生成代码。尽管程序可能带来安全风险,但可通过代码混淆降低可读性。最新版修复了多项识别错误并优化了内存管理。
1966 1
|
5月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
193 0
|
6月前
|
监控 Java Unix
6个Java 工具,轻松分析定位 JVM 问题 !
本文介绍了如何使用 JDK 自带工具查看和分析 JVM 的运行情况。通过编写一段测试代码(启动 10 个死循环线程,分配大量内存),结合常用工具如 `jps`、`jinfo`、`jstat`、`jstack`、`jvisualvm` 和 `jcmd` 等,详细展示了 JVM 参数配置、内存使用、线程状态及 GC 情况的监控方法。同时指出了一些常见问题,例如参数设置错误导致的内存异常,并通过实例说明了如何排查和解决。最后附上了官方文档链接,方便进一步学习。
668 4
|
7月前
|
Oracle Java 关系型数据库
课时37:综合实战:数据表与简单Java类映射转换
今天我分享的是数据表与简单 Java 类映射转换,主要分为以下四部分。 1. 映射关系基础 2. 映射步骤方法 3. 项目对象配置 4. 数据获取与调试
115 1
|
20天前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
58 0
|
1月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
86 16

热门文章

最新文章