스프링
JPA 객체지향 쿼리언어
고양이오즈
2022. 6. 10. 15:13
객체지향 쿼리언어
- 다형성 쿼리
- 부모 엔티티를 조회하면 그 자식 엔티티도 함께 조회함.
- Item이 Book, Album, Movie를 자식으로 두었을 시 Item 조회 시 자식도 함께 조회 됨.
- 엔티티 상속 구조에서 조회 대상을 특정 자식 타입으로 한정할 때 주로 사용.
TREAT(JPA 2.1)SELECT i FROM Item i WHERE type(i) IN (Book, Movie)
- 자바의 타입 캐스팅과 비슷.
- 부모 타입을 특정 자식 타입으로 다룰 때 사용
- JPA표준은 FROM, WHERE 에서 사용가능
- 하이버네이트는 SELECT 절에서 TREAT를 사용할 수 있음.
위 코드의 경우 부모 타입의 Item을 자식 타입인 Book으로 다룸.select i from Item i where treat(i as Book).author = 'kim'
- 따라서 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라는 외래키와 매핑 되어 있다.
이 경우 아래와 같은 sql이 나간다.멤버 테이블이 team_id를 가지고 있으므로 묵시적 조인은 일어나지 않는다.em.createQuery("select m from Member m where m.team = :team", Member.class) .setParameter("team", new Team("팀A"));
- 식별자 값을 직접 주어도 동일하게 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 쿼리는 영속성 유닛 단위로 관리하므로 충돌을 방지하기 위함이다. 그리고 엔티티가 앞에 있으면 관리하기 쉽다.
- @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을 사요하는 것이 더 편리하다. 멀티 라인 문자를 다뤄야 하기 때문!
- 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에 코드를 추가해야 한다.
참고로 orm.xml은 JPA가 매핑 파일로 인식해서 별도의 설정을 하지 않아도 된다. 이름이나 위치가 다르면 추가해줘야 한다.<persistence-unit name="jpatest"> <mapping-file>META-INF/ormMember.xml</mapping-file>
- XML과 어노테이션에 같은 설정이 있으면 XML이 우선권을 가진다. 같은 이름의 Named쿼리가 있으면 XML에 정의된 것이 사용된다. 운영 환경에 따라 다른 쿼리를 실행해야 할 경우 XML만 변경해서 배포하면 된다