JPQL 중급 문법

[강의] 자바 ORM 표준 JPA 프로그래밍 - 기본편


JPQL - 경로 표현식

  • **.(점)**을 찍어 객체 그래프를 탐색하는 것
1
2
3
4
5
6
select 
**m.username** -> 상태 필드
from Member m
join **m.team t -**> 단일 값 연관 필드
join **m.orders o** -> 컬렉션 값 연관 필드
where [t.name](http://t.name/) = '팀A'

경로 표현식 용어 정리

  • 상태 필드(state field) : 단순히 값을 저장하기 위한 필드
    (ex: m.username)
  • 연관 필드(association field) : 연관 관계를 위한 필드
    • 단일 값 연관 필드:
      @ManyToOne, @OneToOne, 대상이 엔티티(ex: m.team)
    • 컬렉션 값 연관 필드:
      @OneToMany, @ManyToMany, 대상이 컬렉션(ex: m.orders)

경로표현식 특징

  • 상태 필드(state field) : 경로 탐색의 끝, 탐색X
  • 단일 값 연관 경로 : 묵시적 내부 조인(inner join) 발생, 탐색O
  • 컬렉션 값 연관 경로 : 묵시적 내부 조인 발생, 탐색X
    • FROM 절에서 명시적 조인을 통해 별칭을 얻으면 별칭을 통해 탐색 가능

상태 필드 경로 탐색

  • 동일하다
1
2
3
4
5
6
7
// JPQL 
**select m.username, m.age**
**from Member m

//** SQL
**select m.username, m.age
from Member m**

단일 값 연관 경로 탐색

1
2
3
4
5
6
7
8
9
//JPQL 
select **o.member**
from Order o

// SQL
select m.*
from Orders o
**inner join Member m**
**on o.member_id = m.id**

명시직 조인, 묵시적 조인

  • 명시적 조인: join 키워드 직접 사용

    • select m from Member m join m.team t
  • 묵시적 조인: 경로 표현식에 의해 묵시적으로 SQL 조인 발생
    (내부 조인만 가능)

    • select m.team from Member m

경로 표현식 예제

1
2
3
4
5
6
7
8
9
10
// 성공
select o.member.team
from Order o

// 성공
select t.members from Team
// 실패
select t.members.username from Team t
// 성공
select m.username from Team t join t.members m

경로 탐색을 사용한 묵시적 조인 시 주의사항

  • 항상 내부 조인
  • 컬렉션은 경로 탐색의 끝, 명시적 조인을 통해 별칭을 얻어야 함
  • 경로 탐색은 주로 SELECT, WHERE 절에서 사용하지만 묵시적 조인으로 인해 SQL의 FROM (JOIN) 절에 영향을 줌

실무 조언

  • 가급적 묵시적 조인 대신에 명시적 조인 사용
  • 조인은 SQL 튜닝에 중요 포인트
  • 묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어려움


JPQL - 페치 조인 (fetch join)

  • 실무에서 정말 정말 중요함

페치 조인 (fetch join)

  • SQL 조인 종류X
  • JPQL에서 성능 최적화를 위해 제공하는 기능이다
  • 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능
  • join fetch 명령어 사용
  • Fetch Join ::= [ LEFT [OUTER] | INNER ] JOIN FETCH 조인경로

엔티티 Fetch Join

  • 회원을 조회하면서 연관된 팀도 함께 조회(SQL 한 번에)
  • SQL을 보면 회원 뿐만 아니라 팀(T.*)도 함께 SELECT
1
2
3
4
5
6
7
8
9
10
[JPQL]
select m
from Member m
**join fetch** m.team

[SQL]
SELECT M.*, **T.***
FROM MEMBER M
**INNER JOIN TEAM T**
ON M.TEAM_ID=T.ID

Untitled

Fetch Join 사용 코드

1
2
3
4
5
6
7
8
String jpql = "select m from Member m join fetch m.team";
List<Member> members = em.createQuery(jpql, Member.class)
.getResultList();
for (Member member : members) {
//Fetch Join으로 회원과 팀을 함께 조회해서 지연 로딩X
System.out.println("username = " + member.getUsername() + ", " +
"teamName = " + member.getTeam().name());
}
  • 결과
  • username = 회원1, teamname = 팀A
    username = 회원2, teamname = 팀A
    username = 회원3, teamname = 팀B

컬렉션 Fetch Join

  • 일대다 관계, 컬렉션 Fetch Join
1
2
3
4
5
6
7
8
9
10
11
[JPQL]
select t
from Team t
**join fetch t.members**
where t.name = ‘팀A'

[SQL]
SELECT T.*, **M.***
FROM TEAM T
INNER JOIN MEMBER M ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A'

Untitled

컬렉션 Fetch Join 사용 코드

1
2
3
4
5
6
7
8
9
String jpql = "select t from Team t join fetch t.members where t.name = '팀A'"
List<Team> teams = em.createQuery(jpql, Team.class).getResultList();
for(Team team : teams) {
System.out.println("teamname = " + team.getName() + ", team = " + team);

for (Member member : team.getMembers()) {
//Fetch Join으로 팀과 회원을 함께 조회해서 지연 로딩 발생 안함
System.out.println(“-> username = " + **member.getUsername()**+ ", member = " + member);
}
  • 결과
  • teamname = 팀A, team = Team@0x100

-> username = 회원1, member = Member@0x200
-> username = 회원2, member = Member@0x300
teamname = 팀A, team = Team@0x100
-> username = 회원1, member = Member@0x200
-> username = 회원2, member = Member@0x300

Fetch Join과 DISTINCT

  • SQL의 DISTINCT는 중복된 결과를 제거하는 명령
  • JPQL의 DISTINCT 2가지 기능 제공
      1. SQL에 DISTINCT를 추가
      1. 애플리케이션에서 엔티티 중복 제거

Fetch Join과 DISTINCT

1
2
3
**select distinct t
from Team t join fetch t.members
where [t.name](http://t.name/) = ‘팀A’**
  • SQL에 DISTINCT를 추가하지만 데이터가 다르므로 SQL 결과에서 중복제거 실패

Untitled

Fetch Join과 DISTINCT

  • DISTINCT가 추가로 애플리케이션에서 중복 제거시도
  • 같은 식별자를 가진 Team 엔티티 제거

Untitled

  • [DISTINCT 추가시 결과]
  • teamname = 팀A, team = Team@0x100

-> username = 회원1, member = Member@0x200
-> username = 회원2, member = Member@0x300

하이버네이트6 변경 사항

  • DISTINCT가 추가로 애플리케이션에서 중복 제거 시도
  • -> 하이버네이트6 부터는 DISTINCT 명령어를 사용하지 않아
    도 애플리케이션에서 중복 제거가 자동으로 적용
    됩니다.

Fetch Join과 일반 조인의 차이

  • 일반 조인 실행 시 연관된 엔티티를 함께 조회하지 않음
1
2
3
4
5
6
7
8
9
10
[JPQL]
select t
from Team t join t.members m
where t.name = ‘팀A'

[SQL]
SELECT **T.***
FROM TEAM T
INNER JOIN MEMBER M ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A'

Fetch Join과 일반 조인의 차이

  • JPQL은 결과를 반환할 때 연관 관계 고려X
  • 단지 SELECT 절에 지정한 엔티티만 조회할 뿐
  • 여기서는 팀 엔티티만 조회하고, 회원 엔티티는 조회X

Fetch Join과 일반 조인의 차이

  • Fetch Join을 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩)
  • Fetch Join은 객체 그래프를 SQL 한번에 조회하는 개념

Fetch Join 실행 예시

  • Fetch Join은 연관된 엔티티를 함께 조회함
1
2
3
4
5
6
7
8
9
10
11
12
[JPQL]
select t
from Team t
**join fetch** t.members
where t.name = ‘팀A'

[SQL]
SELECT **T.*, M.***
FROM TEAM T
INNER JOIN MEMBER M
ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A'

Fetch Join의 특징과 한계

  • Fetch Join 대상에는 별칭을 줄 수 없다.
    • 하이버네이트는 가능, 가급적 사용X
  • 둘 이상의 컬렉션은 Fetch Join 할 수 없다.
  • 컬렉션을 Fetch Join하면 페이징 API(setFirstResult, setMaxResults)를 사용할 수 없다.
    • 일대일, 다대일 같은 단일 값 연관 필드들은 Fetch Join해도 페이징 가능
    • 하이버네이트는 경고 로그를 남기고 메모리에서 페이징(매우 위험)

Fetch Join의 특징과 한계

  • 연관된 엔티티들을 SQL 한 번으로 조회 - 성능 최적화

  • 엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선함

    • //글로벌 로딩 전략
    • @OneToMany(fetch = FetchType.LAZY)
  • 실무에서 글로벌 로딩 전략은 모두 지연 로딩

  • 최적화가 필요한 곳은 Fetch Join 적용

  • m.username 하는 식으로 사용 X

  • 기본적으로 Fetch Join은 결과 전부를 가져오는 것이기에 사용 불가

  • 일부만 끌어오려면 다른 Join을 사용하는 것이 좋다

Fetch Join - 정리

  • 모든 것을 Fetch Join으로 해결할 수 는 없음
  • Fetch Join은 객체 그래프를 유지할 때 사용하면 효과적
  • 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 하면, Fetch Join 보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 DTO로 반환하는 것이 효과적

JPQL - 다형성 쿼리

  • 중요도 - 하

Untitled

TYPE

  • 조회 대상을 특정 자식으로 한정
  • 예) Item 중에 Book, Movie를 조회해라
1
2
3
4
5
6
7
[JPQL]
select i from Item i
where type(i) IN (Book, Movie)

[SQL]
select i from i
where i.DTYPE in (‘B’, ‘M’)

TREAT(JPA 2.1)

  • 자바의 타입 캐스팅과 유사
  • 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용
  • FROM, WHERE, SELECT(하이버네이트 지원) 사용

TREAT(JPA 2.1) 추가 설명

예) 부모인 Item과 자식 Book이 있다.

1
2
3
4
5
6
7
[JPQL]
select i from Item i
where treat(i as Book).author = ‘kim’

[SQL]
select i.* from Item i
where i.DTYPE = ‘B’ and i.author = ‘kim’

JPQL - 엔티티 직접 사용

  • JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용
1
2
3
4
5
6
[JPQL]
select **count([m.id](http://m.id/))** from Member m //엔티티의 아이디를 사용
select **count(m)** from Member m //엔티티를 직접 사용

[SQL](JPQL 둘다 같은 다음 SQL 실행)
select count([m.id](http://m.id/)) as cnt from Member m

엔티티 직접 사용 - 기본 키 값

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 엔티티를 파라미터로 전달
String jpql = “select m from Member m where m = :member”;
List resultList = em.createQuery(jpql)
.setParameter("member", member)
.getResultList();

// 식별자를 직접 전달
String jpql = “select m from Member m where m.id = :memberId”;
List resultList = em.createQuery(jpql)
.setParameter("memberId", memberId)
.getResultList();

//실행된 SQL
select m.* from Member m where m.id=?

엔티티 직접 사용 - 외래 키 값

1
2
3
4
5
6
7
8
9
10
11
12
13
Team team = em.find(Team.class, 1L);
String qlString = “select m from Member m where m.team = :team”;
List resultList = em.createQuery(qlString)
.setParameter("team", team)
.getResultList();

String qlString = “select m from Member m where m.team.id = :teamId”;
List resultList = em.createQuery(qlString)
.setParameter("teamId", teamId)
.getResultList();

// 실행된 SQL
select m.* from Member m where m.team_id=?

JPQL - Named 쿼리

Named 쿼리 - 정적 쿼리

  • 미리 정의해서 이름을 부여해두고 사용하는 JPQL
  • 정적 쿼리
  • 어노테이션, XML에 정의
  • 애플리케이션 로딩 시점에 초기화 후 재사용
  • **애플리케이션 로딩 시점에 쿼리를 검증 *****

Named 쿼리 - 어노테이션

1
2
3
4
5
6
7
8
9
10
11
12
@Entity
@NamedQuery(
name = "Member.findByUsername",
query="select m from Member m where m.username = :username")
public class Member {
...
}

List<Member> resultList =
em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "회원1")
.getResultList();

Named 쿼리 - XML에 정의

  • META-INF/persistence.xml
1
2
<persistence-unit name="jpabook" >
<mapping-file>META-INF/ormMember.xml</mapping-file>
  • META-INF/ormMember.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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>

Named 쿼리 환경에 따른 설정

  • XML이 항상 우선권을 가진다.
  • 애플리케이션 운영 환경에 따라 다른 XML을 배포할 수 있다

JPQL - 벌크 연산

  • 재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면?
  • JPA 변경 감지 기능으로 실행하려면 너무 많은 SQL 실행
      1. 재고가 10개 미만인 상품을 리스트로 조회한다.
      1. 상품 엔티티의 가격을 10% 증가한다.
      1. 트랜잭션 커밋 시점에 변경감지가 동작한다.
  • 변경된 데이터가 100건이라면 100번의 UPDATE SQL 실행

벌크 연산 예제

  • 쿼리 한 번으로 여러 테이블 로우 변경(엔티티)
  • executeUpdate()의 결과영향받은 엔티티 수 반환
  • UPDATE, DELETE 지원
  • INSERT(insert into .. select, 하이버네이트 지원)
1
2
3
4
5
6
7
String qlString = "update Product p " +
"set p.price = p.price * 1.1 " +
"where p.stockAmount < :stockAmount";

int resultCount = em.createQuery(qlString)
.setParameter("stockAmount", 10)
.executeUpdate();

벌크 연산 주의

  • 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리
  • 벌크 연산을 먼저 실행
  • 벌크 연산 수행 후 영속성 컨텍스트 초기화

JPQL

[강의] 자바 ORM 표준 JPA 프로그래밍 - 기본편


JPA는 다양한 쿼리 방법을 지원

  • JPQL
  • JPA Criteria
  • QueryDSL
  • 네이티브 SQL
  • JDBC API 직접 사용, MyBatis, SpringJdbcTemplate 함께 사용

JPQL 소개

  • 가장 단순한 조회 방법
  • EntityManager.find()
  • 객체 그래프 탐색(a.getB().getC())
  • 나이가 18살 이상인 회원을 모두 검색하고 싶다면?

JPQL

  • JPA를 사용하면 엔티티 객체를 중심으로 개발
  • 문제는 검색 쿼리
  • 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색
  • 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능
  • 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL이 필요

JPQL 추가 설명

  • JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공
  • SQL과 문법 유사
    • SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 지원
  • JPQL은 엔티티 객체를 대상으로 쿼리
  • SQL은 데이터베이스 테이블을 대상으로 쿼리

JPQL 예시

  • 일반 쿼리와 유사하다
1
2
3
4
//검색
String jpql = "**select m From Member m where m.name like ‘%hello%'**";
List<Member> result = em.createQuery(jpql, Member.class)
.getResultList();

JPQL 추가 설명

  • 테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리
  • SQL을 추상화해서 특정 데이터베이스 SQL에 의존X
  • JPQL을 한마디로 정의하면 객체 지향 SQL

JPQL과 실행된 SQL

  • JQPL
1
2
3
//검색
String jpql = "select m from Member m where m.age > 18";
List<Member> result = em.createQuery(jpql, Member.class).getResultList();
  • 실행된 SQL
1
2
3
4
5
6
7
8
9
select
m.id as id,
m.age as age,
m.USERNAME as USERNAME,
m.TEAM_ID as TEAM_ID
from
Member m
where
m.age>18

Criteria 소개

  • 문자가 아닌 JAVA 코드로 JPQL을 작성할 수 있음

  • JPQL 빌더 역할

  • JPA 공식 기능

  • 단점

    • 너무 복잡하고 실용성이 없다.
  • Criteria 대신에 QueryDSL 사용 권장

  • 예시

1
2
3
4
5
6
7
8
9
10
11
//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();

QueryDSL 소개

  • 문자가 아닌 자바코드로 JPQL을 작성할 수 있음

  • JPQL 빌더 역할

  • 컴파일 시점에 문법 오류를 찾을 수 있음

  • 동적 쿼리 작성 편리

  • 단순하고 쉬움

  • 실무 사용 권장

  • JPQL만 잘 알면 쉽게 사용 가능하다

  • 예시

1
2
3
4
5
6
7
8
9
10
//JPQL
//select m from Member m where m.age > 18
JPAFactoryQuery query = new JPAQueryFactory(em);
QMember m = QMember.member;

List<Member> list =
query.selectFrom(m)
.where(m.age.gt(18))
.orderBy(m.name.desc())
.fetch();

네이티브 SQL 소개

  • JPA가 제공하는 SQL을 직접 사용하는 기능

  • JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능

  • 예) 오라클 CONNECT BY, 특정 DB만 사용하는 SQL 힌트

  • 예시

1
2
3
4
String sql = 
SELECT ID, AGE, TEAM_ID, NAME FROM MEMBER WHERE NAME = ‘kim’";
List<Member> resultList =
em.createNativeQuery(sql, Member.class).getResultList();

JDBC 직접 사용, SpringJdbcTemplate 등

  • JPA를 사용하면서 JDBC 커넥션을 직접 사용하거나, 스프링 JdbcTemplate, 마이바티스등을 함께 사용 가능
  • 단 영속성 컨텍스트를 적절한 시점에 강제로 플러시 필요
  • 예) JPA를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트 수동 플러시

JPQL(Java Persistence Query Language)

JPQL - 기본 문법과 기능

JPQL 소개

  • JPQL은 객체 지향 쿼리 언어다.
  • 따라서 테이블을 대상으로 쿼리하는 것이 아니라 엔티티 객체를 대상으로 쿼리한다.
  • JPQL은 SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.
  • JPQL은 결국 SQL로 변환된다.

Untitled

Untitled

JPQL 문법

Untitled

JPQL 문법

  • select m from Member as m where m.age > 18
  • 엔티티와 속성은 대소문자 구분O (Member, age)
  • JPQL 키워드는 대소문자 구분X (SELECT, FROM, where)
  • 엔티티 이름 사용, 테이블 이름이 아님(Member)
  • 별칭은 필수(m) (as는 생략가능)

집합과 정렬

  • GROUP BY, HAVING
  • ORDER BY
1
2
3
4
5
6
7
8
select
COUNT(m), //회원수
SUM(m.age), //나이 합
AVG(m.age), //평균 나이
MAX(m.age), //최대 나이
MIN(m.age) //최소 나이
from
Member m

TypeQuery, Query

  • TypeQuery
    • 반환 타입이 명확할 때 사용’
1
2
TypedQuery<Member> query =
em.createQuery("SELECT m FROM Member m", Member.class);
  • Query
    • 반환 타입이 명확하지 않을 때 사용
1
2
Query query = 
em.createQuery("SELECT m.username, m.age from Member m");

결과 조회 API

  • query.getResultList() : 결과가 하나 이상일 때, 리스트 반환
    • 결과가 없으면 빈 리스트 반환
  • query.getSingleResult() : 결과가 정확히 하나, 단일 객체 반환
    • 결과가 없으면: javax.persistence.NoResultException
    • 둘 이상이면: javax.persistence.NonUniqueResultException

파라미터 바인딩 - 이름 기준, 위치 기준

  • 이름 기준
1
2
SELECT m FROM Member m where m.username=:username 
query.setParameter("username", usernameParam);
  • 위치 기준
    • 사용 X
    • 위치일 경우 순서 밀릴 가능성 있음
1
2
SELECT m FROM Member m where m.username=?1
query.setParameter(1, usernameParam);

프로젝션

  • SELECT 절에 조회할 대상을 지정하는 것
  • 프로젝션 대상
    • 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자등 기본 데이터 타입)
  • SELECT m FROM Member m -> 엔티티 프로젝션
  • SELECT m.team FROM Member m -> 엔티티 프로젝션
  • SELECT m.address FROM Member m -> 임베디드 타입 프로젝션
  • SELECT m.username, m.age FROM Member m -> 스칼라 타입 프로젝션
  • DISTINCT로 중복 제거

프로젝션 - 여러 값 조회

  • SELECT m.username, m.age FROM Member m
    1. Query 타입으로 조회
    1. Object[] 타입으로 조회
    1. new 명령어로 조회
    • 단순 값을 DTO로 바로 조회
      SELECT

       **`new** jpabook.jpql.UserDTO(m.username, m.age)` 
      

      FROM

        `Member m`
      
    • 패키지 명을 포함한 전체 클래스 명 입력

    • 순서와 타입이 일치하는 생성자 필요

페이징 API

  • JPA는 페이징을 다음 두 API로 추상화
  • setFirstResult(int startPosition) : 조회 시작 위치
    (0부터 시작)
  • setMaxResults(int maxResult) : 조회할 데이터 수

페이징 API 예시

1
2
3
4
5
6
//페이징 쿼리
String jpql = "select m from Member m order by m.name desc";
List<Member> resultList = em.createQuery(jpql, Member.class)
.setFirstResult(10)
.setMaxResults(20)
.getResultList();

페이징 API - MySQL 방언

1
2
3
4
5
6
7
8
9
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 ?, ?

페이징 API - Oracle 방언

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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_ > ?

조인

  • 내부 조인:
    SELECT m FROM Member m [INNER] JOIN m.team t
  • 외부 조인:
    SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
  • 세타 조인:
    select count(m) from Member m, Team t where m.username= [t.name](http://t.name/)

조인 - ON 절

  • ON절을 활용한 조인(JPA 2.1부터 지원)
    1. 조인 대상 필터링
    1. 연관관계 없는 엔티티 외부 조인(하이버네이트 5.1부터)

1. 조인 대상 필터링

예) 회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인

1
2
3
4
5
6
7
8
9
10
11
12
**// 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'**

2. 연관 관계 없는 엔티티 외부 조인

예) 회원의 이름과 팀의 이름이 같은 대상 외부 조인

