Ian's Archive 🏃🏻

Profile

Ian

Ian's Archive

Developer / React, SpringBoot ...

📍 Korea
Github Profile →
Categories
All PostsAlgorithm19C1CI/CD2Cloud3DB1DesignPattern9ELK4Engineering1Front3Gatsby2Git2IDE1JAVA7JPA5Java1Linux8Nginx1PHP2Python1React9Security4SpatialData1Spring26
thumbnail

자바 ORM 표준 JPA 프로그래밍 (기본편)

JPA
2022.10.13. (수정됨)

Series

  • 1자바 ORM 표준 JPA 프로그래밍 (기본편)
  • 2실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
  • 3실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
  • 4실전 스프링 데이터 JPA
  • 5Querydsl

JPA (JAVA Persistence API)

자바 ORM 기술 표준

ORM(Object-relational mapping)

프레임 워크가 중간에서 매핑

  • JPA는 APP과 JDBC 사이에서 동작
  • 패러다임의 불일치 해결

JPA는 인터페이스 모음 -> Hibernate (구현체)

JPA 설정(Persistence.xml)

JPA 설정하기 위해선 Persistence.xml필요

  • /META_INF/persistence.xml위치
복사
<?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-unit name="hello">
        <properties>
            <!-- 필수 속성 -->
            <!-- 자바 접근 정보-->
            <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=""/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
            <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>
  • persistence-unit name으로 이름 지정
  • javax.persistence -> DB접근 정보
  • hibernate -> 하이버네이트 전용 속성
  • hibernate.dialect -> DB에 맞는 sql 문법 지정
    ex) MySQLDialect, OracleDialect, H2Dialect

주의 사항

  • 엔티티 매니저 팩토리는 애플리케이션 전체에서 공유
  • 엔티티 매니저는 쓰레드간에 공유X (사용하고 버려야 한다)
  • JPA의 모든 데이터 변경은 트랜잭션 안에서 실행

JPQL

SQL을 추상화한 객체 지향 쿼리 언어

특징

  • 객체를 중심으로 개발
  • 검색시 테이블이 아닌 엔티티 객체를 대상으로 검색
  • 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능
  • 필요한 데이터를 DB에서 불러오려면 결국 검색 조건이 포함된 SQL 필요

영속성

  • 객체와 관계형 데이터 베이스 매핑
  • 영속성 컨텍스트

영속성 컨텍스트란?

-> 엔티티를 영구 저장하는 환경
-> DB와 App간에 중간 계층

장점

  • 1차 캐시
  • 동일성 보장
  • 트랜잭션을 지원하는 쓰기 지원
  • 변경 감지
  • 지연 로딩

플러시란?

영속성 컨텍스트의 변경 내용을 DB에 반영 (영속성 컨텍스트를 비우지 않음)

플러시 과정
변경 감지 -> 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록
-> 쿼리를 데이터베이스에 전송

영속성 컨텍스트를 플러시 하는 방법

  • em.flush() : 직접 호출
  • 트랜잭션 커밋 : 플러시 자동 호출
  • JPQL 쿼리 : 플러시 자동 호출

준영속 상태

영속 상태의 엔티티를 영속성 컨텍스트에서 분리

  • em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
  • em.clear() : 초기화
  • em.close() : 종료

Entity Mapping

기본 어노테이션

어노테이션 설명
@Entity @Table entity와 매핑할 테이블 지정
@Id 데이터베이핑 PK와 매핑
@Column 필드와 컬럼 매핑
@ManyToOne @JoinColum 연관관계 매핑

Column 어노테이션

어노테이션 설명
@Column 컬럼 매핑
@Temporal 날짜 타입 매핑
@Enumerated JAVA enum 타입 매핑 (String사용)
@Lob BLOB(byte[]), CLOB(String, char[]) 매핑
@Transient 특정 필드를 컬럼에 매핑하지 않음(메모리상에서만 임시로 값 보관하고 싶을 때)

기본키 매핑

기본키 자동 생성 전략

  1. IDENTITY - 데이터 베이스에 위임
  2. SEQUENCE - 데이터베이스 시퀀스 오브젝트 사용 (Oracle, PostgreSQL, H2)
  3. TABLE - 키 생성용 테이블 사용
