Hello, I am Misty.

Today is the fourth installment of SpringBoot’s old bird series on how to gracefully implement object replication in everyday development.

First of all, why do we need object copy?

Why do YOU need object replication

As mentioned above, it is the most common three-tier MVC architecture model in our daily development. When editing, the Controller layer receives the DTO object from the front end, and the Service layer needs to convert the DTO DO and save it in the database. When the Service layer queries the DO object, it needs to convert the DO object into VO object and then return it to the front end for rendering through the Controller layer.

There’s a lot of object conversion involved, and obviously we can’t copy object properties directly using getters/setters, which seems too low. Imagine your business logic is riddled with getters and setters. How will the old-timers laugh at you during code reviews?

So we have to find a third party tool to help us implement object conversion.

Now, some of you might say, well, why can’t we just use DO objects on both ends? So there’s no object conversion?

Imagine if we didn’t want to define Dtos and VO, and used DO directly on the data access layer, service layer, control layer, and external access interface. When the table is deleted or a field is modified, DO must be modified synchronously. This modification will affect all layers, which does not conform to the principle of high cohesion and low coupling. By defining different Dtos, different attributes can be exposed to different systems, and specific field names can be hidden through attribute mapping. Different businesses use different models. When a business changes and needs to modify fields, it does not need to consider the impact on other businesses. If the same object is used, many inelegant compatibility behaviors may occur due to “not daring to change”.

Object replication tool class is recommended

There are a lot of class library tools for object replication, in addition to the common Apache BeanUtils, Spring BeanUtils, Cglib BeanCopier, there are heavyweight components MapStruct, Orika, Dozer, ModelMapper and so on.

Unless otherwise specified, these utility classes can be used directly, except for Apache’s BeanUtils. The reason is that in order to pursue perfection, the underlying source code of Apache BeanUtils is too much packaged, uses a lot of reflection, and does a lot of verification, so the performance is poor, and it is mandatory to avoid using Apache BeanUtils in alibaba development manual.

For the rest of the heavyweight components, I recommend Orika for performance and ease of use. The underlying Orika uses the Javassist class library to generate the bytecode of the Bean map, and then directly loads the bytecode file that executes the generated bytecode, which is much faster than the assignment using reflection.

Baeldung has carried out detailed tests on the performance of common components. You can visit www.baeldung.com/java-perfor… Look at it.

Basic use of Orika

Orika is easy to use in four simple steps:

  1. Introduction of depend on
<dependency>
  <groupId>ma.glasnost.orika</groupId>
  <artifactId>orika-core</artifactId>
  <version>1.5.4</version>
</dependency>
Copy the code
  1. Construct a MapperFactory
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();  
Copy the code
  1. Registration field mapping
mapperFactory.classMap(SourceClass.class, TargetClass.class)  
   .field("firstName"."givenName")
   .field("lastName"."sirName")
   .byDefault()
   .register();
Copy the code

ByDefault () is used to register attributes with the same name. If you do not want a field to participate in the mapping, use exclude.

  1. mapping
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);  
Copy the code

The SourceClass to TargetClass conversion is completed in the previous four steps. Other ways to use Orika can be found at http://orika-mapper.github.io/orika-docs/index.html

See here, there must be fans will say: you this recommended what stuff ah, this Orika is not easy to use, each time to create MapperFactory, establish field mapping relationship, before mapping conversion.

Don’t worry, I’ve got a utility class for you, OrikaUtils, which you can get from the Github repository at the end of this article.

It provides five public methods:

Corresponding to:

  1. Field consistent entity conversion
  2. Field inconsistency entity conversion (requires field mapping)
  3. Field consistent collection conversion
  4. Field inconsistent collection transformation (requires field mapping)
  5. Field property conversion registration

Let’s focus on the use of this utility class through the unit test case.

Orika utility classes use documentation

First prepare two basic entity classes, Student and Teacher.

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private String id;
    private String name;
    private String email;
}

Copy the code
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private String id;
    private String name;
    private String emailAddress;
}
Copy the code

TC1, base entity mapping

/** * copies only the same attributes */
@Test
public void convertObject(a){
  Student student = new Student("1"."javadaily"."[email protected]");
  Teacher teacher = OrikaUtils.convert(student, Teacher.class);
  System.out.println(teacher);
}
Copy the code

Output result:

Teacher(id=1, name=javadaily, emailAddress=null)
Copy the code

At this time, the field email cannot be mapped due to inconsistent attribute names.

TC2, entity mapping – Field conversion

/** * copy different attributes */
@Test
public void convertRefObject(a){
  Student student = new Student("1"."javadaily"."[email protected]");

  Map<String,String> refMap = new HashMap<>(1);
  // Map key places source attributes and value places target attributes
  refMap.put("email"."emailAddress");
  Teacher teacher = OrikaUtils.convert(student, Teacher.class, refMap);
  System.out.println(teacher);
}
Copy the code

