Mappers (Orika, Dozer) - DmitryGontarenko/usefultricks GitHub Wiki

Orika Mapper

About

Orika Mapper - это среда для маппинга данных из одной модели к другой.
Это может полезно при разработке многослойных приложений.

Dependency

Maven

        <dependency>
            <groupId>ma.glasnost.orika</groupId>
            <artifactId>orika-core</artifactId>
            <version>1.4.2</version>
        </dependency>

Create Classes

Создадим два класса для дальнейшей работы с маппером.
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;
}

Simple Example

Создаем фабрику и конфигурируем маппинг для ранее созданных классов:

        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-класса.

Simple Example with Spring Boot

Для этого примера воспользуемся моделями, созданными ранее.
Создаем класс 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."}
    }
}

Mapper test

Работу созданного меппера можно проверить с помощью теста.
Для начала нам понадобится подключить несколько новых зависимостей - 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() и убедиться в его успешном выполнении.

Sources

Orika. Официальная документация

Dozer

About Dozer

Dozer - это среда для маппинга данных из одной модели к другой.
Это может полезно при разработке многослойных приложений.

Dozer dependency

Maven

        <dependency>
            <groupId>net.sf.dozer</groupId>
            <artifactId>dozer</artifactId>
            <version>5.4.0</version>
        </dependency>

Dozer Create Classes

Для тестировани маппера создадим два класса.

Dozer Simple Example

Рассмотрим 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 Mapping

С помощью 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 Sources

Dozer. Официальная документация
Baeldung. A Guide to Mapping With Dozer

⚠️ **GitHub.com Fallback** ⚠️