Hướng dẫn sử dụng MapStruct + Lombok
Hướng dẫn mì ăn liền kết hợp 2 thư viện để thực hiện mapping entity và dto trong Java.
Các dependencies
- Dependencies trong code SpringBoot(3.4.1), springweb,jpa, sql (ở đây là mysql, nhớ tạo db trước nha mấy bro).
- Dependencies cho lombok và Mapstruct (thêm lombok-mapstruct-binding nếu dùng lombok+mapstruct).
<?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.4.1</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.nhan</groupId> <artifactId>myhibernate</artifactId> <version>0.0.1-SNAPSHOT</version> <name>myhibernate</name> <description>myhibernate</description> <url/> <licenses> <license/> </licenses> <developers> <developer/> </developers> <scm> <connection/> <developerConnection/> <tag/> <url/> </scm> <properties> <java.version>17</java.version> <org.mapstruct.version>1.6.3</org.mapstruct.version> <org.lombok.version>1.18.36</org.lombok.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${org.lombok.version}</version> <optional>true</optional> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${org.mapstruct.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok-mapstruct-binding</artifactId> <version>0.2.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>${java.version}</source> <target>${java.version}</target> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${org.lombok.version}</version> </path> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </path> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok-mapstruct-binding</artifactId> <version>0.2.0</version> </path> </annotationProcessorPaths> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${org.lombok.version}</version> </exclude> <exclude> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
Application.properties
spring.application.name=myhibernate spring.datasource.url=${DATASOURCE_URL:jdbc:mysql://localhost:3306/myhibernate} spring.datasource.username=${DATASOURCE_USERNAME:nhan} spring.datasource.password=${DATASOURCE_PASSWORD:nhan} spring.data.jdbc.dialect=mysql spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
Code implements
Ví dụ với 2 endpoint:
Endpoint | Method | Description | Request Body | Response Body |
---|---|---|---|---|
/customers |
POST | Create new customer | CustomerDto |
CustomerDto |
/customers/all |
GET | Get all customers | Không | List<CustomerDto> |
Entity
Sử dụng Jpa để mapping tự động.
Tạo entity đơn giản là Customer
- Lớp
Customer
@AllArgsConstructor @NoArgsConstructor @Getter @Setter @Entity public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String email; private String gender; }
Dto Customer
public record CustomerDto(Long id, String name, String email, String gender) { }
MapStuct Mapper
@Mapper(componentModel = "spring") public interface CustomerMapper { // Nếu muốn dùng instance thay vì inject
// CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class); @Mapping(target = "id", ignore = true) Customer toEntity(CustomerDto customerDto); CustomerDto toDto(Customer customer); }
Respository
public interface CustomerRepository extends JpaRepository<Customer, Long> { }
Service và impl
public interface CustomerService { CustomerDto createCustomer(CustomerDto customerDto); List<CustomerDto> getAllCustomers(); } // implement
@Service public class CustomerServiceImpl implements CustomerService { // private CustomerMapper customerMapper = CustomerMapper.INSTANCE; private final CustomerMapper customerMapper; private final CustomerRepository customerRepository; public CustomerServiceImpl(CustomerMapper customerMapper, CustomerRepository customerRepository) { this.customerMapper = customerMapper; this.customerRepository = customerRepository; } @Override public CustomerDto createCustomer(CustomerDto customerDto) { Customer customer = customerMapper.toEntity(customerDto); customer = customerRepository.save(customer); return customerMapper.toDto(customer); } @Override public List<CustomerDto> getAllCustomers() { return customerRepository.findAll().stream().map(customerMapper::toDto).toList(); } }
Controller
@RestController @RequestMapping("/customers") public class CustomerController { private final CustomerService customerService; public CustomerController(CustomerService customerService) { this.customerService = customerService; } @PostMapping public CustomerDto createCustomer(@RequestBody CustomerDto customerDto) { return customerService.createCustomer(customerDto); } @GetMapping("/all") public List<CustomerDto> getAllCustomers() { return customerService.getAllCustomers(); } }
Test api
Ở đây mình dùng Httpie Desktop
để test 2 endpoint trên
- Tạo mới Customer.
Endpoint
/customers
, method post, body CusomerDto
{ "id": 2, "name": "tran nhan", "email": "nhan@gmail.com", "gender": "male"
}
Response: (id là generation nên tạo cusomer truyền id không bị ảnh hưởng)
- Lấy danh sách cusomer.
Request:
/cusomers/all
, method get. Response:
Notes
- Để hiểu rõ hơn các class được sinh ra xem các file được tự động mapping ở targets/classses/package_tương_ứng.
- Đi một ngày đàng học một sàng khôn. Viết cho đã, đọc github, stackoverflow cho đã rồi up bài xong search ra bài của ông anh khác đã viết trên viblo là đảo lombok xuống dưới mapstruct là không cần thêm cái lombok-mapstruct-binding. Viết là để chia sẻ kiến thức nha anh em. Cái mới ra đời chưa chắc tốt hơn cái cũ nhưng chắc chắn là sẽ có cái tác dụng gì đó, rác còn có thể tái chế mà ae.