복사
@Id // 직접 할당

// 자동 생성
// AUTO : 사용하는 DB에 따라 자동 지정 (기본값)
@GeneratedValue(strategy = GenerationType.AUTO)

// SEQUENCE : 데이터베이스 시퀀스 오브젝트 사용
@Entity
@SequenceGenerator(
  name = "MEMBER_SEQ_GENERATOR",
  equenceName = "MEMBER_SEQ",    // 매핑할 데이터베이스 시퀀스 이름
  initialValue = 1,
  allocationSize = 1             // 시퀀스 한 번 호출에 증가하는 수(성능 최적화)
)
public class Member {
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator= "MEMBER_SEQ_GENERATOR")
  private Long id;
  ...

// TABLE : 키 생성용 테이블 사용
@TableGenerator(name = "MEMBER_SEQ_GENERATOR",
                table = "MY_SEQUENCES",
                pkColumnValue = "MEMBER_SEQ",
                allocationSize = 1)
public class CLASSNAME {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "MEMBER_SEQ_GENERATOR")
    private Long id;
    ...
SequenceGenerator 설정 값 (Toggle)
속성 설명 기본
name 식별자 생성기 이름 필수
sequenceName 데이터베이스에 등록되어 있는 시퀀스 이름 hibernate_sequenc
initialValue DDL 생성 시에만 사용됨, 시퀀스 DDL 생성 시 시작하는 수를 지정 1
allocationSize 시퀀스 한 번 호출에 증가하는 수 50
catalog, schema 데이터베이스 catalog, schema 이름
TableGenerator 설정 값 (Toggle)
속성 설명 기본
name 식별자 생성기 이름 필수
table 키생성 테이블명 hibernate_sequences
pkColumnName 시퀀스 컬럼명 sequence_name
valueColumnNa 시퀀스 값 컬럼명 next_val
pkColumnValue 키로 사용할 값 이름 엔티티 이름
initialValue 초기 값, 마지막으로 생성된 값이 기준 0
allocationSize 시퀀스 한 번 호출에 증가하는 수 50
catalog, schema 데이터베이스 catalog, schema 이름
uniqueConstraints(DDL) 유니크 제약 조건 지정

권장하는 기본키 전략
-> Long형, 대체키, 기본 키 전략 // 비즈니스 로직 포함 x

연관 관계 매핑

고려 사항

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

단방향, 양방향

테이블 : 외래키 하나로 연관관계를 관리 객체 : 서로 다른 단뱡향 관계 2개

한쪽만 참조하면 단방향 양쪽 서로 참조시 양방향

양방향 매핑 규칙

  • 외래키가 있는 곳을 연관관계 주인으로 지정
  • 연관관계 주인 값이 변경 시 외래키를 변경
  • 연관관계 주인 -> 외래키 관리(등록, 수정)
  • 주인이 아닌 쪽은 읽기만 가능 -> mappedBy 속성으로 주인 지정

주의할 점

  • 값 추가 시 양쪽 모두 값 입력 -> 연관관계 편의 메소드 사용
  • 무한루프 주의
    • lombok toString 사용 x
    • Controller에서 Entity 직접 반환x -> DTO로 변환해 사용

연관관계 편의 메소스

  • 주인 setter + 반대쪽 객체에 값 넣어주는 부분 추가
  • 새로운 함수로 작성해 setter함수와 구분
  • 필요 시 제약조건 검사 구문 추가
  • 한쪽에만 작성
복사

...

public void addMember(Member member) {
  member.setTeam(this);
  members.add(member);
}

...

정리

  • 테이블 설계 -> 연관관계 매핑은 단방향 매핑으로 완성 되어야 함
  • 양방향 매핑은 반대 방향으로 조회 기능을 위해 추가
  • JPQL에서 역방향 탐색할 일이 많음

다중성

연관관계 매핑을 위한 어노테이션

복사
@ManyToOne @JoinColumn(name = "TEAM_ID") // N : 1

@OneToMany(mappedBy = "team") // 1 : N

@OneToOne // 1 : 1

@ManyToMany // N : M 사용x
  • 보통 다대일 관계 사용 - 외래키가 있는 쪽이 연관관계 주인
  • 일대일 관계 - 외래키에 유니크 제약조건 추가

