Spring 입문 2-2 (JPA)

SJ.CHO·2024년 10월 7일

JPA

  • 기존의 JDBC 는 SQL 의존적이라 변경에 취약하고 많은 수의 Step 이 존재함.
  • 수정사항이 추가됨으로 코드변경성이 매우 높음

ORM (Object-Relational Mapping)

  • SQL 작업을 줄여주기 위해서 등장. 객체 : DB 의 관계를 매핑해줌.
  • JPA 는 JavaORM 의 대표적인 표준명세
  • 객체를 통해 간접적으로 DB데이터를 다룰 수 있다.

Hibernate

  • JPA를 구현한 프레임워크 사실상 표준 프레임워크

Entity

@Entity // JPA가 관리할 수 있는 Entity 클래스 지정
@Table(name = "memo") // 매핑할 테이블의 이름을 지정
public class Memo {
    @Id
    private Long id;

    // nullable: null 허용 여부
    // unique: 중복 허용 여부 (false 일때 중복 허용)
    @Column(name = "username", nullable = false, unique = true)
    private String username;

    // length: 컬럼 길이 지정
    @Column(name = "contents", nullable = false, length = 500)
    private String contents;
}
  • @Entity : JPA 가 관리할 수 있는 Entity 클래스로 지정
    (name파라미터를 통해 이름지정가능 기본값은 클래스명)
    • 인스턴스를 생성할 때 기본생성자를 사용하기에 생성자가 존재하는지 확인 필요.
  • @Table : 매핑할 테이블의 이름을 지정
    • 기본 값은 Entity 명.
  • @Column : DB의 컬럼과 동일한 기능.
    • @Column(name = "username") : 필드와 매핑할 테이블의 컬럼을 지정할 수 있다. (default: 객체의 필드명)
    • @Column(nullable = false) : 데이터의 null 값 허용 여부를 지정할 수 있다. (default: true)
    • @Column(unique = true) : 데이터의 중복 값 허용 여부를 지정할 수 있다. (default: false)
    • @Column(length = 500) : 데이터 값(문자)의 길이에 제약조건을 걸 수 있다. (default: 255)
  • id : 해당 테이블의 기본 키를 지정.
    • 영속성 컨텍스트에서 Entity를 구분. 식별자 역할을 수행

      영속성 : 데이터를 생성한 프로그램이 종료되어도 사라지지 않는 데이터의 특성을 말한다

  • @GeneratedValue(strategy = GenerationType.IDENTITY)
    • id 값에 AUTO_INCREMENT 속성을 부여

영속성 컨텍스트

  • Persistence를 객체의 관점으로 해석해 보자면 ‘객체가 생명(객체가 유지되는 시간)이나 공간(객체의 위치)을 자유롭게 유지하고 이동할수 있는 객체의 성질’을 의미
    • Entity 객체를 효율적으로 쉽게 관리하기위한 공간.

  • EntityManager는 이름 그대로 Entity를 관리하는 관리자
  • Entity 를 CRUD 가능.

  • EntityManagerFactory
    • DB 와 1:1 관계로 동작.
    • DB에 대한 정보를 전달하기 위해 /resources/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="memo">
        <class>com.sparta.entity.Memo</class>
        <properties>
            <property name="jakarta.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="jakarta.persistence.jdbc.user" value="root"/>
            <property name="jakarta.persistence.jdbc.password" value="{비밀번호}"/>
            <property name="jakarta.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/memo"/>

            <property name="hibernate.hbm2ddl.auto" value="create" />

            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
        </properties>
    </persistence-unit>
</persistence>
EntityManagerFactory emf = Persistence.createEntityManagerFactory("memo");
EntityManager em = emf.createEntityManager();

트랜잭션

  • 트랜잭션은 DB 데이터들의 무결성과 정합성을 유지하기 위한 하나의 논리적 개념
    • DB의 데이터를 안전하게 관리하기 위한 개념
    • 모든 SQL이 성공적으로 수행이 되면 DB에 영구적으로 변경을 반영하지만 SQL 중 단 하나라도 실패한다면 모든 변경을 되돌림. 성공 OR 롤백
START TRANSACTION; # 트랜잭션을 시작합니다.

