엘리스 트랙 6주차, Spring 2

junto·2024년 2월 3일
0

spring

목록 보기
2/30
post-thumbnail


이번 주는 Spring JPA 개념과 사용법, 트랜잭션 개념, JPA Auditing, 연관관계 매핑 등 많은 걸 배운 한 주 였다. docker로 프로젝트 설정을 하면서도 왜 안 되지? 하며 배운 것들도 많았는데 이건 자료가 좀 더 쌓이면 포스팅해야겠다. 개념을 정리하며 JPA가 정말 막강한 기술이고 이를 잘 다루려면 더 깊이 알 필요가 있다고 느꼈다.

Spring JPA

1. ORM이란

  • Object Relational Mapping(객체 - 관계 - 매핑)을 말한다.
  • 어플리케이션 객체와 관계형 데이터베이스의 데이터를 매핑해주는 도구이다.
  • 대표적인 ORM으로는 Hibernate, JPA 등이 존재한다.

왜 ORM을 사용할까?

  • 객체 모델과 관계형 모델간의 불일치가 존재한다. 객체 지향 프로그래밍은 클래스를 이용하지만, 관계형 데이터베이스는 테이블을 이용한다.
  • 데이터베이스의 접근을 프로그래밍 언어로 접근한다.
  • ORM을 이용해서 객체 간의 관계를 바탕으로 SQL을 자동 생성한다.

ORM의 장점

  • 직관적인 코드로 데이터를 조작할 수 있다.
    • 개발자가 비즈니스 로직에 집중할 수 있다.
  • 재사용 및 유지보수가 편리하다.
    • ORM은 독립적으로 작성되어 있어 객체들 재사용이 가능하다.
    • 매핑 정보가 명확하기에 ERD에 대한 의존도를 낮출 수 있다.
  • DBMS에 대한 종속성이 감소한다.

ORM의 단점

  • 복잡도가 올라가는 경우 ORM만으로는 구현의 한계가 있다.
    • 직접 쿼리를 구현하지 않기에 복잡한 설계는 어렵다.
  • 자주 사용하거나 복잡한 쿼리는 튜닝이 필요할 수 있다.
  • 프로시저가 많은 시스템에서는 ORM의 객체 지향적인 장점을 활용하기 어렵다.

2. JPA와 하이버네이트, Spring Data JPA

JPA

  • JPA란 Java Persistence API를 말한다.
  • 자바 애플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스다.
  • 인터페이스이기에 Hibernate나 EclipseLink 등으로 JPA를 구현한다.

Hibernate

  • JPA 인터페이스 명세를 구현한 구현체 중 하나이다.
  • 가장 널리 사용되고 있는 구현체다.

Spring Data JPA

  • Spring에서 JPA를 편리하게 사용할 수 있게 지원하는 라이브러리다.
    • CRUD 인터페이스 제공한다.
    • repository 개발 시 인터페이스만 작성하면 실행 시점에 객체를 동적으로 생성해서 주입한다.
    • 데이터 접근 계층 개발 시 인터페이스만 작성하면 된다.
  • Hibernate에서 자주 사용되는 기능을 보다 쉽게 사용할 수 있게 구현한 것이다.

3. 영속성 컨텍스트

  • 엔티티(Entity)를 영구 저장하는 환경을 말한다.
    • 엔티티 객체가 영속성 컨텍스트에 들어와 JPA 관리 대상이 되는 시점부터는 해당 객체를 영속객체(Persistence Object)라고 부른다.
  • 애플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스 역할을 수행한다.
    • 세션 단위 생명 주기를 가진다.
    • DB 접근 위한 세션이 만들어지면 영속성 컨텍스트가 만들어지고, 세션이 종료되면 영속성 컨텍스트도 없어진다. 엔티티 매니저는 영속성 컨텍스트에 접근하기 위한 수단으로 사용된다.
  • 엔티티 매니저(Entity Manager)를 통해 영속성 컨텍스트에 접근하며 영속성 컨텍스트는 엔티티를 보관하고 관리한다.
  • 서비스에는 하나의 엔티티 매니저 팩토리(Entity Manager Factory)가 존재하며 엔티티 매니저 팩토리에서 데이터베이스에 접근하는 트랜잭션이 생길 때마다 엔티티 매니저를 생성하여 영속성 컨텍스트에 접근한다.
  • 엔티티 매니저를 통해 영속성 컨텍스트에 접근할 수 있고, 영속성 컨텍스트를 관리할 수 있다.
  • 여러 엔티티 매니저가 같은 영속성 컨텍스트에 접근하는 경우도 존재한다.
  • 엔티티 매니저는 내부에 데이터소스(데이터베이스 커넥션)을 유지하면서 데이터베이스와 통신한다.
  • 엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로 스레드 간 공유하거나 재사용하면 안 된다.

