[자바 ORM 표준 JPA 프로그래밍 - 기본편] 10. 객체지향 쿼리 언어1 - 기본 문법

Turtle·2024년 6월 21일
0
post-thumbnail

🙄개요

  • ✔️JPA는 다양한 쿼리 방법을 지원
    • JPQL
    • JPA Criteria
    • Query DSL
    • 네이티브 SQL
    • JDBC API 직접 사용, MyBatis, SpringJdbcTemplate 함께 사용

🙄JPQL

  • JPA를 사용하면 엔티티 객체를 중심으로 개발
  • 문제는 검색 쿼리
  • 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색
  • 모든 DB 데이터를 엔티티 객체로 변환해서 검색하는 것은 불가능
  • 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL이 필요
  • JPA는 SQL을 추상화한 JPQL이라는 객체지향 쿼리 언어 제공
  • SQL과 문법 유사, SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 지원
  • ❗JPQL은 엔티티 객체를 대상으로 쿼리 → 결과적으로는 JPQL이 SQL로 변환되어 DB Table을 대상으로 쿼리를 날리는 것
  • ❗SQL은 데이터베이스 테이블을 대상으로 쿼리
public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("shop");
        EntityManager em = emf.createEntityManager();
        EntityTransaction et = em.getTransaction();

        et.begin();

        try {
        	//////////////////////////////////////////////////////////////////
            List<Member> members = em.createQuery("SELECT m FROM Member m WHERE m.name like '%hello%'", Member.class)
            .getResultList();
        	//////////////////////////////////////////////////////////////////
            
            for (Member member : members) {
                System.out.println("member = " + member);
            }

            et.commit();
        } catch (Exception e) {
            et.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

실행 결과

Hibernate: 
    /* SELECT
        m 
    FROM
        Member m 
    WHERE
        m.name like '%hello%' */ select
            m1_0.MEMBER_ID,
            m1_0.city,
            m1_0.street,
            m1_0.zipcode,
            m1_0.USERNAME,
            m1_0.TEAM_ID,
            m1_0.endDate,
            m1_0.startDate 
        from
            MEMBERS m1_0 
        where
            m1_0.USERNAME like '%hello%' escape ''
  • ✔️Criteria 소개
    • Criteria는 JPQL을 생성하는 빌더 클래스다. Criteria의 장점은 문자가 아닌 query.setlect(m).where(...)와 같이 프로그래밍 코드로 JPQL을 작성할 수 있다는 점이다.
    • 컴파일 시점에 오류를 발견할 수 있고 IDE를 사용하면 코드 자동 완성을 지원한다.
    • 동적 쿼리를 작성하기 편하다.
    • Criteria가 가진 장점이 많으나 모든 장점을 상쇄할 정도로 복잡하고 장황하다. 따라서 사용하기 불편한 것은 물론이고 Criteria로 작성한 코드도 한 눈에 들어오지 않는다는 단점이 있다.
public class JpaMain {

    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction transaction = em.getTransaction();
        transaction.begin();

        try {
            CriteriaBuilder cb = em.getCriteriaBuilder();
            CriteriaQuery<Member> query = cb.createQuery(Member.class);
            Root<Member> m = query.from(Member.class);
            CriteriaQuery<Member> member = query.select(m).where(cb.equal(m.get("name"), "kim"));
            List<Member> resultList = em.createQuery(member).getResultList();

            transaction.commit();
        } catch (Exception e) {
            transaction.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

실행 결과

Hibernate: 
    /* <criteria> */ select
        m1_0.MEMBER_ID,
        m1_0.city,
        m1_0.street,
        m1_0.zipcode,
        m1_0.name,
        m1_0.TEAM_ID,
        m1_0.endDate,
        m1_0.startDate 
    from
        MEMBERS m1_0 
    where
        m1_0.name=?
  • ✔️QueryDSL 소개
    • 문자가 아닌 자바 코드로 JPQL을 작성할 수 있음
    • 컴파일 시점에 문법 오류 확인 가능
    • 동적 쿼리 작성 편리함
    • 단순하고 쉬움
    • 실무 사용 권장
  • ✔️네이티브 SQL 소개
    • JPA가 제공하는 SQL을 직접 사용하는 기능
    • JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능
public class JpaMain {

    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction transaction = em.getTransaction();
        transaction.begin();

        try {
            List resultList = em.createNativeQuery("select MEMBER_ID, city, street, zipcode, name from MEMBER", Member.class).getResultList();
            for (Object object : resultList) {
                System.out.println("object = " + object);
            }

            transaction.commit();
        } catch (Exception e) {
            transaction.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

실행 결과

Hibernate: 
    /* dynamic native SQL query */ select
        MEMBER_ID,
        city,
        street,
        zipcode,
        name 
    from
        MEMBER
  • ✔️JDBC 직접 사용, SpringJdbcTemplate 등
    • JPA를 사용하면서 JDBC 커넥션을 직접 사용하거나 스프링 JdbcTemplate, 마이바티스 등을 함께 사용 가능
    • 영속성 컨텍스트를 적절한 시점에 강제로 플러시

🙄기본 문법과 쿼리 API

  • ✔️JPQL(Java Persistence Query Language) 소개
    • JPQL은 객체지향 쿼리 언어다.
    • 테이블을 대상으로 쿼리하는 것이 아니라 엔티티 객체를 대상으로 쿼리한다.
    • SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.
    • ❗JPQL은 결국 SQL로 변환이 된다.
  • ✔️JPQL 문법
    • select m from Member as m where m.age >= 18
    • 엔티티와 속성은 대소문자를 구분한다.
    • JPQL 키워드는 대소문자 구분하지 않는다.
    • 엔티티 이름 사용, 테이블 이름이 아님(Member)
    • 별칭은 필수(m) (as는 생략 가능)
  • ✔️TypeQuery, Query
    • TypeQuery : 반환 타입이 명확할 때 사용
    • Query : 반환 타입이 명확하지 않을 때 사용
public class JpaMain {

    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction transaction = em.getTransaction();
        transaction.begin();

        try {
            Member member = new Member();
            member.setName("멤버");
            em.persist(member);

			//////////////////////////////////////////////////////////////////
            TypedQuery<Member> query1 = em.createQuery("select m from Member as m", Member.class);
            TypedQuery<String> query2 = em.createQuery("select m.name from Member as m", String.class);
            Query query = em.createQuery("select m.name, m.age from Member as m");
			//////////////////////////////////////////////////////////////////

            transaction.commit();
        } catch (Exception e) {
            transaction.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}
  • ✔️결과 조회
    • query.getResultList() : 결과가 하나 이상일 때, 리스트 반환
      • 결과가 없으면 빈 리스트 반환
    • query.getSingleResult() : 결과가 정확히 하나, 단일 객체 반환
      • 결과가 없으면 NoResultException
      • 둘 이상이면 NonUniqueResultException
```java
public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpql");
        EntityManager em = emf.createEntityManager();
        EntityTransaction et = em.getTransaction();
        et.begin();

        try {
            Member member = new Member();
            member.setUsername("Member");
            member.setAge(10);
            em.persist(member);

			////////////////////////////////////////////////////////////////
            Member singleResult = em.createQuery("select m from Member as m where m.username = :username", Member.class)
                    .setParameter("username", "Member")
                    .getSingleResult(); 	                   							
            ////////////////////////////////////////////////////////////////
            System.out.println("singleResult = " + singleResult);

            et.commit();
        } catch (Exception e) {
            et.rollback();
            e.printStackTrace();
        } finally {
            em.close();
        }
        emf.close();
    }
}
  • ✔️파라미터 바인딩 - 이름 기준, 위치 기준
    • 위치 기준의 경우 데이터의 삽입/삭제 연산으로 인해 변경될 가능성이 높으니 사용 자제
public class JpaMain {

    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction transaction = em.getTransaction();
        transaction.begin();

        try {
            Member member = new Member();
            member.setName("멤버");
            em.persist(member);

            Member singleResult = em.createQuery("select m from Member as m where m.name = :name", Member.class)
                    .setParameter("name", "멤버")
                    .getSingleResult();
            System.out.println("singleResult = " + singleResult.getName());

            transaction.commit();
        } catch (Exception e) {
            transaction.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

🙄프로젝션

  • ✔️프로젝션
    • SELECT절에 조회할 대상을 지정하는 것
    • 프로젝션 대상 : 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자 등 기본 데이터 타입)
    • SELECT m FROM MEMBER m → 엔티티 프로젝션
    • SELECT m.team FROM Member m → 엔티티 프로젝션
    • SELECT m.address FROM Member m → 임베디드 타입 프로젝션
    • SELECT m.name, m.age FROM Member m → 스칼라 타입 프로젝션
    • DISTINCT로 중복 제거 가능
  • ✔️엔티티 프로젝션
    • 조회한 엔티티는 모두 영속성 컨텍스트에서 관리된다.
List<Member> list = em.createQuery("select m from Member as m", Member.class).getResultList();
Member findMember = list.get(0);
findMember.setAge(20);				// UPDATE 쿼리 호출

실행 결과

Hibernate: 
    /* insert for
        jpql.Member */insert 
    into
        Member (age, TEAM_ID, username, id) 
    values
        (?, ?, ?, ?)
Hibernate: 
    /* select
        m 
    from
        Member as m */ select
            m1_0.id,
            m1_0.age,
            m1_0.TEAM_ID,
            m1_0.username 
        from
            Member m1_0
Hibernate: 
    /* update
        for jpql.Member */update Member 
    set
        age=?,
        TEAM_ID=?,
        username=? 
    where
        id=?
  • ✔️임베디드 프로젝션
List<Member> list = em.createQuery("select m.address from Member m", Member.class).getResultList();
  • ✔️스칼라 프로젝션 - 여러 값 조회(Query 타입으로 조회)
    • 엔티티 대상으로 조회하면 편리하지만 그 중에서도 필요한 데이터만 선택해서 조회할 때도 있다.
    • 프로젝션에 여러 값을 선택하면 TypeQuery를 사용할 수 없고 대신 반환 타입이 정확하지 않을 때 사용하는 Query를 사용한다.
// 반환 타입이 age(숫자), username(문자)이기에 명확한 반환 타입이 아니므로 Query를 사용
Query query = em.createQuery("select m.age, m.username from Member m");
List resultList = query.getResultList();

Iterator iterator = resultList.iterator();
while (iterator.hasNext()) {
	Object[] row = (Object[]) iterator.next();
	int age = (int) row[0];
    String username = (String) row[1];
    System.out.println(username + " : " + age);
}
  • ✔️스칼라 프로젝션 - 여러 값 조회(Object[] 타입으로 조회)
List<Object[]> resultList = em.createQuery("select m.age, m.username from Member m").getResultList();
for (Object[] objects : resultList) {
	int age = (Integer) objects[0];
	String username = (String) objects[1];
	System.out.println(username + " : " + age);
}
  • ✔️스칼라 프로젝션 - 여러 값 조회(new 명령어로 조회)
    • 단순 값을 DTO로 바로 조회
    • 패키지 명을 포함한 전체 클래스명을 입력
    • 순서와 타입이 일치하는 생성자를 필요로 함
List<MemberDTO> resultList = em.createQuery("select new jpql.MemberDTO(m.username, m.age) from Member as m", MemberDTO.class).getResultList();
MemberDTO memberDTO = resultList.get(0);

🙄페이징

  • ✔️페이징 API
    • JPA는 페이징을 다음 두 API로 추상화
    • setFirstResult(int startPosition) : 조회 시작 위치(0부터 시작)
    • setMaxResults(int maxResult) : 조회할 데이터 수
public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpql");
        EntityManager em = emf.createEntityManager();
        EntityTransaction et = em.getTransaction();
        et.begin();

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

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

			///////////////////////////////////////////////////////////////////
            List<Member> list = em.createQuery("select m from Member as m order by m.age desc", Member.class)
                    .setFirstResult(1)
                    .setMaxResults(10)
                    .getResultList();
			///////////////////////////////////////////////////////////////////

            for (Member member : list) {
                System.out.println("member = " + member);
            }

            et.commit();
        } catch (Exception e) {
            et.rollback();
            e.printStackTrace();
        } finally {
            em.close();
        }
        emf.close();
    }
}

🙄조인(Join)

  • ✔️내부 조인(Inner Join)
public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpql");
        EntityManager em = emf.createEntityManager();
        EntityTransaction et = em.getTransaction();
        et.begin();

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

            Member member1 = new Member();
            member1.setUsername("member1");
            member1.setAge(10);

            Member member2 = new Member();
            member2.setUsername("member2");
            member2.setAge(20);

            member1.changeTeam(team);
            member2.changeTeam(team);

            em.persist(member1);
            em.persist(member2);

            em.flush();
            em.clear();
			
            //////////////////////////////////////////////////////////////////
	 		// 내부 조인
            // m.team을 별칭 t로 설정
			String query = "select m from Member as m inner join m.team t";
            List<Member> list = em.createQuery(query, Member.class)
                    .getResultList();
            //////////////////////////////////////////////////////////////////

            et.commit();
        } catch (Exception e) {
            et.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

실행 결과

Hibernate: 
    /* select
        m 
    from
        Member as m 
    inner join
        m.team t */ select
            m1_0.id,
            m1_0.age,
            m1_0.TEAM_ID,
            m1_0.username 
        from
            Member m1_0 
        join
            Team t1_0 
                on t1_0.id=m1_0.TEAM_ID
  • ✔️외부 조인(Outer Join)
public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpql");
        EntityManager em = emf.createEntityManager();
        EntityTransaction et = em.getTransaction();
        et.begin();

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

            Member member1 = new Member();
            member1.setUsername("member1");
            member1.setAge(10);

            Member member2 = new Member();
            member2.setUsername("member2");
            member2.setAge(20);

            member1.changeTeam(team);
            member2.changeTeam(team);

            em.persist(member1);
            em.persist(member2);

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

            String query = "select m from Member as m left join m.team t";
            List<Member> list = em.createQuery(query, Member.class)
                    .getResultList();
            et.commit();
        } catch (Exception e) {
            et.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

실행 결과

Hibernate: 
    /* select
        m 
    from
        Member as m 
    left join
        m.team t */ select
            m1_0.id,
            m1_0.age,
            m1_0.TEAM_ID,
            m1_0.username 
        from
            Member m1_0
  • ✔️세타 조인
public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpql");
        EntityManager em = emf.createEntityManager();
        EntityTransaction et = em.getTransaction();
        et.begin();

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

            Member member1 = new Member();
            member1.setUsername("member1");
            member1.setAge(10);

            Member member2 = new Member();
            member2.setUsername("member2");
            member2.setAge(20);

            member1.changeTeam(team);
            member2.changeTeam(team);

            em.persist(member1);
            em.persist(member2);

            em.flush();
            em.clear();
            
			/////////////////////////////////////////////////////////////
            String query = "select m from Member m, Team t where m.username = t.name";
            List<Member> list = em.createQuery(query, Member.class)
                    .getResultList();
			/////////////////////////////////////////////////////////////

			et.commit();
        } catch (Exception e) {
            et.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

실행 결과

Hibernate: 
    /* select
        m 
    from
        Member m,
        Team t 
    where
        m.username = t.name */ select
            m1_0.id,
            m1_0.age,
            m1_0.TEAM_ID,
            m1_0.username 
        from
            Member m1_0,
            Team t1_0 
        where
            m1_0.username=t1_0.name
  • ✔️조인 - ON절
    • 조인 대상 필터링
    • 연관관계 없는 엔티티 외부 조인

Ex. 회원과 팀을 조회하면서 회원과 연관된 팀을 조회(팀 이름이 A인 것만)

select m, t from Member m left join m.team t on t.name = 'A'

Ex. 회원의 이름과 팀의 이름이 같은 대상 외부 조인

select m, t from Member m LEFT JOIN Team t on m.username = t.name

🙄서브 쿼리

  • ✔️서브쿼리 지원 함수
    • NOT EXISTS (Subquery)
      • 서브쿼리 결과가 존재하는가?
    • ALL, ANY, SOME (Subquery)
      • 비교 연산자와 같이 사용
      • ALL : 조건을 모두 만족하면 참
      • ANY 혹은 SOME : 조건을 하나라도 만족하면 참
    • NOT IN (Subquery)
      • 서브쿼리 결과 중 하나라도 같은 것이 있으면 참
  • ✔️JPA 서브 쿼리 한계
    • JPA는 WHERE, HAVING절에서만 서브 쿼리 사용 가능
    • SELECT절도 사용 가능
    • FROM절의 서브 쿼리는 현재 JPQL에서 사용 불가능
      • JOIN으로 풀 수 있으면 풀어서 해결

🙄JPQL 타입 표현과 기타식

  • ✔️JPQL 타입 표현
    • 문자 : 'HELLO', 'She''s'
    • 숫자 : 10L(Long), 10D(Double), 10F(Float)
    • Boolean : TRUE, FALSE
    • ENUM : jpabook.MemberType.Admin(패키지명 포함)
    • 엔티티 타입 : TYPE(m) = Member (상속 관계에서 사용)
  • ✔️JPQL 기타
    • SQL과 문법이 같은 식
    • EXISTS, IN
    • AND, OR, NOT
    • 부호 연산자
    • BETWEEN, LIKE, IS NULL

🙄조건식(CASE 등등)

  • ✔️조건식 - 기본 CASE

  • ✔️조건식 - 단순 CASE

  • ✔️조건식 - CASE
public class JpaMain {

    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction transaction = em.getTransaction();
        transaction.begin();

        try {
            Member member = new Member();
            member.setName("멤버");
            member.setAge(68);
            member.setMemberType(MemberType.USER);
            em.persist(member);

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

			//////////////////////////////////////////////////////
            String query = 
                    "select " +
                        "case when m.age <= 10 then '학생요금'" +
                        "     when m.age >= 60 then '경로요금'" +
                        "     else '일반요금'" +
                        "end " +
                    "from Member as m";

            List<String> resultList = em.createQuery(query, String.class)
            						.getResultList();
			//////////////////////////////////////////////////////

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

            transaction.commit();
        } catch (Exception e) {
            transaction.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}
  • ✔️조건식 - COALESCE(하나씩 조회해서 null이 아니면 반환)
public class JpaMain {

    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction transaction = em.getTransaction();
        transaction.begin();

        try {
            Member member = new Member();
            member.setName(null);
            member.setAge(68);
            member.setMemberType(MemberType.USER);
            em.persist(member);

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

			//////////////////////////////////////////////////////
            String query = "select coalesce(m.name, '이름 없는 회원') from Member as m";
            List<String> result = em.createQuery(query, String.class)
            					.getResultList();
			//////////////////////////////////////////////////////
            
            for (String s : result) {
                System.out.println("s = " + s);
            }

            transaction.commit();
        } catch (Exception e) {
            transaction.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}
  • ✔️조건식 - NULLIF(두 값이 같으면 null, 다르면 첫 번째 값 반환)
public class JpaMain {

    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction transaction = em.getTransaction();
        transaction.begin();

        try {
            Member member = new Member();
            member.setName("관리자");
            member.setAge(41);
            member.setMemberType(MemberType.ADMIN);
            em.persist(member);

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

			//////////////////////////////////////////////////////
            String query = "select nullif(m.name, '관리자') from Member as m";
            List<String> result = em.createQuery(query, String.class)
            					.getResultList();
			//////////////////////////////////////////////////////

            for (String s : result) {
                System.out.println("s = " + s);
            }

            transaction.commit();
        } catch (Exception e) {
            transaction.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

🙄JPQL 기본 함수

  • ✔️기본 함수
    • CONCAT
    • SUBSTRING
    • TRIM
    • LOWER, UPPER
    • LENGTH
    • LOCATE
    • ABS, SQRT, MOD
    • SIZE, INDEX(JPA 용도)

0개의 댓글