Всем привет, я Мисти.
Сегодня я представляю четвертую часть серии старых птичек SpringBoot, чтобы рассказать о том, как элегантно реализовать репликацию объектов в повседневной разработке.
Сначала давайте посмотрим, зачем нужно копирование объектов?
Зачем нужна репликация объектов
Как и выше, это наиболее распространенная модель трехуровневой архитектуры MVC в нашей обычной разработке.При редактировании слой контроллера получает объект DTO от внешнего интерфейса, а слой службы долженDTOПеревести вDO, а затем сохраните его в базе данных. Во время операции запроса, после того как сервисный уровень запрашивает объект DO, ему необходимоDOобъект, преобразованный вVOЗатем объект возвращается во внешний интерфейс для рендеринга через слой контроллера.
В середине будет задействовано много преобразований объектов, очевидно, мы не можем использовать его напрямую.getter/setterСкопируйте свойства объекта, что кажется слишком низким.Представьте, что ваша бизнес-логика заполнена множествомgetter&setter, Как над вами смеются ветераны во время код-ревью?
Поэтому нам нужно найти сторонний инструмент, который поможет нам реализовать преобразование объектов.
Увидев это, некоторые учащиеся могут спросить, почему фронтальная и бэкенды не могут одинаково использовать объект DO? Значит, нет преобразования объектов?
Представьте, что мы не хотим определять DTO и VO, а напрямую используем DO для уровня доступа к данным, сервисного уровня, уровня управления и внешнего интерфейса доступа. В это время таблица удаляет или изменяет поле, и DO должен быть изменен синхронно, что повлияет на слои, что не соответствует принципу высокой паккатичности низкой связи. Определяя разные DTO, можно управлять разными атрибутами для предоставления доступа к разным системам, конкретное скрытое имя поля может быть реализовано путем сопоставления атрибутов. В разных компаниях используются разные модели, когда для изменения бизнеса требуется поле модификации, нет необходимости учитывать влияние на другие службы. Если используется один и тот же объект, это может привести к большому количеству уникального поведения совместимости, потому что «не смейте изменять ".
Инструмент репликации объектов
Существует множество инструментов библиотеки классов для репликации объектов, в дополнение к обычному Apache.BeanUtils, ВеснаBeanUtils,Cglib BeanCopierи тяжеловесные компонентыMapStruct,Orika,Dozer,ModelMapperЖдать.
Если нет особых требований, эти классы инструментов можно использовать напрямую, за исключением Apache.BeanUtils. причина в томApache BeanUtilsЧтобы добиться совершенства, базовый исходный код добавляет слишком много пакетов, использует много отражений и выполняет много проверок, что приводит к низкой производительности, и обязательно избегать его использования в руководстве по разработке Alibaba.Apache BeanUtils.
Что касается остальных тяжеловесных компонентов, учитывая их производительность и простоту использования, я рекомендую их здесь.Orika. Нижний уровень Orika использует библиотеку классов javassist для генерации байт-кода отображения bean-компонента, а затем напрямую загружает и выполняет сгенерированный файл байт-кода, что намного быстрее, чем использование отражения для присвоения значений.
Иностранный бог baeldung провел подробные тесты на производительность общих компонентов, вы можете пройтиWoohoo. Возьмите Arlington Terrier.com/Java-per для…Проверить.
Основное использование Орика
Использовать 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
Видя это, некоторые фанаты обязательно скажут: что вы рекомендуете, эта орика не проста в использовании, вы должны сделать это сначала каждый разMapperFactory, чтобы установить отношение сопоставления полей перед выполнением преобразования сопоставления.
Не волнуйтесь, я подготовил для вас класс инструментов здесьOrikaUtils, вы можете получить его из репозитория github в конце статьи.
Он предоставляет пять общедоступных методов:
Соответствует:
- Согласованное с полем преобразование сущности
- Преобразование несовместимого объекта поля (требуется сопоставление полей)
- Преобразование согласованного набора полей
- Преобразование набора несогласованности полей (требуется сопоставление полей)
- Регистрация преобразования свойства поля
Далее мы сосредоточимся на использовании этого класса инструментов в примерах модульного тестирования.
Документация по использованию класса инструментов Orika
Сначала подготовьте два основных класса сущностей, Студент и Учитель.
@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)
В настоящее время поле электронной почты не может быть сопоставлено из-за несогласованных имен атрибутов.
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)
На этом этапе, поскольку поля сопоставлены, электронная почта может быть сопоставлена с адресом электронной почты.Обратите внимание, что ключ в refMap здесь является свойством исходного объекта, а значение — свойством целевого объекта, не делайте наоборот.
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)]
В настоящее время из-за несогласованных имен атрибутов поле электронной почты не может быть сопоставлено в коллекции.
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;
}
Теперь вам нужно сопоставить значение nameParts класса Person со значением 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 с учителем.
/**
* 类类型映射
*/
@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 имеют разные поля электронной почты, и их нужно преобразовать и сопоставить; также необходимо сопоставить атрибуты в 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, MyBatis плюс отображение пейджинга
Если вы используете компонент подкачки 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, надеюсь, она будет вам полезна.
Наконец, я Мисти Джем, архитектор, который пишет код, и программист, который работает в области архитектуры. Я с нетерпением жду вашей переадресации и внимания. Конечно, вы также можете добавить мой личный WeChat.jianzh5, давайте поговорим о технологии!
Исходный код старой серии птиц был загружен на GitHub, Если вам нужно ответить на ключевое слово в публичном аккаунте [JAVA Daily Record]0923Получать