일대일

위치 특징 장점 단점
주(access가 많은) 테이블 - 객체지향 개발자 선호
- JPA 매핑 편리
주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능 값이 없으면 외래 키에 null 허용
대상 테이블 - 전통적인 데이터베이스 개발자 선호 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨
@ManyToOne, @OneToMany 설정 값 (Toggle)
속성 설명 기본 값
optional false로 설정하면 연관된 엔티티가 항상 있어야 한다. TRUE
fetch 글로벌 페치 전략을 설정한다. @ManyToOne=FetchType.EAGER
@OneToMany=FetchType.LAZY
cascade 영속성 전이 기능을 사용한다 sequence_name

상속 관계 매핑

객체는 상속을 지원하므로 모델링과 구현이 똑같지만, DB는 상속을 지원하지 않음으로 논리 모델을 물리 모델로 구현할 방법이 필요

유사한 개념으로는 슈퍼타입 서브타입 관계 있어 JPA에서는 상속 구조를 슈퍼타입 - 서브타입 관계에 매핑

DB의 슈퍼타입 서브타입 논리 모델을 실제 물리 모델로 구현하는 방법 3가지

  1. 각각의 테이블 -> 조인 전략
  2. 통합 테이블 -> 단일 테이블 전략
  3. 서브타입 테이블 -> 구현 클래스 마다 테이블 전략
복사
// JOINED: 조인 전략
// SINGLE_TABLE: 단일 테이블 전략
// TABLE_PER_CLASS: 구현 클래스마다 테이블 전략
// 부모 클래스에 어노테이션 추가 + 자식 클래스에서 부모 클래스 상속
@Inheritance(strategy=InheritanceType.XXX)

// 부모 클래스에 선언
// 하위 클래스를 구분하는 용도 (Default = DTYPE)
@DiscriminatorColumn(name=“DTYPE”)

// 자식 클래스에 선언
// 엔티티를 저장할 때 슈퍼타입 구분 컬럼에 저장할 값 지정 (Default : 클래스 명)
@DiscriminatorValue(“XXX”)
전략 장점 단점
조인 전략 - 테이블 정규화
- 외래 키 참조 무결성 제약조건 활용가능
- 저장공간 효율화
- 조회시 조인을 많이 사용, 성능 저하
- 조회 쿼리가 복잡함
- 데이터 저장시 INSERT SQL 2번 호출
단일 테이블 전략 - 조인이 필요 없으므로 일반적으로 조회 성능이 빠름
- 조회 쿼리가 단순함
- 자식 엔티티가 매핑한 컬럼은 모두 null 허용
- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다.
- 상황에 따라서 조회 성능이 오히려 느려질 수 있다.
구현 클래스 마다 테이블 전략 - 서브 타입을 명확하게 구분해서 처리할 때 효과적
- not null 제약조건 사용 가능
- 여러 자식 테이블을 함께 조회할 때 성능이 느림(UNION SQL 필요)
- 자식 테이블을 통합해서 쿼리하기 어려움

정리

  • 기본적으로는 조인 전략 -> 중요, 복잡, 확장 가능성이 높은 경우
  • 심플하고 확장 가능성이 낮은 경우 -> 단일 테이블 전략

@MappedSubperclass

객체의 입장에서 공통 매핑 정보가 필요할 때 사용

  • 상속 받는 자식 클래스에 매핑 정보만 제공
  • 조회, 검색이 불가능

프록시

연관된 객체를 처음부터 DB에서 조회하는 것이 아닌 실제 사용하는 시점에 조회 가능 (가짜 객체)

em.find() - DB를 통해서 실제 엔티티 객체를 조회하는 메서드 em.getReference() - DB의 조회를 미루는 가짜(프록시) 엔티티 객체를 조회하는 메서드

특징

  • 실제 클래스의 상속을 받아 만들어짐
  • 프록시 객체는 실제 객체의 참조값(주소)를 가지고 있음
  • 사용 시 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다
    (JPA 내부에서 알아서 처리해준다.)
@트랜잭션 범위 밖에서 프록시 객체를 조회 (준영속 상태) 문제

초기화 문제 발생

