Всем привет, я Мисти.
Сегодня я представляю четвертую часть серии старых птичек 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Получать