[JPA] JPQL
๐ JPQL์ด๋?
JPQL(Java Persistence Query Language)
๋ JPA์์ ์ฌ์ฉํ๋ ๊ฐ์ฒด ์งํฅ ์ฟผ๋ฆฌ ์ธ์ด์ด๋ค. SQL์ ์ถ์ํํ ์ฟผ๋ฆฌ ์ธ์ด์ด๋ฉฐ, SQL๊ณผ ๋น์ทํ ๋ฌธ๋ฒ์ ๊ฐ์ง์ง๋ง SQL์ ํ
์ด๋ธ์ ๋์์ผ๋ก ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ ๋ฐ๋ฉด JPQL์ ์ํฐํฐ๋ฅผ ๋์์ผ๋ก ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ๋ค.
JPA์์ ์ ๊ณตํ๋ ๊ธฐ๋ณธ ๋ฉ์๋๋ก ๋ณต์กํ ์กฐ๊ฑด์ ๊ฐ์ง ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๊ธฐ์ ํ๊ณ๊ฐ ์์ผ๋ฉฐ, ์ด๋ฅผ ๊ทน๋ณตํ๊ธฐ ์ํด JPQL์ด ๊ฐ๋ฐ๋์๋ค. JPQL์ ์ดํ SQL๋ก ๋ณํ๋๋ค.
๐ ๋ฌธ๋ฒ
1
SELECT m FROMMember m WHERE m.age > 18
- ์ํฐํฐ์ ์์ฑ์ ๋์๋ฌธ์๋ฅผ ๊ตฌ๋ถํ๋ค. ๋จ, SELECT, WHERE๊ณผ ๊ฐ์ JPQL ํค์๋๋ ๋์๋ฌธ์๋ฅผ ๊ตฌ๋ถํ์ง ์๋๋ค.
- ํ
์ด๋ธ ์ด๋ฆ์ด ์๋ ์ํฐํฐ ์ด๋ฆ์ ์ฌ์ฉํ๋ค. ์ํฐํฐ ์ด๋ฆ์
@Entity
์ด๋ ธํ ์ด์ ์ผ๋ก ์ค์ ํ ์ ์๋ค. - ๋ณ์นญ์ ํ์๋ก ์ฌ์ฉํด์ผ ํ๋ฉฐ, AS๋ ์๋ต ๊ฐ๋ฅํ๋ค.
๐ TypedQuery vs. Query
TypedQuery
์ Query
๋ JPQL์ ์ฌ์ฉํ๊ธฐ ์ํ ์ฟผ๋ฆฌ ํ์
์ด๋ค. ๋ ํ์
๋ชจ๋ EntityManager
์ createQuery()
๋ฅผ ํธ์ถํ์ฌ ์์ฑํ๋ค.
TypedQuery
TypedQuery๋ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์ ๋ฆฌํด ํ์ ์ด ๋ช ํํ ๋ ์ฌ์ฉํ๋ค. ๋ ๋ฒ์งธ ์ธ์๋ก ์ํฐํฐ ํด๋์ค๋ฅผ ์ ๋ฌํ๋ค.
1
2
TypedQuery<Member> typedQuery = em.createQuery("select m from Member m where m.age > 18", Member.class);
List<Member> members = typedQuery.getResultList();
getResultList()
๋ ๊ฒฐ๊ณผ๊ฐ ํ๋ ์ด์์ผ ๋ ์ฌ์ฉํ๋ฉฐ ๊ฒฐ๊ณผ๊ฐ ๋ฆฌ์คํธ ํ์ ์ด๋ค.getSingleResult()
๋ ๊ฒฐ๊ณผ๊ฐ ํ๋์ผ ๋ ์ฌ์ฉํ๋ฉฐ ๋จ์ผ ๊ฐ์ฒด๋ฅผ ๋ฆฌํดํ๋ค.
TypedQuery๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฆฌํด๋๋ ๊ฒฐ๊ณผ๋ฅผ ๋ณ๋์ ์บ์คํ ์์ด ๋ฐ๋ก ์ฌ์ฉํ ์ ์๋ค. ๋ํ ํ์ ์์ ์ฑ์ ๋ณด์ฅํ๋ฏ๋ก ์ปดํ์ผ ์์ ์ ํ์ ์ค๋ฅ๋ฅผ ์ก์ ์ ์๋ค.
Query
Query๋ ๋ฆฌํด ํ์ ์ด ๋ช ํํ์ง ์๊ฑฐ๋ ์ฌ๋ฌ ์ปฌ๋ผ์ ์ ํํ์ฌ ์กฐํํ ๋ ์ฌ์ฉํ๋ค.
1
2
Query query = em.createQuery("select m.username, m.age from Member m where m.age > 18");
List<Object[]> resultList = query.getResultList();
Query ํ์ ์ ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฉํ ๋ ์ ์ ํ ํ์ ์ผ๋ก ์บ์คํ ํ์ฌ ์ฌ์ฉํด์ผ ํ๋ค.
๐ ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ
ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ์ JPQL์์ ์ฟผ๋ฆฌ์ ํน์ ๊ฐ์ ๋์ ์ผ๋ก ์ ๋ฌํ๋ ๋ฐฉ๋ฒ์ด๋ค. ์ด๋ฅผ ํตํด SQL Injection์ ๋ฐฉ์งํ ์ ์์ผ๋ฉฐ ์ฟผ๋ฆฌ ์ฌ์ฌ์ฉ์ฑ์ ๋์ผ ์ ์๋ค.
์ด๋ฆ ๊ธฐ์ค ํ๋ผ๋ฏธํฐ
ํ๋ผ๋ฏธํฐ๋ฅผ ์ด๋ฆ์ ๊ตฌ๋ถํ๋ ๋ฐฉ์์ด๋ค. โ:โ์ ์ฌ์ฉํ์ฌ ํ๋ผ๋ฏธํฐ ์ด๋ฆ์ ์ง์ ํ๋ค.
1
2
3
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m WHERE m.username = :username", Member.class);
query.setParameter("username", "member1");
List<Member> resultList = query.getResultList();
์์น ๊ธฐ์ค ํ๋ผ๋ฏธํฐ
โ?โ ๋ค์ ์์น ๊ฐ์ ์ง์ ํ๋ ๋ฐฉ์์ด๋ค. ์์น ๊ฐ์ 1๋ถํฐ ์์ํ๋ค.
1
2
3
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m WHERE m.username = ?1", Member.class);
query.setParameter(1, "member1");
List<Member> resultList = query.getResultList();
์ผ๋ฐ์ ์ผ๋ก ์์น ๊ธฐ์ค ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ๋ณด๋ค ์ด๋ฆ ๊ธฐ์ค ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ์ด ๋ ๊ถ์ฅ๋๋ค.
๋ฉ์๋ ์ฒด์ด๋
JPQL API๋ ๋ฉ์๋ ์ฒด์ธ ํ์์ผ๋ก ์ค๊ณ๋์ด ์๋ค.
1
2
3
List<Member> members = em.createQuery("SELECT m FROM Member m WHERE m.username = :username", Member.class)
.setParameter("username", "member1")
.getResultList();
๐ ํ๋ก์ ์
ํ๋ก์ ์ (Projection)์ JPQL์์์ SELECT์ ์ ์กฐํํ ๋์์ ์ง์ ํ๋ ๊ฒ์ ์๋ฏธํ๋ค.
์ํฐํฐ ํ๋ก์ ์
์ํฐํฐ ์์ฒด๋ฅผ ์กฐํ ๋์์ ์ง์ ํ๋ ๋ฐฉ์์ด๋ค.
1
2
SELECT m FROM Member m
SELECT m.team FROM Member m
์ํฐํฐ ํ๋ก์ ์ ์ผ๋ก ์กฐํ๋ ๊ฒฐ๊ณผ๋ ์์์ฑ ์ปจํ ์คํธ์์ ๊ด๋ฆฌ๋๋ค.
์๋ฒ ๋๋ ํ์ ํ๋ก์ ์
์ํฐํฐ์ ํฌํจ๋ ์๋ฒ ๋๋ ํ์ ์ ์กฐํํ๋ ๋ฐฉ์์ด๋ค.
1
2
3
4
SELECT m.address FROM Member m
-- ๋ถ๊ฐ๋ฅ
SELECT a FROM Address a
์๋ฒ ๋๋ ํ์ ์ ์ํฐํฐ์ ์ ์ฌํ๊ฒ ์ฌ์ฉ๋์ง๋ง, ์กฐํ์ ์์์ ์ด ๋ ์ ์๋ค.
์ค์นผ๋ผ ํ์ ํ๋ก์ ์
์ซ์๋ ๋ฌธ์ ๊ฐ์ ๊ธฐ๋ณธ ๋ฐ์ดํฐ ํ์ ์ ์กฐํํ๋ ๋ฐฉ์์ด๋ค.
1
SELECT m.username FROM Member m
์ฌ๋ฌ ์ค์นผ๋ผ ๊ฐ๋ ํจ๊ป ์กฐํํ ์ ์๋ค. Object[]
ํ์
์ผ๋ก ์กฐํํ๋ ๋ฐฉ๋ฒ๊ณผ JPQL์ new
ํค์๋์ DTO ํด๋์ค๋ฅผ ํตํด ์กฐํํ๋ ๋ฐฉ๋ฒ์ด ์๋ค.
1
2
3
4
5
6
7
8
// Object[]
String jpql = "SELECT m.username, m.age FROM Member m";
List<Object[]> resultList = entityManager.createQuery(jpql).getResultList();
// DTO
String jpql = "SELECT new com.example.MemberDTO(m.username, m.age) FROM Member m";
List<MemberDTO> resultList = entityManager.createQuery(jpql, MemberDTO.class).getResultList();
DTO ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด ๋ ๊ถ์ฅ๋๋ค.
๐ ํ์ด์ง API
JPQL์์ ๋์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ์กฐํํ๊ธฐ ์ํด ํ์ด์ง์ ์ฌ์ฉํ๋ค.
setFirstResult(int startPos)
๋ ์กฐํ ์ ์์ ์์น๋ฅผ ์ง์ ํ๋ ๋ฉ์๋์ด๋ค. startPos + 1
๋ฒ์งธ ๊ฒฐ๊ณผ๋ถํฐ ์กฐํ๋ฅผ ์์ํ๋ค. ์ธ๋ฑ์ค๋ 0๋ถํฐ ์์ํ๋ค.
setMaxResults(int maxResult)
๋ ํ ๋ฒ์ ์กฐํํ ๋ฐ์ดํฐ์ ์ต๋ ๊ฐ์๋ฅผ maxResult
๊ฐ๋ก ์ง์ ํ๋ ๋ฉ์๋์ด๋ค.
1
2
3
4
5
String jpql = "select m from Member m order by m.name desc";
List<Member> resultList = entityManager.createQuery(jpql, Member.class)
.setFirstResult(10)
.setMaxResults(20)
.getResultList();
์ ์ฝ๋๋ 11๋ฒ ์งธ ๊ฒฐ๊ณผ๋ถํฐ 20๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ์ฝ๋์ด๋ค.
JPQL์ ํ์ด์ง API๋ฅผ ์ฌ์ฉํ๋ฉด SQL์ ํ์ด์ง ์ฟผ๋ฆฌ๋ฅผ ์ง์ ์์ฑํ ํ์๊ฐ ์๋ค. ์ฌ๋ฌ DBMS๋ง๋ค ๋ค๋ฅธ ํ์ด์ง ์ฒ๋ฆฌ ์ฟผ๋ฆฌ ๋ฌธ๋ฒ์ด ์กด์ฌํ๊ธฐ์, ๊ฐ๋ฐ์๋ DBMS ์ข ๋ฅ์ ๊ตฌ์ ๋ฐ์ง ์๊ณ ์ผ๊ด๋ ๋ฐฉ์์ผ๋ก ํ์ด์ง ๋ก์ง์ ๊ตฌํํ ์ ์๋ค.
๐ ์ง๊ณ ํจ์
JPQL์ SQL๊ณผ ์ ์ฌํ ์ง๊ณ ํจ์๋ฅผ ์ ๊ณตํ๋ค.
COUNT(x)
: ๊ฒฐ๊ณผ์ ๊ฐ์ ๋ฆฌํด, Long
ํ์
SUM(x)
: ์ซ์ ํ๋์ ํฉ๊ณ ๊ณ์ฐ, Long
, Double
ํ์
AVG(x)
: ํ๊ท ๊ณ์ฐ, Double
ํ์
MAX(x)
: ์ต๋๊ฐ ๊ณ์ฐ
MIN(x)
: ์ต์๊ฐ ๊ณ์ฐ
1
2
SELECT COUNT(m), SUM(m.age), AVG(m.age), MAX(m.age), MIN(m.age)
FROM Member m
๐ JOIN
Inner Join
๋ ์ํฐํฐ ๊ฐ ์ฐ๊ด๊ด๊ณ๊ฐ ์กด์ฌํ๋ ๋ฐ์ดํฐ๋ง ์กฐํํ๋ค. INNER
ํค์๋๋ ์๋ต ๊ฐ๋ฅํ๋ค.
1
SELECT m FROM Member m INNER JOIN m.team t WHERE t.name = :teamName
Outer Join
์ฐ๊ด๊ด๊ณ๊ฐ ์๋๋ผ๋ ๊ธฐ์ค์ด ๋๋ ์ํฐํฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ํฌํจํ์ฌ ์กฐํํ๋ค. ๋ฐ์ดํฐ๊ฐ ์๋ ๊ฒฝ์ฐ NULL
๋ก ํ์๋๋ค. OUTER
ํค์๋๋ ์๋ต ๊ฐ๋ฅํ๋ค.
1
SELECT m FROM Member m LEFT OUTER JOIN m.team t
Theta Join
์ฐ๊ด๊ด๊ณ ์๋ ์ํฐํฐ ๊ฐ์๋ WHERE์ ์ ๋ง์กฑํ๋ ๋ฐ์ดํฐ๋ฅผ ์กฐ์ธํ๋ค.
1
SELECT m FROM Member m, Team t WHERE m.username = t.name
JPA 2.1๋ถํฐ ON
์ ์ ์ฌ์ฉํ์ฌ ์กฐ์ธ ๋์์ ํํฐ๋งํ ์ ์๋ค.
1
SELECT m FROM Member m LEFT JOIN m.team t ON t.name = 'A'
Fetch Join
Fetch Join
์ JPQL์์ ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํด ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ด๋ค. Fetch Join์ ํตํด ์ฐ๊ด๋ ์ํฐํฐ๋ ์ปฌ๋ ์
์ ํ๋ก์ ๊ฐ์ฒด๊ฐ ์๋ ์ค์ ๋ฐ์ดํฐ๋ก SQL ์ฟผ๋ฆฌ์ ํจ๊ป ์กฐํํ์ฌ ๋ก๋ฉํ ์ ์๋ค. ์ด๋ฅผ ํตํด N+1 ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค.
JOIN FETCH
๋ผ๋ ํค์๋๋ฅผ ์ฌ์ฉํ๋ฉฐ, ์กฐํ ์ฃผ์ฒด๊ฐ ๋๋ ์ํฐํฐ๋ฟ๋ง ์๋๋ผ FETCH๋ก ๋ช
์๋ ์ฐ๊ด ์ํฐํฐ๊น์ง ํจ๊ป ์กฐํํ์ฌ ์ฆ์ ๋ก๋ฉ(Eager Loading) ํ ์์ํํ๋ค.
@ManyToOne
, @OneToOne
๊ด๊ณ์์ ์ฌ์ฉํ๋ ํ์น ์กฐ์ธ์ ์ํฐํฐ ํ์น ์กฐ์ธ, @OneToMany
๊ด๊ณ์์ ์ฌ์ฉํ๋ ํ์น ์กฐ์ธ์ ์ปฌ๋ ์
ํ์น ์กฐ์ธ์ด๋ผ๊ณ ํ๋ค.
1
2
3
4
5
-- ์ํฐํฐ ํ์น ์กฐ์ธ
SELECT m FROM Member m JOIN FETCH m.team
-- ์ปฌ๋ ์
ํ์น ์กฐ์ธ
SELECT t FROM Team t JOIN FETCH t.members
์ปฌ๋ ์
ํ์น ์กฐ์ธ ์ ๋ฐ์ดํฐ ์ค๋ณต์ด ๋ฐ์ํ ์ ์๋ค. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด DISTINCT
ํค์๋๋ฅผ ์ฌ์ฉํ๋ค. ์ ํ๋ฆฌ์ผ์ด์
๋ ๋ฒจ์์ ์ํฐํฐ์ ์๋ณ์๋ฅผ ๊ธฐ์ค์ผ๋ก ์ค๋ณต๋ ์ํฐํฐ๋ฅผ ์ ๊ฑฐํ๋ค.
ํ์น ์กฐ์ธ์ FetchType
๊ฐ์ ๊ธ๋ก๋ฒ ๋ก๋ฉ ์ ๋ต๋ณด๋ค ์ฐ์ ์์๊ฐ ๋๋ค. ๋ณดํต ์ํฐํฐ ๋ก๋ฉ ์ ๋ต์ fetch = FetchType.LAZY
๋ก ์ค์ ํ๊ณ ํ์ํ ๊ฒฝ์ฐ ํ์น ์กฐ์ธ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. ๋ํ ํ์น ์กฐ์ธ ๋์์๋ ๋ณ์นญ์ ์ฌ์ฉํ ์ ์๋ค. ๋ ์ด์์ ์ปฌ๋ ์
์ ํ์น ์กฐ์ธํ ์ ์์ผ๋ฉฐ, ํ์ด์ง API๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
์ผ๋ฐ ์กฐ์ธ๊ณผ ํ์น ์กฐ์ธ์ ๊ฐ์ฅ ํฐ ์ฐจ์ด์ ์ ์ฐ๊ด ์ํฐํฐ์ ์กฐํ ๋ฐ ์์ํ ์ฌ๋ถ์ด๋ค. ์ผ๋ฐ ์กฐ์ธ์ ์ฐ๊ด ์ํฐํฐ๋ฅผ ๋ก๋ฉํ์ง ์์ผ๋ฉฐ ์์ํํ์ง ์๋๋ค. ๊ทธ๋ฌ๋ ํ์น ์กฐ์ธ์ ์ฐ๊ด ์ํฐํฐ๋ฅผ ํจ๊ป ๋ก๋ฉํ์ฌ ์์์ฑ ์ปจํ ์คํธ์ ๋ก๋ฉํ๋ค.
๐ @Query
JPA์์ @Query
์ด๋
ธํ
์ด์
์ ํตํด ์ง์ JPQL ๋๋ Native SQL ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์๋ค.
1
2
3
4
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.status = 1")
Collection<User> findAllActiveUsers();
}
nativeQuery = true
์์ฑ์ ํตํด Native SQL ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์๋ค.
1
2
3
4
5
6
public interface UserRepository extends JpaRepository<User, Long> {
@Query(
value = "SELECT * FROM USERS u WHERE u.status = 1",
nativeQuery = true)
Collection<User> findAllActiveUsersNative();
}
๋ฉ์๋์ ํ๋ผ๋ฏธํฐ์ @Param
์ด๋
ธํ
์ด์
์ ํตํด JPQL ๋ด์ ํ๋ผ๋ฏธํฐ ์ด๋ฆ๊ณผ ๋งคํํ ์ ์๋ค.
1
2
3
4
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username = :name")
Member findMembers(@Param("name") String username);
}
INSERT, UPDATE, DELETE์ ๊ฐ์ DML ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๋, @Modifying
์ด๋
ธํ
์ด์
์ ํจ๊ป ์ฌ์ฉํด์ผ ํ๋ค.
1
2
3
4
5
6
public interface UserRepository extends JpaRepository<User, Long> {
@Transactional
@Modifying
@Query("update User u set u.name = :name where u.id = :id")
int updateName(@Param("id") Long id, @Param("name") String name); // ๋ฒํฌ ์ฐ์ฐ์ ์
๋ฐ์ดํธ๋ ํ ์๋ฅผ ๋ฐํ
}
๐ ์ฐธ๊ณ
https://ittrue.tistory.com/270
https://adjh54.tistory.com/479
https://ict-nroo.tistory.com/116
https://velog.io/@kevin_/JPQL