[스프링 JPA] - JPQL/네이티브 SQL/QueryDSL 등

sonnng·2023년 10월 13일
0

Spring

목록 보기
18/41

목차

  1. JPQL
  2. JPA Criteria
  3. QueryDSL
  4. 네이티브 SQL



1. JPQL

  • 가장 단순한 조회 방법
  • JPA를 사용하면 엔티티 객체를 중심으로 개발
  • 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색
  • SQL과 문법 유사, SELECT, FROM, WHERE, GROUP BY,
    HAVING, JOIN 지원
  • JPQL을 한마디로 정의하면 객체 지향 SQL
  • [단점] 동적 쿼리 만들기 어려움
List<Member> result  = em.createQuery(
        "select m From Member m where m.username like '%kim%",
                Member.class)
        .getResultList();
//member 엔티티 자체를 조회해오라는 의미
//단점 :  동적쿼리를 만들기 어렵다.
//String sqlString = "select m From Member m" + String where = "where m.username like '%kim%' 이렇게 하기 어려움



2. Criteria

  • 문자가 아닌 자바코드로 JPQL을 작성할 수 있음
  • JPQL 빌더 역할
  • 컴파일 시점에 문법 오류를 찾을 수 있음
  • Criteria 대신에 QueryDSL 사용 권장
  • [단점] 너무 복잡하고 실용성이 없다.
//2.Criteria(망한 스펙, 실무에서 사용하지 않는 스펙)
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);
//Member에서
Root<Member> m = query.from(Member.class);
//m을 select, m의 username이 kim인 것을 추출
CriteriaQuery<Member> cq = query.select(m).where(cb.equal(m.get("username"), "kim"));

List<Member> resultList = em.createQuery(cq).getResultList();

//장점 : 동적쿼리가 가능/sql 작성을 올바르게 작성해주도록 컴파일시점에 오타를 잡아준다.
//단점 : sql 스럽지 않다. 실무에서는 안쓴다. 유지보수가 어렵다.



3. QueryDSL(실무 사용 권장)

  • 문자가 아닌 자바코드로 JPQL을 작성할 수 있음
  • JPQL 빌더 역할
  • 컴파일 시점에 문법 오류를 찾을 수 있음
  • [장점] 동적쿼리 작성 편리함/단순하고 쉬움



4. 네이티브 SQL

  • JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능 사용할 때 사용된다.
    예) 오라클 CONNECT BY, 특정 DB만 사용하는 SQL 힌트
//4. 네이티브 SQL(잘 안씀)
em.createNativeQuery(
        "select MEMBER_ID, city, street. zipcode, USERNAME from MEMBER"
).getResultList();



5. JDBC 직접 사용, SpringJdbcTemplate 등

  • JPA를 사용하면서 JDBC 커넥션을 직접 사용하거나, 스프링 JdbcTemplate, 마이바티스등을 함께 사용 가능
  • 단 영속성 컨텍스트를 적절한 시점에 강제로 플러시 필요

    예) JPA를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트
    수동 플러시

//5. JDBC
//조심해야할 점 : 영속성 컨텍스트는 플러시가 되어야 db에 데이터가 있다.

//(AUTO전략)flush되는 시점 : commit되기 전, query날아가기 전에 동작
// => flush되면 DB에 적용된다.

//예
Member member =  new Member();
member.setUsername("member1");
em.persist(member);

//이 시점에 em.flush()가 자동으로 날아가 DB에 insert해준다.

//1번의 경우
List<Member> resultList1 = em.createNativeQuery(
        "select MEMBER_ID, city, street, zipcode FROM MEMBER"
).getResultList();

// 반면 JDBC는 jpa가 아니므로 다음과 같이 작성해준다면
//em.flush() x -> DB에 값이 없다. , 오류 발생
//수동으로 em.flush() 작서해야 한다.

//2번의 경우
//dbconn.executeQuery("select * from member");



1. JPQL 소개

  • JPQL은 객체지향 쿼리 언어다.따라서 테이블을 대상으로 쿼리를 날리는 것이 아니라 엔티티 객체를 대상으로 쿼리한다

  • JPQL은 결국 SQL로 변환된다

  • 엔티티와 속성은 대소문자 구분O (Member, age)

  • JPQL 키워드는 대소문자 구분X (SELECT, FROM, where)

  • 엔티티 이름 사용, 테이블 이름이 아님(Member)

  • 별칭은 필수(m) (as는 생략가능)

예) select m from Member as m where m.age > 18




1) [TypeQuery, Query]

  • TypeQuery: 반환 타입이 명확할 때 사용
  • Query: 반환 타입이 명확하지 않을 때 사용
Member member =  new Member();
member.setUsername("member1");
em.persist(member);

//반환 타입이 명확할때 사용 :: TypedQuery<제네릭>
TypedQuery<Member> query = em.createQuery("" +
        "SELECT m FROM Member m", Member.class);
