스프링

JPA 객체지향 쿼리언어

고양이오즈 2022. 6. 10. 15:13

객체지향 쿼리언어

  • 다형성 쿼리
    • 부모 엔티티를 조회하면 그 자식 엔티티도 함께 조회함.
    • Item이 Book, Album, Movie를 자식으로 두었을 시 Item 조회 시 자식도 함께 조회 됨.
    TYPE
    • 엔티티 상속 구조에서 조회 대상을 특정 자식 타입으로 한정할 때 주로 사용.
    SELECT i FROM Item i
    WHERE type(i) IN (Book, Movie)
    
    TREAT(JPA 2.1)
    • 자바의 타입 캐스팅과 비슷.
    • 부모 타입을 특정 자식 타입으로 다룰 때 사용
    • JPA표준은 FROM, WHERE 에서 사용가능
    • 하이버네이트는 SELECT 절에서 TREAT를 사용할 수 있음.
    select i from Item i where treat(i as Book).author = 'kim'
    
    위 코드의 경우 부모 타입의 Item을 자식 타입인 Book으로 다룸.
  • 따라서 Book안의 author에 접근 할 수 있음
  • 사용자 정의 함수 호출
    • JPA 2.1 부터 사용자 정의 함수를 지원함.
    • 방언 클래스를 상속해서 구현해야 하고, 사용할 데이터베이스 함수를 미리 등록해야 함.
    public class MyPostgresDialect extends PostgreSQL94Dialect {
        public MyPostgresDialect() {
            registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
        }
    
    • 그리고 hibernate.dialect에 해당 방언을 등록해야 함.
    <property name="hibernate.dialect" value="jpql.MyPostgresDialect"/>
    
    • 하이버네이트 구현체를 사용하면 다음과 같이 축약해서 사용할 수 있음
    select group_concat(i.name) from Item i
    
  • 기타 정리 내용
    • enum은 = 비교 연산만 지원
    • 임베디드 타입은 비교를 지원하지 않음
    • EMPTY STRING
      • JPA 표준은 ‘’을 길이 0인 Empty String으로 정했지만 데이터베이스에 따라 null로 사용하는 데이터베이스도 있으니 확인이 필요하다.
    • null정의
      • 조건을 만족하는 데이터가 하나도 없으면 null
      • null은 알수 없는 값. null과의 모든 수학적 계산은 null이다
      • null==null은 알 수 없다
      • null is null은 참이다.
  • 엔티티 직접 사용
    • 객체 인스턴스는 참조값으로 식별하고 테이블 로우는 기본 키 값으로 식별함.
    • 따라 jpql에서 엔티티 객체를 직접 사용하면 sql에서는 해당 엔티티의 기본키 값을 씀
    • select count(m) from Member m 으로 엔티티를 직접 넘겨주어도 실제 실행되는 sql은 m.id로 변환되어 실행된다.
    • 엔티티를 파라미터로 받아서 실행해도 기본 키 값을 사용하도록 자동 변환된다.
    • 외래키 값
      • m.team은 현재 team_id라는 외래키와 매핑 되어 있다.
    em.createQuery("select m from Member m where m.team = :team", Member.class)
    		.setParameter("team", new Team("팀A"));
    
    이 경우 아래와 같은 sql이 나간다.멤버 테이블이 team_id를 가지고 있으므로 묵시적 조인은 일어나지 않는다.
  • 식별자 값을 직접 주어도 동일하게 m.team이나 m.team.id나 생성되는 sql은 같다.
  • select m.* from Member m where m.team_id=?(팀 id값)
  • named 쿼리:정적 쿼리
    • 동적 쿼리 : em.createQuery(”select ..”) 처럼 JPQL을 문자로 완성해서 직접 넘기는 것을 동적 쿼리라고 한다.
    • 정적 쿼리 : 미리 정의한 쿼리에 이름을 부여해서 필요할 때 사용할 수 있는데 이것을 Named쿼리라고 한다. Named 쿼리는 한 번 정의하면 변경 할 수 없다.
      • 애플리케이션 로딩 시 jpql 문법을 체크하고 미리 파싱해둔다. 따라 오류를 빨리 확인할 수 있고, 사용 시점에 파싱된 결과를 재사용하므로 성능상 이점이 있다.
      • 변하지 않는 정적 sql이 생성되므로 데이터베이스 조회 성능 최적화에도 도움이 된다.
      • @NamedQuery 어노테이션을 사용해 자바 코드에 작성하거나 xml 문서에 작성한다.
      쿼리 정의사용
      • Meber.findByUserName 처럼 엔티티 이름을 앞에 붙인것은 기능적으로 특별한 의미가 있는 것은 아니지만 Named 쿼리는 영속성 유닛 단위로 관리하므로 충돌을 방지하기 위함이다. 그리고 엔티티가 앞에 있으면 관리하기 쉽다.
      하나의 엔티티에 여러 named쿼리를 작성하고 싶다면 @NamedQueries를 작성한다.
    • @Entity @NamedQueries({ @NamedQuery( name ="Member.findByUsername1", query = "select m from Member m where m.username = :username1"), @NamedQuery( name ="Member.findByUsername2", query = "select m from Member m where m.username = :username2") }) public class Member{ }
    • List<Member> results = em.createNamedQuery("Member.findByUserName", Member.class) .setParameter("username", "회원1") .getResultList();
    • @Entity @NamedQuery( name ="Member.findByUsername", query = "select m from Member m where m.username = :username" ) public class Member{ }
    Named 쿼리를 xml에 정의
    • 어노테이션을 사용하는 것이 직관적이고 편리한 편이지만, Named쿼리에서는 xml을 사요하는 것이 더 편리하다. 멀티 라인 문자를 다뤄야 하기 때문!
    • xml에 쿼리를 정의해 두는 것이 현실적인 대안이다.
    <named-query name="Member.findByUsername">
    		<query><![CDATA[
    			select m
    			from Member m
    			where m.name = :username
    			]]>
    		</query>
    	</named-query>
    
    • xml에서 < & >은 예약문자다. 대신 & ⁢ >를 사용해야 한다.
    • 혹은 <!CDATA[]]>를 사용해 그 사이 문장을 그대로 출력하는 방법도 있다.
    • 이 경우 정의한 ormMember.xml 을 인식하도록 META-INF/persistence.xml에 코드를 추가해야 한다.
    <persistence-unit name="jpatest">
    		<mapping-file>META-INF/ormMember.xml</mapping-file>
    
    참고로 orm.xml은 JPA가 매핑 파일로 인식해서 별도의 설정을 하지 않아도 된다. 이름이나 위치가 다르면 추가해줘야 한다.
  • XML과 어노테이션에 같은 설정이 있으면 XML이 우선권을 가진다. 같은 이름의 Named쿼리가 있으면 XML에 정의된 것이 사용된다. 운영 환경에 따라 다른 쿼리를 실행해야 할 경우 XML만 변경해서 배포하면 된다