introduce
In Java development, we often need to map data between different Java beans, such as conversion from entity classes (Entity) to data transfer objects (DTOs). The traditional approach is to manually write a large number of setters and getter methods to complete the assignment of attributes. This method is not only cumbersome, but also prone to errors. As an annotation-based code generation tool, MapStruct provides us with a more elegant and efficient solution. It automatically generates mapping code at compile time, avoiding the performance overhead caused by runtime reflection and ensuring type safety.
Pros and cons
advantage
- high performance: MapStruct generates mapping code during the compilation phase, and calls these codes directly at runtime, avoiding the use of reflection, thereby significantly improving performance.
- Type safety: Since the mapping code is generated at compile time, the compiler will check the type, so errors such as type mismatch can be found during the compilation stage, avoiding runtime exceptions.
- Concise code: Using MapStruct only requires defining the mapping interface and configuring with annotations, without manually writing a large amount of mapping logic, which greatly reduces the amount of code and improves development efficiency.
shortcoming
- Learning Cost: It is necessary to learn the various annotations provided by MapStruct and their usage methods, which may have a certain learning curve for beginners.
- Dependency management: It is necessary to introduce related dependencies of MapStruct into the project, which increases the complexity of the project's dependency management.
Core annotations and detailed usage syntax instructions
@Mapper
- effect: Used to mark an interface as a mapping interface, MapStruct will generate a specific implementation class for the interface at compile time.
- Use syntax:
import ; import ; @Mapper public interface UserMapper { // Get an instance of the mapping interface through the method UserMapper INSTANCE = (); // Define the mapping method from UserEntity to UserDTO UserDTO toDTO(UserEntity entity); // Define the mapping method from UserDTO to UserEntity UserEntity toEntity(UserDTO dto); }
@Mapping
-
effect: Used to specify the attribute mapping relationship between the source object and the target object, when the attribute name of the source object and the target object
Inconsistent
When using this annotation, you can use theExplicit mapping
。 - Use syntax:
import ; import ; import ; @Mapper public interface UserMapper { UserMapper INSTANCE = (); // Use the @Mapping annotation to specify the entityId to map to id, and the entityName to map to name @Mapping(source = "entityId", target = "id") @Mapping(source = "entityName", target = "name") UserDTO toDTO(UserEntity entity); // Reverse mapping @Mapping(source = "id", target = "entityId") @Mapping(source = "name", target = "entityName") UserEntity toEntity(UserDTO dto); }
@Mappings
-
effect: @Mappings is a collection form of @Mapping, used to specify it at one time
Multiple attribute mapping
relation. - Use syntax:
import ; import ; import ; import ; @Mapper public interface UserMapper { UserMapper INSTANCE = (); @Mappings({ @Mapping(source = "entityId", target = "id"), @Mapping(source = "entityName", target = "name") }) UserDTO toDTO(UserEntity entity); @Mappings({ @Mapping(source = "id", target = "entityId"), @Mapping(source = "name", target = "entityName") }) UserEntity toEntity(UserDTO dto); }
@Context
- effect: Used to pass context information during the mapping process, such as some auxiliary objects, which can be used in the mapping method.
- Use syntax:
import ; import ; import ; import ; @Mapper public interface UserMapper { UserMapper INSTANCE = (); // Pass the Locale object as context information using the @Context annotation UserDTO toDTO(UserEntity entity, @Context Locale locale); }
@AfterMapping
- effect: Used to execute custom logic after the mapping is completed, such as performing additional processing on certain properties of the target object.
- Use syntax:
import ; import ; import ; import ; @Mapper public interface UserMapper { UserMapper INSTANCE = (); UserDTO toDTO(UserEntity entity); // Use @AfterMapping annotation to define custom logic after mapping is completed @AfterMapping default void afterMapping(@MappingTarget UserDTO dto, UserEntity entity) { // Stitch the firstName and lastName of the source object and assign it to the fullName property of the target object (() + " " + ()); } }
Demo Example
Public basic class definition
import ; // User entity class@Data public class UserEntity { private Long id; private String name; private Integer age; private String firstName; private String lastName; private AddressEntity address; private Long entityId; private String entityName; } // User data transmission object class@Data public class UserDTO { private Long id; private String name; private Integer age; private String fullName; private AddressDTO address; private Long entityId; private String entityName; } // Address entity class@Data public class AddressEntity { private String street; private String city; } // Address data transmission object class@Data public class AddressDTO { private String street; private String city; }
Simple mapping example
import ; import ; @Mapper public interface UserMapper { // Get an instance of the mapping interface UserMapper INSTANCE = (); // Mapping method from UserEntity to UserDTO UserDTO toDTO(UserEntity entity); // Mapping method from UserDTO to UserEntity UserEntity toEntity(UserDTO dto); } // Test codepublic class MainSimpleMapping { public static void main(String[] args) { UserEntity userEntity = new UserEntity(); (1L); ("John"); (25); // Map using instances of mapping interface UserDTO userDTO = (userEntity); ("Simple Mapping Example Results:"); ("UserDTO: , name=" + () + ", age=" + ()); } }
Output result:
Simple mapping example results:
UserDTO: id=1, name=John, age=25
Example of mapping with inconsistent field names
import ; import ; import ; @Mapper public interface UserMapper { UserMapper INSTANCE = (); @Mapping(source = "entityId", target = "id") @Mapping(source = "entityName", target = "name") UserDTO toDTO(UserEntity entity); @Mapping(source = "id", target = "entityId") @Mapping(source = "name", target = "entityName") UserEntity toEntity(UserDTO dto); } // Test codepublic class MainFieldNameMismatch { public static void main(String[] args) { UserEntity userEntity = new UserEntity(); (1L); ("John"); (25); UserDTO userDTO = (userEntity); ("Sample result of field name inconsistent mapping:"); ("UserDTO: , name=" + () + ", age=" + ()); } }
Output result:
Example results for field names inconsistent mapping:
UserDTO: id=1, name=John, age=25
Example of nested object mapping
import ; import ; @Mapper public interface UserMapper { UserMapper INSTANCE = (); UserDTO toDTO(UserEntity entity); UserEntity toEntity(UserDTO dto); AddressDTO toDTO(AddressEntity entity); AddressEntity toEntity(AddressDTO dto); } // Test codepublic class MainNestedObjectMapping { public static void main(String[] args) { AddressEntity addressEntity = new AddressEntity(); ("123 Main St"); ("New York"); UserEntity userEntity = new UserEntity(); (1L); ("John"); (addressEntity); UserDTO userDTO = (userEntity); ("Nested Object Mapping Sample Results:"); ("UserDTO: , name=" + ()); ("AddressDTO: street=" + ().getStreet() + ", city=" + ().getCity()); } }
Output result:
Nested object mapping example results:
UserDTO: id=1, name=John
AddressDTO: street=123 Main St, city=New York
Custom mapping logic example
import ; import ; import ; import ; @Mapper public interface UserMapper { UserMapper INSTANCE = (); UserDTO toDTO(UserEntity entity); @AfterMapping default void afterMapping(@MappingTarget UserDTO dto, UserEntity entity) { (() + " " + ()); } } // Test codepublic class MainCustomMappingLogic { public static void main(String[] args) { UserEntity userEntity = new UserEntity(); (1L); ("John"); ("Doe"); UserDTO userDTO = (userEntity); ("Custom Mapping Logic Sample Results:"); ("UserDTO: , fullName=" + ()); } }
Output result:
Custom mapping logic example results:
UserDTO: id=1, fullName=John Doe
Example of using context
import ; import ; import ; import ; @Mapper public interface UserMapper { UserMapper INSTANCE = (); UserDTO toDTO(UserEntity entity, @Context Locale locale); default String localize(String value, @Context Locale locale) { // Localization according to locale return value; } } // Test codepublic class MainWithContext { public static void main(String[] args) { UserEntity userEntity = new UserEntity(); (1L); ("John"); Locale locale = ; UserDTO userDTO = (userEntity, locale); ("Use context example results:"); ("UserDTO: , name=" + ()); } }
Output result:
Use context example results:
UserDTO: id=1, name=John
From the above example, we can see that using MapStruct can easily and quickly complete the mapping between Java Beans, and at the same time, combined with Lombok's @Data annotation, the code is further simplified. And from the output results, the correctness of each mapping scenario can be intuitively verified.
This is the article about the detailed explanation of the use of MapStruct mapping based on annotation in Java. For more related Java MapStruct content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!