영속성 컨텍스트 특징

  • 식별자 값이 반드시 있어야 한다.
    • 영속성 컨텍스트는 엔티티를 식별자 값(@Id로 테이블의 기본 키와 매핑한 값)으로 구분하기에 영속 상태는 식별자 값이 반드시 있어야 한다.
  • 영속성 컨텍스트와 데이터베이스에 저장한다.
    • flush() 를 통해 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영한다.
  • 영속성 컨텍스트가 엔티티를 관리하면 다음과 같은 장점이 있다.
    1. 1차 캐시
      • 엔티티 조회 시 메모리에 있는 1차 캐시에서 식별자 값으로 조회한다.
    2. 동일성 보장
      • 식별자로 인식하기에 실제 인스턴스가 같음을 보장한다.
    3. 트랜잭션을 지원하는 쓰기 지연
      • 엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 INSERT SQL을 차곡차곡 모아둔다. 트랜잭션을 커밋할 때 모아둔 쿼리를 데이터베이스에 보내는데 이것을 트랜잭션을 지원하는 쓰기 지연이라고 한다.
    4. 변경 감지
      • 영속 상태에 있는 엔티티 변경 사항을 DB에 자동으로 반영한다.
    5. 지연 로딩
      • 엔티티를 조회할 때 지연 로딩이 설정된 연관 엔티티는 프록시 객체로 대체 되어 실제 엔티티에 대한 접근이 필요할 때까지 데이터베이스 조회를 지연시킨다.

엔티티 생명 주기

  • 비영속(New/Transient)
    • 엔티티 객체를 생성했지만, 아직 영속성 컨텍스트에 저장하지 않은 상태이다.

      Post post = new Post();
  • 영속(Managed)
    • 엔티티 매니저를 통해서 엔티티를 영속성 컨텍스트에 저장된 상태를 말한다.

    • 영속성 컨텍스트에 의해 관리되는 상태를 말한다.

      EntityManagerFactory emf = Persistence.createEntityManagerFactory("elice");
      EntityManager em = emf.createEntityManager();
      
      em.persist(post);
  • 준영속(Detached)
    • 영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 더 이상 관리하지 않을 때의 상태를 말한다.

    • 영속성 컨텍스트가 제공하는 기능이 동작하지 않은 상태를 말한다.

      // 영속성 컨텍스트에서 분리하여 준영속 상태
      em.detach(member);
      
      // 영속성 컨텍스트를 비워서 준영속 상태
      em.clear();
      
      // 영속성 컨텍스트를 종료해서 준영속 상태
      em.close();
  • 삭제(Removed)
    • 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제된 상태를 말한다.

      em.remove(member);

Spring Data JPA에서 엔티티 매니저

  • Spring Data JPA의 Repository Interface에는 엔티티 매니저가 포함되어 있어 직접 작성하지 않아도 내부에서 자동으로 호출된다.

4. 데이터베이스 연동하기(JPA)

ddl-auto 옵션

  • create: 테이블 삭제 후 다시 생성(DROP + CREATE)
  • create-drop: create와 같지만, 종료 시점에 테이블 DROP
  • update: 변경된 부분 반영
  • validate: 엔티티와 테이블이 정상 매핑되었는지만 확인
  • none: 사용하지 않음

cf. 운영 서버에서는 절대 create, create-drop, update를 사용하지 않는다. 기존 테이블이 변경 또는 삭제될 수 있기 때문이다.

5. Entity 설계하기

