들어가기
Springboot와 JPA를 통해 프로젝트를 진행하던 중 간단한 값들의 리스트를 하나의 엔티티에 넣어두고 싶을 때가 있었다.
이는 '@OneToMany'를 통해서 새로운 테이블과 연관관계를 맺으면 쉽게 구현할 수 있지만...
새로운 테이블을 만들고 조인을 하는 등 복잡한 방법을 쓰고 싶지가 않았다. 오직 원하는 테이블만을 위한 리스트 필드를 가지고 싶은 것이다.
이럴 때 어떻게 사용할 수 있을까?
이 글에서는 간단하게 1. String으로의 변환 2. @ElementCollection 두가지 방법으로 소개하려고 한다.
본론
1. String 타입으로 변환
첫번째로 리스트를 Stirng 타입으로 변환하여 저장하는 것이다.
아래의 사진을 봐보자.
위는 특정 요청을 했을 때 응답 값을 JSON 형식으로 응답한 것이다.
Springboot를 사용하다 보면 JSON 형식의 데이터가 이동되는 것을 쉽게 볼 수 있었을 것이다.
여기서는 이러한 JSON 형식의 String 값으로서 테이블에 리스트를 저장할 것이다.
아래는 엔티티의 구조이다.
@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class TestEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@Convert(converter = StringListConverter.class)
private List<String> columns;
}
주요하게 볼 것은 columns이다. 엔티티를 기반으로 데이터베이스에 테이블이 매칭이 되기 때문에 필드에 List 값을 넣는다면 에러가 발생한다. 하지만 위 코드에서는 에러가 발생하지 않는다. 그 이유는 @Convert 덕분이다. 이는 리스트를 자동으로 StringListConverter.class에 정의한 값으로 변환을 해주기 때문에 데이터베이스 상에서는 지정한 타입이 값이 테이블에 들어간다.
아래는 간단하게 구현한 StringListConverter.class 이다.
@Converter
public class StringListConverter implements AttributeConverter<List<String>, String> {
private final ObjectMapper mapper = new ObjectMapper();
@Override
public String convertToDatabaseColumn(List<String> dataList) {
try {
return mapper.writeValueAsString(dataList);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
@Override
public List<String> convertToEntityAttribute(String data) {
try {
return mapper.readValue(data, List.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
중요한 부분은 'converToDatabaseColumn'과 'converToEntityAttribute'이다. 이 두 가지 덕분에 @Convert를 단 칼럼에 자동으로 리스트와 String의 변환이 이루어지는 것이다.
내용은 단순하게 ObjectMapper를 통해 리스트를 JSON 형식으로, JSON형식을 리스트로 변환한다.
그렇다면 데이터는 어떻게 저장이 되고, 어떻게 조회가 될까?
리스트가 columns라는 필드에 String 타입의 JSON 형식으로 저장이 된 것을 알 수 있다.
조회할 때는 다시 리스트 형식으로 조회가 되는 것을 알 수 있다.
JSON 타입 말고 자신만의 형식으로 저장을 하고 싶을 수 있다.
그럴 때는 StringListConverter를 자신만의 방식으로 설정하면 된다. 아래는 JSON 형식이 아닌 다른 형식으로 리스트를 저장한 예시이다.
@Converter
public class StringListConverter implements AttributeConverter<List<String>, String> {
private static final String SPLIT_CHAR = ", ";
@Override
public String convertToDatabaseColumn(List<String> stringList) {
return String.join(SPLIT_CHAR, stringList);
}
@Override
public List<String> convertToEntityAttribute(String string) {
return Arrays.asList(string.split(SPLIT_CHAR));
}
}
2. '@ElementCollection'을 통한 컬렉션 테이블 생성
@ElementCollection 어노테이션을 사용하는 방식으로 위 어노테이션을 추가한 칼럼은 주키가 원래 테이블의 주키이고 컬렉션 값들을 가지는 테이블이 새로 생성된다.
아래의 사진을 봐보자.
흡사 @OneToMany를 사용한 것 같다. 하지만 이는 @OneToMany와 다르다. 오직 부모 테이블과 연결해서만 이 테이블에 접근할 수 있고, 부모 테이블에서 위 리스트를 접근할 때 join이 발생하지 않는다. 생성될 때나 삭제될 때나 모두 같이 삭제되는 즉, 하나의 테이블 연결채로 볼 수가 있다. 이는 부모 테이블에 종속되어 있다고 할 수 있다.
아래는 구현된 코드이다.
@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class TestEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@ElementCollection(fetch = FetchType.LAZY)
private List<String> columns;
}
단순히 위 어노테이션을 추가해주기만 하면 된다.
참고
https://prohannah.tistory.com/133
https://passionfruit200.tistory.com/346
'JPA' 카테고리의 다른 글
QueryDsl를 사용해보자.(With Spring boot 3.x.x, JPA) (0) | 2023.11.29 |
---|---|
JPA 양방향 연관관계 일 때의 저장 및 연관관계 편의 메서드 (0) | 2021.12.14 |
스프링부트 With JPA - mysql 연동 (0) | 2021.12.06 |
JPA - 읽기 전용으로 데이터를 조회하여 성능 향상(메모리, 속도) (0) | 2021.09.18 |
JPA 2차 캐시 기본 개념 (0) | 2021.09.13 |