하이버네이트 6 버전을 사용하며 JPA 표준으로 지원하지 않는 JSON 관련 함수를 등록해 사용하는 방법에 대해 정리한 글이다.
하이버네이트는 JPA의 구현체중 하나로, 가장 많이 사용되는 JPA 구현체다.
하이버네이트는 기본적으로 데이터베이스 방언 기능을 제공한다. 이 기능은 개발자가 코드를 작성하면 데이터베이스에 맞는 쿼리로 알아서 변환해주는 기능이다.
방언은 일반적인 SQL을 생성하는것이 목적이기 때문에 특정 데이터베이스에서만 제공하는 함수같은 기능은 제공하지 못한다.
MySQL 은 5.7 버전부터 JSON 타입을 지원한다. 어떻게 보면 역정규화된 필드이긴 하지만 잘 사용한다면 편의와 성능등 여러 이점이 있을 수 있다.
나의 경우 상품의 속성을 정의하기 위해 JSON 타입을 사용했다.
{
"color" : "red",
"size" : 100
}
위와같이 상품의 속성을 정의할때 상품마다 어떤 키-값이 올지 모르고, 몇개가 올지 모르기 때문에 JSON을 사용하기로 했다.
MySQL에서는 JSON으로 조건을 걸 수 있는 JSON_CONTAINS
라는 함수를 제공한다.
select * from product where json_contains(attribute, '{"color" : "red"}')
위와같이 사용한다면, 특정 상품에서 속성중에 "color"
키의 값이 "red"
값인 모든 상품이 조회된다.
(일치가 아니라 포함임)
하지만 데이터베이스의 추상화가 목적인 JPA에서는 MySQL의 특화된 쿼리인 JSON_CONTAINS
를 지원하지 않는다.
그래서 JSON_CONTAINS
를 사용하기 위해선 몇가지 작업을 해줘야 한다.
JPA의 구현체인 Hibernate 에 사용자 정의 함수를 추가해 SQL로 번역할 수 있도록 해줘야 한다.
나의 경우 JSON_CONTAINS
함수를 JPA에서 사용할 수 있도록 추가할 거다.
FunctionContributor
인터페이스를 구현한다.
public class CustomFunctionContributor implements FunctionContributor {
@Override
public void contributeFunctions(FunctionContributions functionContributions) {
functionContributions
.getFunctionRegistry()
.registerPattern("json_contains", "json_contains(?1, ?2)",
functionContributions.getTypeConfiguration().getBasicTypeRegistry().resolve(BOOLEAN));
}
}
json_contains
라고 JPQL에서 사용하면, json_contains(?1, ?2)
로 번역해 사용하라고 등록해준다.
그리고 JSON_CONTAINS
의 리턴 타입은 boolean 이니 타입을 잘 맞춰 등록해준다.
구현한 FunctionContributor
를 META-INF에 등록해준다.
src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor
파일에 아래와 같이 작성한 커스텀 함수를 추가한다.
jshop.global.hibernate.CustomFunctionContributor
추가한 json_contains
가 잘 동작하는지 확인해본다.
em.createQuery("select pd from ProductDetail pd where json_contains(pd.attribute, '{\"attr1\" : \"1\"}')",
ProductDetail.class);
잘 번역되어 나가는것을 확인할 수 있다.
https://discourse.hibernate.org/t/migration-of-dialect-to-hibernate-6/6956