엔티티 관련 기본 애노테이션

  • @Entity
    • 해당 클래스가 엔티티임을 명시하기 위한 애노테이션이다.
    • 클래스 자체가 테이블과 1:1로 매칭된다.
  • @Table
    • 클래스의 이름과 테이블의 이름이 다른 경우 사용되는 어노테이션이다.
    • 자바에서 명명법과 데이터베이스의 명명법이 다르기 때문에 자주 사용된다.
  • @Id
    • 테이블의 기본값을 지정하는 애노테이션이다.
  • @GeneratedValue
    • 해당 필드의 값이 어떤 방식으로 자동 생성될지 결정한다.
      • 직접 할당의 경우 Generated Value를 사용하지 않는다.
      • AUTO: 기본값을 데이터베이스에 맞게 자동 생성한다.
      • IDENTITY: 기본값 생성을 데이터베이스에 위임한다.
      • SEQUENCE: 식별자 생성기를 직접 설정하고 이를 통해 기본 키를 위한 유일한 값을 생성할 때 사용한다.
      • TABLE: 키 생성 테이블을 사용한다.
  • @Column
    • 객체 필드를 테이블 컬럼에 매핑한다.
    • 많이 사용되는 요소
      • name: 데이터베이스 컬럼명 설정, 명시하지 않으면 필드명으로 지정
      • nullable: null 값의 허용 여부
      • length: 데이터베이스에 저장하는 데이터의 최대 길이
      • unique: 해당 컬럼의 유일성 여부
      • ColumnDefinition: 데이터베이스 컬럼 정보를 직접 설정 @Column(columnDefinition = "char(3)"
  • @Enumerated
    • enum 타입을 매핑할 때 사용한다.
      • EnumType.ORDINAL: enum 순서를 데이터베이스에 저장 (default)
      • EnumType.STRING: enum 이름을 데이터베이스에 저장

6. Repository 설계하기

  • Spring Data JPA는 JpaRepositry를 기반으로 쉽게 데이터베이스 사용이 가능하다.
  • repository는 엔티티에 대한 인터페이스 생성 후 JpaRepository를 상속하여 생성한다.
    • 대상 엔티티와 기본값 타입 지정이 필수적이다.
public interface MemberRepository extends JpaRepository<Member, Long> {}
  • JpaRepository를 상속받으면 기본적으로 별도의 메소드 구현 없이 많은 기능을 사용할 수 있다.
    • JpaRepository는 CrudRepository, PagingAndSortingRepository, QueryByExampleExecutor 등을 상속한다.

Repository 메서드 생성 규칙

  • 메서드 이름으로 JPA에서 쿼리를 자동으로 생성한다.
  • 조회 메서드(find)에 조건으로 붙일 수 있는 대표적인 기능
    • findBy: sql 문의 where 절 역할
    • AND, OR: 조건을 여러개 설정하기 위한 역할
    • Like/Not Like: sql 문의 like와 동일
    • isNull/isNotNull: Null이거나 Null이 아닌 값을 확인
    • LessThan/GreaterThan: 특정 값을 기준으로 대소 비교 시 사용
    • Between: 두 값 사이에 데이터를 조회
    • OrderBy: SQL 문에서 order by와 동일
    • countBy: SQL 문에서 count와 동일

Spring Data JPA

1. 페이징과 정렬처리

Pagination

  • DataSet을 더 작고 관리하기 쉬운 Chunk로 나누는 기술을 말한다.
  • 웹에 적용해 보면 Server가 client 요청에 전체 dataset을 응답하는 것이 아니라 나눠진 dataset을 응답한다.
  • 지금 보고 있는 페이지에 표시할 데이터만 데이터베이스에서 가져오게 된다.

Pagination은 왜 필요할까?

  • 전체 데이터 대신 필요한 데이터만 빠르게 응답할 수 있다.
  • 예를 들어, 수십만 개의 데이터를 한 번에 응답한다면? 리소스를 많이 사용하게 되어 클라이언트에서는 응답이 매우 느려진다.
  • 사용자에게 반응이 빠른 인터페이스를 제공한다.

PagingAndSortingRepository는 무엇일까?

  • 전체 문서들을 개별 페이지로 나누는 Pagination과 정렬하는 Sorting을 사용할 수 있게 한다.
  • 즉, 데이터 건수, 시작 위치, 정렬 조건 등을 사용할 수 있다.
public interface PagingAndSortingRepository<T, ID> {
		Iterable<T> findAll(Sort sort);
		Page<T> findAll(Pageable pageable);
}

// example
Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);

2. @Query 어노테이션

  • JPQL(Java Persistence Query Language)를 사용하여 Query를 생성한다.

JPQL

  • 객체지향 쿼리로 DB테이블이 아닌 엔티티 객체를 조회한다.
  • JPA 일부에 정의되어 있으며 DB 종류에 영향을 받지 않고 작성할 수 있는 장점이 있다.
  • 위치 기반 바인딩
