[JAVA] Copie de bean à l'aide de MapStruct

1. Pourquoi MapStruct

Il existe plusieurs bibliothèques couramment utilisées lors de la copie de beans à l'aide de Java.

Parmi ceux-ci, celui que je recommande le plus est «mapstruct». La raison en est que «mapstruct» est ** le plus rapide **. Les sources suivantes indiquent le temps nécessaire à chaque copie de bean en utilisant les cinq bibliothèques ci-dessus.

@Slf4j
public class CopyDemoTest {

    public UserMainBO bo;

    public static int count = 1000000;

    @Before
    public void init(){
        bo = new UserMainBO();
        bo.setId(1L);
    }

    @Test
    public void mapstruct() {
        UserMainVOMapping INSTANCE = Mappers.getMapper( UserMainVOMapping.class );
        log.info("star------------");
        for (int i = 1; i <=count; i++) {
            UserMainVO vo = INSTANCE.toVO(bo);
        }
        log.info("end------------");
    }

    @Test
    public void beanCopier() {
        log.info("star------------");
        BeanCopier copier = BeanCopier.create(UserMainBO.class, UserMainVO.class, false);
        for (int i = 1; i <=count; i++) {
            UserMainVO vo = new UserMainVO();
            copier.copy(bo, vo, null);
        }
        log.info("end------------");
    }

    @Test
    public void springBeanUtils(){
        log.info("star------------");
        for (int i = 1; i <=count; i++) {
            UserMainVO vo = new UserMainVO();
            BeanUtils.copyProperties(bo, vo);
        }
        log.info("end------------");
    }

    @Test
    public void apacheBeanUtils() throws InvocationTargetException, IllegalAccessException {
        for (int i = 1; i <=count; i++) {
            UserMainVO vo = new UserMainVO();
            org.apache.commons.beanutils.BeanUtils.copyProperties(bo, vo);
        }
        log.info("end------------");
    }

    @Test
    public void apachePropertyUtils() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        log.info("star------------");
        for (int i = 1; i <=count; i++) {
            UserMainVO vo = new UserMainVO();
            PropertyUtils.copyProperties(bo, vo);
        }
        log.info("end------------");
    }

}

résultat de l'inspection:

1,000 fois 10,000 fois 100,000 fois 1,000,000 fois
apache.BeanUtils 550ms 1085ms 4287ms 32088ms
apache.PropertyUtils 232ms 330ms 2080ms 20681ms
cglib.BeanCopier 73ms 106ms 102ms 99ms
mapstruct 91ms 5ms 7ms 12ms
spring.BeanUtils 5ms 188ms 336ms 844ms

À partir des résultats de vérification ci-dessus, il peut être confirmé que mapstruct a de bien meilleures performances que les autres bibliothèques.

2. Pour utiliser MapStruct

Pour utiliser MapStruct, vous devez importer la bibliothèque MapStruct dans votre projet. Voici un exemple d'utilisation de maven.

pom.xml


<properties>
    <org.mapstruct.version>1.3.1.Final</org.mapstruct.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${org.projectlombok.version}</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source> <!-- depending on your project -->
                <target>1.8</target> <!-- depending on your project -->
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

2.1 Utilisation combinée avec lombok

Lorsque vous utilisez MapStruct avec la version 1.2.0.Final ou antérieure et lombok ensemble, les paramètres suivants sont requis:

pom.xml


<properties>
    <org.mapstruct.version>1.1.0.Final</org.mapstruct.version>
    <org.projectlombok.version>1.18.12</org.projectlombok.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${org.projectlombok.version}</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source> <!-- depending on your project -->
                <target>1.8</target> <!-- depending on your project -->
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>${org.projectlombok.version}</version>
                    </path>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

2.2 Utilisation combinée avec swagger-ui

Puisque l'intérieur de swagger-ui utilise une ancienne version de MapSruct, il est nécessaire d'exclure la dépendance sur MapSruct de la bibliothèque swagger-ui lors de son utilisation avec swagger-ui.

