관리 메뉴

ballqs 님의 블로그

[Spring] JPA(Java Persistence API)란? 본문

코딩 공부/Spring

[Spring] JPA(Java Persistence API)란?

ballqs 2024. 8. 14. 19:29

JPA란?

Java ORM(Object-Relational Mapping)기술의 대표적인 표준 명세이다.

 

  • JPA는 애플리케이션과 JDBC 사이에서 동작된다
  • JPA를 사용하면 DB 연결 과정을 직접 개발하지 않아도 자동으로 처리해준다.
  • 또한 객체를 통해 간접적으로 DB 데이터를 다룰 수 있기 때문에 매우 쉽게 DB 작업을 처리가 가능하다.

 하이버네이트(Hibernate)란?

  • JPA 는 표준 명세이고, 이를 실제 구현한 프레임워크 중 사실상 표준 하이버네이트이다.
  • 스프링 부트에서는 기본적으로 ‘하이버네이트’ 구현체를 사용 중이다.

영속성 컨텍스트란?

  • Persistence를 객체의 관점으로 해석해 보자면 ‘객체가 생명(객체가 유지되는 시간)이나 공간(객체의 위치)을 자유롭게 유지하고 이동할수 있는 객체의 성질’을 의미한다.
  • 영속성 컨텍스트를 좀 더 쉽게 표현해 보자면 Entity 객체를 효율적으로 쉽게 관리하기 위해 만들어진 공간이다.

영속성 컨텍스트의 기능

  • 캐시 기능
    • 영속성 컨텍스트는 내부적으로 캐시 저장소를 가지고 있다.
    • 캐시 저장소는 Map 자료구조이다.
    • key에는 @Id로 매핑한 기본키로 저장
    • value는 해당 Entity 클래스의 객체를 저장

캐시 저장소에 조회하는 Id가 존재하지 않은 경우

@Test
@DisplayName("Entity 조회 : 캐시 저장소에 해당하는 Id가 존재하지 않은 경우")
void test2() {
    try {

        Memo memo = em.find(Memo.class, 1);
        System.out.println("memo.getId() = " + memo.getId());
        System.out.println("memo.getUsername() = " + memo.getUsername());
        System.out.println("memo.getContents() = " + memo.getContents());


    } catch (Exception ex) {
        ex.printStackTrace();
    } finally {
        em.close();
    }

    emf.close();
}

DB에서 데이터를 조회만 하는 경우에 데이터의 변경이 발생하는 것이 아니기 때문에 트랜잭션이 없어도 조회가 가능하다.

Memo memo = em.find(Memo.class, 1); 호출 시 캐시 저장소에 해당 값이 존재하지 않기 때문에 DB에 SELECT 조회하여 캐시 저장소에 저장한 후 반환한다.

 

  • 쓰기 지연 저장소(ActionQueue)
    • JPA는 DB의 트랜잭션의 기능을 활용해 SQL를 모아서 한번에 DB에 반영한다.