1
2
3
4
5
6
7
8
9
10
11
// 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

서브 쿼리

1
2
3
4
5
6
7
// 나이가 평균보다 많은 회원
select m from Member m
where m.age > **(select avg(m2.age) from Member m2)**

// 한 건이라도 주문한 고객
select m from Member m
where **(select count(o) from Order o where m = o.member)** > 0

서브 쿼리 지원 함수

  • [NOT] EXISTS (subquery): 서브쿼리에 결과가 존재하면 참
    • {ALL | ANY | SOME} (subquery)
    • ALL 모두 만족하면 참
    • ANY, SOME: 같은 의미, 조건을 하나라도 만족하면 참
  • [NOT] IN (subquery): 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참

서브 쿼리 - 예제

1
2
3
4
5
6
7
8
9
10
11
// 팀A 소속인 회원
select m from Member m
where exists (select t from m.team t where t.name = ‘팀A')

//전체 상품 각각의 재고보다 주문량이 많은 주문들
select o from Order o
where o.orderAmount > **ALL** (select p.stockAmount from Product p)

// 어떤 팀이든 팀에 소속된 회원
select m from Member m
where m.team = **ANY** (select t from Team t)

JPA 서브 쿼리 한계

  • JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능
  • SELECT 절도 가능(하이버네이트에서 지원)
  • FROM 절의 서브 쿼리는 현재 JPQL에서 불가능
    • 조인으로 풀 수 있으면 풀어서 해결

하이버네이트6 변경 사항

  • 하이버네이트6 부터는 FROM 절의 서브쿼리를 지원합니다.

JPQL 타입 표현

  • 문자: ‘HELLO’, ‘She’’s’
  • 숫자: 10L(Long), 10D(Double), 10F(Float)
  • Boolean: TRUE, FALSENUM: jpabook.MemberType.Admin (패키지명 포함)\
  • 엔티티 타입: TYPE(m) = Member (상속 관계에서 사용)

JPQL 기타

  • SQL과 문법이 같은 식
  • EXISTS, IN
  • AND, OR, NOT
  • =, >, >=, <, <=, <>
  • BETWEEN, LIKE, IS NULL

조건식 - CASE 식

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 기본 CASE 식
select
case when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m

// 단순 CASE 식
select
case t.name
when '팀A' then '인센티브110%'
when '팀B' then '인센티브120%'
else '인센티브105%'
end
from Team t

조건식 - CASE 식

  • COALESCE
    • 하나씩 조회해서 null이 아니면 반환
  • NULLIF
    • 두 값이 같으면 null 반환, 다르면 첫번째 값 반환
1
2
3
4
5
6
// 사용자 이름이 없으면 이름 없는 회원을 반환
select coalesce(m.username,'이름 없는 회원') from Member m

// 사용자 이름이 ‘관리자’면 null을 반환하고 나머지는 본인의 이름을 반환
select NULLIF(m.username, '관리자') from Member m

JPQL 기본 함수

  • CONCAT
  • SUBSTRING
  • TRIM
  • LOWER, UPPER
  • LENGTH
  • LOCATE
  • ABS, SQRT, MOD
  • SIZE, INDEX(JPA 용도

사용자 정의 함수 호출

  • 하이버네이트는 사용 전 방언에 추가해야 한다.
  • 사용하는 DB 방언을 상속받고, 사용자 정의 함수를 등록한다
1
**select function**('group_concat', i.name) **from** Item i

JPA 값 타입

[강의] 자바 ORM 표준 JPA 프로그래밍 - 기본편


  • 중요 - 임베디드 타입, 값 타입 컬렉션

JPA의 데이터 타입 분류

  • 엔티티 타입

    • @Entity로 정의하는 객체
    • 데이터가 변해도 식별자로 지속해서 추적 가능
    • 예) 회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식 가능
  • 값 타입

    • int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
    • 식별자가 없고 값만 있으므로 변경시 추적 불가
    • 예) 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체