public interface MemberRepository extends JpaRepository<Member, Long> {
		@Qeury("select m from member m where m.emailAddress = ?1")
		User findByEmailAddress(String emailAddress);
}
  • parameter 바인딩
public interface BoardRepository extends JpaRepository<Board, Long> {
		@Query("select * from board b where b.content like %:keyword")
		Board findByKeyword(@Param("keyword") String keyword);
}
  • Native Query
    • 유연하게 쿼리가 작성이 가능하다.
    • Spring Data JPA가 제공하는 특정 DB에 종류에 종속되지 않는 장점을 누릴 수 없다.
    • Pagination을 할 때 추가적인 작업이 필요하다.
    • 동적 정렬을 지원하지 않아서 order by와 같이 따로 처리가 필요하다.
@Query(value = "SELECT * FROM Users u
WHERE u.status = :status and u.name = :name",
nativeQuery = true)
User findUserByStatusAndNameNamedParamsNative(
	@Param("status") Integer status, @Param("name") String name);

3. JPA Auditing과 Base Entity

Audit(감사)란 무엇일까?

  • 엔티티와 관련된 이벤트를 추적하고 기록한다.
  • 여기서 이벤트는 Entity의 Create, Update, Delete와 같은 작업을 말한다.
  • 정보 보안, 데이터 관리 측면에서 데이터에 Auditing(감사) 정보를 추가해 기록한다.
  • Spring Data JPA에서는 기본적으로 Auditing 정보를 추가하는 기능을 제공한다.
  • 엔티티를 생성하거나 변경한 사람과 이러한 일이 발생한 시점을 투명하게 추적할 수 있게 지원한다.
  • 어노테이션을 통해 엔티티 클래스에 적어주어야 한다.

Audit 구현하기

  • Spring Data JPA는 @EntityListeners 어노테이션을 사용해 자체 JPA 엔티티 리스너 클래스인 AuditingEntityListener를 제공한다.
  • 다음과 같은 설정들로 이벤트 리스너가 Content에 대한 정보를 Auditing 할 수 있게 된다.
  • SpringBootAplication에 @EnableJpaAuditing과 엔티티에 EntityListeners(AuditingEntityListener.class)를 설정해야 한다.
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Post {
	@Column(name = "created_date", nullable = false,  updatable = false)
	// Entity 생성 시간 자동 저장
	@CreatedDate
	private LocalDateTime createdDate;

	// Entity 변경 시간 자동 저장
	@Column(name = "modified_date")
	@LastModifiedDate
	private LocalDateTime modifiedDate;
}

JPA Auditing 위한 Base Entity

  • 모든 Entity에 Auditing 설정을 모두 작성하면 중복 코드가 발생하고 관리가 어렵게 된다. 따라서 베이스 엔티티를 만들어 다른 엔티티에서는 이를 상속하게 한다.
@MappedSuperClass
@EntityListeners(AuditingEntityListener.class)
public abstract class AuditableBaseEntity {
	@CreatedDate
	private LocalDateTime createdDate;
	@LastModifedDate
	private LocalDateTime lastModifedDate;
	@CreatedBy
	private String createdBy;
	@LastModifedBy
	private String lastModifiedBy;
}
  • 다른 엔티티에서 베이스 엔티티 상속
@Entity
public class User extends AuditableBaseEntity {
}

4. Transaction

DB Transaction

  • DB와 관련되어 수행되는 여러 처리를 하나의 단위로 취급하는 작업 단위를 말한다.
  • Transaction으로 작업을 진행하다 문제 발생 시 이전 상태로 되돌리는 롤백을 진행한다. 즉, 작업 하나를 안전하게 처리하도록 보장한다.
  • Transaction Commit이란 작업이 성공해서 DB에 반영하는 것을 말한다.
  • Transaction Rollback이란 하나의 작업이라도 실패하면 전체 작업이 DB 반영 이전으로 되돌리는 것을 말한다.

Transaction 특징

  • Atomicity(원자성): 트랜잭션이 완전히 성공하거나 완전히 실패하는 단일 단위로 처리되도록 보장한다.
  • Consistency(일관성): 무결성 제약 조건처럼 모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야 한다.
  • Isolation(격리성): 동시에 실행되는 여러 트랜잭션은 독립적으로 실행되어야 한다.
  • Durability(지속성): 트랜잭션이 성공하면 결과가 기록되어야 한다.

