ObjectMapper và HibernateModule

I. Giới thiệu

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ó

1. Entity
1.1. Department
        
    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;
    }
        
      
1.2. Employee
        
    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;
    }
        
      
2. Repository
2.1 EmployeeRepo
        
    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> {
    }
            
        
      
3. DTO
3.1 DepartmentDto
        
    package com.dto;

    import lombok.Getter;
    import lombok.Setter;
    
    @Getter
    @Setter
    public class DepartmentDto {
        
        private Long id;
        
        private String name;
    }
        
      
3.2 EmployeeDto
        
    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;
    }
        
      
Và đây là đoạn dùng ObjectMapper để chuyển đổi từ Employee sang EmployeeDto
        

    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

II. Sử dụng
1. pom.xml
        
    <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

2. ObjectMapperConfig
        
    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

ObjectMapperConfig
        
    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

Bài đăng phổ biến từ blog này

IBM BPM - Date

BPM WebSphere - Create Datasource (Connect to DB via JDBC)

IBM BPM - Error: Save error