[영속성] JDBC, MyBatis는 구닥다리 잖아요! JPA (이론편)

tech_bae·2025년 8월 13일

영속성

목록 보기
3/3
post-thumbnail

JDBC, MyBatis를 전에 정리했지만, 실제 프로젝트에서 사용한건 JPA밖에 없다. 저 둘에 비하면 딸깍충 그자체인 JPA에 대해서 알아보자. 이 글은 JPA의 이론을 간략하게 정리한 것 이다.

ORM (Object Relational Mapping)

Java와 DB는 서로 데이터를 관리하는 방식이 다르다.

Java는 객체의 형태로 데이터를 관리하지만 RDB는 이를 표 형식으로 저장하고 연관관계를 외래키를 이용하여 형성한다.

이런 두 패러다임의 괴리를 객체-관계 불일치라고 하고, 이 때문에 DB의 데이터를 객체로 변환하는 과정에서 적절한 매핑이 필요하다.

ORM은 바로 이러한 괴리를 해결하기위해 데이터베이스와 객체의 매핑을 수행하는 방법을 제공한다.

추가로 ORM은 이러한 매핑을 통해 SQL문을 직접 생성하고 수행까지 한다.

물론 단점(trade-off)도 존재하는데 SQL 쿼리가 개발자을 안거치고 자동으로 만들어져 수행되기에 의도와 다른 쿼리가 발생할 수도 있고, 성능이 떨어질 수도 있다.

그렇기에 상황에 따라 ORM에 쿼리를 맡길지 직접 작성할지 판단이 필요하다.


JPA (Java Persistensce API)

JPA는 자바 진영의 ORM 표준 명세로, 애플리케이션과 데이터베이스 간 객체-관계 매핑을 일관된 방식으로 다룰 수 있도록 하는 인터페이스 집합이다.

JPA 자체가 기능을 구현하는 것은 아니며, Hibernate, EclipseLink와 같은 구현체가 실제 동작을 담당한다.

JPA를 사용하면 다음과 같은 장점을 얻을 수 있다.
엔티티와 테이블 매핑: 자바 클래스와 DB 테이블 간 매핑을 애노테이션으로 정의
DDL 자동 생성: 매핑 정보를 기반으로 테이블 스키마 자동 생성
JPQL 지원: 객체 지향 쿼리를 작성하면 구현체가 이를 SQL로 변환하여 실행
DB 독립성 향상: 여러 DB 벤더 간 쿼리 차이를 최소화

단, 자동 쿼리 생성과 매핑 편의성 뒤에는 성능 문제가 숨어 있을 수 있다.
따라서 연관관계 설계와 쿼리 튜닝은 여전히 중요하며, 상황에 따라 직접 SQL/네이티브 쿼리를 사용하는 판단도 필요하다.


Hibernate

Hibernate는 JPA를 구현한 구현체 프레임워크 중 하나이다.

Spring Data JPA는 JPA 위의 추상화입니다.
기본적으로 Hibernate를 많이 쓰지만 필수는 아님(EclipseLink 등 다른 구현체도 가능)

이를 한층 더 추상화하여 개발자가 Repository만 정의해도 쿼리 없이 DB 연동이 가능하게 해준다.

Hibernate는 JDBC나 MyBatis에 비해 설정해야 할 것들이 적고, 상당부분이 자동화되어 있다.

또한 변경감지, 지연로딩, 캐시, 트랜잭션 관리 등의 기능을 통해 강력한 영속성 관리 기능을 제공한다.

순수 Hibernate환경에서는 /resources/META-INF/persistence.xml을 통해 데이터베이스 연결 정보, 사용할 엔티티 클래스, JPA 구현체(Hibernate) 등을 설정해야 한다.
이 파일은 JPA의 표준 설정 파일로, Hibernate가 아닌 다른 JPA 구현체에도 공통적으로 사용된다.

