본문 바로가기
프로그래밍/Spring

[JPA] 테이블의 한 컬럼 내에서 N개의 값을 가지는 방법

by 사바라다 2020. 12. 12.

안녕하세요. 오늘은 처음으로 여러분들께 JPA에 관련하여 포스팅을 하려고합니다. 저는 대부분의 프로젝트에서 JPA와 QueryDsl을 사용하고 있습니다. 사용하는 이유는 확실히 간편하기 때문입니다.. 그리고 String이 아닌 메서드 형으로 쿼리를 작성하기 때문에 만약 잘못된 점이 있다면 QueryDsl 덕분에 컴파일 타임에 찾을 수 있고 객체로 DB Table을 관리할 수 있다는 점이 있습니다. 사용하기 위한 러닝커브는 상당히 높은 편이지만 익숙해지기만 한다면 다시 Mybatis로는 돌아갈 수 없는 편함을 제공합니다.

여러분들과 오늘 이야기 나눠보고자 하는 내용은 JPA를 이용할 때 테이블의 컬럼에 내의 N개의 값을 가지는 방법입니다.

요구사항

한 게임에 게임에 대해서 리뷰를 남깁니다. 리뷰를 남길 때 점수를 메길 수 있습니다. 이때 1점 ~ 10점을 선택할 수 있습니다. 관리해야할 항목으로 게임에 대한 리뷰의 평균점수와 세부 내역이 있었습니다. 이런 항목들은 호출 할 때 마다 전체 리뷰에서 가져와서 평균을 산출하면 오버헤드가 크기 때문에 주기적으로 평균을 내어 게임의 평균점수 테이블에서 따로 관리하도록 하였습니다.

테이블에 필요한 내용은 아래와 같습니다.

  • 게임 ID
  • 평균 점수
  • 평균 점수의 세부 내역 (1점 ~ 10점까지 실제로 받은 횟수)

여기서 문제가 있는데 세부 내역을 어떻게 관리할 것인가에 대한 문제입니다.

  1. 기존 테이블을 연결하여 처리
  2. 한 컬럼 내에서 처리
  3. 세부 내역을 각각 컬럼으로 쪼개기

1 정규화를 만족 시키기 위해서는 별도의 테이블로 처리하는 것이 맞습니다. 하지만 성능으로 볼 때 가치가 있을지 보면 사실 의문이 드는게 사실입니다. 따라서 저는 한 컬럼 내에서 처리하는것으로 방향을 정하고 개발했습니다.

JPA에서 1 : N을 지원하는 방법

JPA에서 1 : N 연관관계를 제공하는 방법은 모두 테이블간의 연관관계입니다. 아래의 방법으로 1 : N 연관관계를 맺을 수 있습니다.

먼저 별도의 Entity Class를 만들고 1 : N 연관관계를 맺는 방법입니다. 워낙 이 방법은 유명하기 때문에 지금이 아니라 추후에 제가 JPA의 연관관계에 대해서 포스팅하게 된다면 좀 더 자세히 설명하도록 하겠습니다.

@OneToMany(mappedBy = "gameId" ...)
private List<Screenshot> screenshots = new ArrayList<>();

또 하나의 방법은 1개의 값을 위해 별도의 Entity를 만드는것이 부담된다면 아래와 같이 사용할 수 있습니다. 아래 처럼 사용한다며 별도의 Entity를 만들 필요가 없이 JPA에서 자동으로 처리해줍니다. 테이블의 구성은 해당 테입ㄹ의 Primary Key와 N 개의 연관된 값으로 구성되게 됩니다.

@ElementCollection
@CollectionTable(name = "data" ....)
private List<Integer> screenshots = new ArrayList<>();

Converter를 이용한 한 컬럼 내에서 N개의 값을 가지게 만들기

아쉽게도 한 컬럼 내에서 1 대 N의 관계를 구성은 기본적으로 제공하고 있지않습니다. 따라서 구성하기 위해서 다른 기능을 통해 간접적으로 작업을 진행해줘야합니다. 저는 @Convert 어노테이션을 통해 해당작업을 진행하였습니다.

@Convert 어노테이션은 객체(Object)와 DB의 컬럼이 정확히 일치하지 않을 때 맞춰주는 용도로 사용할 수 있습니다. 실행되는 시점은 실제 DB에 쓰여질 때 또는 DB에서 가지고 올 때 입니다

사용하는 방법은 @Converter 어노테이션을 클래스에 선언해주며 AttributeConverter를 상속받아 구현해주면 됩니다. 그러면 Object에서 DB Table에 들어갈 때 변경할 수 적용되는 convertToDatabaseColumn 메서드와 DB Table의 데이터를 Object에 매핑시킬 때 구현하는 convertToEntityAttribute메서드가 있습니다.

아래의 예제를 보시면 이해하실 수 있으실 겁니다. convertToDatabaseColumn 에서는 List<Integer>의 객체를 DB table로 넣을 때 값들 사이사이에 ,를 넣어 String 형태로 변환하여 Table에 넣습니다. 반대로 convertToEntityAttribute 에서는 String data를 ,를 이용해 split 한 후 List<Integer>로 변형하여 반환합니다.

@Converter
public class IntegerArrayConverter implements AttributeConverter<List<Integer>, String> {

  private static final String SPLIT_CHAR = ",";

  @Override
  public String convertToDatabaseColumn(List<Integer> attribute) {
    return attribute.stream().map(String::valueOf).collect(Collectors.joining(SPLIT_CHAR));
  }

  @Override
  public List<Integer> convertToEntityAttribute(String dbData) {
    return Arrays.stream(dbData.split(SPLIT_CHAR))
        .map(Integer::parseInt)
        .collect(Collectors.toList());

  }
}

사용하는 방법은 간단합니다. 원하는 해당 Entity에 @Convert 어노테이션을 이용하여 아래와 같이 사용하면 됩니다.

@Convert(converter = IntegerArrayConverter.class)
private List<Integer> totalScoreSpecific;

마무리

오늘은 이렇게 JPA에서 한 컬럼 내에서 N개의 다른 값을 가지는 방법에 대해서 알아보았습니다.

무조건 맞다 틀리다라는 것은 없다고 생각합니다. 그 상황에 맞게 퍼포먼스, 들어가는 리소스를 생각하여 본인의 프로젝트에 맞게 진행 할 수 있는 능력이 중요하다고 생각합니다.

감사합니다.

참조

baeldung_JPA Attribute Converters

댓글