값 타입 분류

  • 기본값 타입
    • 자바 기본 타입(int, double)
    • 래퍼 클래스(Integer, Long)
    • String
  • 임베디드 타입(embedded type, 복합 값 타입)
  • 컬렉션 값 타입(collection value type)

기본값 타입

  • 기본값 타입
    • 예): String name, int age
  • 생명주기를 엔티티의 의존
    • 예) 회원을 삭제하면 이름, 나이 필드도 함께 삭제
  • 값 타입은 공유하면 X
    • 예) 회원 이름 변경 시 다른 회원의 이름도 함께 변경되면 안됨

참고: 자바의 기본 타입은 절대 공유X

  • int, double 같은 기본 타입(primitive type)은 절대 공유X
  • 기본 타입은 항상 값을 복사함
  • Integer같은 래퍼 클래스나 String 같은 특수한 클래스는 공유 가능한 객체이지만 변경X

임베디드 타입(복합 값 타입)

임베디드 타입

  • 새로운 값 타입을 직접 정의할 수 있음
  • JPA는 임베디드 타입(embedded type)이라 함
  • 주로 기본 값 타입을 모아서 만들어서 복합 값 타입이라고도 함
  • int, String과 같은 값 타입

Untitled

임베디드 타입 사용법

  • @Embeddable
    • 값 타입을 정의하는 곳에 표시
  • @Embedded
    • 값 타입을 사용하는 곳에 표시
  • 기본 생성자 필수