하지만 스프링부트에선 application.yml로 보다 간편한 설정이 가능하다. 또한 스프링부트는 @Entity가 붙은 클래스를 스캔해서 자동으로 등록하기에 다른 설정이 필요하지 않다. persistence.xml로 설정해본적 한번도 없다.스프링부트 최고!

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/oauth
    username: dev_bae
    password:

  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true
    properties:
      hibernate:
        format_sql: true

EntityManagerFactory

밑에서 설명할 EntityManager을 생성하는 팩토리 객체이다.

EntityManagerFactory는 EntityManager뿐만 아니라 설정정보를 토대로 DB연결 정보, 커넥션 풀, 매핑된 엔터티 클래스들, 캐시전략, 영속성 컨텍스트등을 생성하기 때문에 생성비용이 크다.

따라서 어플리케이션 전체에서 한 번 생성하고 어플리케이션이 종료되면 close()를 호출해 닫는다.

EntityManager

EntityManager 말 그대로 Entity를 관리하는 객체이다.

즉, 자바객체와 DB의 테이블을 매핑하고 INSERT, SELECT, UPDATE, DELETE을 통한 CRUD작업을 객체 중심으로 쉽게 수행할 수 있게 한다.

EntityManager는 영속성 컨텍스트(Persistence Context)를 이용해 엔터티 객체의 생명주기를 관리한다. 영속성 컨텍스트는 DB에서 가져오거나 저장하는 엔터티 객체를 1차 캐시에 보관하고, 해당 객체의 상태를 추적하여 변경 사항이 감지되면 자동으로 SQL을 생성한다.

또한 트랜잭션의 관리도 맡고있다. 트랜잭션의 시작, 커밋, 롤백을 제어하는 역할도 한다. 보통 Spring에서는 @Transactional을 사용하여 선언적 트랜잭션을 처리한다.

EntityManager는 쓰레드당 하나가 생성되며, EntityManager는 트랜잭션 단위로 Entity를 관리한다.

😓 추가 학습에 따른 수정
EntityManager는 스레드 세이프하지 않다고 합니다. 그러므로 여러 스레드에서 공유해서는 안 됩니다.
EntityManager는 보통 요청(Request) 또는 트랜잭션 단위로 생성·관리되며, 트랜잭션이 시작되면 해당 범위 내에서 엔티티를 관리합니다.
스프링에서는 이미 생성된 EntityManager 프록시가 주입되고, 트랜잭션이 시작되면 실제 DB 연결이 바인딩됩니다.

영속성 컨텍스트 (Persistence Context)

EntityManager는 영속성컨텍스트를 이용하여 Entity를 관리한다고 했다. 그럼 영속성컨텍스트는 어떻게 이를 관리하고 어떠한 동작들을 하는가?

영속성컨텍스트는 Entity의 상태를 추적하고, 해당 Entity가 실제 DB와 상호작용하는 방법과 시점을 결정하는 역할을 가지고 있다.

