본 글은 인프런의 김영한님 강의 자바 ORM 표준 JPA 프로그래밍 - 기본편
을 수강하며 기록한 필기 내용을 정리한 글입니다.
-> 인프런
-> 자바 ORM 표준 JPA 프로그래밍 - 기본편 강의
Item 중에 Book, Movie를 조회한다.
JPQL : SELECT i FROM Item i WHERE TYPE(i) IN (Book, Movie)
SQL : SELECT i.* FROM Item i WHERE i.DTYPE IN ('B', 'M')
→ 부모 테이블에서 자식 테이블 데이터 종류를 구분하기 위한 컬럼 DTYPE
에 IN
구문을 적용해준다.
TREAT
SELECT i FROM Item i WHERE TREAT(i as Book).author = 'kim'
SELECT i.* FROM Item i WHERE i.DTYPE = 'B' AND [i.author](http://i.author) = 'kim'
SELECT COUNT(m.id) FROM Member m
SELECT COUNT(m) FROM Member m
→ 둘 모두 동일한 SQL이 실행된다.
SELECT COUNT(m.id) as cnt FROM Member m
String jpql = "SELECT m FROM Member m WHERE m = :member";
List resultList = em.createQuery(jpql)
.setParameter("member", member)
.getResultList();
→ member 엔티티 자체를 전달String jpql = "SELECT m FROM Member m WHERE m.id = :memberId";
List resultList = em.createQuery(jpql)
.setParameter("memberId", member.getId())
.getResultList();
→ member 기본키를 전달 ⇒ 두 방식 모두 동일한 SQL이 생성된다.SELECT m.* FROM Member m WHERE m.id=?
String jpql = "SELECT m FROM Member m WHERE m.team = :team";
List resultList = em.createQuery(jpql)
.setParameter("team", teamA)
.getResultList();
String jpql = "SELECT m FROM Member m WHERE m.team.id = :teamId";
List resultList = em.createQuery(jpql)
.setParameter("teamId", team.getId())
.getResultList();
SELECT m.* FROM Member m WHERE m.TEAM_ID=?
@Entity
@NamedQuery(
name = "Member.findByUsername",
query = "SELECT m FROM Member m WHERE m.username = :username")
public class Member {
...
}
<Named 쿼리 활용>List<Member> resultList = em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "회원1")
.getResultList();
<persistence-unit>
태그 내 다음 내용을 등록한다.<persistence-unit name="~~">
<mapping-file>META-INF/ormMember.xml</mapping-file>
...
META-INF/ormMember.xml
파일에 다음 내용을 작성한다.<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" version="2.1">
<named-query name="Member.findByUsername">
<query><![CDATA[
SELECT m
FROM Member m
WHERE m.username=:username
]]></query>
<named-query>
<named-query name="Member.count">
<query>SELECT count(m) FROM Member m</query>
</named-query>
</entity-mappings>
@Query
어노테이션을 부여함으로써 바로 Named 쿼리를 생성할 수 있다.public interface UserRepository extends JpaRepository<User, Long> {
...
@Query("SELECT u FROM User u WHERE u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
...
}
→ 이렇게 되면 애플리케이션 로딩 시점에 모든 오류를 잡아준다.executeUpdate()
했던 것처럼 똑같이 해당 메서드를 활용할 수 있다.String jpql = "UPDATE Product p SET p.price = p.price * 1.1 WHERE p.stockAmount < :stockAmount";
int resultCount = em.createQuery(jpql)
.setParameter("stockAmount", 10)
.executeUpdate();
→ 영향을 받은 엔티티 수를 반환한다.벌크 연산을 활용할 경우, 다음 사항을 유의해야한다.
벌크 연산은 영속성 컨텍스트를 무시하고 DB에 직접 쿼리를 보내는 것이다.
따라서 영속성 컨텍스트와 다음과 같은 충돌이 일어날 수 있다.
다음과 같이 Member 데이터를 저장하는데, age는 설정하지 않고 persist 한다. : 0으로 설정된다.
Member member1 = new Member();
member1.setUsername("회원1");
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
em.persist(member3);
이후 바로 모든 Member의 age를 20으로 수정하는 벌크 연산을 수행한다.
int resultCount = em.createQuery("update Member m set m.age = 20")
.executeUpdate();
이렇게 되면 다음과 같이 DB에는 반영이 되어있지만,
다음과 같이 영속성 컨텍스트에는 반영되어 있지 않게 된다.
Member findMember1 = em.find(Member.class, member1.getId());
Member findMember2 = em.find(Member.class, member2.getId());
Member findMember3 = em.find(Member.class, member3.getId());
System.out.println("member1 age : " + findMember1.getAge());
System.out.println("member2 age : " + findMember2.getAge());
System.out.println("member3 age : " + findMember3.getAge());
벌크 연산도 JPQL 이기 때문에 JPQL 쿼리 날아가기 전에 flush() 되어 영속성 컨텍스트에 저장되고 DB에 반영된다. : 피할 수 없다.
이와 같은 문제를 해결하기 위해서는 다음 두 가지 방안 중 하나에 따르면 된다.
이에 따라 위 예시를 다음과 같이 수정해주어야 한다.
Member member1 = new Member();
member1.setUsername("회원1");
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
em.persist(member3);
int resultCount = em.createQuery("update Member m set m.age = 20")
.executeUpdate();
em.clear(); // 영속성 컨텍스트를 초기화해주어야 한다.
Member findMember1 = em.find(Member.class, member1.getId());
Member findMember2 = em.find(Member.class, member2.getId());
Member findMember3 = em.find(Member.class, member3.getId());
System.out.println("member1 age : " + findMember1.getAge());
System.out.println("member2 age : " + findMember2.getAge());
System.out.println("member3 age : " + findMember3.getAge());
Spring Data JPA 에서는 해당 과정이 자동으로 이루어질 수 있다.