pom.xml


<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>${springfox-swagger.version}</version>
    <exclusions>
        <exclusion>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>${springfox-swagger.version}</version>
    <exclusions>
        <exclusion>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
        </exclusion>
    </exclusions>
</dependency>

3. Comment utiliser MapStruct

3.1 Facile à utiliser

Il existe deux classes de haricots ci-dessous.

Student.java


import lombok.Data;
import lombok.ToString;

@Data
@ToString
public class Student {
    private String name;
    private String address;
    private String phone;
}

Person.java


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Person {
    private String name;
    private String address;
    private String telephone;
}

A partir de maintenant, bean copie de l'objet Person vers l'objet Student. Avant de copier le bean, il est nécessaire de préparer la classe Mapper à l'avance.

StudentMapper.java


import model.Person;
import model.Student;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper
public interface StudentMapper {
    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    Student personToStudent(Person person); //Le nom de la méthode est arbitraire
}

Copions le haricot.

main_method


Person person = new Person("lisa", "Tokyo", "12345");
Student student = StudentMapper.INSTANCE.personToStudent(person);
System.out.println(student);

//production
// Student(name=Lisa, address=Tokyo, phone=null)

Par défaut, tout champ dont le nom ne correspond pas est automatiquement ** ignoré ** et ne peut pas être copié de «téléphone» vers «téléphone».

3.2 Copie du champ de non-concordance de nom

Si les noms des champs source et cible ne correspondent pas, vous devez spécifier @ Mapping lorsque vous essayez de copier.

StudentMapper.java


import model.Person;
import model.Student;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface StudentMapper {
    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    @Mapping(source = "telephone", target = "phone")
    Student personToStudent(Person person);
}

Résultat de l'exécution:

Student(name=Lisa, address=Tokyo, phone=12345)

3.3 Exclure les champs

Si vous ne souhaitez copier aucun champ, vous pouvez les exclure de la copie via l'attribut @ Mapping ʻignore`.

StudentMapper.java


import model.Person;
import model.Student;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface StudentMapper {
    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    @Mapping(source = "telephone", target = "phone")
    @Mapping(target = "address", ignore = true)
    Student personToStudent(Person person);
}

Résultat de l'exécution:

Student(name=Lisa, address=null, phone=12345)

3.4 Copie de plusieurs beans vers un seul bean

MapStruct peut copier des champs de plusieurs beans vers un seul bean.

LoginInfo.java


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginInfo {
    private String id;
    private String password;
}

UserProfile.java


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserProfile {
    private String email;
    private String address;
}

UserInfo.java


@ToString
@Data
public class UserInfo {
    private String id;
    private String password;
    private String email;
    private String address;
}

Préparez Mapper.

UserInfoMapper.java


import model.LoginInfo;
import model.UserInfo;
import model.UserProfile;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface UserInfoMapper {
    UserInfoMapper INSTANCE = Mappers.getMapper(UserInfoMapper.class);

    @Mapping(target = "id", source = "loginInfo.id")
    @Mapping(target = "password", source = "loginInfo.password")
    @Mapping(target = "email", source = "userProfile.email")
    @Mapping(target = "address", source = "userProfile.address")
    UserInfo fromLoginInfoAndUserProfile(LoginInfo loginInfo, UserProfile userProfile);
}

faire une copie.

main_method


LoginInfo loginInfo = new LoginInfo("12345", "54321");
UserProfile userProfile = new UserProfile("[email protected]", "Tokyo");

UserInfo userInfo = UserInfoMapper.INSTANCE.fromLoginInfoAndUserProfile(loginInfo, userProfile);
System.out.println(userInfo);

//production
// UserInfo(id=12345, password=54321, [email protected], address=Tokyo)

3.5 Mettre à jour le bean créé

MapStruct peut non seulement copier et créer des beans, mais également mettre à jour les beans créés.

