網頁

2020/10/24

Spring Data JPA AttributeConverter 轉換列舉屬性為資料表欄位值 convert Enum attribute to table column value

資料表中常有欄位用來儲存資料的類型(type)、種類(category)、狀態(state)、情況(status),這些欄位值通常對應到Java的enum。JPA 2.1可使用AttributeConverter轉換資料表欄位與entity的enum資料型態屬性。

範例環境:

  • Spring Boot 2.3.2.RELEASE
  • Spring Data JPA (Hibernate 5.4.x, JPA 2.2)
  • H2 Database
  • Lombok

例如下面的enum State代表員工狀態,每種狀態有對應的數值代碼(code)。

State

package com.abc.demo.entity.enumeration;

import lombok.Getter;

/** 員工狀態 */
public enum State {

    /** 正常 */
    NORMAL(0),
    /** 缺席 */
    LEAVE(1),
    /** 離職 */
    QUIT(2),
    /** 其他 */
    OTHERS(3),
    ;

    @Getter
    private final int code;

    State(int code) {
        this.code = code;
    }

    public static State of(int code) {
        for (State state : State.values()) {
            if (state.getCode() == code) {
                return state;
            }
        }
        return State.OTHERS;
    }
}

Employee為對應EMPLOYEE資料表的entity。屬性state型態為上面的State,而資料表的EMPLOYEE.STATE欄位的型態為數值,例如Oracle的NUMBER或MySQL的INT。所以需要把State的值轉為對應的數值存到欄位中,在屬性前加上@Convert,並將converter設為自訂的AttributeConverter,也就是下面的StateConverter

Employee

package com.abc.demo.entity;

import com.abc.demo.entity.enumeration.State;
import com.abc.demo.entity.enumeration.converter.StateConverter;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Employee implements Serializable {
    private static final Long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Convert(converter = StateConverter.class)
    private State state;

}

StateConverter實作AttributeConverter介面,類別名稱前標註@Converter,為轉換enum為欄位型態的實作。

覆寫convertToDatabaseColumn()把enum屬性值轉為資料表欄位值;
覆寫convertToEntityAttribute()把資料庫欄位值轉為enum屬性值。

StateConverter

package com.abc.demo.entity.enumeration.converter;

import com.abc.demo.entity.enumeration.State;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter
public class StateConverter implements AttributeConverter<State, Integer> {
    
    @Override
    public Integer convertToDatabaseColumn(State attribute) {
        return attribute.getCode();
    }

    @Override
    public State convertToEntityAttribute(Integer dbData) {
        if (dbData == null) {
            return State.OTHERS;
        }

        return State.of(dbData);
    }
    
}

存取EmployeeEmployeeRepository

EmployeeRepository

package com.abc.demo.repository;

import com.abc.demo.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

使用EmployeeRepositoryTests測試是否有成功轉換。

EmployeeRepositoryTests

package com.abc.demo.repository;

import com.abc.demo.entity.Employee;
import com.abc.demo.entity.enumeration.State;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Optional;

@SpringBootTest
class EmployeeRepositoryTests {

    @Autowired
    private EmployeeRepository employeeRepository;

    /**
     * 測試資料表欄位值轉entity屬性值
     */
    @Test
    void testStateConverter_tableColumnToEntityAttribute() {
        State state = employeeRepository.findById(1L)
                .map(Employee::getState).orElse(State.OTHERS);

        Assertions.assertEquals(State.NORMAL, state);
    }

    /**
     * 測試entity屬性值轉資料表欄位值
     */
    @Test
    void testStateConverter_entityAttributeToTableColumn() {
        Employee employee = Employee.builder()
                .name("Mike")
                .state(State.LEAVE)
                .build();

        State state = Optional.of(employeeRepository.save(employee))
                .map(Employee::getState).orElse(State.OTHERS);

        Assertions.assertEquals(State.LEAVE, state);
    }
    
}

參考github


沒有留言:

張貼留言