임베디드 타입의 장점

  • 재사용
  • 높은 응집도
  • Period.isWork() 처럼 해당 값 타입만 사용하는 의미 있는 메소
    드를 만들 수 있음
  • 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티
    티에 생명주기를 의존

임베디드 타입과 테이블 매핑

  • 임베디드 타입은 엔티티의 값일 뿐이다.
  • 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다.
  • 객체와 테이블을 아주 세밀하게(find-grained) 매핑하는 것이 가
  • 잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래
    스의 수가 더 많음

Untitled

임베디드 타입과 연관관계

Untitled

@AttributeOverride: 속성 재정

  • 한 엔티티에서 같은 값 타입을 사용하면?

  • 컬럼 명이 중복됨

  • @AttributeOverrides, @AttributeOverride를 사용해서 컬럼 명 속성을 재정의

  • 사용 예제

1
2
3
4
5
6
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "city", column = @Column(name = "WORK_CITY")),
@AttributeOverride(name = "street", column = @Column(name = "WORK_STREET")),
@AttributeOverride(name = "zipcode", column = @Column(name = "WORK_ZIP")),
})

값 타입과 불변 객체

  • 값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고 만든 개념이다.
  • 따라서 값 타입은 단순하고 안전하게 다룰 수 있어야 한다

값 타입 공유 참조

  • 임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험함
  • 부작용(side effect) 발생

Untitled

값 타입 복사

  • 값 타입의 실제 인스턴스인 값을 공유하는 것은 위험
  • 대신 값(인스턴스)를 복사해서 사용

Untitled

객체 타입의 한계

  • 항상 값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용
    을 피할 수 있다.
  • 문제는 임베디드 타입처럼 직접 정의한 값 타입은 자바의 기본 타입이 아니라 객체 타입이다.
  • 자바 기본 타입에 값을 대입하면 값을 복사한다.
  • 객체 타입은 참조 값을 직접 대입하는 것을 막을 방법이 없다.
  • 객체의 공유 참조는 피할 수 없다

객체 타입의 한계

Untitled

불변 객체

  • 객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단
  • 값 타입은 불변 객체(immutable object)로 설계해야함
  • 불변 객체: 생성 시점 이후 절대 값을 변경할 수 없는 객체
  • 생성자로만 값을 설정하고 수정자(Setter)를 만들지 않으면 됨
  • 참고: Integer, String은 자바가 제공하는 대표적인 불변 객체

  • 값 타입: 인스턴스가 달라도 그 안에 값이 같으면 같은 것으로 봐
    야 함
1
2
int a = 10; 
int b = 10;
1
2
Address a = new Address(“서울시”)
Address b = new Address(“서울시”)

값 타입의 비교

  • 동일성(identity) 비교 : 인스턴스의 참조 값을 비교
    • == 사용
  • 동등성(equivalence) 비교 : 인스턴스의 값을 비교
    • equals() 사용
  • 값 타입은 a.equals(b)를 사용해서 동등성 비교를 해야 함
  • 값 타입의 equals() 메소드를 적절하게 재정의(주로 모든 필드 사용)

값 타입 컬렉션

  • 값 타입을 하나 이상 저장할 때 사용
  • @ElementCollection, @CollectionTable 사용
  • 데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.
  • 컬렉션을 저장하기 위한 별도의 테이블이 필요

Untitled

값 타입 컬렉션 사용

  • 값 타입 저장 예제
  • 값 타입 조회 예제
    • 값 타입 컬렉션도 지연 로딩 전략 사용
  • 값 타입 수정 예제
  • 참고: 값 타입 컬렉션은 영속성 전에(Cascade) + 고아 객체 제거 기능을 필수로 가진다고 볼 수 있다.

값 타입 컬렉션의 제약사항

  • 값 타입은 엔티티와 다르게 식별자 개념이 없다.
  • 값은 변경하면 추적이 어렵다.
  • 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
  • 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 함:
    • null 입력X, 중복 저장X

값 타입 컬렉션 대안

  • 실무에서는 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려
  • 일대다 관계를 위한 엔티티를 만들고, 여기에서 값 타입을 사용
  • 영속성 전이(Cascade) + 고아 객체 제거를 사용해서 값 타입 컬렉션 처럼 사용
    • EX) AddressEntity

정리

  • 엔티티 타입의 특징

    • 식별자O
    • 생명 주기 관리
    • 공유
  • 값 타입의 특징

    • 식별자X
    • 생명 주기를 엔티티에 의존
    • 공유하지 않는 것이 안전(복사해서 사용)
    • 불변 객체로 만드는 것이 안전
  • 값 타입은 정말 값 타입이라 판단될 때만 사용 엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안됨

  • 식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면 그것은 값 타입이 아닌 엔티티

JPA 프록시, 연관 관계

[강의] 자바 ORM 표준 JPA 프로그래밍 - 기본편


프록시


Member를 조회할 때 Team도 함께 조회해야 할까?

Untitled

  • 회원과 팀 함께 출력
1
2
3
4
5
6
public void printUserAndTeam(String memberId) {
Member member = em.find(Member.class, memberId);
Team team = member.getTeam();
System.out.println("회원 이름: " + member.getUsername());
System.out.println("소속팀: " + team.getName());
}
  • 회원만 출력
1
2
3
4
5
public void printUser(String memberId) {
Member member = em.find(Member.class, memberId);
Team team = member.getTeam();
System.out.println("회원 이름: " + member.getUsername());
}

프록시 기초

  • em.find() vs em.getReference()

  • em.find()

    • 데이터베이스를 통해서 실제 엔티티 객체 조회
  • em.getReference()

    • 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
  • Proxy가 생성한 가짜 객체

Untitled

프록시 특징

Untitled

프록시 특징

  • 프록시 객체는 실제 객체의 참조(target)를 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출

Untitled

프록시 초기화

1
2
Member member = em.**getReference**(Member.class, “id1”);
member.getName();

Untitled

프록시 특징

  • 프록시 객체는 처음 사용할 때 한 번만 초기화
  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님
    • 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근만 가능
  • 프록시 객체는 원본 엔티티를 상속 받음, 따라서 타입 체크시 주의해야 함
    • (== 비교 실패, 대신 instance of 사용)
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference() 호출해도 실제 엔티티 반환
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생
    • 하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림

프록시 확인

  • 프록시 인스턴스의 초기화 여부 확인

PersistenceUnitUtil.isLoaded(Object entity)

  • 프록시 클래스 확인 방법

entity.getClass().getName() 출력(..javasist.. or HibernateProxy…)

  • 프록시 강제 초기화

org.hibernate.Hibernate.initialize(entity);

  • 참고: JPA 표준은 강제 초기화 없음
    강제 호출: member.getName()

즉시 로딩과 지연 로딩

  • 프록시를 활용한다

Member를 조회할 때 Team도 함께 조회해야 할까?

  • 단순히 member 정보만 사용하는 비즈니스 로직

println(member.getName());

Untitled

지연 로딩 LAZY을 사용해서 프록시로 조회

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;

@Column(name = "USERNAME")
private String name;

@ManyToOne(fetch = FetchType.**LAZY**) //**
@JoinColumn(name = "TEAM_ID")
private Team team;
..
}

지연로딩

Untitled

지연 로딩 LAZY을 사용해서 프록시로 조회

  • 코드
1
**Member member = em.find(Member.class, 1L);**

Untitled

  • 코드
1
2
**Team team = member.getTeam();
team.getName(); // 실제 team을 사용하는 시점에 초기화(DB 조회)**

Untitled

즉시 로딩

  • 가급적 사용하지 말 것

  • EAGER 가 보인다면 ‘즉시 로딩’

    • 거기에 더해 N:1, 1:1 의 경우도 기본이 ‘즉시 로딩’
  • 즉시 로딩 EAGER 를 사용해서 함께 조회

1
2
3
4
5
6
7
8
9
10
11
12
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.**EAGER**) //**
@JoinColumn(name = "TEAM_ID")
private Team team;
.
}