ʻAjoutez la source suivante à UserInfoMapper.java`:

UserInfoMapper.java


@Mapping(target = "email", source = "email")
@Mapping(target = "address", source = "address")
void updateUserProfile(UserProfile userProfile, @MappingTarget UserInfo userInfo);

Mettons à jour:

main_method


LoginInfo loginInfo = new LoginInfo("12345", "54321");
UserProfile userProfile = new UserProfile("[email protected]", "Tokyo");

UserInfo userInfo = UserInfoMapper.INSTANCE.fromLoginInfoAndUserProfile(loginInfo, userProfile);
System.out.println(userInfo);
//production
// UserInfo(id=12345, password=54321, [email protected], address=Tokyo)

userProfile = new UserProfile("[email protected]", "Fukuoka");
UserInfoMapper.INSTANCE.updateUserProfile(userProfile, userInfo);
System.out.println(userInfo);
//production
// UserInfo(id=12345, password=54321, [email protected], address=Fukuoka)

3.6 Conversion de format

Dans MapStruct, lors de la conversion de Date / Date locale en String, lors de la conversion de int en String, vous pouvez spécifier le format et convertir.

Person.java


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.time.LocalDate;

@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    private String name;
    private LocalDate birthday;
    private int salary;
}

Employee.java


import lombok.Data;
import lombok.ToString;

@Data
@ToString
public class Employee {
    private String name;
    private String birthday;
    private String salary;
}

PersonMapper.java


import model.Employee;
import model.Person;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface PersonMapper {
    PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);

    @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy/MM/dd")
    @Mapping(source = "salary", target = "salary", numberFormat = "#,###")
    Employee personToEmployee(Person person);
}

main_method


Person person = new Person("lisa", LocalDate.of(1990, 1, 20), 1234567);
Employee employee = PersonMapper.INSTANCE.personToEmployee(person);
System.out.println(employee);
//production
// Employee(name=lisa, birthday=1990/01/20, salary=1,234,567)

3.7 expression

Lors de la copie d'un bean, si la complexité doit être convertie, elle peut être facilement réalisée avec ʻexpression`.

Par exemple, mettez en majuscule le champ name lors de la copie d'un objet Person vers un objet Employee.

PersonMapper.java


import model.Employee;
import model.Person;
import org.apache.commons.lang3.StringUtils;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper(imports = {StringUtils.class}) //Importer des StringUtils
public interface PersonMapper {
    PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "name", expression = "java(StringUtils.upperCase(person.getName()))")
    Employee personToEmployee(Person person);
}

3.8 méthode par défaut

À partir de Java8, l'interface peut prendre en charge la méthode par défaut. MapStruct peut également utiliser la méthode par défaut pour créer une logique de copie complexe.

PersonMapper.java


import model.Employee;
import model.Person;
import org.apache.commons.lang3.StringUtils;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import java.time.format.DateTimeFormatter;

@Mapper(imports = {StringUtils.class})
public interface PersonMapper {
    PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);

    default Employee personToEmployee(Person person, String dateFormat) {
        Employee employee = new Employee();
        //Mettre en majuscule le nom
        employee.setName(StringUtils.upperCase(person.getName()));
        //Convertir l'anniversaire au format spécifié
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(dateFormat);
        employee.setBirthday(person.getBirthday().format(dateTimeFormatter));

        return employee;
    }
}

Exécutez la méthode par défaut.

main_method


Person person = new Person("lisa", LocalDate.of(1990, 1, 20), 1234567);
Employee employee = PersonMapper.INSTANCE.personToEmployee(person, "aaaa année MM mois jj jour");
System.out.println(employee);
//production
// Employee(name=LISA, birthday=20 janvier 1990, salary=null)

Recommended Posts

Copie de bean à l'aide de MapStruct
Effectuer une cartographie de bean à grande vitesse à l'aide de MapStruct
Cartographie de Bean avec MapStruct Partie 1
Cartographie Bean avec MapStruct Partie 3
Cartographie de Bean avec MapStruct Partie 2