(하이버네이트는 org.hibernate.LazyInitializationException 예외) -> 해결책으로 Spring Boot에서 open-in-view 설정 true (영속성 컨텍스트(하이버네이트 세션)를 뷰 렌더링하는 시점까지 유지시키는 방법) https://kingbbode.tistory.com/27

실전 JPA강의에 해당 내용 강의 듣고 추가 정리

즉시 로딩, 지연 로딩

복사
@ManyToOne(fetch = FetchType.LASY) // 지연 로딩
@ManyToOne(fetch = FetchType.EAGER) // 즉시 로딩

연관관계를 설정한 엔티티 조회 시 프록시 객체로 가져온다

즉시 로딩 사용 시 N+1 문제 발생 가능
JPA N+1 문제 해결 방법 및 실무 적용 팁 - 삽질중인 개발자
JPA N+1 발생원인과 해결방법 - Yun Blog

  • 모든 연관관계에서 지연 로딩 사용
  • @ManyToOne, @OneToOne, @XXXToOne 어노테이션들은 기본이 즉시 로딩(EAGER)
    => LAZY로 명시적으로 설정해서 사용
  • JPQL fetch 조인, 엔티티 그래프 기능, Batch Size 사용

영속성 전이(CASCADE)

특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속상태로 만들 때

연관관계 주인이 아닌 자식 객체에 설정

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

종류

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

고아 객체

부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제

  • ** 참조하는 곳이 하나**일 때 사용
  • 특정 엔티티가 개인 소유할 때 사용
    (@OneToOne, @OneToMany 일 경우)
복사
@OneToMany(CascadeType.ALL + orphanRemovel=true)

-> 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명 주기 관리 가능 (도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용)

값 타입

JPA는 크게 엔티티타입, 값 타입으로 분류

엔티티 타입 : @Entity 정의, 데이터가 변해도 식별자로 추적 가능 값 타입 : 자바 기본 타입이나 객체, 식별자가 없어 추적 불가

값 타입 종류

생명주기를 엔티티에 의존

1. 기본 값 타입

  • 값타입 공유 x -> 복사해 사용
  • 생명주기를 엔티티에 의존
  • 불변 객체로 만들어 사용

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

  • 새로운 값 타입을 정의할 수 있음
  • 재사용성, 높은 응집도
  • 해당 값 타입만 사용하는 메소드 만들 수 있음
  • 값이 null이면 매핑한 컬럼은 모두 null
  • 불변 객체로 만들어 사용해야 함
    • setter 만들지 않으면 된다
    • 값을 복사해 사용
    • 값을 공유해 사용하기 위해선 엔티티를 만들어 사용
복사
@Embeddable // 값 타입을 정의하는 곳에 사용
@Embedded // 값 타입을 사용하는 곳에 사용

@AttributeOverrides // 한 엔티티에서 같은 값 타입을 사용하면 어노테이션 사용해서 재정의

// 사용 예시
@Embedded
@AttributeOverrides({
  @AttributeOverride(name="city", column=@Column(name="COMPANY_CITY")),
  @AttributeOverride(name="street", column=@Column(name="COMPANY_STREET")),
  @AttributeOverride(name="zipcode", column=@Column(name="COMPANY_ZIPCODE"))
})
Address companyAddress;

3. 컬렉션 값 타입

  • 컬렉션을 저장하기 위한 별도의 테이블 필요
  • 일반 엔티티와 동일하게 지연로딩 사용 가능
  • 값 타입 컬렉션은 영속성 전이 + 고아 객체 제거 기능을 필수로 가짐

제약 사항

  • 식별자 개념 없다.
  • 변경 시 추적이 어려움
  • 변경사항 발생 시, 값 타입 컬렉션에 있는 모든 값을 다시 저장
  • 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본키 구성 - null x, 중복 저장x
복사
@ElementCollection // 컬렉션 값 타입 사용시 매핑

@CollectionTable // 테이블 설정

실무에 적용 시 상황에 따라 일대다 관계 고려

엔티티 vs 값 타입
-> 식별자 필요, 지속해서 값 추적 필요 시 엔티티

쿼리

종류

  • JPQL
  • JPA Criteria
  • QueryDSL
  • 네이티브 SQL
  • JDBC API 직접사용, MyBatis, SpringJdbcTemplate 함께 사용
    -> 영속성 컨텍스트를 적절한 시점에 강제로 플러시 필요

