이 글은 김영한 님의 저서 「자바 ORM 표준 JPA 프로그래밍」을 학습한 내용을 정리한 글입니다. 모든 출처는 해당 저서에 있습니다.
JPQL은 특정 데이터베이스에 종속적인 기능 지원 x
💡 인라인 뷰
From 절에서 사용하는 서브쿼리
JPA는 특정 데이터베이스에 종속적인 기능을 지원하는 방법들을 제공하며, 특히 JPA 표준보다 JPA 구현체에서 다양한 방법을 지원
CONNECT BY
JPQL을 사용할 수 없을 때 JPA가 SQL을 직접 사용할 수 있도록 제공하는 기능
네이티브 SQL vs JPQL
네이티브 SQL | JPQL |
---|---|
개발자가 SQL 직접 정의 | JPA가 SQL 생성 |
수동 모드 | 자동 모드 |
네이티브 SQL vs JDBC API 직접 사용
네이티브 SQL | JDBC API |
---|---|
엔티티 조회 가능 | 데이터의 나열 조회 |
JPA가 지원하는 영속성 컨텍스트 기능 그대로 사용 가능 |
//결과 타입 정의(엔티티 조회)
public Query createNativeQuery(String sqlString, Class resultClass);
//결과 타입 정의 불가능 할 때(값 조회)
public Query createNativeQuery(String sqlString);
//결과 매핑 사용
public Query createNativeQuery(String sqlString, String resultSetMapping);
em.createNativeQuery(SQL, 결과 클래스)
//SQL 정의
String sql = "SELECT ID, AGE, NAME, TEAM_ID FROM MEMBER WHERE AGE > ?";
Query nativeQuery = em.createNativeQuery(sql, Member.class)
.setParameter(1, 20);
List<Member> resultList = netiveQuery.getResultList();
📖 참고
- JPA는 공식적으로 네이티브 SQL에서 위치 기반 파라미터만 지원
- 하이버네이트는 네이티브 SQL에 이름 기반 사용 가능
📖 참고
em.createNativeQuery()
호출 시 타입 정보 지정에도 불구하고TypeQuery
가 아닌Query
리턴
→ JPA1.0에서 API 규약이 정의되었기 때문
//SQL 정의
String sql = "SELECT ID, AGE, NAME, TEAM_ID FROM MEMBER WHERE AGE > ?";
Query nativeQuery = em.createNativeQuery(sql)
.setParameter(1, 10);
List<Object[]> resultList = netiveQuery.getResultList();
for (Object[] row : resultList) {
System.out.println("id = " + row[0]);
System.out.println("age = " + row[1]);
System.out.println("name = " + row[2]);
System.out.println("team_id = " + row[3]);
}
@SqlResultMapping
: 네이티브 SQL 쿼리 결과의 매핑 지정@Target(value=TYPE)
@Retention(value=RUNTIME)
public @interface SqlResultSetMapping {...}
//SQL 정의
String sql =
"SELECT M.ID, AGE, NAME, TEAM_ID, I.ORDER_COUNT " +
"FROM MEMBER M " +
"LEFT JOIN " +
" (SELECT IM.ID, COUNT(*) AS ORDER_COUNT " +
" FROM ORDERS O, MEMBER IM " +
" WHERE O.MEMBER_ID = IM.ID) I " +
"ON M.ID = I.ID";
//두 번째 파라미터에 결과 매핑 정보의 이름 사용
Query nativeQuery = em.createNativeQuery(sql, "memberWithOrderCount");
List<Object[]> resultList = nativeQuery.getResultList();
for(Object[] row : resultList) {
Member member = (Member) row[0];
BigInteger orderCount = (BigInteger)row[1];
System.out.println("member = " + member);
System.out.println("orderCount = " + orderCount);
}
@Entity
@SqlResultSetMapping( //회원 엔티티와 ORDER_COUNT 컬럼 매핑
name = "memberWithOrderCount",
entities = {@EntityResult(entityClass = Member.class)},
columns = {@ColumnResult(name = "ORDER_COUNT")}
)
public class Member {...}
ID
, AGE
, NAME
, TEAM_ID
→ Member
엔티티ORDER_COUNT
→ 값entities
, columns
→ 여러 엔티티와 여러 컬럼 매핑 가능Query q = em.createNativeQuery(
"SELECT o.id AS order_id, " +
"o.quantity AS order_quantity, " +
"o.item AS order_item, " +
"i.name AS item_name, " +
"FROM Order o, Item i " +
"WHERE (order_quentity > 25) AND (order_item = i.id)", "OrderResults";
@SqlResultSetMapping(
name="OrderResults",
entities={
@EntityResult(
entityClass=com.acme.Order.class,
fields={
@FieldResult(name="id", column="order_id"),
@FieldResult(name="quantity", column="order_quantity"),
@FieldResult(name="item", column="order_item")
}
)
},
columns={@ColumnResult(name="Item_name")}
)
@FieldResult
@Column
보다 우선순위@FieldResult
로 매핑해야 함@FieldResult
로 매핑@SqlResultSetMapping
속성속성 | 기능 |
---|---|
name | 결과 매핑 이름 |
entites | @EntityResult 를 사용하여 엔티티를 결과로 매핑 |
columns | @ColumnResult 를 사용하여 컬럼을 결과로 매핑 |
@EntityResult
속성속성 | 기능 |
---|---|
entityClass | 결과로 사용할 엔티티 클래스 지정 |
fields | @FieldResult 를 사용하여 결과 컬럼을 필드와 매핑 |
discriminatorColumn | 엔티티의 인스턴스 타입을 구분하는 필드(상속에서 사용) |
@FieldResult
속성속성 | 기능 |
---|---|
name | 결과를 받을 필드명 |
column | 결과 컬럼명 |
@ColumnResult
속성속성 | 기능 |
---|---|
name | 결과 컬럼명 |
@Entity
@NamedNativeQuery( //Named 네이티브 SQL 등록
name = "Member.memberSQL",
query = "SELECT ID, AGE, NAME, TEAM_ID FROM MEMBER WHERE AGE > ?",
resultClass = Member.class
)
public class Member {...}
TypedQuery<Member> nativeQuery =
em.createNamedQuery("Member.memberSQL", Member.class)
.setParameter(1, 20);
createNamedQuery
메소드 사용TypeQuery
사용 가능@Entity
@SqlResultSetMapping(
name = "memberWithOrderCount"
entities = {@EntityResult(entityClass = Member.class)},
columns = {@ColumnResult(name = "ORDER_COUNT")}
)
@NamedNativeQuery(
name = "Member.memberWithOrderCount",
query = "SELECT M.ID, AGE, NAME, TEAM_ID, I.ORDER_COUNT" +
"FROM MEMBER M " +
"LEFT JOIN" +
" (SELECT IM.ID, COUNT(*) AS ORDER_COUNT " +
" FROM ORDERS O, MEMBER IM " +
"ON M.ID = I.ID",
resultSetMapping = "memberWithOrderCount" //조회 결과 매핑 대상 지정
)
public class Member {...}
List<Object[]> resultList =
em.createNamedQuery("Member.memberWithOrderCount")
.getResultList();
@NamedNativeQuery
@NamedNativeQuery
속성속성 | 기능 |
---|---|
name | 네임드 쿼리 이름(필수) |
query | SQL 쿼리(필수) |
hints | ◾ 벤더 종속적인 힌트 ◾ JPA 구현체에 제공하는 힌트 |
resultClass | 결과 클래스 |
resultSetMapping | 결과 매핑 사용 |
@NamedNativeQueries({
@NamedNativeQuery(...),
@NamedNativeQuery(...)
})
<entity-mappings ...>
<named-native-query name="Member.memberWithOrderCountXml"
result-set-mapping="memberWithOrderCountResultMap">
<query><CDATA[
SELECT M.ID, AGE, NAME, TEAM_ID, I.ORDER_COUNT
FROM MEMBER M
LEFT JOIN
(SELECT IM.ID, COUNT(*) AS ORDER_COUNT
FROM ORDERS O, MEMBER IM
WHERE O.MEMBER_ID = IM.ID) I
ON M.ID = I.ID
]></query>
</named-native-query>
<sql-result-set-mapping name="memberWithOrderCountResultMap">
<entity-result entity-class="jpabook.domain.Member" />
<column-result name="ORDER_COUNT" />
</sql-result-set-mapping>
</entity-mapping>
<named-native-query>
를 먼저 정의한 후 <sql-result-set-mapping>
를 정의해야 함List<Object[]> resultList =
em.createNativeQuery("Member.memberWithOrderCount")
.getResultList();
📖 참고
- 네이티브 SQL을 사용하는 경우
- JPQL로 작성하기 어려운 복잡한 SQL 쿼리 작성 시
- SQL을 최적화하여 데이터베이스 성능을 향상시킬 때
- 위와 같은 케이스에 해당하는 경우, 쿼리들이 대체로 복잡하고 라인수가 많음
→ 어노테이션보다는 XML 사용 권장
- 자바 : 멀티 라인 문자열 지원 x
→ 라인 변경할 때마다 문자열을 더해야 함- XML : SQL 개발 도구에서 완성한 SQL을 바로 붙여넣을 수 있음
Query
, TypeQuery
(Named 네이티브 쿼리의 경우에만) 반환될 수 있으면 표준 JPQL을 사용하고, 기능이 부족하면 차선책으로 JPA 구현체가 제공하는 기능 사용
DELIMITER //
/*proc_multiply : 입력 값 두 배 증가*/
//파라미터 : 첫 번째 - 입력 값, 두 번째 - 결과 반환
CREATE PROCEDURE proc-multiply (INOUT inParam INT, INOUT outParam INT)
BEGIN
SET outParam = inParam * 2;
END //
StoredProcedureQuery spq =
em.createStoredProcedureQuery("proc_multiply"); //proc_multiply → 사용할 프로시저 이름
//프로시저에서 사용할 파라미터를 순서, 타입, 파라미터 모드 순으로 정의
spq.registerStoredProcedureParameter(1, Integer.class, ParameterMode.IN);
spq.registerStoredProcedureParameter(2, Integer.class, ParameterMode.OUT);
spq.setParameter(1, 100);
spq.execute();
Integer out = (Integer)spq.getOutputParameterValue(2);
System.out.println("out = " + out); //결과 = 200
public enum ParameterMode {
IN, //INPUT 파라미터
INOUT, //INPUT, OUTPUT 파라미터
OUT, //OUTPUT 파라미터
REF_CURSOR //CURSOR 파라미터
}
StoredProcedureQuery spq =
em.createStoredProcedureQuery("proc_multiply");
spq.registerStoredProcedureParameter("inParam", Integer.class, ParameterMode.IN);
spq.registerStoredProcedureParameter("outParam", Integer.class, ParameterMode.OUT);
spq.setParameter("inParam", 100);
spq.execute();
Integer out = (Integer)spq.getOutputParameterValue("outPram");
System.out.println("out = " + out); //결과 = 200
💡 Named 스토어드 프로시저
스토어드 프로시저 쿼리에 이름을 부여해서 사용하는 것
//@NamedStoredProcedureQueries //둘 이상 정의 시
@NamedStoredProcedureQuery( //정의
name = "multiply", //이름 부여
procedureName = "proc_multiply", //실제 호출할 프로시저 이름
parameters = {
@StoredProcedureParameter( //파라미터 정보 정의
name = "inParam",
mode = ParameterMode.IN,
type = Integer.class
),
@StoredProcedureParameter(
name = "outParam",
mode = ParameterMode.OUT,
type = Integer.class
)
}
)
@Entity
public class Member {...}
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
version="2.1">
<named-stored-procedure-query name="multiply"
procedure-name="proc_multiply>
<parameter name="inParam" mode="IN" class="java.lang.Integer" />
<parameter name="outParam" mode="OUT" class="java.lang.Integer" />
</named-stored-procedure-query>
</entity-mappings>
StoredProcedureQuery spq =
em.createNamedStoredProcedureQuery("multiply");
spq.setParameter("inParam", 100);
spq.execute();
Integer out = (Integer) spq.getOutputParamterValue("outParam");
System.out.println("out = " + out);
em.createNamedStoredProcedureQuery()
메소드에 등록한 Named 스토어드 프로시저 이름을 파라미터로 사용하여 조회 가능📚 참고