SpringBoot如何进行对象复制的实践

简介: 本文主要介绍了SpringBoot 如何进行对象复制,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

本文主要介绍了SpringBoot 如何进行对象复制,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

目录
  • 为什么需要对象复制
  • 对象复制工具类推荐
  • Orika基本使用
  • 引入依赖
  • Orika工具类使用文档
  • TC1,基础实体映射
  • TC2,实体映射 - 字段转换
  • TC3,基础集合映射
  • TC4,集合映射 - 字段映射
  • TC5,集合与实体映射
  • TC6,类类型映射
  • TC7,多重映射
  • TC8,MyBaits plus分页映射
  • 小结

首先我们看看为什么需要对象复制?

 

为什么需要对象复制

网络异常,图片无法展示
|

如上,是我们平时开发中最常见的三层MVC架构模型,编辑操作时Controller层接收到前端传来的DTO对象,在Service层需要将DTO转换成DO,然后在数据库中保存。查询操作时Service层查询到DO对象后需要将DO对象转换成VO对象,然后通过Controller层返回给前端进行渲染。

这中间会涉及到大量的对象转换,很明显我们不能直接使用getter/setter复制对象属性,这看上去太low了。想象一下你业务逻辑中充斥着大量的getter&setter,代码评审时老鸟们会如何笑话你?

网络异常,图片无法展示
|

所以我们必须要找一个第三方工具来帮我们实现对象转换。

看到这里有同学可能会问,为什么不能前后端都统一使用DO对象呢?这样就不存在对象转换呀?

设想一下如果我们不想定义 DTO 和 VO,直接将 DO 用到数据访问层、服务层、控制层和外部访问接口上。此时该表删除或则修改一个字段,DO 必须同步修改,这种修改将会影响到各层,这并不符合高内聚低耦合的原则。通过定义不同的 DTO 可以控制对不同系统暴露不同的属性,通过属性映射还可以实现具体的字段名称的隐藏。不同业务使用不同的模型,当一个业务发生变更需要修改字段时,不需要考虑对其它业务的影响,如果使用同一个对象则可能因为 “不敢乱改” 而产生很多不优雅的兼容性行为。

 

对象复制工具类推荐

对象复制的类库工具有很多,除了常见的Apache的BeanUtils,Spring的BeanUtilsCglib BeanCopier,还有重量级组件MapStructOrikaDozerModelMapper等。

如果没有特殊要求,这些工具类都可以直接使用,除了Apache的BeanUtils。原因在于Apache BeanUtils底层源码为了追求完美,加了过多的包装,使用了很多反射,做了很多校验,所以导致性能较差,并在阿里巴巴开发手册上强制规定避免使用 Apache BeanUtils

网络异常,图片无法展示
|

至于剩下的重量级组件,综合考虑其性能还有使用的易用性,我这里更推荐使用Orika。Orika底层采用了javassist类库生成Bean映射的字节码,之后直接加载执行生成的字节码文件,在速度上比使用反射进行赋值会快很多。

国外大神 baeldung 已经对常见的组件性能进行过详细测试,大家可以通过 https://www.baeldung.com/java-performance-mapping-frameworks 查看。

 

Orika基本使用

要使用Orika很简单,只需要简单四步:

 

引入依赖

<dependency>

 <groupId>ma.glasnost.orika</groupId>

 <artifactId>orika-core</artifactId>

 <version>1.5.4</version>

</dependency>


构造一个MapperFactory

MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();  


注册字段映射

mapperFactory.classMap(SourceClass.class, TargetClass.class)  

  .field("firstName", "givenName")

  .field("lastName", "sirName")

  .byDefault()

  .register();


当字段名在两个实体不一致时可以通过.field()方法进行映射,如果字段名都一样则可省略,byDefault()方法用于注册名称相同的属性,如果不希望某个字段参与映射,可以使用exclude方法。

进行映射

MapperFacade mapper = mapperFactory.getMapperFacade();


SourceClass source = new SourceClass();  

// set some field values

...

// map the fields of "source" onto a new instance of PersonDest

TargetClass target = mapper.map(source, TargetClass.class);  


