目录:
- 为什么需要DTO
- mapstruct进行domain和DTO转换
参考/来源:
为什么需要DTO
在调用业务方法时,DTO对象数据是由Action生成(可能是用户输入的Form数据,也可能是其他情况)
为了说的清楚简洁,请允许我举个例子:用户更新自己的注册信息,例如修改密码之类的操作。
用户提交页面–>ActionForm提取Form数据–>构造并对UserDTO赋值–>调用业务方法changePassword(UserDTOdto)把DTO对象传入业务方法–>业务方法内部把UserDTO转化为Entity User–>调用UserDAO.update(User)–>DAO调用hibernate进行持久化操作。
我理解dto是做表示层(展示给用户)的,而实体是数据对象(表)
表示层dto的是由多个实体构成,或一个实体的一个部分,或多个实体的各个部分的结合体?
如果你认为dto没有必要,难道你做数据库的时候,表与页面的展示是一摸一样的吗?不太现实吧。
dto是面向对象的,实体是面向关系数据库的。
DTO data transfer object
数据传输对象,这个对象封装你需要传输的数据 在M,V,C这三个层传递
dto其实就是简单的JAVABEAN,实现Serializable借口,可以在网络间传输
dto是为了解决entity bean可能很庞大,影响网络传输性能而产生的一种想法
比如entity bean印射到一个表字段很多,但实际使用中可能只要取得几个值,比如ID等什么的,那么就定义一个class(类),这个class在服务器端,调用entity的local接口,或直接用jdbc操作表,所以不会对网络传输产生不利,然后网络传输这个class给客户端(可序列化),减小了网络传输。
mapstruct进行domain和DTO转换
概念
mapstruct
的插件,它就是专门用来处理 domin 实体类与 model 类的属性映射的,我们只需定义 mapper 接口,mapstruct 在编译的时候就会自动的帮我们实现这个映射接口,避免了麻烦复杂的映射实现。
那可能有的小伙伴就要问了?为啥不用 BeanUtils
的 copyProperties
方法呢?不也照样可以实现属性的映射么?BeanUtils
只能同属性映射,或者在属性相同的情况下,允许被映射的对象属性少;但当遇到被映射的属性数据类型被修改或者被映射的字段名被修改,则会导致映射失败。而 mapstruct
把我们可能会遇到的情况都给考虑到了。
如下是这个插件的开源项目地址和各种例子:
- Github地址:https://github.com/mapstruct/mapstruct/
- 使用例子:https://github.com/mapstruct/mapstruct-examples
依赖
首先需要把依赖包导入,主要由两个包组成:
org.mapstruct:mapstruct
:包含了一些必要的注解,例如@Mapping。r若我们使用的JDK版本高于1.8,当我们在pom里面导入依赖时候,建议使用坐标是:org.mapstruct:mapstruct-jdk8
,这可以帮助我们利用一些Java8的新特性。org.mapstruct:mapstruct-processor
:注解处理器,根据注解自动生成mapper的实现。
<dependency>
<groupId>org.mapstruct</groupId>
<!-- jdk8以下就使用mapstruct -->
<artifactId>mapstruct-jdk8</artifactId>
<version>1.2.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.2.0.Final</version>
</dependency>
实体类和被映射类
// 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
private Integer id;
private String name;
private String createTime;
private LocalDateTime updateTime;
}
// 被映射类VO1:和实体类一模一样
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserVO1 {
private Integer id;
private String name;
private String createTime;
private LocalDateTime updateTime;
}
// 被映射类VO1:比实体类少一个字段
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserVO2 {
private Integer id;
private String name;
private String createTime;
}
定义接口
@Mapper(componentModel = "spring")
public interface UserCovertBasic {
UserCovertBasic INSTANCE = Mappers.getMapper(UserCovertBasic.class);
/**
* 字段数量类型数量相同,利用工具BeanUtils也可以实现类似效果
* @param source
* @return
*/
UserVO1 toConvertVO1(User source);
User fromConvertEntity1(UserVO1 userVO1);
/**
* 字段数量类型相同,数量少:仅能让多的转换成少的,故没有fromConvertEntity2
* @param source
* @return
*/
UserVO2 toConvertVO2(User source);
}
从 mybatis3.4.0 开始加入的 @Mapper 注解,目的就是为了不再写mapper映射文件。
我们只需要在 dao 层定义的接口上使用注解就可以实现sql语句的编写,例如:
@Select("select * from user where name = #{name}")
public User find(String name);
如上就是一个简单的使用,虽然简单,但也确实体现出了这个注解的优越性,至少少写了一个xml文件。
@Mapper
注解的 componentModel
属性,用于指定自动生成的接口实现类的组件类型,这个属性支持四个值:
- default: 这是默认的情况,mapstruct 不使用任何组件类型, 可以通过Mappers.getMapper(Class)方式获取自动生成的实例对象。
- cdi: the generated mapper is an application-scoped CDI bean and can be retrieved via @Inject
- spring: 生成的实现类上面会自动添加一个@Component注解,可以通过Spring的 @Autowired方式进行注入
- jsr330: 生成的实现类上会添加@javax.inject.Named 和@Singleton注解,可以通过 @Inject注解获取
调用
@RestController
public class TestController {
@GetMapping("convert")
public Object convertEntity() {
User user = User.builder()
.id(1)
.name("张三")
.createTime("2020-04-01 11:05:07")
.updateTime(LocalDateTime.now())
.build();
List<Object> objectList = new ArrayList<>();
objectList.add(user);
// 使用mapstruct
UserVO1 userVO1 = UserCovertBasic.INSTANCE.toConvertVO1(user);
objectList.add("userVO1:" + UserCovertBasic.INSTANCE.toConvertVO1(user));
objectList.add("userVO1转换回实体类user:" + UserCovertBasic.INSTANCE.fromConvertEntity1(userVO1));
// 输出转换结果
objectList.add("userVO2:" + " | " + UserCovertBasic.INSTANCE.toConvertVO2(user));
// 使用BeanUtils
UserVO2 userVO22 = new UserVO2();
BeanUtils.copyProperties(user, userVO22);
objectList.add("userVO22:" + " | " + userVO22);
return objectList;
}
}
当字段、属性等不一样时
映射类
@Data @NoArgsConstructor @AllArgsConstructor @Builder public class UserVO3 { private String id; private String name; // 实体类该属性是String private LocalDateTime createTime; // 实体类该属性是LocalDateTime private String updateTime; }
修改接口
@Mappings({ @Mapping(target = "createTime", expression = "java(com.java.mmzsblog.util.DateTransform.strToDate(source.getCreateTime()))"), }) UserVO3 toConvertVO3(User source); User fromConvertEntity3(UserVO3 userVO3);
public class DateTransform { public static LocalDateTime strToDate(String str){ DateTimeFormatter df = DateTimeFormatter.ofPattern("yyy-MM-dd HH:mm:ss"); return LocalDateTime.parse("2018-01-12 17:07:05",df); } }
上面是
expression
指定的表达式内容。其他情况