ObjectMapper và HibernateModule
Object Mapper là class nằm trong bộ thư viện jackson giúp ta có thể chuyển đổi data trong khi lập trình, copy data từ object này sang object kia một cách dễ dàng mà không phải viết từng dòng set get.
Ta có bài toán sau:
Giờ ta cần chuyển đổi object Employee (entity) sang EmployeeDto (dto) với đầy đủ các thuộc tính trong entity đang có
package com.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "DEPARTMENT")
public class Department {
@Id
@Column(name = "ID")
private Long id;
@Column(name = "NAME")
private String name;
}
package com.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "EMPLOYEE")
public class Employee {
@Id
@Column(name = "ID")
private Long id;
@Column(name = "NAME")
private String name;
@Column(name = "DEPARTMENT_ID")
private Long departmentId;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "DEPARTMENT_ID", insertable = false, updatable = false)
private Department department;
}
package com.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.entity.Employee;
@Repository
public interface EmployeeRepo extends JpaRepository<Employee, Long> {
}
package com.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class DepartmentDto {
private Long id;
private String name;
}
package com.dto;
import lombok.Getter;
import lombok.Setter;
import com.dto.Department;
@Getter
@Setter
public class EmployeeDto {
private Long id;
private String name;
private Long departmentId;
private Department department;
}
import org.springframework.beans.factory.annotation.Autowired;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.entity.Employee;
import com.dto.EmployeeDto;
import com.repository.EmployeeRepo;
import java.util.Optional;
@Service
public class EmployeeService {
@Autowired
private EmployeeRepo repo;
@Autowired
private ObjectMapper objectMapper;
public EmployeeDto getEmployeeDto(Long id){
Optional<Employee> optional = repo.findById(id);
if(optional.isPresent()) {
Employee entity = optional.get();
return objectMapper.convertValue(entity, EmployeeDto.class);
}
return null;
}
}
Tiến hành chạy và kết quả thu được là một EmployeeDto được objectMapper convert từ Employee có các trường được map tương ứng với nhau theo tên.
Giờ ta sẽ chuyển mối quan hệ ManyToOne từ EAGER sang LAZY (Khi gọi hàm get department mới call db)
package com.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "EMPLOYEE")
public class Employee {
@Id
@Column(name = "ID")
private Long id;
@Column(name = "NAME")
private String name;
@Column(name = "DEPARTMENT_ID")
private Long departmentId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "DEPARTMENT_ID", insertable = false, updatable = false)
private Department department;
}
Tiến hành chạy lại và kết quả thu được sẽ là 1 exception được bắn ra
java.lang.IllegalArgumentException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.entity.Employee["department"]->com.entity.Department$HibernateProxy$lLfUN6Wl["hibernateLazyInitializer"])
Tại sao khi ta chuyển từ EAGER sang LAZY lại lỗi như này. Lý do vì ObjectMapper không support các đối tượng kiểu hibernate LAZY
Để fix lỗi trên mà vẫn sử dụng ObjectMapper ta dùng thêm thư viện jackson-datatype
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
<version>2.12.5</version>
</dependency>
Chú ý: jackson-datatype có từng phiên bản khác nhau cho các bản hibernate khác nhau nên các bạn chọn đúng artifactId tương thích với bản hibernate đang dùng.
Ví dụ
Hibernate Version | jackson-datatype Version |
---|---|
5.x | jackson-datatype-hibernate5 |
4.x | jackson-datatype-hibernate4 |
3.x | jackson-datatype-hibernate3 |
Trong ví dụ này mình dùng bản hibernate 5.x nên sẽ thêm thư viện jackson-datatype-hibernate5
package com.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
@Configuration
public class ObjectMapperConfig {
@Bean
public Module hibernate5Module() {
return new Hibernate5Module();
}
}
Tiến hành chạy lại và không còn lỗi nào nữa, dto đã có đủ các thuộc tính từ entity.
Chú ý vấn đề performance vì ObjectMapper sẽ tự mapping theo tên thuộc tính giống nhau nên nó sẽ tự gọi hàm department để set vào dto (thực hiện call xuống db để lấy department của Employee).
Mặc định Hibernate5Module sẽ không mapping những thuộc tính có @Transient. Nếu muốn mapping những thuộc tính này ta thêm config cho module như sau
package com.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
@Configuration
public class ObjectMapperConfig {
@Bean
public Module hibernate5Module() {
Hibernate5Module hibernate5Module = new Hibernate5Module();
hibernate5Module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
return hibernate5Module;
}
}
Nhận xét
Đăng nhận xét