domain与DTO


目录:

  1. 为什么需要DTO
  2. 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 在编译的时候就会自动的帮我们实现这个映射接口,避免了麻烦复杂的映射实现。

那可能有的小伙伴就要问了?为啥不用 BeanUtilscopyProperties 方法呢?不也照样可以实现属性的映射么?BeanUtils 只能同属性映射,或者在属性相同的情况下,允许被映射的对象属性少;但当遇到被映射的属性数据类型被修改或者被映射的字段名被修改,则会导致映射失败。而 mapstruct 把我们可能会遇到的情况都给考虑到了。

如下是这个插件的开源项目地址和各种例子:

依赖

首先需要把依赖包导入,主要由两个包组成:

  • 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 指定的表达式内容。

  • 其他情况

    详见:https://www.cnblogs.com/mmzs/p/12735212.html


文章作者: 小小千千
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 小小千千 !
评论
  目录