영속성 컨텍스트는 여러 역할을 맡고있다.

  1. Entity 상태 관리(상태 추적)

    Entity의 생명주기(Transient → Managed → Detached → Removed)를 관리

    영속화(persist())하면 Managed(영속상태)로 진입하여, 영속성 컨텍스트가 이를 추적한다.

    ⇒ 이로인해 변경감지가(Dirty Checking)이 가능.

    • Entity의 생명주기 상태
      • 비영속(new / transient) 영속성 컨텍스트가 관리하지 않는 상태이다. 그러므로 변경감지가 이루어지지 않고 DB에 저장되지도 않는다. 이를 영속성 컨텍스트가 관리하게 하기 위해선 merge나 persist되어야 한다.
      • 영속(persist()/managed) 영속성 컨텍스트에 의해 관리되고 있는 상태이다. Entity의 변경사항을 추적하고, 트랜잭션이 커밋되면 변경사항이 DB에 반영된다.
      • 준영속(detach() / detached) 더 이상 영속성 컨텍스트의 관리를 받지 않는 상태이다. (영속상태였다가 분리 된 상태) 비영속 상태와 동일하게 변경감지가 되지 않고 커밋하여도 DB에 반영되지 않는다. merge()를 통해 영속상태로 변경이 가능하다.
      • 삭제(remove() / removed) 영속성 컨텍스트 뿐만 아니라 DB에서도 삭제를 하기 위한 상태이다. 커밋 시 DB에서 삭제된다.
  2. 변경감지(Dirty Checking)

    영속성 컨텍스트에서 관리되고 있는 Entity객체의 상태변화를 자동으로 감지하여 이를 DB에 반영한다.

    이를 위해 스냅샷(Snapshot)이 사용되는데, 이는 Entity객체의 초기 상태를 저장하고 있다. 이후, Entity 객체체의 상태가 변경되면 가장 최신의 상태와 스냅샷을 비교하여 변경을 파악한다. 이 변경을 커밋시에 DB에 반영된다.

  1. 1차캐시

    영속성 컨텍스트에 저장된 Entity는 DB에 조회하지 않고 바로 꺼내 반환한다.

    동일한 Entity가 동일한 영속성 컨텍스트에서 여러번 조회된다면, 하나하나 DB에 접근하여 반환받아오는 것이 아니고 이를 캐시된 Entity를 반환한다. ⇒ 성능 최적화

    영속성 컨텍스트가 종료되면 해당 캐시는 소멸함.

  2. 지연로딩(Lazy Loading)를 통한 연관관계 관리

    지연로딩이란 객체의 전체 데이터를 즉시 불러오지 않고, 실제로 필요한 시점이 되서야 느긋하게 로딩을 하는 전략이다.

    지연로딩을 위해 Hibernate는 프록시객체를 사용한다.

    실제 Entity객체 대신 프록시객체를 반환하는데, 이 프록시 객체는 해당 Entity객체의 메타데이터와 식별자만 보유하고 있고 실제 이 객체에 접근해야할때 실제 DB에서 해당 데이터를 로드한다.

예를 들어, Member 객체가 Profile를 참조하고 있다.
이때 Member 객체를 조회하면 Profile의 실제객체가 아니라 프록시객체가 Member필드에 할당된다. Profile의 데이터를 실제 가져와야 할 때 해당 쿼리가 실행되어 Profile 데이터가 로딩된다.

  1. 쓰기지연(Write-behind)

    쓰기지연을 이해하기위해 선행되어야할 개념인 Action Queue라는게 있다.

    Action Queue는 Entity를 관리할 쿼리들을 모아놓은 큐이다.

    즉, Hibernate는 Action Queue를 통해 쿼리를 생성할때마다 DB에 질의하지 않고 모아놨다가 한번에 반영시킨다.

그러면 안되겠지만 변기에 쓰레기를 버리는 못된 사람이 있다고 치자 이사람이 쓰레기를 하나 버릴때마다 물을 내리면 비효율적이지 않겠나? 이를 쓰레기를 변기에 모아놨다가 한번에 물을 내리는(flush)것 이다.

물을 내리는 행위 즉, 모아놨던 쿼리를 DB에 한번에 반영하는 방법은 flush()트랜잭션 커밋, JPQL쿼리 발생이 있다.

  1. 동일성(identity) 보장

    같은 PK를 가지는 Entity를 영속성 컨텍스트 내에서 같은 객체로 판단하여 관리한다. 이는 ==비교도 동일성 확인이 가능하며, DB의 일관성을 보장해준다.


💡
개인적으로 표현하자면 영속성컨텍스트는 DB와의 효율적인 상호작용을 위해 어플리케이션과 DB의 중간다리를 통제하는 것이라고 이해하고 있다.

매핑객체

JPA가 사용할 Entity를 정의하여야 이를 DB에 매핑할 수 있다.

@Entity