经过上面四步我们就完成了SourceClass到TargetClass的转换。至于Orika的其他使用方法大家可以参考 http://orika-mapper.github.io/orika-docs/index.html

看到这里,肯定有粉丝会说:你这推荐的啥玩意呀,这个Orika使用也不简单呀,每次都要这先创建MapperFactory,建立字段映射关系,才能进行映射转换。

别急,我这里给你准备了一个工具类OrikaUtils,你可以通过文末github仓库获取。

它提供了五个公共方法:

网络异常,图片无法展示
|

分别对应:

  • 字段一致实体转换
  • 字段不一致实体转换(需要字段映射)
  • 字段一致集合转换
  • 字段不一致集合转换(需要字段映射)
  • 字段属性转换注册

接下来我们通过单元测试案例重点介绍此工具类的使用。

 

Orika工具类使用文档

先准备两个基础实体类,Student,Teacher。

@Data

@AllArgsConstructor

@NoArgsConstructor

public class Student {

   private String id;

   private String name;

   private String email;

}



@Data

@AllArgsConstructor

@NoArgsConstructor

public class Teacher {

   private String id;

   private String name;

   private String emailAddress;

}


 

TC1,基础实体映射

/**

* 只拷贝相同的属性

*/

@Test

public void convertObject(){

 Student student = new Student("1","javadaily","jianzh5@163.com");

 Teacher teacher = OrikaUtils.convert(student, Teacher.class);

 System.out.println(teacher);

}


输出结果:

Teacher(id=1, name=javadaily, emailAddress=null)

此时由于属性名不一致,无法映射字段email。

 

TC2,实体映射 - 字段转换

/**

* 拷贝不同属性

*/

@Test

public void convertRefObject(){

 Student student = new Student("1","javadaily","jianzh5@163.com");


 Map<String,String> refMap = new HashMap<>(1);

 //map key 放置 源属性,value 放置 目标属性

 refMap.put("email","emailAddress");

 Teacher teacher = OrikaUtils.convert(student, Teacher.class, refMap);

 System.out.println(teacher);

}


输出结果:

Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com)

此时由于对字段做了映射,可以将email映射到emailAddress。注意这里的refMap中key放置的是源实体的属性,而value放置的是目标实体的属性,不要弄反了。

 

TC3,基础集合映射

/**

 * 只拷贝相同的属性集合

 */

@Test

public void convertList(){

 Student student1 = new Student("1","javadaily","jianzh5@163.com");

 Student student2 = new Student("2","JAVA日知录","jianzh5@xxx.com");

 List<Student> studentList = Lists.newArrayList(student1,student2);


 List<Teacher> teacherList = OrikaUtils.convertList(studentList, Teacher.class);


 System.out.println(teacherList);

}


输出结果:

[Teacher(id=1, name=javadaily, emailAddress=null), Teacher(id=2, name=JAVA日知录, emailAddress=null)]

此时由于属性名不一致,集合中无法映射字段email。

 

TC4,集合映射 - 字段映射

/**

* 映射不同属性的集合

*/

@Test

public void convertRefList(){

 Student student1 = new Student("1","javadaily","jianzh5@163.com");

 Student student2 = new Student("2","JAVA日知录","jianzh5@xxx.com");

 List<Student> studentList = Lists.newArrayList(student1,student2);


 Map<String,String> refMap = new HashMap<>(2);

 //map key 放置 源属性,value 放置 目标属性

 refMap.put("email","emailAddress");


 List<Teacher> teacherList = OrikaUtils.convertList(studentList, Teacher.class,refMap);


 System.out.println(teacherList);

}


输出结果:

[Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com), Teacher(id=2, name=JAVA日知录, emailAddress=jianzh5@xxx.com)]

也可以通过这样映射:

Map<String,String> refMap = new HashMap<>(2);

refMap.put("email","emailAddress");

List<Teacher> teacherList = OrikaUtils.classMap(Student.class,Teacher.class,refMap)

       .mapAsList(studentList,Teacher.class);


 

TC5,集合与实体映射

有时候我们需要将集合数据映射到实体中,如Person类

@Data

public class Person {

   private List<String> nameParts;

}


