JPA Attribute Converters

Dev.Hammy·2023년 7월 1일
0

JPA_Hibernate

목록 보기
10/14

JPA 3.0 부터 사용가능한 기능이다. JDBC 타입을 자바 클래스에 맵핑할 수 있다. 아래 예시에서는 Hibernate 6를 사용했다.

JPA는 내부적으로 JDBC를 사용하여 데이터베이스와 통신합니다. 즉, JPA가 데이터베이스와의 연결, SQL 쿼리 실행, 트랜잭션 관리 등을 처리할 때 JDBC를 사용합니다. JPA는 개발자가 직접 JDBC 코드를 작성하지 않아도 데이터베이스와 상호작용할 수 있도록 추상화된 API를 제공하므로, 개발자는 더 간편하게 데이터베이스와 작업할 수 있습니다.

Creating a Converter

예시) 나중에 변환될 PersonName 클래스

public class PersonName implements Serializable {

    private String name;
    private String surname;

    // getters and setters
}

예시) @Entity 어노테이션을 사용하는 클래스에 PersonName 타입의 속성 추가

@Entity(name = "PersonTable")
public class Person {
   
    private PersonName personName;

    //...
}

이제 PersonName 속성을 데이터베이스 열로 그리고 그와 반대로도 변환할 수 있는 컨버터를 생성해야 한다. @Converter 어노테이션을 사용하고 AttributeConverter 인터페이스를 구현함으로써 가능하다. 인터페이스의 매개변수에 데이터베이스 열과 클래스의 타입을 적용한다.

@Converter
public class PersonNameConverter implements 
  AttributeConverter<PersonName, String> {

    private static final String SEPARATOR = ", ";

    @Override
    public String convertToDatabaseColumn(PersonName personName) {
        if (personName == null) {
            return null;
        }

        StringBuilder sb = new StringBuilder();
        if (personName.getSurname() != null && !personName.getSurname()
            .isEmpty()) {
            sb.append(personName.getSurname());
            sb.append(SEPARATOR);
        }

        if (personName.getName() != null 
          && !personName.getName().isEmpty()) {
            sb.append(personName.getName());
        }

        return sb.toString();
    }

    @Override
    public PersonName convertToEntityAttribute(String dbPersonName) {
        if (dbPersonName == null || dbPersonName.isEmpty()) {
            return null;
        }

        String[] pieces = dbPersonName.split(SEPARATOR);

        if (pieces == null || pieces.length == 0) {
            return null;
        }

        PersonName personName = new PersonName();        
        String firstPiece = !pieces[0].isEmpty() ? pieces[0] : null;
        if (dbPersonName.contains(SEPARATOR)) {
            personName.setSurname(firstPiece);

            if (pieces.length >= 2 && pieces[1] != null 
              && !pieces[1].isEmpty()) {
                personName.setName(pieces[1]);
            }
        } else {
            personName.setName(firstPiece);
        }

        return personName;
    }
}

PersonNameConverter 클래스는 AttributeConverter<PersonName, String>를 구현하고 있으므로, PersonName 타입의 속성을 데이터베이스에 String 타입으로 변환하고, 데이터베이스에서 읽어온 String 타입을 다시 PersonName 타입으로 변환하는 작업을 수행합니다.

@Override 어노테이션은 상위 클래스나 인터페이스에 정의된 메서드를 재정의(오버라이드)한다는 것을 나타냅니다. 따라서 convertToDatabaseColumn 메서드는 AttributeConverter 인터페이스에 정의된 메서드인 convertToDatabaseColumn을 재정의하고 있습니다. 이 메서드는 PersonName 객체를 데이터베이스에 저장 가능한 형태로 변환하여 반환하는 역할을 합니다.

Using the Converter

예시) @Convert 어노테이션의 속성으로 사용하고 싶은 컨버터 클래스를 지정

@Entity(name = "PersonTable")
public class Person {

    @Convert(converter = PersonNameConverter.class)
    private PersonName personName;
    
    // ...
}

테스트를 위해 데이터베이스에 Person 객체를 저장

@Test
public void givenPersonName_whenSaving_thenNameAndSurnameConcat() {
    String name = "name";
    String surname = "surname";

    PersonName personName = new PersonName();
    personName.setName(name);
    personName.setSurname(surname);

    Person person = new Person();
    person.setPersonName(personName);

    Long id = (Long) session.save(person);

    session.flush();
    session.clear();
}

컨버터에 정의한 내용대로 PersonName이 저장되었는지, 데이터베이스 테이블에서 필드를 반환함으로써 테스트

@Test
public void givenPersonName_whenSaving_thenNameAndSurnameConcat() {
    // ...

    String dbPersonName = (String) session.createNativeQuery(
      "select p.personName from PersonTable p where p.id = :id")
      .setParameter("id", id)
      .getSingleResult();

    assertEquals(surname + ", " + name, dbPersonName);
}

바인딩 변수(?, SELECT * FROM employees WHERE department_id = ?;) 와 쿼리 매개변수(:, String sql = "SELECT * FROM employees WHERE department_id = :deptId"; )는 모두 SQL 쿼리를 더 동적으로 만들고 실행할 때 사용되지만, 바인딩 변수는 데이터베이스 시스템에서 사용되며 쿼리 실행 전에 값이 할당되고, 쿼리 매개변수는 프로그래밍 언어와 데이터베이스 간의 상호작용에서 사용되며 실행 시에 값이 대체됩니다

데이터베이스 값이 PersonName 클래스에 저장되는 작업도 잘 적용되었는지 Person 클래스 전체를 쿼리로 반환하여 테스트

@Test
public void givenPersonName_whenSaving_thenNameAndSurnameConcat() {
    // ...

    Person dbPerson = session.createNativeQuery(
      "select * from PersonTable p where p.id = :id", Person.class)
        .setParameter("id", id)
        .getSingleResult();

    assertEquals(dbPerson.getPersonName()
      .getName(), name);
    assertEquals(dbPerson.getPersonName()
      .getSurname(), surname);
}

0개의 댓글