Entity는 DB의 테이블을 자바 클래스 형태로 표현한 것이다. 이 Entity클래스의 인스턴스는 DB의 행(Row)를 의미한다.

@Entity 어노테이션으로 선언한다.

@Table어노테이션을 통해 DB의 어떤 테이블과 매핑되어야 하는지를 지정한다. @Table을 사용하지 않으면 클래스의 이름으로 매칭될 테이블의 이름을 유추하여 매핑을 수행한다.

JPA는 내부적으로 리플렉션을 사용해 Entity객체를 만드므로 기본생성자가 필수로 필요하다.

CREATE TABLE members(
	member_id bigint not null primary key auto_increment,
	name varchar(20) not null,
	email varchar(50) not null
);
@Getter
@Entity
@Table(name = "members")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Members {
    
   ...
}

@Id

테이블의 PK와 매핑되는 필드를 @Id어노테이션으로 선언할 수 있다.

@GeneratedValue를 통해 자동으로 생성되게 하거나, 수동으로 설정할 수 도 있다.

일반적으로 auto_increment와 동일한 IDENTITY전략을 사용한다. 물론 id 생성전략은 여러가지가 있다. 이에 대해 아래에서 알아보겠다.

  • 자동 전략 (GenerationType.AUTO)
    JPA 구현체가 현재 사용 중인 데이터베이스 방언(dialect)에 맞춰 적절한 기본키 생성 전략을 자동으로 선택한다.
    예를 들어, MySQL에서는 IDENTITY, Oracle에서는 SEQUENCE 전략을 선택하는 식이다.

  • 시퀀스 전략 (GenerationType.SEQUENCE)
    시퀀스 객체를 사용하여 자동으로 id값을 생성한다. @SequenceGenerator를 통해 시퀀스의 이름과 시작 값등을 정의할 수도 있다.

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq_gen")
    @SequenceGenerator(
        name = "user_seq_gen",
        sequenceName = "user_seq",   // DB 시퀀스 이름과 일치
        allocationSize = 1
    )
    private Long id;

    이렇게 해놓으면 우리가 시퀀스를 직접 호출하지 않아도 JPA가 INSERT전에 자동으로 시퀀스를 호출하여 id값을 생성한다.

  • 식별자 전략 (GenerationType.IDENTITY)
    auto_increment와 같은 방식이라고 생각해면 된다. DB에서 auto_increment방식으로 생성된 PK값을 가져와 매핑한다.

  • 테이블 전략(GenerationType.TABLE)
    시퀀스 기능이 없는 DB에서 시퀀스전략과 비슷한 전략을 취할 수 있는 전략이다. @TableGenerator를 사용해 기본키 값을 생성하는 테이블의 설정을 정의할 수 있다.

  • UUID 전략 (GenerationType.UUID)
    우주유일값을 기본키값으로 설정하는 전략이다! 짱 멋지다.

@Column

Hibernate는 기본적으로 필드 이름을 기반으로 DB의 칼럼을 매핑한다.

하지만 서로의 이름이 다르거나 명시적으로 표현해야할 때 @Column의 name을 통해 이름을 명시적으로 매핑 시킬 수 있다.

또한 DB 속성의 제약조건을 표현해 줄 수 있다. (columnDefinition, nullable, unique …)

이를 바탕으로 AutoDDL시 좀 더 구체적으로 테이블을 생성할 수도 있다.

@Enumerated