Output result:

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

Because the fields are mapped, you can map email to emailAddress. Note that in the refMap, key places the attributes of the source entity and value places the attributes of the target entity.

TC3, basic set mapping

/** * copies only the same set of attributes */
@Test
public void convertList(a){
  Student student1 = new Student("1"."javadaily"."[email protected]");
  Student student2 = new Student("2"."JAVA Daily Guide"."[email protected]");
  List<Student> studentList = Lists.newArrayList(student1,student2);

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

  System.out.println(teacherList);
}
Copy the code

Output result:

[Teacher(id=1, name=javadaily, emailAddress=null), Teacher(id=2, name=JAVA daterecord, emailAddress=null)]
Copy the code

At this point, the field email cannot be mapped in the collection due to inconsistent attribute names.

TC4, set mapping – Field mapping

/** * maps a collection of different attributes */
@Test
public void convertRefList(a){
  Student student1 = new Student("1"."javadaily"."[email protected]");
  Student student2 = new Student("2"."JAVA Daily Guide"."[email protected]");
  List<Student> studentList = Lists.newArrayList(student1,student2);

  Map<String,String> refMap = new HashMap<>(2);
  // Map key places source attributes and value places target attributes
  refMap.put("email"."emailAddress");

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

  System.out.println(teacherList);
}
Copy the code

Output result:

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

It can also be mapped like this:

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);
Copy the code

TC5, set entity mapping

Sometimes we need to map collection data to entities, such as the Person class

@Data
public class Person {
    private List<String> nameParts;
}
Copy the code

Now you need to map the value of the Person class nameParts to Student, which you can do

/** * array and List mapping */
@Test
public void convertListObject(a){
   Person person = new Person();
   person.setNameParts(Lists.newArrayList("1"."javadaily"."[email protected]"));

    Map<String,String> refMap = new HashMap<>(2);
    // Map key places source attributes and value places target attributes
    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);
}
Copy the code

Output result:

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

TC6, class type mapping

Sometimes we need a class type object map, such as the BasicPerson class

@Data
public class BasicPerson {
    private Student student;
}
Copy the code

Now you need to map BasicPerson to Teacher

/** * Class type mapping */
@Test
public void convertClassObject(a){
    BasicPerson basicPerson = new BasicPerson();
    Student student = new Student("1"."javadaily"."[email protected]");
    basicPerson.setStudent(student);

    Map<String,String> refMap = new HashMap<>(2);
    // Map key places source attributes and value places target attributes
    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);
}
Copy the code

Output result:

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

TC7, multiple mapping

Sometimes we encounter multiple mappings, such as mapping StudentGrade to TeacherGrade

@Data
public class StudentGrade {
    private String studentGradeName;
    private List<Student> studentList;
}

@Data
public class TeacherGrade {
    private String teacherGradeName;
    private List<Teacher> teacherList;
}
Copy the code

This scenario is slightly complicated. The attributes of Student and Teacher have different email fields, so conversion mapping needs to be done. Attributes in StudentGrade and TeacherGrade also need to be mapped.

/** * one-to-many mapping */
@Test
public void convertComplexObject(a){
  Student student1 = new Student("1"."javadaily"."[email protected]");
  Student student2 = new Student("2"."JAVA Daily Guide"."[email protected]");
  List<Student> studentList = Lists.newArrayList(student1,student2);

  StudentGrade studentGrade = new StudentGrade();
  studentGrade.setStudentGradeName("Master");
  studentGrade.setStudentList(studentList);

  Map<String,String> refMap1 = new HashMap<>(1);
  // Map key places source attributes and value places target attributes
  refMap1.put("email"."emailAddress");
  OrikaUtils.register(Student.class,Teacher.class,refMap1);


  Map<String,String> refMap2 = new HashMap<>(2);
  // Map key places source attributes and value places target attributes
  refMap2.put("studentGradeName"."teacherGradeName");
  refMap2.put("studentList"."teacherList");


  TeacherGrade teacherGrade = OrikaUtils.convert(studentGrade,TeacherGrade.class,refMap2);
  System.out.println(teacherGrade);
}
Copy the code

Multiple mapping scenarios require oriKautils.register () to register field mappings as appropriate.

Output result:

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

TC8, MyBaits Plus paging mapping

This can be done if you are using the paging component of 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 is converted to 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));
}
Copy the code

summary

In MVC architecture, object copy and attribute transformation are definitely needed, which can be easily implemented by using Orika components. This paper encapsulates the utility class on the basis of Orika, and further simplifies the operation of Orika. I hope it will be helpful to you.

Finally, I am Miao Jam, an architect writing code and programmer working on architecture. I look forward to your forwarding and attention. Of course, you can also add my personal wechat jianzh5, let’s talk about technology together!

Old bird series source code has been uploaded to GitHub, need in the public number [JAVA daily records] reply keyword 0923 to obtain