现在需要将Person类nameParts的值映射到Student中,可以这样做

/**

* 数组和List的映射

*/

@Test

public void convertListObject(){

  Person person = new Person();

  person.setNameParts(Lists.newArrayList("1","javadaily","jianzh5@163.com"));


   Map<String,String> refMap = new HashMap<>(2);

   //map key 放置 源属性,value 放置 目标属性

   refMap.put("nameParts[0]","id");

   refMap.put("nameParts[1]","name");

   refMap.put("nameParts[2]","email");


   Student student = OrikaUtils.convert(person, Student.class,refMap);

   System.out.println(student);

}


输出结果:

Student(id=1, name=javadaily, email=jianzh5@163.com)

 

TC6,类类型映射

有时候我们需要类类型对象映射,如BasicPerson类

@Data

public class BasicPerson {

   private Student student;

}


现在需要将BasicPerson映射到Teacher

/**

* 类类型映射

*/

@Test

public void convertClassObject(){

   BasicPerson basicPerson = new BasicPerson();

   Student student = new Student("1","javadaily","jianzh5@163.com");

   basicPerson.setStudent(student);


   Map<String,String> refMap = new HashMap<>(2);

   //map key 放置 源属性,value 放置 目标属性

   refMap.put("student.id","id");

   refMap.put("student.name","name");

   refMap.put("student.email","emailAddress");


   Teacher teacher = OrikaUtils.convert(basicPerson, Teacher.class,refMap);

   System.out.println(teacher);

}


输出结果:

Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com)

 

TC7,多重映射

有时候我们会遇到多重映射,如将StudentGrade映射到TeacherGrade

@Data

public class StudentGrade {

   private String studentGradeName;

   private List<Student> studentList;

}


@Data

public class TeacherGrade {

   private String teacherGradeName;

   private List<Teacher> teacherList;

}


这种场景稍微复杂,Student与Teacher的属性有email字段不相同,需要做转换映射;StudentGrade与TeacherGrade中的属性也需要映射。

/**

* 一对多映射

*/

@Test

public void convertComplexObject(){

 Student student1 = new Student("1","javadaily","jianzh5@163.com");

 Student student2 = new Student("2","JAVA日知录","jianzh5@xxx.com");

 List<Student> studentList = Lists.newArrayList(student1,student2);


 StudentGrade studentGrade = new StudentGrade();

 studentGrade.setStudentGradeName("硕士");

 studentGrade.setStudentList(studentList);


 Map<String,String> refMap1 = new HashMap<>(1);

 //map key 放置 源属性,value 放置 目标属性

 refMap1.put("email","emailAddress");

 OrikaUtils.register(Student.class,Teacher.class,refMap1);



 Map<String,String> refMap2 = new HashMap<>(2);

 //map key 放置 源属性,value 放置 目标属性

 refMap2.put("studentGradeName", "teacherGradeName");

 refMap2.put("studentList", "teacherList");



 TeacherGrade teacherGrade = OrikaUtils.convert(studentGrade,TeacherGrade.class,refMap2);

 System.out.println(teacherGrade);

}


多重映射的场景需要根据情况调用OrikaUtils.register()注册字段映射。

输出结果:

TeacherGrade(teacherGradeName=硕士, teacherList=[Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com), Teacher(id=2, name=JAVA日知录, emailAddress=jianzh5@xxx.com)])

 

TC8,MyBaits plus分页映射

如果你使用的是mybatis的分页组件,可以这样转换

public IPage<UserDTO> selectPage(UserDTO userDTO, Integer pageNo, Integer pageSize) {

 Page page = new Page<>(pageNo, pageSize);

 LambdaQueryWrapper<User> query = new LambdaQueryWrapper();

 if (StringUtils.isNotBlank(userDTO.getName())) {

   query.like(User::getKindName,userDTO.getName());

 }

 IPage<User> pageList = page(page,query);

 // 实体转换 SysKind转化为SysKindDto

 Map<String,String> refMap = new HashMap<>(3);

 refMap.put("kindName","name");

 refMap.put("createBy","createUserName");

 refMap.put("createTime","createDate");

 return pageList.convert(item -> OrikaUtils.convert(item, UserDTO.class, refMap));

}


 

