Mappers (Orika, Dozer) - DmitryGontarenko/usefultricks GitHub Wiki
Orika Mapper - это среда для маппинга данных из одной модели к другой.
Это может полезно при разработке многослойных приложений.
Maven
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.4.2</version>
</dependency>
Создадим два класса для дальнейшей работы с маппером.
BankParameters
- будет играть роль DTO слоя, который используется для передачи данных между подсистемами/слоями приложения.
@Data
@AllArgsConstructor
public class BankParameters {
private String firstName;
private String secondName;
private PersonalInfo personalInfo;
}
@Data
@AllArgsConstructor
public class PersonalInfo {
private String phoneNumber;
private String email;
private String address;
}
WebBankParameters
- будет использован для передачи на UI.
@Data
public class WebBankParameters {
private String name;
private String phone;
private String address;
}
Создаем фабрику и конфигурируем маппинг для ранее созданных классов:
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
MapperFacade mapper = mapperFactory.getMapperFacade();
mapperFactory.classMap(WebBankParameters.class, BankParameters.class)
.field("name", "firstName")
.field("address", "personalInfo.address")
.field("phone", "personalInfo.phoneNumber")
.byDefault()
.register();
В случае, когда одно или несколько полей в отображаемых классах имеют совпадающие имена, можно использовать метод byDefault()
, такие поля будут сопоставлены автоматически.
В данном примере показан как прямой маппинг name -> firstName
, так и маппинг к вложенному типу address -> personalInfo.address
.
Теперь создадим экземпляр DTO класса и передадим его для маппинга:
BankParameters bankParameters = new BankParameters(
"John", "Tuesday",
new PersonalInfo("8915675543",
"[email protected]",
"Monday str.")
);
WebBankParameters webBankParameters = mapper.map(bankParameters, WebBankParameters.class);
System.out.println(webBankParameters); // WebBankParameters(name=John, phone=8915675543, address=Monday str.)
Как показывает результат, веб-модель успешно принимает значения из полей dto-класса.
Для этого примера воспользуемся моделями, созданными ранее.
Создаем класс BankParametersMapper
и наследуемся от класса ConfigurableMapper
для конфигурирования маппера:
@Component
public class BankParametersMapper extends ConfigurableMapper {
@Override
protected void configure(MapperFactory factory) {
factory.classMap(WebBankParameters.class, BankParameters.class)
.field("name", "firstName")
.field("address", "personalInfo.address")
.field("phone", "personalInfo.phoneNumber")
.byDefault()
.register();
}
}
Создадим интерфейс и реализуем все его необходимые методы в сервисе BankParametersServiceImpl
, а так же внедрим зависимость MapperFacade
:
@Service
public class BankParametersServiceImpl implements BankParametersService {
@Autowired
private MapperFacade mapperFacade;
@Override
public WebBankParameters getItem() {
return mapperFacade.map(getBankParameters(), WebBankParameters.class);
}
...
}
Реализация маппера готова, теперь можно проверить его работу через контроллер BankParametersController
:
@RestController
public class BankParametersController {
@Autowired
private BankParametersService bankParametersService;
@GetMapping("/")
public WebBankParameters getItem() {
return bankParametersService.getItem(); // {"name":"John","phone":"8915675543","address":"Monday str."}
}
}
Работу созданного меппера можно проверить с помощью теста.
Для начала нам понадобится подключить несколько новых зависимостей - JUnit, Spring-test, Spring-boot-test, Unitils-core и Podam:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>uk.co.jemos.podam</groupId>
<artifactId>podam</artifactId>
<version>7.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.unitils</groupId>
<artifactId>unitils-core</artifactId>
<version>3.4.2</version>
</dependency>
Создадим класс MapperTest
, который будет инициализировать PodamFactory и содержать в себе MapperFacade:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class MapperTest {
@Autowired
protected MapperFacade mapperFacade;
private PodamFactory factory;
@Before
public void initPodamFactory() {
factory = new PodamFactoryImpl();
}
protected PodamFactory podamFactory() {
return factory;
}
protected void addExcludedFields(Class<?> pojoClass, String... fieldNames) {
for (String fieldName : fieldNames) {
((AbstractClassInfoStrategy) podamFactory().getClassStrategy()).addExcludedField(pojoClass, fieldName);
}
}
}
В метод addExcludedFields
можно будет добавлять поля, которые не должны заполняться данными.
Исключить поля из заполнения можно еще одним способом - установкой аннотации @PodamExclude
над полем. Но установка дополнительных аннотаций для dto-слоя не приветствуется, поэтому воспользуемся нашим более универсальным методом.
Создадим класс BankParametersMapperTest
для тестирования конкретных классов и наследуемся от ранее созданного MapperTest
, а так же исключим из заполнения те поля, которых нет в веб-моделе:
@ComponentScan("com.home.springorika")
public class BankParametersMapperTest extends MapperTest {
@Test
public void testMapWebToDto() {
PodamFactory factory = podamFactory();
addExcludedFields(BankParameters.class, "secondName");
addExcludedFields(PersonalInfo.class, "email");
BankParameters dtoObject = factory.manufacturePojo(BankParameters.class);
WebBankParameters webObject = mapperFacade.map(dtoObject, WebBankParameters.class);
BankParameters resultObject = mapperFacade.map(webObject, BankParameters.class);
ReflectionAssert.assertReflectionEquals(dtoObject, resultObject);
}
}
Готово! Теперь можно запустить тест testMapWebToDto()
и убедиться в его успешном выполнении.
Orika. Официальная документация
Dozer - это среда для маппинга данных из одной модели к другой.
Это может полезно при разработке многослойных приложений.
Maven
<dependency>
<groupId>net.sf.dozer</groupId>
<artifactId>dozer</artifactId>
<version>5.4.0</version>
</dependency>
Для тестировани маппера создадим два класса.
Рассмотрим Dozer на простом примере.
Создадим экземпляр класса DozerBeanMapper
(который реализует интерфейс Mapper
), а затем реализуем метод BeanMappingBuilder#configure()
для настройки маппинга двух моделей с разным наименованием полей.
В том случае, если поля имеют одинаковые названия, маппинг происходит автоматически.
DozerBeanMapper mapper = new DozerBeanMapper();
// конфигурируем сопостовления полей
BeanMappingBuilder builder = new BeanMappingBuilder() {
@Override
protected void configure() {
mapping(WebBankParameters.class, BankParameters.class)
.fields("name", "firstName")
.fields("address", "personalInfo.address")
.fields("phone", "personalInfo.phoneNumber");
}
};
mapper.addMapping(builder);
Теперь создадим экземпляр dto-класса и передадим его для маппинга:
BankParameters bankParameters = new BankParameters(
"John", "Tuesday",
new PersonalInfo("8915675543",
"[email protected]",
"Monday str.")
);
WebBankParameters webBankParameters = mapper.map(bankParameters, WebBankParameters.class);
System.out.println(webBankParameters); // WebBankParameters(name=John, phone=8915675543, address=Monday str.)
Как показывает результат, веб-модель успешно принимает значения из полей dto-класса.
С помощью XML конфигурации можно также настроить сопостовление полей классов.
Создадим файл dozer_mapping.xml
в resources:
<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozer.sourceforge.net"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://dozer.sourceforge.net
http://dozer.sourceforge.net/schema/beanmapping.xsd">
<mapping>
<class-a>com.home.orica.model.WebBankParameters</class-a>
<class-b>com.home.orica.model.BankParameters</class-b>
<field>
<a>name</a>
<b>firstName</b>
</field>
<field>
<a>address</a>
<b>personalInfo.address</b>
</field>
<field>
<a>phone</a>
<b>personalInfo.phoneNumber</b>
</field>
</mapping>
</mappings>
Теперь создадим метод configureMapper
, для удобного добавления путей файлов-конфигураций:
private void configureMapper(String... mappingFileUrls) {
mapper.setMappingFiles(Arrays.asList(mappingFileUrls));
}
И наконец добавим путь и наименование созданного нами xml файла в конфигурацию маппера:
...
configureMapper("dozer_mapping.xml");
WebBankParameters webBankParameters = mapper.map(bankParameters, WebBankParameters.class);
System.out.println(webBankParameters); // WebBankParameters(name=John, phone=8915675543, address=Monday str.)
Dozer. Официальная документация
Baeldung. A Guide to Mapping With Dozer