Transaction 격리 수준(Isolation Level)

  • DEFAULT: 기본적으로 데이터베이스의 디폴트 격리 수준을 사용한다.
  • READ_UNCOMMITTED: 다른 트랜잭션에서 커밋되지 않은 데이터를 읽을 수 있다.
  • READ_COMMITTED: 다른 트랜잭션에서 커밋된 데이터만 읽을 수 있다. 대부분의 데이터베이스 기본 설정이다.
  • REPEATABLE_READ: 트랜잭션 내에서 같은 데이터를 여러 번 읽을 때, 처음 읽은 데이터와 동일한 데이터를 보장받는다.
  • SERIALIZABLE: 트랜잭션을 순차적으로 실행하여 여러 트랜잭션이 동시에 동일한 데이터에 접근하는 것을 방지한다.

JDBC 트랜잭션

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class MyDatabaseService {

    public void executeBusinessLogic() {
        Connection connection = null;
        try {
            // 데이터베이스 연결
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "username", "password");

            // 트랜잭션 시작
            connection.setAutoCommit(false);

            // 비즈니스 로직 수행
            // 예: 데이터베이스 업데이트, 조회 등
            updateDatabase(connection);

            // 비즈니스 로직이 성공적으로 수행되면, 트랜잭션 커밋
            connection.commit();
        } catch (Exception e) {
            // 오류 발생 시, 트랜잭션 롤백
            if (connection != null) {
                try {
                    connection.rollback();
                } catch (SQLException se) {
                    se.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            // 자원 정리
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException se) {
                    se.printStackTrace();
                }
            }
        }
    }

    private void updateDatabase(Connection connection) {
        // 여기에 데이터베이스 업데이트 로직 작성
        // 예: SQL 실행
        // ...
    }
}

Programmatic 트랜잭션

import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

public class MyService {

    private final TransactionTemplate transactionTemplate;
	private final MyRepository myRepository;  
  
	public MyService(PlatformTransactionManager transactionManager,MyRepositoryV3 myRepository) {  
	//transactionManager 에서 TransactionTemplate 생성시킴
		this.transactionTemplate = new TransactionTemplate(transactionManager); 
	    this.myRepository = myRepository;  
	}  
  
    // TransactionTemplate 주입 (생성자, 세터 등을 통해)
    public MyService(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }

    public void executeBusinessLogic() {
    // 내부에서 실행
        transactionTemplate.execute(new TransactionCallback<Void>() {
            @Override
            public Void doInTransaction(TransactionStatus status) {
                try {
                    // 비즈니스 로직 수행
                    // 예: 데이터베이스 업데이트, 조회 등
                    updateDatabase();

                    // 성공 시 커밋은 자동으로 처리됨
                } catch (Exception e) {
                    // 실패(언체크 예외 발생) 시 트랜잭션 롤백
                    status.setRollbackOnly();
                    throw e;
                }
                return null;
            }
        });
    }

    private void updateDatabase() {
        // 여기에 데이터베이스 업데이트 로직 작성
        // 예: SQL 실행
        // ...
    }
}

트랜잭션을 서비스계층(비즈니스 로직)으로 부터 어떻게 분리할 수 있을까?

  • 비즈니스 로직이 실행되는 동안 데이터베이스에 무결성을 만족하기 위해 트랜잭션을 관리해 주어야 한다. 하지만 서비스 계층에서 트랜잭션 관리 로직을 실행한다면 코드의 복잡도가 증가하고 재사용하기 어려워진다. AOP를 이용한다면 트랜잭션을 별도의 Aspect로 정의하여 비즈니스 로직이 실행될 때 적절히 적용할 수 있다.
  • @Transaction 어노테이션을 달면 외부에서도 접근할 수 있는 public 메소드 들에 Transaction 적용이 된다.

선언적 트랜잭션 @Transaction

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class MyService {
	private final MyRepository myRepository;  
  
	public MyService(MyRepositoryV3 myRepository) {  
	    this.myRepository = myRepository;  
	}  
  
    // 해당 메소드는 트랜잭션으로 관리됨
    @Transactional
    public void executeBusinessLogic() throws SQLException {
            updateDatabase();
    }

    private void updateDatabase() {
        // 데이터베이스 업데이트 비즈니스 로직
        // ...
    }
}