자바의 enum타입이랑 매핑할때 사용

  • @Enumerated(EnumType.ORDINAL: enum클래스에 정의된 순서대로 숫자로 매핑
  • @Enumerated(EnumType.STRING : enum의 이름이 그래도 매핑

기본값은 ORDINAL이라 굉장히 킹받는 상황이 생길 수 있다. 웬만하면 @Enumerated(EnumType.STRING)을 사용하자.(순서 변경 시 데이터 깨짐 방지).


연관관계

위에서 정리한 내용을 토대로 팀과 선수 Entity를 만들었다고 생각해보자. 이는 이대로 괜찮을까?

그렇지 않다 왜냐하면 둘의 관계가 정의되어 있지 않다.

객체와 테이블의 불일치를 해소하는 것이 orm이거늘.. 하지만 객체의 세상엔 관계라는 것이 따로 존재하지 않는다. 그로인해 앞으로 알아볼 Entity의 연관관계에는 Table의 관계와는 차이가 있을 수 밖에 없다.

Entity의 연관관계 매핑을 위해서는 3가지의 개념을 생각해봐야 한다.


다중성

다중성이란 테이블이 다른 대상과 얼마다 연결될 수 있는지를 정량적으로 표현한 것 이다.

DB를 기준으로 다중성을 결정한다.

일대일

말 그대로 하나의 객체가 다른 단 하나의 객체와만 연관될 때의 연관관계이다.

ex) 학생과 책상(학생은 각 하나의 책상을 사용한다.)

@Entity
public class Student{
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long sudentId;
	
	@OneToOne
	private Desk desk;

}

@OneToOne 어노테시션으로 선언한다.

일대다, 다대일

실생활에서도 가장 흔하게 볼 수 있는 연관관계 다중성이다. 하나의 엔터티가 여러 엔터티와 연관될 때 사용한다.

ex) 팀과 선수관계, 반과 학생관계, 게시판과 게시글의 관계 등

@Entity
public class Post{

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long postId;
	
	@ManyToOne
	@JoinColumn(name = "board_id")
	private Board board;
	
@Entity
public class Board{

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long boardId;
	
	@OneToMany(mappedBy = "board")
	private List<Post> posts;
	

@ManyToOne@OneToMany를 사용하여 지정한다.

다대다

다대다 연관관계는 여러 엔터티가 여러 엔터티와 연관되는 관계이다.

ex) 학생과 전공수업

@Entity
public class Student{

	...
	
	@ManyToMany
	private List<Class> classes;
@Entity
public class Class{

