- vừa được xem lúc

Spring Boot MapStruct Lombok không sử dụng Maven Compiler Plugin

0 0 19

Người đăng: Vũ Văn Huy

Theo Viblo Asia

image.png

Giới thiệu

Các bạn đã quá mệt mõi với việc sử dụng MapStruct trong dự án Spring Boot của mình khi nó:

  • Thường xuyên thông báo lỗi compile với Lombok.
  • Config thủ công và rườm rà.

Đừng lo lẵng nữa bài viết này sẽ giúp các bạn giải thoát khỏi những phiền toái đó 🥳.

Hướng giải quyết

Các bạn tạo dự án Spring Boot bằng Spring Initializr nhé Screenshot 2024-05-02 at 21.35.45.png Tải dự án xuống và mở bằng IDE (ở đây mình sử dụng Intellij IDEA)

Dựa theo trang chủ của MapStruct, các bạn copy dependencies vào file pom.xml nhé.

<properties> <org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
</properties> <dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${org.mapstruct.version}</version> </dependency>
</dependencies>

Dependency trên có vai trò cung cấp các Annotation, chúng ta sử dụng chúng để thiết lập những Mapper Utilities logic.

Các bạn có thể thấy trên trang chủ của MapStruct họ có gợi ý chúng ta sử dụng Maven Compiler Plugin để quét các MapStruct Annotation xuất hiện trong dự án, tuy nhiên theo mình đánh giá cách này không thuật tiện trong dự án Spring Boot , thế nên chúng ta sẽ sử dụng MapStruct Annotation Processor để cài đặt MapStruct nhé.

Nguyên lý hoạt động của nó sẽ giống với Lombok Spring Boot Starter, các bạn có thể research để tìm hiểu thêm.

Các bạn thêm dependency này vào dự án của mình nhé, nó sẽ hoạt động cùng với dependency ở trên.

<dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </dependency>
</dependencies>

Cuối cùng file pom.xml của chúng ta sẽ có cấu trúc như thế này.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>io.huyvu</groupId> <artifactId>mapstruct</artifactId> <version>0.0.1-SNAPSHOT</version> <name>mapstruct</name> <description>Demo project for Spring Boot x MapStruct x Lombok</description> <properties> <java.version>21</java.version> <org.mapstruct.version>1.5.5.Final</org.mapstruct.version> </properties> <dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${org.mapstruct.version}</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>

Viết một vài MapStruct mapper để kiểm tra kết quả nào.

.
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src ├── main │ ├── java │ │ └── io │ │ └── huyvu │ │ └── mapstruct │ │ ├── MapstructApplication.java │ │ ├── UserDTO.java │ │ ├── UserEntity.java │ │ └── UserMapper.java │ └── resources │ └── application.properties └── test └── java └── io └── huyvu └── mapstruct └── MapstructApplicationTests.java 13 directories, 10 files
package io.huyvu.mapstruct; public class UserDTO { private Long id; private String username; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } } package io.huyvu.mapstruct; public class UserEntity { private Long userId; private String username; public Long getUserId() { return this.userId; } public String getUsername() { return this.username; } public void setUserId(Long userId) { this.userId = userId; } public void setUsername(String username) { this.username = username; }
} package io.huyvu.mapstruct; import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers; @Mapper
public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper( UserMapper.class ); @Mapping(source = "id", target = "userId") UserEntity toEntity(UserDTO dto);
} 

Build dự án bằng lệnh

mvn clean package

Kết quả