小结

在MVC架构中肯定少不了需要用到对象复制,属性转换的功能,借用Orika组件,可以很简单实现这些功能。本文在Orika的基础上封装了工具类,进一步简化了Orika的操作,希望对各位有所帮助。

到此这篇关于SpringBoot如何进行对象复制的实践的文章就介绍到这了,更多相关SpringBoot 对象复制内容请搜索米米素材网以前的文章或继续浏览下面的相关文章希望大家以后多多支持米米素材网!

原文链接:https://www.mimisucai.com/teach/java/36642.html

相关文章
|
4月前
|
Java 开发者 Spring
深入理解 Spring Boot 中的 @EnableAutoConfiguration 注解:概念与实践
【4月更文挑战第21天】在 Spring Boot 项目中,@EnableAutoConfiguration 注解是实现自动配置的核心,它可以根据项目的依赖和配置,自动地配置 Spring 应用程序中的 Bean
85 3
|
1月前
|
前端开发 JavaScript Java
Spring Boot应用中的资源分离与高效打包实践
通过实施资源分离和高效打包策略,不仅可以提升Spring Boot应用的开发和部署效率,还能显著提高用户体验。在实际项目中,根据项目的实际情况和团队的技术栈选择合适的工具和方案是关键。希望本文能为读者在Spring Boot项目中实现资源分离和高效打包提供一些有价值的参考。
|
3月前
|
XML Java UED
使用 Spring Boot 实现重试和补偿功能:从理论到实践
【6月更文挑战第17天】在分布式系统中,服务之间的调用可能会因为网络故障、服务器负载等原因偶尔失败。为了提高系统的可靠性和稳定性,我们经常需要实现重试和补偿功能。
88 6
|
1月前
|
Java Spring 容器
Java SpringBoot 中,动态执行 bean 对象中的方法
Java SpringBoot 中,动态执行 bean 对象中的方法
34 0
|
3月前
|
XML 缓存 Java
Spring Boot 优雅实现降级功能:Hystrix 与 Resilience4j 的实践
【6月更文挑战第19天】在分布式系统中,服务降级是一种重要的容错机制。当某个服务不可用或响应慢时,降级机制可以保证系统的整体稳定性。本文将详细介绍如何在 Spring Boot 中使用 Hystrix 和 Resilience4j 实现降级功能。
261 7
|
3月前
|
NoSQL 算法 Java
使用 Spring Boot 实现限流功能:从理论到实践
【6月更文挑战第18天】在微服务和高并发系统中,限流(Rate Limiting)是一种非常重要的技术手段,用于保护系统免受过载,确保服务的稳定性。限流可以控制请求的速率,防止单个客户端或恶意用户消耗过多的资源,从而影响其他用户。
245 5
|
3月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的社区帮扶对象管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的社区帮扶对象管理系统附带文章源码部署视频讲解等
28 4
|
2月前
|
Kubernetes Java Docker
使用Kubernetes部署Spring Boot应用的实践
使用Kubernetes部署Spring Boot应用的实践
|
2月前
|
监控 Java API
使用Spring Boot构建企业级应用的实践
使用Spring Boot构建企业级应用的实践
|
4月前
|
NoSQL Java MongoDB
【MongoDB 专栏】MongoDB 与 Spring Boot 的集成实践
【5月更文挑战第11天】本文介绍了如何将非关系型数据库MongoDB与Spring Boot框架集成,以实现高效灵活的数据管理。Spring Boot简化了Spring应用的构建和部署,MongoDB则以其对灵活数据结构的处理能力受到青睐。集成步骤包括:添加MongoDB依赖、配置连接信息、创建数据访问对象(DAO)以及进行数据操作。通过这种方式,开发者可以充分利用两者优势,应对各种数据需求。在实际应用中,结合微服务架构等技术,可以构建高性能、可扩展的系统。掌握MongoDB与Spring Boot集成对于提升开发效率和项目质量至关重要,未来有望在更多领域得到广泛应用。
202 3
【MongoDB 专栏】MongoDB 与 Spring Boot 的集成实践