TypedQuery<String> query1 = em.createQuery(
        "SELECT m.username FROM Member m", String.class
);

//반환타입이 명확하지 않을 때 사용 :: Query
Query query2 = em.createQuery(
        "SELECT m.username, m.age FROM Member m"
);



2) [결과조회 API]

  • 1) query.getResultList(): 결과가 하나 이상일 때, 리스트 반환
    • 결과가 없으면 빈 리스트 반환
  • 2) query.getSingleResult(): 결과가 정확히 하나, 단일 객체 반환
    • 결과가 없으면: javax.persistence.NoResultException
    • 둘 이상이면: javax.persistence.NonUniqueResultException



3) [파라미터 바인딩]

 Member member =  new Member();
member.setUsername("member1");
em.persist(member);

//반환 타입이 명확할때 사용 :: TypedQuery<제네릭>
TypedQuery<Member> query = em.createQuery(
        "SELECT m FROM Member m WHERE m.username = :username", Member.class);
//파라미터 바인딩- 이름기준일때
query.setParameter("username", "member1");
Member singleResult = query.getSingleResult();
System.out.println("singleResult = "+singleResult);

//파라미터 바인딩-위치기준(추천 X)

tx.commit();



4) [프로젝션]

  • SELECT 절에 조회할 대상을 지정하는 것
  • 프로젝션 대상: 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자등 기본 데이터 타입)
Member member =  new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);

em.flush();
em.clear();

(1) 엔티티 프로젝션
 //1. 엔티티 프로젝션
List<Member> result = em.createQuery(
        "SELECT m FROM Member m", Member.class
).getResultList();

Member findMember = result.get(0);
findMember.setAge(20);
//UPDATE쿼리 발생..JPA에 의해 관리됨을 알 수 있다.

(2) 엔티티 프로젝션
 //2. 엔티티 프로젝션
List<Team> result2 = em.createQuery(
        "SELECT t FROM Member m JOIN m.team t", Team.class
).getResultList();
//Member객체에 Team이 포함되어있어도 이렇게 해서 team과 조인하도록 작성 추천
//쿼리 날아가는 것도 확인해보면 join됨을 알 수 있다.

(3) 임베디드 타입 프로젝션
//3. 임베디드 타입 프로젝션
em.createQuery(
        "SELECT o.address FROM Order o", Address.class
).getResultList();

(4) 스칼라 타입 프로젝션

SELECT m.username, m.age FROM Member m





[프로젝션 - 여러값을 조회하는 경우 총 3가지]

예) SELECT m.username, m.age FROM Member m

(1) Query 타입으로 조회

//4. 스칼라 타입 프로젝션
//(1) Query타입으로 조회하는 경우
List resultList = em.createQuery(
        "select m.username, m.age from Member m"
).getResultList();

Object o = resultList.get(0);
Object[] result3 = (Object[]) o;
System.out.println("username = "+result3[0]);
System.out.println("age = "+result3[1]);


(2) Object[] 타입으로 조회

 //(2) Object[] 타입으로 조회하는 경우
List <Object[]> resultList1 = em.createQuery(
        "select m.username, m.age from Member m"
).getResultList();

Object[] result4 = resultList1.get(0);
System.out.println("username = "+result4[0]);
System.out.println("age = "+result4[1]);


(3) new 명령어로 조회

 //(3) new 명령어로 조회하는 경우
List<MemberDTO> resultList2 = em.createQuery(
        "select new hellojpa.MemberDTO(m.username, m.age) from Member m"
        , MemberDTO.class
).getResultList();

MemberDTO memberDTO = resultList2.get(0);
System.out.println("username :: "+memberDTO.getUsername());
  • 패키지 명을 포함한 전체 클래스 명 입력
  • 순서와 타입이 일치하는 생성자 필요


5) [페이징 API]

  • JPA는 페이징을 다음 두 API로 추상화

1) setFirstResult(int startPosition) : 조회 시작 위치
(0부터 시작)

2) setMaxResults(int maxResult) : 조회할 데이터 수

for(int i=0;i<100;i++) {
    Member member = new Member();
    member.setUsername("member"+(i+1));
    member.setAge(i);
    em.persist(member);
}

em.flush();
em.clear();


List<Member> resultList2 = em.createQuery(
        "select m from Member m order by m.age desc"
        , Member.class
).setFirstResult(0) //0부터 시작해서
.setMaxResults(10) //10개까지 가져온다.
.getResultList();

System.out.println("size : "+ resultList2.size());
for(Member m : resultList2){
System.out.println(m);
}

tx.commit();


이렇게 하면 다음의 결과를 얻게 된다.

Hibernate: 
    /* select
        m 
    from
        Member m 
    order by
        m.age desc */ select
            member0_.id as id1_0_,
            member0_.age as age2_0_,
            member0_.TEAM_ID as TEAM_ID4_0_,
            member0_.username as username3_0_ 
        from
            Member member0_ 
        order by
            member0_.age desc limit ?