JPQL문법

  • 엔티티 이름 사용(테이블 아님)
  • 별칭 필수

반환 타입

복사
// 반환 타입이 명확할 경우
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);

// 반환 타입이 명확하지 않을 경우
Query query = em.createQuery("SELECT m.username, m.age from Member m");

결과 조회

복사
query.getResultList()   // 결과가 하나 이상일 때 리스트 반환
query.getSingleResult() // 결과가 하나

파라미터 바인딩

이름 , 위치 기준

복사
// SQL
SELECT m FROM Member m where m.username=:username

query.setParameter("username", usernameParam);

프로젝션

프로젝션 대상으로 엔티티, 임베디드 타입, 스칼라 타입 가능

페이징 API

복사
setFirstResult(int startPoint) // 조회 시작 위치
setMaxResults(int maxResult)   // 조회할 데이터 수

서브 쿼리

  • SELECT, WHERE, HAVING 절에서 서브 쿼리 사용 가능
  • FROM 절 서브쿼리는 사용 불가

경로 표현식

복사
select m.username -> 상태 필드
from Member m
  join m.team t -> 단일 값 연관 필드
  join m.orders o -> 컬렉션 값 연관 필드
where t.name = '팀A'

상태 필드(state field) : 단순히 값을 저장하기 위한 필드 (ex: m.username)
연관 필드(association field) : 연관관계를 위한 필드

묵시적 내부 조인 발생

  • 단일 값 연관 필드 : @ManyToOne, @OneToOne, 대상이 엔티티(ex: m.team)
  • 컬렉션 값 연관 필드 : @OneToMany, @ManyToMany, 대상이 컬렉션(ex: m.orders)

주의 사항

  • 컬렉션은 탐색의 끝, 명시적 조인을 통해 별칭을 얻어야 함
  • 묵시적 조인 사용 x -> 명시적 조인 사용

페치 조인

SQL 조인 종류가 아닌 성능 최적화를 위해 제공하는 기능으로 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회 (N+1 문제 해결책)

복사
select m from Member m join fetch m.team

->
SELECT M.*, T.* FROM MEMBER M
INNER JOIN TEAM T ON M.TEAM_ID=T.ID

DISTINCT

  1. SQL에 DISTINCT를 추가
  2. 애플리케이션에서 같은 식별자를 가진 엔티티 중복 제거

한계

  • 패치 조인은 기본적으로 모든 연관 그래프 가지고 오는 개념
  • 패치 조인 대상에는 별칭을 줄 수 없다
  • 둘 이상의 컬렉션은 패치 조인 할 수 없다
  • 컬렉션을 패치 조인하면 페이징 API를 사용할 수 없다.

다형성 쿼리

Type : 조회 대상을 특정 자식으로 한정

복사
-- [JPQL]
select i from Item i
where type(i) IN (Book, Movie)

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

TREAT : 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용

복사
-- [JPQL]
select i from Item i
where treat(i as Book).auther = ‘kim’

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

Named 쿼리(정적 쿼리)

미리 정의해서 이름을 부여해두고 사용하는 JPQL

  • 어노테이션, XML 정의
  • 어플리케이션 로딩 시점에 초기화 후 재사용, 쿼리 검증

Tip!!

  • JPA에서 타입 비교시 instance of(객체 비교) 사용

  • 벌크 연산 수행 후 영속성 컨텍스트 초기화(em.clear()) 필수!

  • 다대다 연관관계 사용 x -> 일대다, 다대일로 관계를 풀어서 사용
    (보통 중간에 매핑 테이블 필요 ex - category - gategory_item - item)

  • @Entity 클래스는 @Entity나 @MappedSuperclass로 지정한 클래스만 상속할 수 있다.

  • Entity는 기본 생성자 필요 -> protect 레벨로 생성 (추가로 필요한 내용 정리 예정)

복사
@Entity
@NoArgsconstructor(access = AccessLevel.PROTECTED)
public class Member {
  ...
}

Reference

Previous Post
React Redux, Routing
Next Post
실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
Thank You for Visiting My Blog, I hope you have an amazing day 😆
© 2023 Ian, Powered By Gatsby.