Untitled

프록시와 즉시 로딩 주의

  • 가급적 지연 로딩만 사용(특히 실무에서)
  • 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩

-> LAZY로 설정

  • @OneToMany, @ManyToMany는 기본이 지연 로딩

Untitled


영속성 전이 : CASCADE

  • 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들도 싶을 때
    • 예: 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장

Untitled

영속성 전이 : 저장

1
@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST)

Untitled

영속성 전이: CASCADE - 주의!

  • 영속성 전이는 연관 관계를 매핑하는 것과 아무 관련이 없음
  • 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐

CASCADE의 종류

  • ALL: 모두 적용
  • PERSIST: 영속
  • REMOVE: 삭제
  • MERGE: 병합
  • REFRESH: REFRESH
  • DETACH: DETACH

고아 객체

  • 어감, 뜻이 신경 쓰인다면 ‘Orphan 객체’ 라고 불러도 괜찮지 않을까

  • 고아 객체 제거: 부모 엔티티와 연관 관계가 끊어진 자식 엔티티를 자동으로 삭제

  • orphanRemoval = true

  • Parent parent1 = em.find(Parent.class, id);

    parent1.getChildren().remove(0);

    //자식 엔티티를 컬렉션에서 제거

  • DELETE FROM CHILD WHERE ID=?

고아 객체 - 주의

  • 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
  • 참조하는 곳이 하나일 때 사용해야 함!
  • 특정 엔티티가 개인 소유할 때 사용
  • @OneToOne, @OneToMany만 가능
  • 참고 : 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다.
  • 이것은 CascadeType.REMOVE처럼 동작한다.

JPA 고급 매핑

[강의] 자바 ORM 표준 JPA 프로그래밍 - 기본편


상속 관계 매핑

  • 관계형 데이터베이스는 상속 관계X
  • 슈퍼 타입, 서브 타입 관계라는 모델링 기법이 객체 상속과 유사
  • 상속 관계 매핑
    • 객체의 상속과 구조와 DB의 슈퍼 타입 , 서브 타입 관계를 매핑

Untitled

상속관계 매핑

  • 슈퍼 타입, 서브 타입 논리 모델을 실제 물리 모델로 구현하는 방법
  • 각각 테이블로 변환 -> 조인 전략
  • 통합 테이블로 변환 -> 단일 테이블 전략
  • 서브 타입 테이블로 변환 -> 구현 클래스마다 테이블 전략

주요 어노테이션

  • @Inheritance(strategy=InheritanceType.XXX)
    • JOINED : 조인 전략
    • SINGLE_TABLE : 단일 테이블 전략
    • TABLE_PER_CLASS : 구현 클래스마다 테이블 전략
  • @DiscriminatorColumn(name=“DTYPE”)
  • @DiscriminatorValue(“XXX”)

조인 전략

  • 조인 사용
  • DTYPE
    • Albun, Movie, Book 테이블 중 어느 것과 조인되어 있는지 표시해 줄 컬럼

Untitled

단일 테이블 전략

  • 한 테이블에 모두 몰아서 넣는다
  • 즉, JOIN 이 필요 없다

Untitled

구현 클래스마다 테이블 전략

  • 모든 테이블에 공통으로 컬럼을 넣는다
  • 어떤 테이블이든 있으니 가능

Untitled

D TYPE

  • 생성 방법

    • @Inheritance(strategy = InheritanceType.옵션)
    • @DiscriminatorColumn
  • 생성 방법 예시

  • 이를 거쳐 생성하면 D TYPE 컬럼이 같이 생성된다