[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.897 s -- in io.huyvu.mapstruct.MapstructApplicationTests
[INFO] [INFO] Results:
[INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] [INFO] [INFO] --- jar:3.3.0:jar (default-jar) @ mapstruct ---
[INFO] Building jar: /Users/mac/Downloads/mapstruct/target/mapstruct-0.0.1-SNAPSHOT.jar
[INFO] [INFO] --- spring-boot:3.2.5:repackage (repackage) @ mapstruct ---
[INFO] Replacing main artifact /Users/mac/Downloads/mapstruct/target/mapstruct-0.0.1-SNAPSHOT.jar with repackaged archive, adding nested dependencies in BOOT-INF/.
[INFO] The original artifact has been renamed to /Users/mac/Downloads/mapstruct/target/mapstruct-0.0.1-SNAPSHOT.jar.original
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.760 s
[INFO] Finished at: 2024-05-02T22:09:43+07:00
[INFO] ------------------------------------------------------------------------ .
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src
└── target ├── classes ├── generated-sources │ └── annotations │ └── io │ └── huyvu │ └── mapstruct │ └── UserMapperImpl.java ├── generated-test-sources ├── mapstruct-0.0.1-SNAPSHOT.jar ├── mapstruct-0.0.1-SNAPSHOT.jar.original ├── maven-archiver ├── maven-status

Hãy để ý file UserMapperImpl.java, file này được MapStruct tạo ra dựa trên Interface UserMapper.java trong dự án. Điều này chứng tỏ MapStruct đã hoạt động đúng 👏.

package io.huyvu.mapstruct; import javax.annotation.processing.Generated; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2024-05-02T22:09:41+0700", comments = "version: 1.5.5.Final, compiler: javac, environment: Java 21.0.3 (Oracle Corporation)"
)
public class UserMapperImpl implements UserMapper { @Override public UserEntity toEntity(UserDTO dto) { if ( dto == null ) { return null; } UserEntity userEntity = new UserEntity(); userEntity.setUserId( dto.getId() ); userEntity.setUsername( dto.getUsername() ); return userEntity; }
} 

UserMapper.java đã hoàn thành công việc chuyển đổi từ lớp UserDTO thành lớp UserEntity.

Tuy nhiên nếu để ý các bạn sẽ thấy 2 lớp POJO trên mình tạo Getter, Setter theo cách thủ công.

Chúng ta hãy thử sử dụng Lombok để tự động tạo Getter Setter xem chuyện gì sẽ sảy ra.

package io.huyvu.mapstruct; import lombok.*; @Data
public class UserEntity { private Long userId; private String username;
} package io.huyvu.mapstruct; import lombok.*; @Data
public class UserDTO { private Long id; private String username; } 

Build lại dự án bằng lệnh:

mvn clean package

Kết quả