트랜잭션 설정

  • readOnly
    • 트랜잭션을 읽기 전용으로 설정한다.
    • @Transactional(readOnly = true)
    • 성능 최적화에 도움이 된다.
  • propagation
    • 트랜잭션의 전파 행위를 정의한다.
    • Propagtion.REQURIED, Propagation.REQUIRED_NEW…
    • REQUIRED: 기본 동작 방식으로 현재 진행 중인 트랜잭션이 있으면 참여하고, 없으면 새로운 트랜잭션을 실행한다.
    • REQURIED_NEW: 항상 새로운 트랜잭션을 시작한다. 현재 진행 중인 트랜잭션이 있으면 일시 중단된다.
    • SUPPORTS: 이미 시작된 트랜잭션이 있으면 참여하고, 없으면 트랜잭션 없이 실행한다.
  • isolation
    • 트랜잭션의 격리 수준을 설정한다.
    • Isolation.READ_COMMITTED, Isolation.SERIALIZABLE …
  • timeout
    • 트랜잭션이 완료되기를 기다리는 최대 시간(초 단위)을 지정한다. 지정된 시간을 초과하면 트랜잭션은 강제로 롤백된다.
    • @Transactional(timout = 10)
  • rollbackFor, noRollbackFor
    • 특정 예외 발생 시 롤백을 수행할지, 아니면 롤백을 수행하지 않을지 정의한다.
    • @Transactional(rollbackFor = Exception.class), @Transactional(noRollBackFor = SpecifiedException.class)

Spring JPA 연관관계 매핑

  • 객체지향에서는 객체 간 참조를 사용해 객체들을 서로 연결하지만, DB에서는 테이블의 외래키를 사용해 정규화한다. 즉 테이블 간의 연관관계와 엔티티 간 연관관계 차이가 발생한다.

1. 연관관계 매핑 종류와 방향

연관관계 매핑

  • JPA에서 성능을 결정하는 중요한 요소이다.
  • 연관관계 설정을 통해 객체 참조와 테이블의 외래키를 매핑시켜 사용한다.
  • 방향성(Direction), 다중성(Multiplicity) 두 기준이 있다.

방향성

  • 단방향: 두 엔티티 관계에서 한쪽 엔티티만 참조하는 것을 말한다.
  • 양방향: 두 엔티티 관계에서 양쪽 엔티티가 서로 참조하는 것을 말한다.
  • 테이블은 FK로 양쪽을 참고할 수 있으므로 언제나 양방향 관계이다.

연관관계 주인(Owner)

  • 연관관계는 두 테이블 간의 관계를 말한다.
  • 한 테이블에서 다른 테이블 PK(Primary Key, 기본값)를 FK로 가지게 된다.
  • 일반적으로 FK를 가진 테이블이 주인이 된다.
  • 주인이 아닌 엔티티는 읽는 작업만 가능하다.

다중성(Multiplicity)

  • 다대일, 일대다, 일대일, 다대다가 있다.
  • 연관관계를 어떤 엔티티를 중심으로 보는지에 따라 연관관계 표현은 달라진다.

2. 다대일 단/양방향 매핑

다대일(N:1) 단방향 매핑


1. 댓글들은 하나의 글에 달려있다 (댓글 N: 글 1)
2. 댓글을 조회했을 때 댓글에 속해 있는 글도 조회된다.
3. 글을 통해 댓글에 접근할 수 없다.

@Entity
public class Comment {
		@ManyToOne
		@JoinColumn(name="POST_ID")
		private Post post;
}
// Post 객체는 Comment로부터 참조 당하기에 추가할 설정이 없다

다대일(N:1) 양방향 매핑


1. 댓글들은 하나의 글에 달려있다 (댓글 N: 글 1)
2. 댓글을 조회했을 때 댓글에 속해 있는 글도 조회된다.
3. 글을 통해 댓글에 접근할 수 있다.

@Entity
public class Comment {
		@Id @GeneratedValue
		private Long id
		
		@ManyToOne
		@JoinColumn(name="POST_ID") // FK가 있는 쪽이 주인, FK 관리
		Private Post
}
@Entity
public class Post {
		...
		@OneToMany(mappedBy="post")
		privlate List<Comment> comments = new ArrayList<>();
}