1
2
3
4
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public class Locker {

조인 전략 정리

  • 장점
    • 테이블 정규화
    • 외래 키 참조 무결성 제약조건 활용가능
    • 저장 공간 효율화
  • 단점
    • 조회 시 조인을 많이 사용, 성능 저하
    • 조회 쿼리가 복잡함
    • 데이터 저장시 INSERT SQL 2번 호출

단일 테이블 전략 정리

  • 장점
    • 조인이 필요 없으므로 일반적으로 조회 성능이 빠름
    • 조회 쿼리가 단순함
  • 단점
    • 자식 엔티티가 매핑한 컬럼은 모두 null 허용
    • 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다.
    • 상황에 따라서 조회 성능이 오히려 느려질 수 있다.

구현 클래스마다 테이블 전략

  • 실문에서 사용 X
  • 이 전략은 데이터베이스 설계자와 ORM 전문가 둘 다 추천 X
  • 장점
    • 서브 타입을 명확하게 구분해서 처리할 때 효과적
    • not null 제약조건 사용 가능
  • 단점
    • 여러 자식 테이블을 함께 조회할 때 성능이 느림(UNION SQL 필요)
    • 자식 테이블을 통합해서 쿼리하기 어려움

@MappedSuperclass

  • 공통 매핑 정보가 필요할 때 사용(id, name)
  • 컬럼을 상속 시켜줄 슈퍼 클래스가 사용한다

Untitled

@MappedSuperclass 정리

  • 상속 관계 매핑X

  • 엔티티X, 테이블과 매핑X

  • 부모 클래스를 상속 받는 자식 클래스에 매핑 정보만 제공

  • 조회, 검색 불가(em.find(BaseEntity) 불가)

  • 직접 생성해서 사용할 일이 없으므로 추상 클래스 권장

  • 테이블과 관계 없고, 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할

  • 주로 등록일, 수정일, 등록자, 수정자 등 전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사

  • 참고

    • @Entity 클래스는 엔티티나 @MappedSuperclass로 지정한 클래스만 상속 가능

JPA 연관 관계 매핑

[강의] 자바 ORM 표준 JPA 프로그래밍 - 기본편


학습 목표

  • 객체와 테이블 연관 관계의 차이를 이해
  • 객체의 참조와 테이블의 외래 키를 매핑

용어 이해

  • 방향(Direction): 단방향, 양방향
  • 다중성(Multiplicity)
    • 다대일(N:1)
    • 일대다(1:N)
    • 일대일(1:1)
    • 다대다(N:M)
  • 연관 관계의 주인**(Owner)** : 객체 양방향 연관 관계는 관리 주인이 필요

연관 관계가 필요한 이유

  • 객체 지향 설계의 목표자율적인 객체들의 협력 공동체를 만드는 것이다.

  • 예제 시나리오

    • 회원과 팀이 있다.
    • 회원은 하나의 팀에만 소속될 수 있다.
    • 회원과 팀은 다대일 관계다
  • 객체를 테이블에 맞추어 모델링

Untitled


객체를 테이블에 맞추어 데이터 중심으로 모델링하면**,
협력 관계를 만들 수 없다**


객체 지향 모델링 (객체 연관 관계 사용)

Untitled

객체 지향 모델링

  • 객체의 참조와 테이블의 외래 키를 매핑
  • 코드 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entity
public class Member {

@Id
@GeneratedValue
private Long id;

@Column(name = "USERNAME")
private String name;

private int age;

// 다대일 관계
// 외래키 키 매핑
@ManyToOne
@**JoinColumn**(name = "TEAM_ID")
private Team team;
  • 객체 지향 모델링

Untitled

  • 연관 관계 저장
1
2
3
4
5
6
7
8
9
10
//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);

//회원 저장
Member member = new Member();
member.setName("member1");
member.setTeam(team); //단방향 연관관계 설정, 참조 저장
em.persist(member);

양방향 연관 관계와 연관 관계의 주인

  • 양방향 매핑

Untitled

  • 양방향 매핑
  • Member 엔티티는 단방향과 동일
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;

@Column(name = "USERNAME")
private String name;

private int age;

// 키 매핑
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
  • (Team 엔티티는 컬렉션 추가)
  • mappedBy 사용
1
2
3
4
5
6
7
8
9
10
11
12
@Entity
public class Team {
@Id
@GeneratedValue
private Long id;

private String name;

@OneToMany(**mappedBy** = "team")
List<Member> members = new ArrayList<Member>();

}
  • 양방향 매핑
  • 반대 방향으로 객체 그래프 탐색
1
2
3
//조회
Team findTeam = em.find(Team.class, team.getId());
int memberSize = findTeam.getMembers().size(); //역방향 조회

객체와 테이블이 관계를 맺는 차이

Untitled

객체와 테이블이 관계를 맺는 차이

Untitled

  • 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단뱡향 관계 2개다.
  • 객체를 양방향으로 참조하려면 단방향 연관 관계를 2개 만들어야 한다

Untitled

  • 테이블은 외래 키 하나로 두 테이블의 연관 관계를 관리
  • MEMBER.TEAM_ID 외래 키 하나로 양방향 연관 관계 가짐
    (양쪽으로 조인할 수 있다.)
1
2
3
4
5
6
7
SELECT * 
FROM **MEMBER** M
JOIN **TEAM** T ON M.TEAM_ID = T.TEAM_ID

SELECT *
FROM TEAM T
JOIN **MEMBER** M ON T.TEAM_ID = M.TEAM_ID
  • 둘 중 하나로 외래 키를 관리해야 한다
  • 둘 중 외래키를 관리하는 쪽이 주인(= Owner)

Untitled

누구를 주인으로?

  • 외래 키가 있는 있는 곳을 주인으로 정해야 한다
  • 여기서는 Member.team이 연관 관계의 주인

Untitled

양방향 매핑시 가장 많이 하는 실수

  • 연관 관계의 주인에 값을 입력하지 않음
1
2
3
4
5
6
7
8
9
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");
//역방향(주인이 아닌 방향)만 연관관계 설정
team.getMembers().add(member);
em.persist(member);

Untitled

양방향 매핑시 연관 관계의 주인에 값을 입력해야 한다.

  • 순수한 객체 관계를 고려하면 항상 양쪽다 값을 입력해야 한다.
1
2
3
4
5
6
7
8
9
10
11
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");
team.getMembers().add(member);

//연관관계의 주인에 값 설정
member.setTeam(team); //**
em.persist(member);

Untitled

눈에 띄게 변경하는 법

  • settter 함수를 사용하면 되지만
  • 좀 더 의도를 드러내면서 바꿀 방법이다
    • 다음과 같이 간단한 함수를 만들어서 사용하면 된다
1
2
3
4
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}

양방향 연관 관계 주의

  • 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자
  • 연관 관계 편의 메소드를 생성하자
  • 양방향 매핑 시에 무한 루프를 조심하자
    • 예: toString(), lombok, JSON 생성 라이브러리

양방향 매핑 정리

  • 단방향 매핑 만으로도 이미 연관 관계 매핑은 완료
    • 보통은 단방향 매핑만 해도 충분하다
  • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
  • JPQL에서 역방향으로 탐색할 일이 많음
  • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨
    • 테이블에 영향을 주지 않음

다양한 연관 관계 매핑

연관 관계 매핑시 고려 사항 3가지

  • 다중성
  • 단방향, 양방향
  • 연관 관계의 주인

다중성

  • 다대일: @ManyToOne

  • 일대다: @OneToMany

  • 일대일: @OneToOne

  • 다대다: @ManyToMany

    • 이건 실무에서 사용금지

테이블

  • 외래 키 하나로 양쪽 조인 가능
  • 사실 방향이라는 개념이 없음

객체

  • 참조용 필드가 있는 쪽으로만 참조 가능
  • 한쪽만 참조하면 단방향
  • 양쪽이 서로 참조하면 양방향

연관관계의 주인

  • 테이블은 외래 키 하나로 두 테이블이 연관 관계를 맺음
  • 객체 양방향 관계는 A->B, B->A 처럼 참조가 2군데
  • 객체 양방향 관계는 참조가 2군데 있음. 둘 중 테이블의 외래 키를 관리할 곳을 지정해야 함
  • 연관 관계의 주인
    • 외래 키를 관리하는 참조
  • 주인의 반대편
    • 외래 키에 영향을 주지 않음, 단순 조회만 가능

다 대 일 [N : 1]

  • 다대일 단방향
  • 앞글자가 관계의 주인 (Owner)이므로
  • 다(N) 쪽이 주인이다
    • 그리고 FK가 있는 쪽이다

Untitled

  • 다대일 양방향
  • 앞글자가 관계의 주인 (Owner)이므로
  • 다(N) 쪽이 주인이다
    • 그리고 FK가 있는 쪽이다

Untitled

다 대 일 정리

  • 외래 키가 있는 쪽이 연관 관계의 주인
  • 양쪽을 서로 참조하도록 개발

일 대 다 [1 : N]

  • 일대다 단방향
  • 일(1) 쪽이 관계의 주인이다

Untitled

정리

  • 일대다 단방향은 일대다(1:N)에서 일(1)이 연관관계의 주인
  • 테이블 일대다 관계는 항상 다(N) 쪽에 외래 키가 있음
  • 객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조
  • @JoinColumn을 꼭 사용해야 함.
    • 그렇지 않으면 조인 테이블 방식을 사용함 (중간에 테이블을 하나 추가함)

일 대 다 단방향 정리

  • 일대다 단방향 매핑의 단점
  • 엔티티가 관리하는 외래 키가 다른 테이블에 있음
  • 연관 관계 관리를 위해 추가로 UPDATE SQL 실행
  • 일대다 단방향 매핑 보다는 다대일 양방향 매핑을 사용하자

일 대 다 양방향

  • 이런 매핑은 공식적으로 존재 X

Untitled

일 대 다 양방향 정리

  • 이런 매핑은 공식적으로 존재X
  • @JoinColumn(insertable=false, updatable=false)
  • 읽기 전용 필드를 사용해서 양방향 처럼 사용하는 방법
  • 다대일 양방향을 사용하자

일 대 일 관계 [1 : 1]

일 대 일 단방향

  • 일대일 관계는 그 반대도 일대일

  • 주 테이블이나 대상 테이블 중에 외래 키 선택 가능

  • 주 테이블에 외래 키

  • 대상 테이블에 외래 키

  • 외래 키에 데이터베이스 유니크(UNI) 제약 조건 추가

  • 다대일(@ManyToOne) 단방향 매핑과 유사

Untitled

일 대 일 양방향

  • 다대일 양방향 매핑처럼 외래 키가 있는 곳이 연관관계의 주인
  • 반대편은 mappedBy 적용

Untitled

일대일: 대상 테이블에 외래 키 단방향

Untitled

일대일: 대상 테이블에 외래 키 단방향 정리

  • 단방향 관계는 JPA 지원X
  • 양방향 관계는 지원

일대일: 대상 테이블에 외래 키 양방향

  • 사실 일대일 주 테이블에 외래 키 양방향과 매핑 방법은 같음

Untitled

일 대 일 정리

  • 주 테이블에 외래 키
    • 주 객체가 대상 객체의 참조를 가지는 것 처럼
      주 테이블에 외래 키를 두고 대상 테이블을 찾음
    • 객체지향 개발자 선호
    • JPA 매핑 편리
    • 장점: 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
    • 단점: 값이 없으면 외래 키에 null 허용
  • 대상 테이블에 외래 키
    • 대상 테이블에 외래 키가 존재

    • 전통적인 데이터베이스 개발자 선호

    • 장점: 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지

    • 단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨

           (프록시는 뒤에서 설명)
      

다 대 다

  • 실무에서 사용 X
  • 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현 불가능
  • 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야함

Untitled

정리

  • 편리해 보이지만 실무에서 사용X
  • 연결 테이블이 단순히 연결만 하고 끝나지 않음
  • 주문시간, 수량 같은 데이터가 들어올 수 있음

@JoinColumn

  • 외래 키를 매핑할 때 사용

Untitled

@ManyToOne - 주요 속성

  • 다대일 관계 매핑

Untitled

@OneToMany - 주요 속성

  • 일대다 관계 매핑

Untitled

JPA 엔티티 매핑

[강의] 자바 ORM 표준 JPA 프로그래밍 - 기본편


Mapping 소개

  • 객체와 테이블 : @Entity, @Table
  • 필드와 컬럼 : @Column
  • 기본 키 : @Id
  • 연관 관계 : @ManyToOne,@JoinColumn

@Entity

  • @Entity가 붙은 클래스는 JPA가 관리, 엔티티라 한다.
  • JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 필수
  • 주의
    • 기본 생성자 필수(파라미터가 없는 public 또는 protected 생성자)
    • final 클래스, enum, interface, inner 클래스 사용X
    • 저장할 필드에 final 사용 X

@Table

  • @Table은 Entity와 매핑할 테이블 지정

Untitled

데이터베이스 스키마 자동 생성

  • DDL을 애플리케이션 실행 시점에 자동 생성
  • 테이블 중심 -> 객체 중심
  • 데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성
  • 이렇게 생성된 DDL은 개발 장비에서만 사용
  • 생성된 DDL은 운영서버에서는 사용하지 않거나, 적절히 다듬은 후 사용

데이터베이스 스키마 자동 생성 - 속성

  • hibernate.hbm2ddl.auto
  • persistence.xml 에서 사용 가능하다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<properties>

<!-- 필수 속성 -->
<!-- 드라이버, id, password, localhost 등 -->
...
...

<!-- 옵션 -->
<!-- 실행 시, 쿼리 문을 보여주는 옵션, 줄 맞춰주는 옵션 등 -->
...
...
<property name="**hibernate.hbm2ddl.auto**" value="**옵션**" />

</properties>
  • 옵션

Untitled

DB 스키마 자동 생성 - 실습

  • persistence.xml 에서 원하는 옵션으로 적어넣는다.
  • @Entity 사용된 파일에서 옵션을 고려하여 변경하거나 그대로 둔다
  • 하던 것처럼 Run 하여 실행시킨다
  • H2 DB 확인하면 반영된 것을 확인 가능

데이터베이스 스키마 자동 생성 - 주의점

  • 운영 장비에는 절대 create, create-drop, update 사용하면 안된다.

  • 개발 진행 중에만 사용해야 한다

  • 개발 초기 단계는 create 또는 update

  • 테스트 서버는 update 또는 validate

  • 스테이징과 운영 서버는 validate 또는 none


필드와 컬럼 매핑

  • 실습 상황
  1. 회원은 일반 회원과 관리자로 구분해야 한다.
  2. 회원 가입일과 수정일이 있어야 한다.
  3. 회원을 설명할 수 있는 필드가 있어야 한다. 이 필드는 길이 제
    한이 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package hellojpa; 
import javax.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
@Entity
public class Member {
@Id
private Long id;

@Column(name = "name")
private String username;

private Integer age;

@Enumerated(EnumType.STRING)
private RoleType roleType;

@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;

@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;

@Lob
private String description;

//Getter, Setter…
}

매핑 어노테이션 정리

  • hibernate.hbm2ddl.auto

Untitled

@COLUMN

Untitled

  • column 어노테이션 - 사용 예시
1
2
@column ( name = “테이블 컬럼 이름”, nullable = false )
private String username;

@Enumerated

  • 자바 enum 타입을 매핑할 때 사용
  • 주의! ORDINAL 사용X

Untitled

  • ORDINAL = 순서 저장 ( DEFAULT 설정 )

    • 숫자로 저장된다 ( Integer )
  • STRING = 이름 저장 ( 이것만 사용하자 )

  • 주의! ORDINAL 사용X

    • 순서를 저장하지만 순서가 꼬일 수 있다

Enum 개념

  • Enum = 관련 있는 상수들의 집합
  • 상수 = final, String 등으로 고정된 값 (영어로는 Constant)
  • 예시
1
2
3
4
// RoleType.java
public enum RoleType {
USER, ADMIN
}
  • 사용예시
    • @Entity 페이지에서 선언하고
    • enum 클래스에 있는 값 중에서 넣어서 사용한다
1
2
3
4
5
6
7
//Member.java
@Enumerated(EnumType.**STRING**)
private RoleType roleType;

// JpaMain.java
Member member = new Member();
member.setRoleType(RoleType.User);

@Temporal

  • 날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용
  • 참고: LocalDate, LocalDateTime을 사용할 때는 생략 가능(최신 하이버네이트 지원)

Untitled

@LOB

  • 데이터베이스 BLOB, CLOB 타입과 매핑

  • @Lob에는 지정할 수 있는 속성이 없다.

  • 매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑

  • CLOB: String, char[], java.sql.CLOB

  • BLOB: byte[], java.sql. BLOB

@Transient

  • 필드 매핑X
  • 데이터베이스에 저장X, 조회X
  • 주로 메모리 상에서만 임시로 어떤 값을 보관하고 싶을 때 사용
  • 예시
1
2
@Transient
private Integer temp;

기본키 매핑

기본 키 매핑 어노테이션

  • @Id
  • @GeneratedValue
1
2
3
@Id 
@GeneratedValue(strategy = GenerationType.**옵션**)
private Long id;
  • 직접 할당: @Id만 사용

  • 자동 생성(@GeneratedValue) 옵션

Untitled

  • 사용자가 지정해주지 않아도 알아서 값이 들어간다
    • 1부터 들어간다

IDENTITY 전략 - 특징

  • 기본 키 생성DB에 위임
  • 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용
    (예시: MySQL의 AUTO_ INCREMENT)
  • JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
  • AUTO_ INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있음
  • IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회
  • 코드
1
2
3
4
5
6
@Entity 
public class Member {

@Id
@GeneratedValue(strategy = GenerationType.**IDENTITY**)
private Long id;

SEQUENCE 전략 - 특징

  • DB 시퀀스는 유일한 값을 순서대로 생성하는 특별한 DB 오브젝트 (예시: 오라클 시퀀스)
  • 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용
  • 코드
1
2
3
4
5
6
7
8
9
10
@Entity 
@SequenceGenerator(
name = "MEMBER_SEQ_GENERATOR",
sequenceName = "MEMBER_SEQ",
initialValue = 1, allocationSize = 1)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.**SEQUENCE**,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;

SEQUENCE - @SequenceGenerator

  • 주의: allocationSize 기본값 = 50

Untitled

Table 전략

  • 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략
    • 장점: 모든 데이터베이스에 적용 가능
    • 단점: 성능
  • 코드
1
2
3
4
5
create table MY_SEQUENCES ( 
sequence_name varchar(255) not null,
next_val bigint,
primary key ( sequence_name )
)
1
2
3
4
5
6
7
8
9
10
Entity 
@TableGenerator(
name = "MEMBER_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumnValue = “MEMBER_SEQ", allocationSize = 1)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;

@TableGenerator - 속성

  • 잘 안 쓰임

Untitled

권장하는 식별자 전략

  • 기본 키 제약 조건
    • not Null
    • 유일
    • 변하면 안된다.
  • 미래까지 이 조건을 만족하는 자연키는 찾기 어렵다. 대리키(대체키)를 사용하자.
    • 예를 들어 주민등록번호도 기본 키로 적절하기 않다.
  • 권장
    • Long형 + 대체키 + 키 생성전략 사용

실습

  • 회원은 상품을 주문할 수 있다.

  • 주문 시 여러 종류의 상품을 선택할 수 있다

  • 회원과 주문의 관계: 회원은 여러 번 주문할 수 있다. (일대다)

  • 주문과 상품의 관계: 주문할 때 여러 상품을 선택할 수 있다. 반
    대로 같은 상품도 여러 번 주문될 수 있다.

  • 주문상품 이라는 모델을 만들어서 다대다 관계를 일다대, 다대일 관계로 풀어냄

Untitled

  • ERD

Untitled

D:\study\jpashop\src\main\java\jpabook\resources\META_INF

JPA 영속석 컨텍스트

영속성 컨텍스트

[강의] 자바 ORM 표준 JPA 프로그래밍 - 기본편


JPA에서 가장 중요한 2가지

  • 객체와 관계형 데이터베이스 매핑하기 (Object Relational Mapping)
  • 영속성 컨텍스트

엔티티 매니저 펙토리와 엔티티 매니저

Untitled

영속성 컨텍스트

  • JPA를 이해하는데 가장 중요한 용어
  • 엔티티를 영구 저장하는 환경”이라는 뜻
  • EntityManager.persist(entity);

엔티티의 생명주기

  • 비영속 (new/transient)
    영속성 컨텍스트와 전혀 관계가 없는 새로운 상태

  • 영속 (managed)
    영속성 컨텍스트에 관리되는 상태

  • 준영속 (detached)
    영속성 컨텍스트에 저장되었다가 분리된 상태

  • 삭제 (removed)
    삭제된 상태

영속

  • Entity Manager 안에 담은 상태

Untitled

비영속

  • Entity Manager 안에 담지 않은 상태

Untitled

DB에 저장되는 시점

  • persist(member) 하더라도 DB에 저장되지 않는다.
  • tx.commit( );
    • Transaction Commit 실행 후에서야 저장된다

준영속, 삭제

  • detach( ) 사용한 상태?
  • delete( ) 사용한 상태?
1
2
3
4
5
//**회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태**
em.detach(member);

//**객체를 삭제한 상태(삭제)**
em.remove(member);

영속성 컨텍스트의 이점

  • 1차 캐시
  • 동일성(identity) 보장
  • 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
  • 변경 감지(Dirty Checking)
  • 지연 로딩(Lazy Loading)

이점 1 : 1차 캐시

  • DB까지 가지 않고 값을 가져 올 수 있는 경우가 있다

Untitled

Untitled


이점 2 : 영속 엔티티의 동일성 보장

  • 1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공
1
2
3
4
Member a = em.find(Member.class, "**member1**");
Member b = em.find(Member.class, "**member1**");

System.out.println(**a == b**); //동일성 비교 **true**

이점 3 : 엔티티 등록 트랜잭션을 지원하는 쓰기 지연

  • 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
1
2
3
4
5
6
7
8
9
10
11
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.

transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.

transaction.commit(); // [트랜잭션] 커밋
  • member1, member2 있을 경우, 하나씩 차례로 작업한다

Untitled


이점 4 : 엔티티 수정 변경 감지 (Dirty Checking)

1
2
3
4
5
6
7
8
9
10
11
12
13
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작

// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");

// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member) 이런 코드가 있어야 하지 않을까?

transaction.commit(); // [트랜잭션] 커밋
  • 엔티티 vs 스냅샷
  • 값을 비교하여 수정되었는지 확인한다

Untitled


이점 5 : 플러시

  • 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 것

  • 플러시 발생

    • 변경 감지
    • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
    • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송
      (등록, 수정, 삭제 쿼리)
  • 영속성 컨텍스트를 플러시하는 방법

1
2
3
4
5
em.flush() - 직접 호출 

트랜잭션 커밋 - 플러시 자동 호출

JPQL 쿼리 실행 - 플러시 자동 호출

JPQL 쿼리 실행 시 플러시가 자동으로 호출되는 이유

-

1
2
3
4
5
6
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//중간에 JPQL 실행
query = em.createQuery("select m from Member m", Member.class);
List<Member> members= query.getResultList();

플러시는!

  • 영속성 컨텍스트를 비우지 않음
  • 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화
  • 트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화하면 됨

준영속

  • 영속 -> 준영속
  • 영속 상태의 엔티티가 영속성 컨텍스트에서 분리 (detached)
  • 영속성 컨텍스트가 제공하는 기능을 사용 못함

준영속 상태로 만드는 방법

  • em.detach(entity)
    특정 엔티티만 준영속 상태로 전환

  • em.clear()
    영속성 컨텍스트를 완전히 초기화

  • em.close()
    영속성 컨텍스트를 종료

JPA CRUD

= INSERT, SELECT, UPDATE, DELETE

[강의] 자바 ORM 표준 JPA 프로그래밍 - 기본편


INSERT

  • persist( ) 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {
public static void main(String[] args) {

EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPABOOK");

EntityManager em = emf.createEntityManager();

// 트랜잭션
EntityTransaction tx = em.getTransaction();
tx.begin();

try {
Member member = new Member();
member.setId(1L);
member.setName("HelloA");

// member 인스턴스에 들어있는 값을 em에 저장하다 = db에 삽입하다
em.persist(member);

tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}

}

SELECT

  • find( ) 사용
1
2
3
4
5
6
7
try {
Member findMember = em.find(Member.class, 1L);
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.name = " + findMember.getName());

tx.commit();
} catch (Exception e) {
  • 결과

Untitled

UPDATE

  • find( ) 사용
  • setter 함수 사용
1
2
3
4
5
6
try {
Member findMember = em.find(Member.class, 1L);
findMember.setName("HelloJPA");

tx.commit();
} catch (Exception e) {

DELETE

  • find( ) 사용
  • remove( ) 사용
1
2
3
4
5
6
try {
Member findMember = em.find(Member.class, 1L);
em.removd(findMember);

tx.commit();
} catch (Exception e) {

SELECT 주의할 점

  • 그냥 find( ) 사용으로는 불가능한 것도 있다
  • 그럴 때 사용하는 것이 JPQL

JPQL 개념

  • 객체 대상으로 작동하는 쿼리
  • 객체 지향 쿼리

Untitled

Untitled


JPQL 사용법

Untitled

SELECT

  • JPQL예시 코드
1
2
3
4
5
6
7
try {
List<Member> result = em.createQuery("select m from Member as m", Member.class)
.getResultList();

for (Member member : result) {
System.out.println("Member.name = " + member.getName());
}
  • 결과

Untitled

JPA 환경 설정 & 실행

[강의] 자바 ORM 표준 JPA 프로그래밍 - 기본편

H2 데이터베이스 설치와 실행

  • 최고의 실습용 DB

  • 가볍다.(1.5M)

  • 웹 전용 쿼리 툴 제공

  • MySQL, Oracle 데이터베이스 시뮬레이션 기능

  • 시퀀스, AUTO INCREMENT 기능 지원

  • 설치

    • http://www.h2database.com/html/main.html
    • downloads → archive downloads → 1.4.199 버전 다운로드
    • 이후에 Spring과 결합하여 사용할 때 호환되는 버전을 고려해야 하기 때문
  • h2 console 실행 또는 PC 화면 하단의 tool bar에서 선택한다

Untitled

Untitled

  • 설정
    • Geniric H2 (Server)
    • 연결

H2 연결 에러

  • 에러 : Database “~/test” not found, and IFEXISTS=true, so we cant auto-create it [90146-199]

  • 해결

    • 다음 이미지와 같이 JDBC URL에 입력
    • jdbc:h2:~/test
  • 이후 접속

    • jdbc:h2:tcp://localhost/~/test 입력해서 접속해야 한다

Untitled

Extension 설치

Untitled

프로젝트 생성

  • VSCode에서 프로젝트 생성
  • 명령 입력 창 : Ctrl + Shift + P
  • Create project
  • Maven

내가 선택한 것들

  • maven quick start
  • maven 1.0 version
  • groupid = com.jpabasic
  • artifactId = ex1hellojpa
  • 버전 입력 창 : 1.0.0eefefeef

pom.xml

  • 기존 양식에 덧붙이기
  • h2와 hibernate 버전 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependencies>

<!-- JPA 하이버네이트 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.3.10.Final</version>
</dependency>
<!-- H2 데이터베이스 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.199</version>
</dependency>

</dependencies>

JPA 설정하기 - persistence.xml

  • 파일 생성
  • 경로 중요

Untitled

  • 필요한 코드 넣기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<!-- persistence 이름이 JPABOOK-->
<persistence-unit name="JPABOOK">
<properties>
<!-- 필수 속성 -->
<!-- h2 드라이버 사용 -->
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<!-- localhost 가 test -->
<property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
<!-- h2 다이렉트 사용 -->
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>

<!-- 옵션 -->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.use_sql_comments" value="true"/>
<!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
</properties>
</persistence-unit>
</persistence>
  • .java 파일 생성
  • 예시) JpaMain.java
  • 코드
    • Persistence.createEntityManagerFactory(“hello”);
    • “hello”는 pom.xml 파일의 persistence-unit name = “helloe” 의 값을 넣은 것이다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class JpaMain {
public static void main(String[] args) {

EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPABOOK");

EntityManager em = emf.createEntityManager();
// code
em.close();

emf.close();

}

}

h2 연결

  • JDBC URL
    • pom.xml 에서 localhost에 적힌 부분을 그대로 넣어서 연결해야 한다
1
2
3
// pom.xml
<property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>

  • 드라이버 클래스, 사용자명, 비밀번호의 경우도 마찬가지다

    • driver, user, password 가 적힌 부분과 일치시켜서 연결해야 한다
    1
    2
    3
    4
    // pom.xml
    <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
    <property name="javax.persistence.jdbc.user" value="sa"/>
    <property name="javax.persistence.jdbc.password" value=""/>

Untitled

  • DB에 Table 생성
1
2
3
4
5
create table Member(
id bigint not null,
name varchar(255),
primary key(id)
);

Untitled

객체와 테이블 생성하고 매핑하기

Untitled

  • @Entity 필수
  • getter setter 함수 생성해야 한다
    • extension 설치 : Generate Getter and Setters
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package hellojpa;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Member {

@Id
private Long id;
private String name;

// getter setter 함수들
...
...

}

트랜잭션

  • 간단하게 풀이하자면 DB Connection 이다
  • db 관련 기능 실행할 때마다 필요하다는 듯
1
2
3
4
5
6
7
8
9
// 트랜잭션
EntityTransaction tx = em.getTransaction();
tx.begin();

...
// 함수 실행
...

tx.commit();

에러 해결 - hibernate 관련

  • 에러 메시지 : hibernate property not found

  • db 관련 코드에 문제가 발생하면 hibernate가 db 매핑에 실패하기에 생긴 에러

  • 검색한 바로는 jdk가 11 버전일 때도 생길 수 있다고 한다

  • 해결

    • @id 노테이션을 2번 사용한 것이 문제였다
    • 이건 1개의 Pk에 2개의 컬럼을 지정해준 것과 비슷한 문제다
    • 따라서 @id 한 번만 사용함으로써 해결

첫 실행. persist

  • 실행할 함수 위에 Run | Debug 표시가 있다.

  • JpaMain.java

  • Member 클래스는 아까 정의한 id, name이 들어있는 클래스이다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {
public static void main(String[] args) {

EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPABOOK");

EntityManager em = emf.createEntityManager();

// 트랜잭션
EntityTransaction tx = em.getTransaction();
tx.begin();

Member member = new Member();
member.setId(1L);
member.setName("HelloA");

// member 인스턴스에 들어있는 값을 em에 저장하다 = db에 삽입하다
em.persist(member);

tx.commit();

em.close();

emf.close();

}

}
  • Run 실행

Untitled

  • 결과
  • SELECT 문을 이용해 조회한다
  • DB에 INSERT 완료

Untitled

주의점

  • 엔티티 매니저 팩토리는 하나만 생성해서 애플리케이션 전체에서 공유

  • 엔티티 매니저는 쓰레드 간에 공유X (사용하고 버려야 한다).

  • JPA의 모든 데이터 변경은 트랜잭션 안에서 실행

  • 이 코드를 말하는 듯 하다

1
2
3
EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPABOOK");

EntityManager em = emf.createEntityManager();