@Test
@DisplayName("쓰기 지연 저장소 확인")
void test6() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {
        Memo memo = new Memo();
        memo.setId(2L);
        memo.setUsername("Robbert");
        memo.setContents("쓰기 지연 저장소");
        em.persist(memo);

        Memo memo2 = new Memo();
        memo2.setId(3L);
        memo2.setUsername("Bob");
        memo2.setContents("과연 저장을 잘 하고 있을까?");
        em.persist(memo2);

        System.out.println("트랜잭션 commit 전");
        et.commit();
        System.out.println("트랜잭션 commit 후");

    } catch (Exception ex) {
        ex.printStackTrace();
        et.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

em > actionQueue를 확인해보면 insertions > executables에 Insert할 memo#2, memo#3 Entity 객체 2개가 들어가 있는 것을 확인할수 있다.

 

트랜잭션 commit 후

 

  • 변경 감지(Dirty Checking)
    • JPA는 영속성 컨텍스트에 Entity를 저장할 때 최초 상태(LoadedState)를 저장한다.
      • 트랜잭션이 commit되고 em.flush(); 가 호출되면 Entity의 현재 상태와 저장한 최초 상태를 비교한다.
      • 변경 내용이 있다면 Update SQL을 생성하여 쓰기 지연 저장소에 저장하고 모든 쓰기지연 저장소의 SQL을 DB에 요청한다.
      • 마지막으로 DB의 트랜잭션이 commit 되면서 반영한다.
    • 따라서 변경하고 싶은 데이터가 있다면 먼저 데이터를 조회하고 해당 Entity 객체의 데이터를 변경하면 자동으로 Update SQL이 생성되고 DB에 반영한다.
@Test
@DisplayName("변경 감지 확인")
void test8() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {
        System.out.println("변경할 데이터를 조회합니다.");
        Memo memo = em.find(Memo.class, 4);
        System.out.println("memo.getId() = " + memo.getId());
        System.out.println("memo.getUsername() = " + memo.getUsername());
        System.out.println("memo.getContents() = " + memo.getContents());

        System.out.println("\n수정을 진행합니다.");
        memo.setUsername("Update");
        memo.setContents("변경 감지 확인");

        System.out.println("트랜잭션 commit 전");
        et.commit();
        System.out.println("트랜잭션 commit 후");

    } catch (Exception ex) {
        ex.printStackTrace();
        et.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

  • ntityInstance는 Entity 객체의 현재 상태이다.
  • entityEntry > loadedState는 조회했을 때 즉, 해당 Entity의 최초 상태이다
  • 트랜잭션 commit 후 em.flush(); 메서드가 호출되면 현재 상태와 최초 상태를 비교하고 변경이 있다면 Update SQL을 생성하여 쓰기 지연 저장소에 저장한 후 DB에 요청한다.

 

 

JPA의 트랜잭션

JPA는 DB의 트랜잭션 개념을 사용하여 효율적으로 Entity를 관리하고 있다.

 

@Test
@DisplayName("EntityTransaction 성공 테스트")
void test1() {
    EntityTransaction et = em.getTransaction(); // EntityManager 에서 EntityTransaction 을 가져옵니다.

    et.begin(); // 트랜잭션을 시작합니다.

    try { // DB 작업을 수행합니다.

        Memo memo = new Memo(); // 저장할 Entity 객체를 생성합니다.
        memo.setId(1L); // 식별자 값을 넣어줍니다.
        memo.setUsername("Robbie");
        memo.setContents("영속성 컨텍스트와 트랜잭션 이해하기");

        em.persist(memo); // EntityManager 사용하여 memo 객체를 영속성 컨텍스트에 저장합니다.

        et.commit(); // 오류가 발생하지 않고 정상적으로 수행되었다면 commit 을 호출합니다.
        // commit 이 호출되면서 DB 에 수행한 DB 작업들이 반영됩니다.
    } catch (Exception ex) {
        ex.printStackTrace();
        et.rollback(); // DB 작업 중 오류 발생 시 rollback 을 호출합니다.
    } finally {
        em.close(); // 사용한 EntityManager 를 종료합니다.
    }

    emf.close(); // 사용한 EntityManagerFactory 를 종료합니다.
}

 

  • et.begin();
    • 트랜잭션을 시작하는 명령어이다
  • et.commit();
    • 트랜잭션의 작업들을 영구적으로 DB에 반영하는 명령어이다.
  • et.rollback();
    • 오류가 발생했을 때 트랜잭션의 작업을 모두 취소하고, 이전 상태로 되돌리는 명령어이다.

적용하면서 공부

build.gradle 적용

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

 

application.properties 적용

#create 테이블부터 다시 만들어서 진행하는 방법
#create-drop create와 대부분 같으며 끝날시 테이블 drop까지 하는 것
#update 검색시 기존에 없으면 생성 있으면 그대로 이용하는 갱신방법
#validate
#non 아무것도 하지 않는 것
spring.jpa.hibernate.ddl-auto=update

#sql 보여주기
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true

#database 버전이 달라서 생기는 오류! 밑에 줄을 추가해줘야 에러 해결
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQLDialect

MySQLDialect 에러 해결 -> 링크

 

Repository 적용

import com.sparta.memo.entity.Memo;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface MemoRepository extends JpaRepository<Memo, Long> {
}

interface로 만들어서 JpaRepository를 상속받아야 하나보다...

 

메서드

save

public MemoResponseDto createMemo(MemoRequestDto requestDto) {
    // RequestDto -> Entity
    Memo memo = new Memo(requestDto);

    // DB 저장
    Memo saveMemo = memoRepository.save(memo);

    // Entity -> ResponseDto
    MemoResponseDto memoResponseDto = new MemoResponseDto(saveMemo);

    return memoResponseDto;
}

 

findAll

public List<MemoResponseDto> getMemos() {
    // DB 조회
    return memoRepository.findAll().stream().map(MemoResponseDto::new).toList();
}

 

findById

private Memo findMemo(Long id) {
    return memoRepository.findById(id).orElseThrow(() ->
            new IllegalArgumentException("선택한 메모는 존재하지 않습니다.")
    );
}

 

update

@Transactional
public Long updateMemo(Long id, MemoRequestDto requestDto) {
    // 해당 메모가 DB에 존재하는지 확인
    Memo memo = findMemo(id);

    // memo 내용 수정
    memo.update(requestDto);

    return id;
}
  • impleJpaRepository에 update라는 메서드는 존재하지 않는다.
  • 따라서 영속성 컨텍스트 기능중 하나인 변경감지가 적용되기 위해 해당 메서드에 @Transactional을 추가했다.

 

delete

public Long deleteMemo(Long id) {
    // 해당 메모가 DB에 존재하는지 확인
    Memo memo = findMemo(id);

    // memo 삭제
    memoRepository.delete(memo);

    return id;
}


마무리

모든 내용을 다 적기에는 무리가 있다....

강의에서 들은 내용을 일단 까먹지 않기 위해 이해한 부분만 작성했다...

다만 어떻게 말로 풀어야할지 몰라서 설명을 그대로 가져오긴 했다. ㅠㅠㅠ