양방향 매핑과 주인(Owner)

  • 누가 FK를 관리(등록 / 수정)할 것인가?
  • 둘 중 하나 관계를 연관관계 주인으로 정하고 FK를 관리한다.
  • 주인이 아닌 쪽은 mappedBy 속성을 사용하고 조회만 가능하다.
  • FK 키가 있는 곳을 주인으로 정해야 한다.

@ManyToOne 어노테이션 속성

  • optional
    • 관계가 필수적인지 지정한다.
    • false로 선택하면 연관된 엔티티가 반드시 있어야 한다.
    • 이너조인으로 변경 시 사용된다.
    • 기본값은 true로 연관된 엔티티가 있지 않아도 된다.
  • fetch
    • 관련 엔티티를 언제 로딩할지 결정한다.
    • 지연로딩(Lazy): 연관 엔티티를 실제 사용할 때 조회한다.
    • 즉시 로딩(Eage): 엔티티를 로드할 때 관련 엔티티도 함께 로드한다.
    • 기본값은 @OneToMany에선 Lazy, @ManyToOne에선 Eager
  • cascade
    • 엔티티 생명주기 동작을 현재 엔티티에서 관련 엔티티로 전파할지 여부를 결정한다.
    • 엔티티 관리(저장 - PERSIST / 삭제 - REMOVE / 수정 - UPDATE)를 할 때 관련 엔티티에도 같은 작업을 적용한다.
    • 관계된 엔티티들을 함께 관리 할 필요가 있을 때 유용하다.

댓글을 주인으로 할 때와 게시글을 주인으로 할 때 어떤 차이가 존재할까?

  • 댓글이 주인(일반적으로 게시글을 외래키로 가지는 댓글이 주인)
    • 댓글을 추가하거나 삭제할 때 게시글 엔티티를 건드리지 않아도 된다.
    • 댓글을 추가할 때 해당 댓글이 속한 게시글만 알면 되기에 연관 관계 관리가 쉽다.
    • FK가 댓글 테이블에 있기 때문에 댓글 관련 작업이 더 빈번할 경우 이 방식이 효율적이다.
    • 단, 게시글을 삭제할 때는 해당 게시글에 속한 모든 댓글의 FK를 확인하고 삭제해야 하므로 큰 비용이 발생할 수 있다.
  • 게시글이 주인
    • 게시글 엔티티에 댓글 목록을 관리해야 하므로 댓글이 추가될 때마다 게시글 엔티티를 불러와 업데이트해야 한다.
    • 게시글에서 속한 댓글을 바로 관리할 수 있어 직관적이고, 게시글을 삭제할 때 게시글 엔티티와 연관된 댓글들도 함께 쉽게 처리할 수 있다. (JPA의 cacade 옵션)

3. 영속성 전이(cascade)

영속성 전이

  • 특정 엔티티끼리 생명주기가 같은 경우가 있음
    • 예를 들어 글이 삭제되는 경우 글에 달린 댓글들은 독립적으로 존재할 수 없기에 같이 삭제되어야 한다.
  • 엔티티 생명주기가 다른 엔티티와 연관되어 있는 경우 영속성 전이(cascade)를 사용해 관리한다.
  • @ManyToOne(cascade=CasCadeType.ALL) 처럼 어노테이션 옵션으로 사용한다.

영속성 전이 타입 종류

  • ALL
    • 모든 작업(상태)에 대해 영속성 전이한다.
  • PERSIST
    • Persist 할 때 연관된 엔티티도 Persist한다.
  • MERGE
    • 엔티티를 영속성 컨텍스트에 Merge 할 때 연관 엔티티도 Merge한다.
  • REMOVE
    • DB와 영속성 컨텍스트에서 연관 엔티티도 제거한다.
  • REFRESH
    • 엔티티 새로고침 시 연관 엔티티도 새로고침 된다.
  • DETACH
    • 엔티티를 영속성 컨텍스트에서 Detach하면 같이 Detach된다.
@Entity
public class Post {
		@OneToMany(mappedBy="post", cascade = CasCadeType.ALL)
		private List<Comment> comments = new ArrayList<>();
}

4. 일대다, 일대일, 다대다 매핑

다중성과 매핑


#코딩독학 #코딩인강 #코딩배우기 #개발자 #코딩이란 #코딩교육
#프론트엔드부트캠프 #백엔드부트캠프 #국비지원부트캠프 #개발자 #백엔드 #AI부트캠프 #개발자국비지원 #백엔드개발자 #프론트엔드개발자

profile
꾸준하게

0개의 댓글