[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR : [INFO] -------------------------------------------------------------
[ERROR] /Users/mac/Downloads/mapstruct/src/main/java/io/huyvu/mapstruct/UserMapper.java:[11,23] No property named "id" exists in source parameter(s). Type "UserDTO" has no properties.
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.952 s
[INFO] Finished at: 2024-05-02T22:22:09+07:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile (default-compile) on project mapstruct: Compilation failure
[ERROR] /Users/mac/Downloads/mapstruct/src/main/java/io/huyvu/mapstruct/UserMapper.java:[11,23] No property named "id" exists in source parameter(s). Type "UserDTO" has no properties.
[ERROR] [ERROR] -> [Help 1]
[ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

Các bạn cũng đã thấy, Maven trả lỗi với nội dung không tìm thấy property với tên 'id' trong đối tượng nguồn (ý chỉ source trong UserMapper - UserDTO).

Lý giải cho điều này đó chính là do trong quá trình biên dịch, MapStruct dựa vào các Getter hoặc Setter để tìm các properties của đối tượng nguồn.

Thay vì sử dụng Getter Setter được cấu hình thủ công như lúc đầu, chúng ta đã sử dụng Lombok để thay thế, vì một lý do nào đó MapStruct đã không nhận diện được các Getter Setter do Lombok tạo ra.

Chúng ta hãy xem lại file cấu hình dependencies pom.xml

<dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${org.mapstruct.version}</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> 

Rõ ràng nhận ra về mặt thứ tự, dependency MapStruct được cấu hình trước so với dependency Lombok.

MapStruct và Lombok sử dụng chung một cơ chế hook vào kỳ biên dịch của Java Compiler.

Thế nên thứ tự sắp xếp các dependencies trong dự án cũng sẽ ảnh hưởng đến thứ tự hoạt động của chúng.

Điều này sẽ dẫn đến lỗi trên tức MapStruct không thể nhận diện được những Getter Setter do Lombok tạo ra.

Cách giải quyết cực kỳ đơn giản, chúng ta chỉ cần đảo ngược thứ tự của chúng.

	<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${org.mapstruct.version}</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>

Build lại dự án và xem kết quả

mvn clean build [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.897 s -- in io.huyvu.mapstruct.MapstructApplicationTests
[INFO] [INFO] Results:
[INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] [INFO] [INFO] --- jar:3.3.0:jar (default-jar) @ mapstruct ---
[INFO] Building jar: /Users/mac/Downloads/mapstruct/target/mapstruct-0.0.1-SNAPSHOT.jar
[INFO] [INFO] --- spring-boot:3.2.5:repackage (repackage) @ mapstruct ---
[INFO] Replacing main artifact /Users/mac/Downloads/mapstruct/target/mapstruct-0.0.1-SNAPSHOT.jar with repackaged archive, adding nested dependencies in BOOT-INF/.
[INFO] The original artifact has been renamed to /Users/mac/Downloads/mapstruct/target/mapstruct-0.0.1-SNAPSHOT.jar.original
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.819 s
[INFO] Finished at: 2024-05-02T22:39:27+07:00
[INFO] ------------------------------------------------------------------------ .
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src
└── target ├── classes ├── generated-sources │ └── annotations │ └── io │ └── huyvu │ └── mapstruct │ └── UserMapperImpl.java ├── generated-test-sources ├── mapstruct-0.0.1-SNAPSHOT.jar ├── mapstruct-0.0.1-SNAPSHOT.jar.original ├── maven-archiver ├── maven-status ├── surefire-reports └── test-classes 37 directories, 27 files

Hãy cùng kiểm tra lại file UserMapperImpl.java nhé.

package io.huyvu.mapstruct; import javax.annotation.processing.Generated; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2024-05-02T22:39:25+0700", comments = "version: 1.5.5.Final, compiler: javac, environment: Java 21.0.3 (Oracle Corporation)"
)
public class UserMapperImpl implements UserMapper { @Override public UserEntity toEntity(UserDTO dto) { if ( dto == null ) { return null; } UserEntity userEntity = new UserEntity(); userEntity.setUserId( dto.getId() ); userEntity.setUsername( dto.getUsername() ); return userEntity; }
} 

Tadaaa, nó đã hoạt động trở lại rồi 🥳.

Các bạn có thể thấy cách triển khai dự án của mình rất gọn gàng và đơn giản, không như những example khác đưa một đống Maven Compiler Plugin vào làm conflick một đống haha 🤣.

Mình mong bài viết này sẽ giúp ích cho các bạn, cảm ơn các bạn đã đọc.

Nếu thấy hay hãy cho mình một upvote nhé 😘.

source: github.com/huyvu8051

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tổng hợp các bài hướng dẫn về Design Pattern - 23 mẫu cơ bản của GoF

Link bài viết gốc: https://gpcoder.com/4164-gioi-thieu-design-patterns/. Design Patterns là gì. Design Patterns không phải là ngôn ngữ cụ thể nào cả.

0 0 302

- vừa được xem lúc

Học Spring Boot bắt đầu từ đâu?

1. Giới thiệu Spring Boot. 1.1.

0 0 277

- vừa được xem lúc

Cần chuẩn bị gì để bắt đầu học Java

Cần chuẩn bị những gì để bắt đầu lập trình Java. 1.1. Cài JDK hay JRE.

0 0 50

- vừa được xem lúc

Sử dụng ModelMapper trong Spring Boot

Bài hôm nay sẽ là cách sử dụng thư viện ModelMapper để mapping qua lại giữa các object trong Spring nhé. Trang chủ của ModelMapper đây http://modelmapper.org/, đọc rất dễ hiểu dành cho các bạn muốn tìm hiểu sâu hơn. 1.

0 0 194

- vừa được xem lúc

[Java] 1 vài tip nhỏ khi sử dụng String hoặc Collection part 1

. Hello các bạn, hôm nay mình sẽ chia sẻ về mẹo check String null hay full space một cách tiện lợi. Mình sẽ sử dụng thư viện Lớp StringUtils download file jar để import vào thư viện tại (link).

0 0 71

- vừa được xem lúc

Deep Learning với Java - Tại sao không?

Muốn tìm hiểu về Machine Learning / Deep Learning nhưng với background là Java thì sẽ như thế nào và bắt đầu từ đâu? Để tìm được câu trả lời, hãy đọc bài viết này - có thể kỹ năng Java vốn có sẽ giúp bạn có những chuyến phiêu lưu thú vị. DJL là tên viết tắt của Deep Java Library - một thư viện mã ng

0 0 139