📌 JPQL
- Application이 필요한 data만 db에서 쏙쏙 뽑아서 가져오려면, 결국 검색 조건이 포함된 SQL이 필요하다.
- JPQ는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공한다.
- JPQL은 Entity 객체를 대상으로 쿼리한다.
- 반면에, SQL은 Table 대상으로 쿼리한다.
- JPQL은 SQL로 변환돼서 실행된다.
- JPQL을 한 마디로 정의하면 객체 지향 SQL이다.
- 쿼리에 의해 가져온 Entity는 전부 영속성 컨텍스트에 의해 관리된다.
▸ JPQL 실습 -1) wildcard
import entity.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
// 영속성 컨텍스트
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member memberA = new Member();
memberA.setUsername("memberA");
Member memberB = new Member();
memberB.setUsername("BB");
Member memberC = new Member();
memberC.setUsername("memberC");
Member memberD = new Member();
memberD.setUsername("memberD");
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
em.persist(memberD);
em.flush();
em.clear();
List<Member> memberList = em.createQuery("select m from Member m where m.username like 'member%'", Member.class)
.getResultList();
memberList.forEach(System.out::println);
tx.commit();
}catch(Exception e){
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
▸ JPQL 실습 -2) setParameter
import entity.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
// 영속성 컨텍스트
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member memberA = new Member();
memberA.setUsername("memberA");
Member memberB = new Member();
memberB.setUsername("BB");
Member memberC = new Member();
memberC.setUsername("memberC");
Member memberD = new Member();
memberD.setUsername("memberD");
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
em.persist(memberD);
em.flush();
em.clear();
Member findMember = em.createQuery("select m from Member m where m.username = :username", Member.class)
.setParameter("username","memberA")
.getSingleResult();
System.out.println(findMember);
tx.commit();
}catch(Exception e){
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
▸ JPQL 실습 -3) paging
import entity.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
// 영속성 컨텍스트
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member memberA = new Member();
memberA.setUsername("memberA");
Member memberB = new Member();
memberB.setUsername("BB");
Member memberC = new Member();
memberC.setUsername("memberC");
Member memberD = new Member();
memberD.setUsername("memberD");
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
em.persist(memberD);
em.flush();
em.clear();
List<Member> memberList = em.createQuery("select m from Member m where m.username like 'member%'", Member.class)
.setFirstResult(0)
.setMaxResults(2)
.getResultList();
memberList.forEach(System.out::println);
tx.commit();
}catch(Exception e){
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
- setFirstResult()
- 첫 시작 index를 지정한다.
- setMaxResult()
- 몇 개를 가져올 것인지 지정한다.
📌 JPQL join
▸ inner join
import entity.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
// 영속성 컨텍스트
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Team teamA = new Team();
teamA.setName("teamA");
Member memberA = new Member();
memberA.setUsername("memberA");
memberA.setTeam(teamA);
Member memberB = new Member();
memberB.setUsername("BB");
Member memberC = new Member();
memberC.setUsername("memberC");
Member memberD = new Member();
memberD.setUsername("memberD");
em.persist(teamA);
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
em.persist(memberD);
em.flush();
em.clear();
List<Member> resultList = em.createQuery("select m from Member m join m.team", Member.class)
.getResultList();
resultList.forEach(System.out::println);
tx.commit();
}catch(Exception e){
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
▸ outer join
List<Member> resultList = em.createQuery("select m from Member m left join m.team", Member.class)
.getResultList();
▸ 서브쿼리
- where , having 절에서 서브쿼리를 날릴 수 있다.
- from 절의 서브 쿼리는 현재 JPQL에서는 불가능
- join으로 풀 수 있으면 join으로 해결하면 된다.
▸ cf) 묵시적 조인
List<Team> resultList = em.createQuery("select m.team from Member m", Team.class)
.getResultList();
- join 조건을 명시해주지 않아도 join이 가능하다.
- 이를 묵시적 join이라고 한다.
- 그런데, 이렇게 묵시적으로 하면 나중에 query 를 튜닝해야 했을 때 어렵다.
- 그래서 명시적으로 join해야 한다.
▸ 참고
- 현재는 간단한 형태의 query만 다루었지만, JPQL 역시 자잘하게 학습해야 하는 양이 방대하다. 이번 스터디에서는 필수적으로 알아야하는 query의 형태에 대해서만 학습하고, 나머지는 프로젝트에서 실습하면서 학습하는 것이 좋을 것 같다.
📌 Spring data jpa
▸ 실습환경
▸ application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: "jdbc:mysql://localhost:3306/스키마이름?userSSL=false&useUnicode=true&serverTimezone=Asia/Seoul"
username: 이름
password: 비밀번호
hikari:
auto-commit: false
connection-test-query: SELECT 1
minimum-idle: 10
maximum-pool-size: 50
transaction-isolation: TRANSACTION_READ_UNCOMMITTED
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
# show_sql: true
format_sql: true
logging:
level:
org.hibernate.SQL: debug
# org.hibernate:type: trace
📌 실습 -1) save() , find()
▸ Member.java
@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String name;
public Member(String name) {
this.name = name;
}
}
▸ MemberJpaRepository.java
package com.datajpastudy.entity;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Repository
public class MemberJpaRepository {
@PersistenceContext
private EntityManager em;
public Member save(Member member){
em.persist(member);
return member;
}
public Member find(Long id){
return em.find(Member.class,id);
}
}
▸ MemberRepository.java
package com.datajpastudy.entity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MemberRepository extends JpaRepository<Member,Long> {
}
▸ MemberJpaRepositoryTest.java
package com.datajpastudy.entity;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@SpringBootTest
@Transactional
@Rollback
class MemberJpaRepositoryTest {
@Autowired
MemberJpaRepository memberJpaRepository;
@Autowired
MemberRepository memberRepository;
@DisplayName("1. 기본 jpa 를 이용")
@Test
void testMember(){
Member member = new Member("memberA");
Member savedMember = memberJpaRepository.save(member);
Member findMember = memberJpaRepository.find(savedMember.getId());
Assertions.assertThat(savedMember.getId()).isEqualTo(findMember.getId());
}
@Test
void testMember2() {
Member member = new Member("memberB");
Member savedMember = memberRepository.save(member);
Optional<Member> findMember = memberRepository.findById(savedMember.getId());
findMember.ifPresent((mem)->{
System.out.println("===============");
System.out.println(mem.getName());
System.out.println("===============");
});
}
}
▸ 어떻게 interface만 있는데 기능을 가질 수 있는가?
@Test
void testRepo(){
System.out.println(memberRepository.getClass());
}
Spring data jpa가 상속받은 interface를 보고 구현 클래스를 알아서 만들어서 꽂아 버린다.
📌 인터페이스 기능
▸ MemberJpaRepository.java
@Repository
public class MemberJpaRepository {
@PersistenceContext
private EntityManager em;
public Member save(Member member){
em.persist(member);
return member;
}
public void delete(Member member){
em.remove(member);
}
public List<Member> findAll(){
return em.createQuery("select m from Member m",Member.class)
.getResultList();
}
public Optional<Member> findById(Long id){
Member member = em.find(Member.class, id);
return Optional.ofNullable(member);
}
public long count(){
return em.createQuery("select count(m) from Member m",Long.class)
.getSingleResult();
}
public Member find(Long id){
return em.find(Member.class,id);
}
}
위의 메서드들 전부 jpaRepository에서 제공한다.
📌 쿼리 메서드
▸ MemberRepository.java
public interface MemberRepository extends JpaRepository<Member,Long> {
List<Member> findByUsername(String username);
}
- 찾고 싶은 값이 있다면 위와 같이 findBy~ 로 메서드를 정의해주면 된다.
- 만약 username과 age가 해당 parameter보다 큰 값을 찾고 싶다면?
▸ MemberJpaRepository.java
public List<Member> findByNameAndAgeGreaterThan(String name,int age){
return em.createQuery("select m from Member m where m.name = :name and m.age > :age")
.setParameter("name",name)
.setParameter("age",age)
.getResultList();
}
▸ MemberRepository.java
public interface MemberRepository extends JpaRepository<Member,Long> {
List<Member> findByUsernameAndAgeGreaterThan(String username,int age);
}
▸ MemberJpaRepositoryTest.java
@Test
void findByUsernameAndAgeGreaterThen() {
// given
Member m1 = new Member("AAA",10);
Member m2 = new Member("AAA",20);
Member m3 = new Member("AAA",30);
// when
memberJpaRepository.save(m1);
memberJpaRepository.save(m2);
memberJpaRepository.save(m3);
List<Member> aaa = memberRepository.findByUsernameAndAgeGreaterThan("AAA", 15);
aaa.forEach(System.out::println);
// then
}
공식문서 참조
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods
parameter가 2개 정도까지는 해당 기능을 이용하되, 2개가 넘어가면 JPQL을 이용하는게 낫다. ( QueryDSL )
'SSAFY 6기 > 📌project' 카테고리의 다른 글
jpa study #3 - 프록시 & 연관관계 매핑 & 고급매핑 (0) | 2022.01.13 |
---|---|
jpa study #2 - jpa의 동작 원리 영속성 컨텍스트 (0) | 2022.01.11 |
jpa study #1 - jpa의 동작 원리 영속성 컨텍스트 (0) | 2022.01.09 |
Apato project (0) | 2021.11.18 |
댓글