size : 10
Member{id=100, username='member100', age=99}
Member{id=99, username='member99', age=98}
Member{id=98, username='member98', age=97}
Member{id=97, username='member97', age=96}
Member{id=96, username='member96', age=95}
Member{id=95, username='member95', age=94}
Member{id=94, username='member94', age=93}
Member{id=93, username='member93', age=92}
Member{id=92, username='member92', age=91}
Member{id=91, username='member91', age=90}



6) [조인]

1) 내부조인

String query = "select m from Member m inner join m.team t";

2) 외부조인

String query2 = "select m from Member m left outer join m.team t";

3) 세타조인

String query3 = "select m from Member m, Team t where m.username = t.name";




7) [조인-ON절을 활용한 조인]

1) 조인대상 필터링

String query4 = "select m from Member m left join m.team t on t.name = 'teamA'";

2) 연관관계 없는 엔티티 외부 조인

String query5 = "select m from Member m left join Team t on m.username = t.name";




8) [서브쿼리]

• [NOT] EXISTS (subquery): 서브쿼리에 결과가 존재하면 참

예) select m from Member m

where exists (select t from m.team t where t.name = ‘팀A')


• {ALL | ANY | SOME} (subquery)

• ALL 모두 만족하면 참

예) 전체 상품 각각의 재고보다 주문량이 많은 주문들

select o from Order o

where o.orderAmount > ALL (select p.stockAmount from Product p)

• ANY, SOME: 같은 의미, 조건을 하나라도 만족하면 참

예) 어떤 팀이든 팀에 소속된 회원

select m from Member m

where m.team = ANY (select t from Team t)

• [NOT] IN (subquery): 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참

  • JPA는 SELECT, WHERE, HAVING 절에서만 서브 쿼리 사용 가능
  • FROM 절의 서브 쿼리는 현재 JPQL에서 불가능(조인으로 풀어서 해결)



9) [JPQL 타입 표현]

타입 표현은 아래와 같이 JPQL문에 함께 작성할 수 있다.

• 문자: ‘HELLO’, ‘She’’s’

• 숫자: 10L(Long), 10D(Double), 10F(Float)

• Boolean: TRUE, FALSE

• ENUM: jpabook.MemberType.Admin (패키지명 포함)

Team team = new Team();
team.setName("teamA");
em.persist(team);

Member member = new Member();
member.setUsername("member1");
member.setAge(10);
member.setMemberType(MemberType.ADMIN);

member.changeTeam(team);
em.persist(member);


em.flush();
em.clear();
//파라미터 바인딩하지 않은 경우
String query = "select m.username, 'HELLO', true from Member m "+
        "where m.type = hellojpa.MemberType.USER";

//파라미터 바인딩을 한 경우
String query2 = "select m.username, 'HELLO', true from Member m "+
        "where m.type = :usertype";

List<Object[]> result = em.createQuery(query)
        .getResultList();

List<Object[]> result2 = em.createQuery(query2)
        .setParameter("usertype", MemberType.ADMIN)
        .getResultList();

for(Object[] objects: result){
System.out.println("objects = "+objects[0]);
System.out.println("objects = "+objects[1]);
System.out.println("objects = "+objects[2]);
}
  • Enum Type은 패키지명에 따라 = 비교로 where문에 작성
  • Enum Type은 @Enumerated(EnumType.STRING)으로 문자열 전략을 사용
@Enumerated(EnumType.STRING)
private MemberType type;
package hellojpa;

public enum MemberType {
    ADMIN, USER
}



10) [JPQL 기타]

  • SQL과 문법이 같은 식
  • EXISTS, IN
  • AND, OR, NOT
  • =, >, >=, <, <=, <>
  • BETWEEN, LIKE, IS NULL
    모두 사용가능하다.



11) [조건식-CASE 식]

1) 기본 CASE식
select case when m.age <= 10 then '학생요금' when m.age >= 60 then '경로요금' else '일반요금' end from Member m

2) 단순 CASE식

select case t.name when '팀A' then '인센티브110%' when '팀B' then '인센티브120%' else '인센티브105%' end from Team t



  • COALESCE: 하나씩 조회해서 null이 아니면 반환
  • NULLIF: 두 값이 같으면 null 반환, 다르면 첫번째 값 반환
Team team = new Team();
team.setName("teamA");
em.persist(team);

Member member = new Member();
member.setUsername("관리자");
member.setAge(10);
member.setMemberType(MemberType.ADMIN);

member.changeTeam(team);
em.persist(member);

em.flush();
em.clear();

String query = "select NULLIF(m.username, '관리자') from Member m";
List<String> resultList = em.createQuery(query, String.class).getResultList();

for(String s : resultList){
System.out.println(s);
}

null이 출력된다.

0개의 댓글