	...
	@ManyToMany
	private List<Student> students;

위에서 언급했듯이 다대다 관계는 문제를 일으킬 가능성이 높다.

다대다연관관계가 생성되면 자동으로 중간테이블(외래키만 가지는)이 생기는데 이를 통하는 조인발생 시 예상보다 복잡한 쿼리 때문에 성능의 저하가 일어날 수 있다.

다대다를 일대다 혹은 다대일로 풀어서 설계하는 것이 좋겠다.


방향

DB는 관계는 따로 방향이 없다. 데이터베이스 테이블은 외래 키 하나로 양 쪽 테이블 조인이 가능하다.

CREATE TABLE players(
    id bigint primary key not null auto_increment,
    name varchar(20) not null,
    team_id bigint not null ,
    CONSTRAINT fk_team
                    FOREIGN KEY (team_id)
                    REFERENCES teams(id)
                    ON DELETE CASCADE
                    ON UPDATE CASCADE
);

CREATE TABLE teams(
    id bigint primary key not null auto_increment,
    name varchar(20) not null
)

위처럼 teams테이블의 id를 참조하는 player테이블을 생성하면

위와 같은 관계가 생긴다. players가 외래키를 가지고 있지만 DB에서는 양쪽에서 조인이 가능하다.

SELECT 
    p.name AS player_name,
    t.name AS team_name
FROM 
    players p
JOIN 
    teams t ON p.team_id = t.id;
SELECT 
    t.name AS team_name,
    p.name AS player_name
FROM 
    teams t
JOIN 
    players p ON t.id = p.team_id;

객체관계의 방향

그렇다면 Entity객체는 어떨까?

Players이 Team을 가지고 있기에 이제 접근이 가능하다. (플레이어에서 팀으로)

하지만 팀에서 플레이어한테 접근을 할 수가 없다..! DB에서와는 다른 특징을 보인다.

그래서 Team에서도 Players에 접근이 가능하도록 Team에 players를 추가했다.

이제 서로 참조가 가능하다.

이를 양방향 관계라고 하지만 사실 양방향 관계는 단방향 참조를 각각 가지는 것이므로 단방향 두개라고 생각할 수 있다.

https://noticon-static.tammolo.com/dgggcrkxq/image/upload/v1677813562/noticon/z1wvbmjlizsmcvmfksfq.png그럼 걍 전부다 양방향으로 하면 되는거 아님?
https://noticon-static.tammolo.com/dgggcrkxq/image/upload/v1701358143/noticon/rdgvkj1ghnucix4urkek.png ㄴㄴ 그럼 안됨. 딱봐도 복잡해질 거 같지 않음? 유지보수도 힘들어지고 성능도 안좋아지고 JPA가 어떻게 매핑해야하는 지 헷갈려함. Post와 Board가 양방향 관계를 맺고 있을 때 님이 게시글을 다른 게시판으로 옮기고 싶다고 해보면, 이걸 Post에서 setBoard()로 할지 Board에서 getPosts()로 할지 헷갈릴 수 있지 않겠음?
그래서 일반적으로 모두 단방향으로 해놓고, 나중에 꼭 필요하다고 생각되는 것만 양방향을 고려하는 식으로 함.
그래서 이제 JPA가 양방향 관계에서 안헷갈리게 하기 위해서 연관관계 소유자라는 걸 지정해줘야함.

연관관계 소유자

위 처럼 양방향 관계에서 어떤 Entity가 외래키를 관리하는지 JPA에게 알려줘야 안헷갈려하지 않겠는가.

이를 위해서 우리는 양방향 연관관계 어떤 연관관계가 외래키를 진짜 가지고있는지를 설정해주어야 한다.

연관관계의 주인만이 두 객체 사이에서 조회, 수정, 삭제가 가능하고 아니라면 조회만 가능하다.

외래키를 가지는(연관관계 소유자)객체에는 @JoinColumn으로 지정하고

연관 관계의 주인이 아닌 객체에 mappedBy속성으로 지정할 수 있다.(Table로 따지면 외래키를 가지지 않는 / 참조당하는 쪽)

⇒ 참고로 단방향일땐 @JoinColumn만 사용하면 된다.

Team과 Players의 관계로 예를 들어보자.

@Entity
public class Players {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Teams team;

Players가 다(Many)쪽에 속하므로 Playsers가 외래키를 갖는 것이 적합하여 Players에 @JoinColumn을 붙여주고, Team에는 mappedBy을 사용하여 연관관계 소유자를 지정해주어야 한다.

@Entity
public class Teams {

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

    private String name;

    @OneToMany(mappedBy = "team")
    private List<Players> players;
}

이처럼 mappedBy로 지정할 때 값은 대상이 되는 변수명을 따라 지정한다.

다대다의 연관관계 소유자

다대다 관계에선 자동으로 중간테이블(외래키만 가지는)을 생성한다. 이 중간 테이블의 이름이나 칼럼명을 명시적으로 커스터마이징할 때 @JoinTable을 사용한다. 물론 mappedBy을 통해 주인이 아님을 명시적으로 정의해야한다.

@Entity
public class Student{
	
	...
	
	@ManyToMany
	@JoinTable(
		name = "student_class_table",
		joinColumns = @JoinColumn(name="student_id"), //현재 엔터티의 외래키
		inverseJoinColumns = @JoinColumn(name="class_id") //상대 엔터티의 외래키
	)
	private List<Classes> classes;
	
