자바 ORM 표준 JPA 프로그래밍 - 기본편 수업을 듣고 정리한 내용입니다.
JPA
는 복잡한 검색 조건을 사용해서 엔티티 객체를 조회하는 다양한 쿼리 기술을 지원한다.
✏️ JPA가 지원하는 다양한 쿼리 방법
JPQL
: 표준 문법JPA Criteria, QueryDSL
: 문자가 아닌 프로그래밍 코드로JPQL
작성네이티브 SQL
: 표준 SQL 문법을 벗어나는 쿼리를 작성할 수 있는 기능 제공JDBC API
직접 사용,MyBatis
와 같은 SQL 매퍼 프레임워크 사용
표준 문법이다!
✔️ 소개
EntityManager.find()
a.getB().getC()
)
✔️ 필요성
JPA
를 사용하면 테이블이 아닌 엔티티 객체를 중심으로 개발한다.
✔️ 특징
JPA
는 SQL을 추상화한 JPQL
이라는 객체 지향 쿼리 언어를 제공한다.JPQL
은 SQL과 문법이 유사하며, 엔티티 객체를 대상으로 쿼리한다. JPQL
을 한마디로 정의하면 객체 지향 SQL이다!
✔️ 예제
//검색
String jpql = "select m From Member m where m.username like '%kim%'";
List<Member> result = em.createQuery(jpql, Member.class).getResultList();
Member
는 테이블이 아닌 엔티티이다.
Hibernate:
/* select
m
From
Member m
where
m.username like '%kim%' */ select
member0_.MEMBER_ID as MEMBER_I1_4_,
member0_.city as city2_4_,
member0_.street as street3_4_,
member0_.ZIPCODE as ZIPCODE4_4_,
member0_.USERNAME as USERNAME5_4_
from
Member member0_
where
member0_.USERNAME like '%kim%'
문자가 아닌 자바코드로
JPQL
을 작성할 수 있다.
JPQL
빌더 역할JPA
공식 기능//Criteria 사용 준비
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);
//루트 클래스 (조회를 시작할 클래스)
Root<Member> m = query.from(Member.class);
//쿼리 생성
CriteriaQuery<Member> cq = query.select(m).where(cb.equal(m.get("username"), "kim"));
List<Member> resultList = em.createQuery(cq).getResultList()
코드가 많이 복잡하다.
문자가 아닌 자바코드로
JPQL
을 작성할 수 있으며, 실용성이 없는Criteria
대신 오픈 소스 라이브러리인QueryDSL
을 사용한다.
JPQL
빌더 역할String
문자가 아닌 자바 코드로 작성하기 때문에 컴파일 시점에 문법 오류를 찾을 수 있다.//JPQL
//select m from Member m where m.age > 18
JPAFactoryQuery queryFactory= new JPAQueryFactory(em);
QMember m = QMember.member;
List<Member> list = queryFactory.selectFrom(m)
.where(m.age.gt(18))
.orderBy(m.name.desc())
.fetch()
QMember
는 Member
엔티티 클래스를 기반으로 생성한 QueryDSL
쿼리 전용 클래스이다.
➡️ Criteria
와 QueryDSL
을 비교하였을 때, QueryDSL
가 편리하고 코드가 단순하고 쉽다. (훨씬 직관적이다.)
JPA
가 제공하는 SQL을 직접 사용하는 기능
JPQL
로 해결할 수 없는 특정 데이터베이스에 의존적인 기능을 사용할 때 쓰인다.CONNECT BY
, 특정 DB
만 사용하는 SQL 힌트String sql = "SELECT ID, AGE, TEAM_ID, NAME FROM MEMBER WHERE NAME = 'kim'";
List<Member> resultList = em.createNativeQuery(sql, Member.class).getResultList();
JPA
를 사용하면서JDBC
커넥션을 직접 사용하거나,SpringJdbcTemplate
,MyBatis
등을 함께 사용할 수 있다.
JPA
를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트 수동 플러시
JPQL
(Java Persistence Query Language
)
JPQL
은 객체지향 쿼리 언어이다. → 테이블이 아닌, 엔티티 객체를 대상으로 쿼리한다.JPQL
은 SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.JPQL
은 결국 SQL로 변환된다.Member
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String username;
private int age;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
// getter, setter
...
}
}
Team
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
// getter, setter
...
}
}
Product
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
private int price;
private int stockAmount;
// getter, setter
...
}
}
Order
@Entity
@Table(name="ORDERS")
public class Order {
@Id @GeneratedValue
private Long id;
private int orderAmount;
@Embedded
private Address address;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
// getter, setter
...
}
}
Address
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
// getter, setter
...
}
}
select_문 :: =
select_절
from_절
[where_절]
[groupby_절]
[having_절]
[orderby_절]
update_문 :: = update_절 [where_절]
delete_문 :: = delete_절 [where_절]
select m from Membebr as m where m.age > 18
Member
는 테이블이 아닌 엔티티Member
, age
)JPQL
키워드는 대소문자 구분하지 않는다. (SELECT
, FROM
, where
)Member
)m
) (as
는 생략 가능)
✔️ 집합과 정렬
select
COUNT(m), //회원수
SUM(m.age), //나이 합
AVG(m.age), //평균 나이
MAX(m.age), //최대 나이
MIN(m.age) //최소 나이
from Member m
//TypeQuery
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");
TypeQuery
: 반환 타입이 명확할 때 사용한다. (두 번쨰로는 보통 엔티티를 준다.)Query
: 반환 타입이 명확하지 않을 때 사용한다. (호출하는 필드 값들의 타입이 각각 다를 때)
query.getResultList()
: 결과가 하나 이상일 때, 리스트 반환query.getSingleResult()
: 결과가 정확히 하나일 때, 단일 객체 반환 (값이 보장)javax.persistence.NoResultException
javax.persistence.NonUniqueResultException
✔️ 이름 기준
SELECT m FROM Member m where m.username=:username
query.setParameter("username", usernameParam);
✔️ 위치 기준
SELECT m FROM Member m where m.username=?1
query.setParameter(1, usernameParam);
위치 기준은 사용하지 않는 것이 좋다.
프로젝션 :
SELECT
절에 조회할 대상을 지정하는 것
SELECT m FROM Member m
→ 엔티티 프로젝션 Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
// 영속성 컨텍스트 비우기
em.flush();
em.clear();
// 엔티티에서 조회하고 엔티티가 반환되었다.
List<Member> result = em.createQuery("select m from Member m", Member.class).getResultList();
// 엔티티 프로젝션을 하면, `"select m from Member m"`로 인해 여러가지 데이터가 나올 수 있는데
// 나온 데이터들이 모두 영속성 컨텍스트에 반영된다. → 엔티티 프로젝션
Member findMember = result.get(0);
findMember.setAge(20);
tx.commit();
결과
업데이트 된 것을 확인할 수 있다.
"select m from Member m"
로 인해 여러가지 데이터가 나올 수 있는데 나온 데이터들이 모두 영속성 컨텍스트에 반영된다. → 엔티티 프로젝션 (결과로 보면 20으로 나이가 업데이트 되었다.)
(2) SELECT m.team FROM Member m
→ 엔티티 프로젝션
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
// 영속성 컨텍스트 비우기
em.flush();
em.clear();
// 엔티티에서 조회하고 엔티티가 반환되었다.
List<Team> result = em.createQuery("select m.team from Member m", Team.class).getResultList();
// 조인을 사용해도 된다.
List<Team> result = em.createQuery("select t from Member m join m.team t", Team.class).getResultList();
실행 결과
(3) SELECT m.address FROM Member m
→ 임베디드 타입 프로젝션
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
// 영속성 컨텍스트 비우기
em.flush();
em.clear();
// 엔티티에서 조회하고 엔티티가 반환되었다.
em.createQuery("select o.address from Order o", Address.class).getResultList();
실행 결과
(4) SELECT m.username, m.age FROM Member m
→ 스칼라 타입 프로젝션
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
// 영속성 컨텍스트 비우기
em.flush();
em.clear();
em.createQuery("select distinct m.username, m.age from Member m").getResultList();
DISTINCT
를 붙여서 중복을 제거할 수도 있다.
✔️ 여러 값 조회
SELECT m.username, m.age FROM Member m
Query
타입으로 조회 List resultList = em.createQuery("select m.username, m.age from Member m").getResultList();
Object o = resultList.get(0);
Object[] result = (Object[]) o;
System.out.println("result[0] = " + result[0]);
System.out.println("age = " + result[1]);
Object[]
타입으로 조회 List<Object[]> resultList = em.createQuery("select m.username, m.age from Member m").getResultList();
Object[] result = resultList.get(0);
System.out.println("result[0] = " + result[0]);
System.out.println("age = " + result[1]);
new
명령어로 조회 (이게 제일 깔끔함, 많이 사용한다.)MemberDTO.class
생성
public class MemberDTO {
private String username;
private int age;
public MemberDTO(String username, int age) {
this.username = username;
this.age = age;
}
// getter, setter
...
}
}
JpaMain
List<MemberDTO> resultList = em.createQuery("select new jpql.MemberDTO(m.username, m.age) from Member m", MemberDTO.class).getResultList();
MemberDTO memberDTO = resultList.get(0);
System.out.println("memberDTO.getUsername() = " + memberDTO.getUsername());
System.out.println("memberDTO.getAge() = " + memberDTO.getAge());
SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m
JPA
는 페이징을 다음 두 API로 추상화한다.
setFirstResult(int startPosition)
: 조회 시작 위치 (0부터 시작)setMaxResults(int maxResult)
: 조회할 데이터 수
✔️ 페이징 API 예시
//페이징 쿼리
String jpql = "select m from Member m order by m.name desc";
List<Member> resultList = em.createQuery(jpql, Member.class)
.setFirstResult(1)
.setMaxResults(10)
.getResultList();
1번째부터 10개를 가져올 것이다.
H2Dialect 표준 방언 : limit ? offset ?
MySQL 방언
SELECT
M.ID AS ID,
M.AGE AS AGE,
M.TEAM_ID AS TEAM_ID,
M.NAME AS NAME
FROM
MEMBER M
ORDER BY
M.NAME DESC LIMIT ?, ?
Oracle 방언
SELECT * FROM
( SELECT ROW_.*, ROWNUM ROWNUM_
FROM
( SELECT
M.ID AS ID,
M.AGE AS AGE,
M.TEAM_ID AS TEAM_ID,
M.NAME AS NAME
FROM MEMBER M
ORDER BY M.NAME
) ROW_
WHERE ROWNUM <= ?
)
WHERE ROWNUM_ > ?
JPA
방언으로 지정된 데이터베이스의 종류에 따라 MySQL
과 Oracle
코드가 다르다!
스프링 JPA가 페이징을 제공하므로 실제 db 쿼리에 입력하지 않아도 되니 되게 편하다는 것을 알 수 있다.
setFirstResult(int startPosition)
와 setMaxResults(int maxResult)
만 지정하면 나머지는 JPA가 처리해준다.
✔️ 내부 조인
SELECT m FROM Member m [INNER] JOIN m.team t
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
// 영속성 컨텍스트 비우기
em.flush();
em.clear();
String jpql = "select m from Member m inner join m.team t";
List<Member> resultList = em.createQuery(jpql, Member.class)
.getResultList();
tx.commit();
실행 결과
✔️ 외부 조인
SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
// 영속성 컨텍스트 비우기
em.flush();
em.clear();
String jpql = "select m from Member m left join m.team t";
List<Member> resultList = em.createQuery(jpql, Member.class)
.getResultList();
tx.commit();
✔️ 세타 조인 - 연관관계가 없는 것을 조인하고 싶을 때 사용한다.
select count(m) from Member m, Team t where m.username = t.name
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("teamA");
member.setAge(10);
em.persist(member);
// 영속성 컨텍스트 비우기
em.flush();
em.clear();
String jpql = "select m from Member m, Team t where m.username = t.name";
List<Member> resultList = em.createQuery(jpql, Member.class)
.getResultList();
System.out.println("resultList.size() = " + resultList.size());
tx.commit();
✔️ JOIN - ON 절을 활용한 조인
JPA 2.1 부터 지원한다.
(1) 조인 대상 필터링
(2) 연관관계 없는 엔티티 외부 조인 (하이버네이트 5.1부터)
ex) 회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인
✔️ JPQL
SELECT m, t FROM
Member m LEFT JOIN m.team t on t.name = 'A'
✔️ SQL
SELECT m.*, t.* FROM
Member m LEFT JOIN Team t ON m.TEAM_ID=t.id and t.name='A'
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("teamA");
member.setAge(10);
em.persist(member);
// 영속성 컨텍스트 비우기
em.flush();
em.clear();
String jpql = "select m from Member m left join m.team t on t.name='teamA'";
List<Member> resultList = em.createQuery(jpql, Member.class)
.getResultList();
System.out.println("resultList.size() = " + resultList.size());
tx.commit();
ex) 회원의 이름과 팀 이름이 같은 대상 외부 조인
✔️ JPQL
SELECT m, t FROM
Member m LEFT JOIN Team t on m.username = t.name
✔️ SQL
SELECT m.*, t.* FROM
Member m LEFT JOIN Team t ON m.username = t.name
연관관계가 전혀 없는 테이블을 LEFT JOIN하고 싶을 때는 on절에 삽입하면 된다.
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("teamA");
member.setAge(10);
em.persist(member);
// 영속성 컨텍스트 비우기
em.flush();
em.clear();
String jpql = "select m from Member m left join Team t on m.username = t.name";
List<Member> resultList = em.createQuery(jpql, Member.class)
.getResultList();
System.out.println("resultList.size() = " + resultList.size());
tx.commit();