일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- .dockerignore
- Plating
- 자바 암호화
- 프로그래머스 코딩테스트
- 개발자 인턴
- docker image
- 신입 개발자 자바 면접 질문
- 개발자 면접
- 자바 stream
- docker
- 개발자 정규직
- 신입 개발자 자바 면접
- 신입 개발자 필수 면접 질문
- Linux
- 개발자 코딩테스트
- 코딩테스트 연습
- 신입 개발자 면접
- 샐러드
- 신입 개발자 면접 질문
- 식단일기
- 식단
- 개발자 면접 질문
- 개발자 채용연계형인턴
- 셰프의찾아가는구내식당
- 도커
- 자바 암호화 복호화
- 플레이팅
- 프로그래머스
- 자바 암호화 알고리즘
- 직장인점심구독
- Today
- Total
달리는 두딘
[JPA] JPQL 사용 방법(@Query & nativeQuery & DTO Mapping & function) 본문
JPA(Java Persistence API)를 사용하여 서비스를 구현하다 보면,
JPA의 Query Methods만으로는 조회가 불가능한 경우가 존재한다.
이러한 경우 JPQL(Java Persistence Query Language)를 이용하여
SQL과 비슷한 형태의 쿼리를 작성하여 조회를 할 수 있다.
JPQL를 작성하기 위한 방법에는 여러가지 방법이 존재하나
@Query Annotation과 EntityManager.createQuery 등을 사용하여 JPQL를 작성하는 방법에 대해 작성하려고 한다.
@Query
@Query Annotation는 Entity의 JpaRepository를 상속받는 인터페이스에 정의하게 된다.
기본적인 작성 방법은 from 구문에 Entity의 객체를 선언하여
해당 객체의 속성명을 통해서 조건과 파라미터를 작성하게 된다.
주의할 점은 각 문장에 끝에 띄어쓰기를 추가하도록 한다.
각 문장이 문자열로 이어져 있기 때문에
문자의 처음 또는 끝에 띄어쓰기가 없는 경우 한 문장으로 인식되어 정상적인 문장이 아니게 된다.
@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "user")
@AllArgsConstructor
public class User {
@Id
private String id;
private String name;
private String phone;
private String registerInfo;
private String deptId;
}
JpaRepository
public interface UserRepository extends JpaRepository<User, String> {
@Query(value = "select user " +
"from User user " +
"where user.name = :name")
List<User> findByName(@Param("name") String name);
DTO Mapping
JPQL를 사용게되는 이유는 여러가지가 존재하지만
가장 큰 이유는 function과 join의 경우일 것이다.
이러한 경우 정의한 Entity의 속성외에 속성이 추가 될것이며 이를 위해 DTO 반환이 필요하다.
@Query Annotation를 사용하여 DTO 반환을 하기위해서는
select 구분에서 생성자를 통해서 객체를 반환하여야 한다.
DTO
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
private String id;
private String name;
private String phone;
private String deptId;
private String deptName;
}
return DTO & join
public interface UserRepository extends JpaRepository<User, String> {
@Query(value = "select " +
"new io.velog.youmakemesmile.jpql.UserDto(user.id, user.name, user.phone, user.deptId, dept.name) " +
"from User user " +
"left outer join Dept dept on user.deptId = dept.id ")
List<UserDto> findUserDept();
}
SQL Function
위에서 이야기한 것과 같이 JPQL를 사용하게 되는 이유중 하나는 SQL Function이다.
JPQL에서는 기본적으로 select 구문의 max, min, count, sum, avg를 제공하며,
기본 function으로는 COALESCE, LOWER, UPPER 등을 지원한다.
public interface UserRepository extends JpaRepository<User, String> {
@Query(value = "select max(user.id) " +
"from User user " +
"where user.deptId is not null")
String findMaxUserId();
}
이러한 JPQL에서 기본적으로 지원하는 ANSI Query Function만으로는
비지니스의 조회를 해결하기는 한계가 존재한다.
MSA에서는 데이터 저장 방법이 각 서비스에 맞게 변화될 수 있게 설계되어야 한다고 하지만
현실적으로는 성능과 비용을 생각할 때
DataBase에서 제공하는 function을 사용하지 않을 수 없다.
DataBase Function를 사용하는 방식은
JPQL에서 function()을 활용하여 hibernate에 등록된 각 DataBase의 Dialect에 정의된 function을 사용하는 방식이다.
public interface UserRepository extends JpaRepository<User, String> {
@Query(value = "select function('date_format', :date, '%Y/%m/%d') " +
"from User user ")
String findNow(@Param("date") LocalDateTime date);
}
하지만 hinbernate에서 기본적으로 등록되는 function에서도 누락되는 function이 존재한다.
이러한 경우 이전에는 Dialect를 상속받아 구현하는 방식을 사용하였으나
현재에는 MetadataBuilderContributor의 구현체를 구현하는 방식을 제공하고 있다.
public class MyMetadataBuilderContributor implements MetadataBuilderContributor {
@Override
public void contribute(MetadataBuilder metadataBuilder) {
metadataBuilder.applySqlFunction("JSON_EXTRACT", new StandardSQLFunction("JSON_EXTRACT", StringType.INSTANCE))
.applySqlFunction("JSON_UNQUOTE", new StandardSQLFunction("JSON_UNQUOTE", StringType.INSTANCE))
.applySqlFunction("STR_TO_DATE", new StandardSQLFunction("STR_TO_DATE", LocalDateType.INSTANCE))
.applySqlFunction("MATCH_AGAINST", new SQLFunctionTemplate(DoubleType.INSTANCE, "MATCH (?1) AGAINST (?2 IN BOOLEAN MODE)"));
}
}
applySqlFunction의 첫번째 파라미터는 JPQL에서 function("함수명") 함수명에 해당하는 등록명이다.
StandardSQLFunction은 기본적인 함수를 등록하기위한 Class로
생성자의 첫번째 파라미터는 실제 DataBase Function명이며 두번째 파라미터는 function의 리턴 타입이다.
StandardSQLFunction의 경우 파라미터는 함수 순서에 맞게
JPQL function('등록 함수명', 파라미터1, 파리마터2 ...) 정의하여 사용하면 된다.
SQLFunctionTemplate은 문법이 존재하는 function를 등록할때 사용 가능하며,
첫번 째 파라미터가 function의 리턴타입
두번 째 파라미터가 function이다.
?1, ?2와 같이 명시하여
JPQL function()에서 전달되는 파라미터의 순서대로 파싱되어 SQL이 생성된다.
public interface UserRepository extends JpaRepository<User, String> {
@Query(value = "select user " +
"from User user " +
"where function('JSON_UNQUOTE', function('JSON_EXTRACT',user.registerInfo,'$.id')) = 'admin' ")
List<User> findAllByRegisterAdmin();
}
public interface UserRepository extends JpaRepository<User, String> {
@Query(value = "select user " +
"from User user " +
"where function('MATCH_AGAINST', user.name, :name) >0 ")
List<User> findAllByName(@Param("name") String name);
}
EntityManager 사용
@Query를 이용한 정의 방식 이외에 java에서 EntityManager를 활용하여 JPQL를 작성할 수도 있다.
EntityManager.createQuery() 메소드에 JPQL 문자열과 리턴 타입을 전달하면 된다.
@Query에서와 동일하게 DTO를 리턴하는 경우 해당 DTO를 생성하고 생성자 형태로 JPQL 문장을 작성하면 된다.
@Repository
public class sample{
@PersistenceContext
private EntityManager entityManager;
public void test() {
List<User> resultList = entityManager
.createQuery("select user from User user where function('MATCH_AGAINST', user.name, :name) > 0 ", User.class)
.setParameter("name", "le")
.getResultList();
List<UserDto> resultList2 = entityManager
.createQuery("select " +
"new io.velog.youmakemesmile.jpql.temp.UserDto(user.id, user.name, user.phone, user.deptId, dept.name) " +
"from User user " +
"left outer join Dept dept on user.deptId = dept.id ", UserDto.class)
.getResultList();
}
}
NativeQuery
NatvieQuery는 JPQL이 아닌 SQL를 직접 정의하여 사용하는 방식이다.
위에서 이야기 한 것과 같이 function과 join를 하는 경우 JPQL를 사용할 수 있지만
SQL를 직접 정의할 수 있는 NativeQuery를 사용할 수도 있다.
@Query
@Query를 이용하여 NativeQuery를 작성하는 방법은
@Query 속성중 nativeQuery의 값을 true로 설정하며 value에는 SQL문을 그대로 작성하면 된다.
DTO 맵핑의 경우 JPQL를 사용할때와는 다르게 DTO Class가 아닌 Getter 만 존재하는 interface를 정의하며
네이밍은 SQL의 리턴되는 칼럼명과 일치해야 한다.
다음은 예제이기 때문에 Repository에 interface를 정의하였으며, 구조에 맞는 위치에 정의해도 상관없다.
public interface UserRepository extends JpaRepository<User, String> {
@Query(value = "select user.id as id, user.name as name, user.phone as phone, json_unquote(json_extract(user.register_info,'$.id')) as registerInfo , str_to_date(json_unquote(json_extract(user.register_info, '$.date')), '%Y-%m-%d') as registerDate, user.dept_id as deptId, dept.name as deptName " +
"from user user " +
"left outer join dept dept on user.dept_id = dept.id " +
"where match (user.name) against (:name in boolean mode) > 0", nativeQuery = true)
List<UserNativeVo> findTest4(@Param("name") String name);
interface UserNativeVo {
String getId();
String getName();
String getPhone();
String getRegisterInfo();
String getRegisterDate();
String getDeptId();
String getDeptName();
}
}
EntityManager
EntityManager를 이용하여 NativeQuery를 작성하는 방법은
createNativeQuery()를 통해서 SQL 문장을 작성하며,
EntityManager를 사용하는 경우 Hibernate의 NativeQuery.class를 이용하여
setResultTransformer를 통해 DTO class를 매핑하여 결과를 리턴받는 방법이 있다.
setResultTransformer 메소드가 @Deprecated로 예정되어 있지만 현재로서는 해당 방법만이 존재한다.
DTO Class
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
private String id;
private String name;
private String phone;
private String deptId;
private String deptName;
}
createNativeQuery
public List<UserDto> test7(){
return entityManager.createNativeQuery("select user.id as id, user.name as name, user.phone as phone, user.dept_id as deptId, dept.name as deptName " +
"from user user " +
"left outer join dept dept on user.dept_id = dept.id " +
"where match (user.name) against (:name in boolean mode) > 0").setParameter("name","le")
.unwrap(NativeQuery.class)
.setResultTransformer(Transformers.aliasToBean(UserDto.class))
.getResultList();
}
출처
'지식노트' 카테고리의 다른 글
그림과 작동 원리로 쉽게 이해하는 서버의 기초 - 1. 서버란 (4) | 2023.06.06 |
---|---|
[DB] Oracle - MariaDB 차이 (2) | 2023.05.18 |
[JAVA] HTTP 다양한 통신 방법 (URLConnection / RestTemplate / HttpClient / WebClient) (17) | 2023.03.13 |
[Linux] haproxy 설명 및 haproxy.cfg 설정 (0) | 2023.03.09 |
[Linux] tcpdump 사용법 (0) | 2023.03.09 |