	...
	
}

다만, 위에서도 언급했듯이 다대다연관관계는 설계적 결함이 있는 경우라고 생각되어진다. 고로 일대다/일대일 구조로 바꾸어 설계하는 것이 성능/유지보수등 여러 방면에서 이로울 것 이다.


영속성 전이 : CASCADE

게시판과 게시글 엔터티가 있을 때, 게시판은 여러개의 게시글을 자식 엔터티로 가지고 있을테다. 이때 만약 게시판을 영속상태로 만들고 싶을때 자식 엔터티인 게시글들도 일일히 영속상태로 만들기 귀찮고 복잡할 수 있다. 이 때 사용할 수 있는 것이 영속성 전이이다.

영속성 전이는 JPA에서 특정 엔터티를 영속상태로 만들때 이와 연관된 엔터티도 함께 영속상태로 만들때 사용한다.

영속성 전이를 적용하면 게시판(부모 엔터티)을 저장하면 게시글(자식 엔터티)도 함께 저장되고, 삭제를 하는 경우에도 같은 방식으로 게시판이 삭제되면 게시글도 삭제 된다.

영속성 전이는 @ManyToOne@OneToMany관계에서 cascade속성을 통해 사용한다.

@Entity
public class Boards{

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

    private String title;
    private String description;

    @OneToMany(mappedBy = "board", cascade = CascadeType.ALL)
    private List<Posts> posts;
}

CASCADE의 종류

  • CascadeType.ALL : 모든 Cascade 옵션을 적용
  • CascadeType.PERSIST : 엔티티를 영속화할 때, 연관된 엔티티도 함께 영속화
  • CascadeType.REMOVE : 엔티티를 제거할 때, 연관된 엔티티도 함께 제거
  • CascadeType.MERGE : 엔티티 상태를 병합할 때, 연관된 엔티티도 함께 병합
  • CascadeType.REFRESH : 부모 엔티티를 Refresh하면, 연관된 엔티티도 함께 Refresh
  • CascadeType.DETACH : 부모 엔티티를 Detach하면, 연관된 엔티티도 함께 Detach

대부분의 경우 ALL을 사용하지만, 상황과 목적에 맞게 사용하면 된다.


고아객체

이름부터 폭력적인 이 고아객체는 말 그대로 부모가 버리거나, 부모가 없어진 객체이다.

Board board = em.find(Board .class, 1L);
board.getPosts().remove(0);

위 코드처럼 게시판에서 게시글을 삭제하면 삭제된 게시글은 부모가 없어져 고아객체가 된다. 이때 고아객체 제거 기능이 참이라면 해당 게시글은 게시판에서만 삭제되는 것이아니라 DB에서 삭제된다.

DELETE FROM posts WHERE ID = xxx;

실제로 위와 같은 SQL쿼리가 DB에 적용되며 부모잃은 불쌍한 게시글은 DB에서 삭제되고 만다.

이는 게시판 자체가 삭제되어도 같은 원리로 삭제된 게시판과 연관된 모든 게시글이 삭제된다.(CascadeType.REMOVE와 비슷)

부모 삭제 시 자식 자동 삭제는 orphanRemoval만으로 보장되지 않을 수 있으므로 보통 cascade = REMOVE를 함께 설정한다.


고아객체 제거는 위 설명과 같이 자식 엔터티가 부모 엔터티에 종속되어있을 때 사용된다. 부모로부터 참조가 제거되었다면 유일한 참조를 잃었다고 판단되는 객체가 고아객체이다. 즉 고아객체 제거 기능은 자식엔터티를 참조하는 곳이 하나만 존재할 때 사용할 수 있다.

고아객체 제거 기능은 @OneToOne, @OneToMany관계에서만 사용가능한데 orphanRemoval속성을 true로 주어 활성화한다.(기본값은 false이다)

@Entity
public class Boards{

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

    private String title;
    private String description;

    @OneToMany(mappedBy = "board", cascade = CascadeType.ALL, orphanRemoval= true)
    private List<Posts> posts;
}

참고로 위와 같이 cascade와 orphanRemoval= true를 함께 사용하면 자식 엔터티의 생명주기를 부모 엔터티로 관리가 가능하다. ⇒ 자식엔터티의 생명주기가 부모 엔터티에 의존

profile
전 아무고토 몰루고 아무고토 못해여

0개의 댓글