Dev/Spring

Spring Boot (JPA/ORM, JPA 사용 시 장점, JPA 사용 시 단점, 엔티티, 엔티티 매니저 팩토리, 엔티티 매니저, 영속성 컨텍스트, Board 엔티티와 JpaRepository, Select 기능 테스트, update 기능 테스트)

Walker_ 2024. 5. 9. 11:26

1.JPA / ORM

 - ORM : 자바와 같은 객체지향 언어에서 의미하는 RDB Relational Database의 테이블을 자동으로 매핑하는 방법

 - 클래스는 데이터베이스의 테이블과 매핑하기 위해 만들어진 것이 아니기 때문에 RDB 테이블 어쩔 수 없는 불일치 존재

 - ORM이 이 둘의 불일치와 제약사항을 해결하는 역할

 - ORM을 이용하면 쿼리문이 아닌 코드(메서드)로 데이터 조작 가능

 

 - JPA : ORM 기술 표준으로 채택된 인터페이스 모음

 - ORM이 큰 개념이라면 JPA는 더 구체화된 스펙을 포함

 - 즉 JPA 또한 실제로 동작하는 것이 아니고 어떻게 동작해야 하는지 메커니즘을 정리한 표준 명세

 

2. JPA 사용 시 장점

 - 특정 데이터베이스에 종속되지 않음

 - 객체지향적 프로그램

 - 생산성 향상

 

3. JPA 사용 시 단점

 - 복잡한 쿼리 처리

 - 성능 저하 위험

 - 학습 시간

 

4. 엔티티

 - 데이터베이스의 테이블에 대응하는 클래스

 - @Entity가 붙은 클래스는 JPA에서 관리하며 엔티티라고 함

 - 클래스 자체나 생성한 인스턴스도 엔티티라 부름

 

5. 엔티티 매니저 팩토리

 - 엔티티 매니저 인스턴스를 관리하는 주체

 - 애플리케이션 실행 시 한 개만 만들어지며 사용자로 부터 요청이 오면 엔티티 매니저 팩토리로 부터

 - 엔티티 매니저르 생성

 

6. 엔티티 매니저

 - 영속성 컨텐스트에 접근하여 엔티티에 대한 데이터베이스 작업을 제공.

 - 내부적으로 데이터베이스 커넥션을 사용해서 데이터베이스에 접근

 

7. 영속성 컨텍스트

 - JPA를 이해하기 위해서는 영속성 컨텍스트를 이해하는 것이 가장 중요

 - 엔티티를 영구 저장하는 환경으로 엔티티 매니저를 통해 영속성 컨텍스트에 접근

 

8. Board 엔티티와 JpaRepository

 - 패키지와 클래스 생성

package com.example.spring_boot_project.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

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

    private String title;

    private String content;

    private String writer;
}

 - 자바 파일에 위 코드 작성

 

 - domain 폴더에 BaseEntity 파일 추가

package com.example.spring_boot_project.domain;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@MappedSuperclass
@EntityListeners(value = {AuditingEntityListener.class})
@Getter
public class BaseEntity {
    @CreatedDate
    @Column(name="regdate", updatable = false)
    private LocalDateTime regDate;

    @LastModifiedDate
    @Column(name = "moddate")
    private LocalDateTime modDate;
}

 

 - BaseEntity 파일에 위 코드 추가

package com.example.spring_boot_project;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
public class SpringBootProjectApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootProjectApplication.class, args);
    }

}

 - 메인 메서드에 @EnableJpaAuditing 어노테이션 추가

package com.example.spring_boot_project.domain;

import jakarta.persistence.*;
import lombok.*;

@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Board extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long bno;

    @Column(length = 500, nullable = false) // 컬럼의 길이와 null 허용 여부
    private String title;

    @Column(length = 2000, nullable = false)
    private String content;

    @Column(length = 50, nullable = false)
    private String writer;
}

 - Board.java에 코드 추가

 - repository 패키지 생성 후 > BoardRepository 인터페이스 파일 생성

package com.example.spring_boot_project.repository;

import com.example.spring_boot_project.domain.Board;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BoardRepository extends JpaRepository<Board, Long> {

}

 - 위 코드 작성

 - 테스트에 폴더와 파일 생성

package com.example.spring_boot_project.repository;

import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
@Log4j2
public class BoardRepositoryTests {
    @Autowired
    private BoardRepository boardRepository;
}

 - 위 코드 작성

package com.example.spring_boot_project.repository;

import com.example.spring_boot_project.domain.Board;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
@Log4j2
public class BoardRepositoryTests {
    @Autowired
    private BoardRepository boardRepository;

    @Test
    public void testInsert() {
        for (int i = 1; i <= 100; i++) {
            Board board = Board.builder()
                    .title("title...")
                    .content("content..." + i)
                    .writer("uesr" + (i % 10))
                    .build();
            Board result = boardRepository.save(board);
            log.info("BNO: " + result.getBno());
        }
    }
}

 - 테스트 코드 작성

 - 테스트 실행 후 로그 확인

 - 데이터 베이스에 테이블 생성되고 > 데이터 추가 된 지 확인

 

9. Select 기능 테스트

@Test
public void testSelect() {
    Long bno = 100L;
    Optional<Board> result = boardRepository.findById(bno);
    Board board = result.orElseThrow();
    log.info(board);
}

 - Select 기능 테스트 코드 추가

 - 테스트 실행 후 위 처럼 코드 확인. 정상 작동.

 

10. update 기능 테스트

 - update 기능은 insert와 동일하게 save()를 통해서 처리

package com.example.spring_boot_project.domain;

import jakarta.persistence.*;
import lombok.*;

@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Board extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long bno;

    @Column(length = 500, nullable = false) // 컬럼의 길이와 null 허용 여부
    private String title;

    @Column(length = 2000, nullable = false)
    private String content;

    @Column(length = 50, nullable = false)
    private String writer;

    public void change(String title, String content) {
        this.title = title;
        this.content = content;
    }
}

 - Board.java 파일에 위 메서드 코드 추가

@Test
public void testUpdate() {
    Long bno = 100L;
    Optional<Board> result = boardRepository.findById(bno);
    Board board = result.orElseThrow();

    board.change("update... title 100", "update content 100");
    boardRepository.save(board);
}

 - 업데이트 테스트 코드 추가

 - Test 실행 후 Update 정상 작동 확인

@Test
public void testUpdate3() {
    // 없는 bno를 지정한 경우
    Long bno = 1000L;
    Board board = Board.builder()
            .bno(bno)
            .title("title...")
            .content("content...update3")
            .writer("uesr..update")
            .build();
    boardRepository.save(board);
}

 - 없는 @id 값을 지정하면 update가 아니라 insert가 실행.

 

 


공부 과정을 정리한 것이라 내용이 부족할 수 있습니다.

부족한 내용은 추가 자료들로 보충해주시면 좋을 것 같습니다.

읽어주셔서 감사합니다 :)