INSERT INTO memo (id, username, contents) VALUES (1, 'Robbie', 'Robbie Memo');
INSERT INTO memo (id, username, contents) VALUES (2, 'Robbert', 'Robbert Memo');
SELECT * FROM memo;

COMMIT; # 트랜잭션을 커밋합니다.

SELECT * FROM memo;

  • JPA에서도 영속성 컨텍스트로 관리하고 있는 변경이 발생한 객체들의 정보를 쓰기 지연 저장소에 전부 가지고 있다가 마지막에 SQL을 한번에 DB에 요청해 변경을 반영
@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 를 종료합니다.
}
  • EntityTransaction et = em.getTransaction();
  • 해당 코드를 호출하여 EntityTransaction을 가져와 트랜잭션을 관리
  • et.begin();
    • 트랜잭션을 시작하는 명령어.
  • et.commit();
    • 트랜잭션의 작업들을 영구적으로 DB에 반영하는 명령어.
  • et.rollback();
    • 오류가 발생했을 때 트랜잭션의 작업을 모두 취소하고, 이전 상태로 되돌리는 명령어.

영속성 컨텍스트의 기능

1차캐시

  • Map 형태의 캐시 저장소를 사용.
  • id : @id 필드값 저장.
  • Entity : 해당 Class 의 객체를 저장.

저장

  • K : V 구조로 정보가 저장됌.

조회

  • 1차캐시에 조회 ROW가 존재하지 않는 경우

  • 존재하지 않는경우

  • 장점 :

    • DB의 접근(조회 횟수를 줄임)
    • DB row 1개당 객체 1개가 사용되는것을 보장 (객체 동일성 보장)
      • 1개의 행은 1개의 객체로만 사용.

삭제

  • 바로 삭제하는게 아닌 Entity 객체를 DELETED 상태로 만들고 트랜잭션 commit 후 Delete SQL이 DB에 요청

쓰기 지연 저장소(ActionQueue)

  • 쓰기 지연 저장소를 만들어 SQL을 모아두고 있다가 트랜잭션 commit 후 한번에 DB에 반영
  • flush()

    • 영속성 컨텍스트의 변경 내용들을 DB에 반영하는 역할을 수행(쓰기지연 저장소의 SQL 문들을 DB에 반영하는 역할을 수행)
    • 트랙잭션 환경을 설정하지 않고 flush 를 실행 하면 TransactionRequiredException 예외가 발생.

변경 감지(Dirty Checking)

  • 최초 상태(LoadedState)를 저장 이후 em.flush(); 가 호출되면 Entity의 현재 상태와 저장한 최초 상태를 비교

  • 변경내용이 있다면 Update SQL을 생성하여 쓰기 지연 저장소에 저장하고 모든 쓰기지연 저장소의 SQL을 DB에 요청 이후 트랜잭션을 Commit

  • find 를 통해 해당 데이터를 조회 후 데이터를 변경시 자동으로 반영된다.
    트랜잭션 환경이 세팅되어있어야 한다.

@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();
}

Entity 의 상태

  • 비영속 (Transient)

    • new 연산자를 통해 인스턴스화는 되었지만, 영속성 컨텍스트에 저장되지 않았기 때문에 JPA의 관리를 받지 않음.
  • 영속 (Managed)

    • persist(entity) : 비영속 Entity를 EntityManager를 통해 영속성 컨텍스트에 저장하여 관리되고 있는 상태로 만듬.
  • 준영속 (Detached)

    • 영속성 컨텍스트에 관리되다가 분리된 상태
    • 관리상태가 아닐때는 변경감지가 이루어지지 않는다.
      • detach(entity) : 특정 Entity만 준영속 상태로 전환
      • clear() : 모든 Entity를 준영속 상태로 전환
        • 틀자체는 유지하지만 내용은 전부 비워버린다. 계속 사용가능
      • close() : 영속성 컨텍스트를 완전 종료.
        • 관리하던 Entity들을 준영속상태로 전환. 계속 사용불가.
      • merger(entity) : 전달받은 Entity를 사용하여 새로운 영속 상태의 Entity를 반환
        • DB에 존재할경우 수정 존재하지 않을 경우 저장
  • 삭제 (removed)

    • remove(entity) : 조회해온 영속상태의 Entity를 전달받아 삭제상태로 전환
profile
70살까지 개발하고싶은 개발자

0개의 댓글