4. JPA 적용하기

이월(0216tw)·2024년 5월 11일
post-thumbnail
  • 이 글은 인프런 이중석님의 👉[스프링부트-실전-상품주문-tdd] 강의를 들으며 작성하였습니다. (소중한 지식 나눔에 감사드립니다👍)

  • 개인 공부 목적이며 상업적 용도가 아님을 밝힙니다.

  • 강의를 들으며 추가로 궁금한 내용 및 오류를 정리한 글 입니다.

섹션1.상품 등록 API 개발

4. JPA 적용하기


JPA 사용을 위한 의존성 설정
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

implementation 'com.h2database:h2:2.1.210' //h2 Database 사용

질문1. JpaRepository란?

[interface JpaRepository레퍼런스]

JPA (Java Persistence API) 의 약자이며, 자바 객체(DTO , VO) 등을 DB에 영구적으로(Persistence) 저장하고 조회할 수 있는 ORM기술

  • ORM : Object Relational Mapping (객체관계매핑, 객체와 테이블)
  • Persistence : 메모리상에 임시 저장이 아니라 DB에 영구저장한다는 의미

PSA( Portable Service Abstraction , 일관된 추상 서비스) 중 하나로
최대한 자바스럽게(POJO의 객체지향적) DB의 쿼리를 이용할 수 있다.

JPA 사용방법

(1) Repository를 interface로 바꾸고 JpaRepository를 상속한다

interface ProductRepository extends JpaRepository<Product , Long> {

}

타입매개변수 받은 Product는 @Entity로 사용할 클래스를 의미하고
Long은 Product 엔터티의 primary key 데이터 형식을 의미한다
(DB로 따지면 Product테이블의 Primary key)


(2) 엔터티로 사용할 Product 클래스를 변경한다


@Entity  //이건 엔터티 클래스임을 의미 
@Table(name = "products") //DB와 매핑할 테이블이름 
@Getter 
@NoArgsConstructor(access = AccessLevel.PROTECTED)

class Product {

    @Id //해당 엔터티의 primary key로써 사용 (id 필드를 key로 사용)     
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    //키 전략 : 자동증가 (mysql 의 AUTO_INCREMENT 같은거) 
    private Long id;

	private String name ;
    private int price ;
    private DiscountPolicy discountPolicy;

    public Product(final String name, final int price, final DiscountPolicy discountPolicy) {
        Assert.hasText(name, "상품명은 필수입니다.");
        Assert.isTrue(price > 0, "상품 가격은 0보다 커야 합니다.");
        Assert.notNull(discountPolicy, "할인 정책은 필수입니다.");

        this.name = name;
        this.price = price;
        this.discountPolicy = discountPolicy;
    } 
}

질문1.1) @NoArgConstructor 뒤의 accessLevel은 뭐지?

NoArgConstructor는 매개변수가 없는 생성자를 만드는거고,
accessLevel은 해당 생성자의 접근 제어자를 의미한다.
즉 다음 코드와 동일하다

class Product {
    protected Product() {}  //protected 제어자인 생성자
    ... 
}

질문1.2) protected로 접근제어자를 쓴 이유는?

public 으로 하면 무분별한 객체가 생성될 수 있고
private 로 하면 프록시 객체 생성이 되지 않기 때문이다.
결국 무분별한 불완전 객체 생성을 막기 위해 접근제어를 protected로 한 것



질문2. DB관련 에러 발생

에러 1.

General error: "The write format 3 is larger than the supported format 2 [2.1.210/5]" [50000-210]

나는 h2 database를 JPA와 연동하기 위해 사용했는데 위와 같은 오류가 나왔다.
현재 의존성 설정으로 h2의 2.1.210 버전을 사용하고 있었는데 Stackoverflow을 확인해보니 이 버전은 H2 1.4.200 버전이나 그 이하 버전의 DB파일을 열 수 없다 라고 한다.

그래서 버전을 바꿔보았다.

기존
implementation 'com.h2database:h2:2.1.210'
변경
implementation 'com.h2database:h2:1.4.200'

이번에는 두번째 에러가 발생했다.

에러2.

File corrupted while reading record: null. Possible solution: use the recovery tool [90030-200]

파일이 손상되었다고 한다. 이것도 해결방안을 찾아보았다.

(1) 본인 User 폴더 내의 test.mv.db 파일 삭제

(2) 새 파일 만들기로 test.mv.db와 파일명 일치시키기

(3) h2 재구동

이 문제는 해결 되었지만 또 에러가 발생했다.

에러3.

Database may be already in use: null. Possible solutions: close all other connection(s); use the server mode [90020-200]

해결방법으로는 연결할떄 url을 jdbc:h2:tcp://localhost/~/test로 해야한다. 그래서 application.properties에 연결설정을 아래와 같이 변경했다.
(혹시 모르니 h2 콘솔도 함께 변경했다)

변경 전
jdbc:h2:~/test
변경후
jdbc:h2:tcp://localhost/~/test

이로써 정상적으로 작동이 되었다!



질문3. DatabaseCleanup 코드 분석 (테스트마다 DB 초기화용)

강의에서 테스트마다 DB를 초기화하기 위한 코드를 정리했으며 이를 분석해보았다.

<코드>

@Component
public class DatabaseCleanup implements InitializingBean {

    @PersistenceContext
    private EntityManager entityManager;

    private List<String> tableNames;

    @Override
    public void afterPropertiesSet() {
        final Set<EntityType<?>> entities = entityManager.getMetamodel().getEntities(); //JPA 엔터티 모두 가져옴

        tableNames = entities.stream()
                .filter(e -> isEntity(e) && hasTableAnnotation(e)) //엔터티 어노테이션과 테이블 어노테이션이 붙어있으면
                .map(e -> e.getJavaType().getAnnotation(Table.class).name()) //해당 테이블 이름을 가져와서 리스트화
                .collect(Collectors.toList());

        final List<String> entityNames = entities.stream()
                .filter(e -> isEntity(e) && !hasTableAnnotation(e))  //엔터티 어노테이션은 있지만 테이블 어노테이션이 없다면
                .map(e-> CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE , e.getName()))//ProductItem -> product_item 으로 바꿔줌
                .toList();

        tableNames.addAll(entityNames);
    }

    private boolean isEntity(final EntityType<?> e) {
        return null != e.getJavaType().getAnnotation(Entity.class);
    }

    private boolean hasTableAnnotation(final EntityType<?> e) {
        return null != e.getJavaType().getAnnotation(Table.class);
    }

    @Transactional
    public void execute() {
        entityManager.flush();
        entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate(); //테이블 간 참조무결성을 작동하지 않도록 한다.

        for(final String tableName : tableNames) {
            entityManager.createNativeQuery("TRUNCATE TABLE " + tableName).executeUpdate(); //테이블 데이터 모두 삭제
            entityManager.createNativeQuery("ALTER TABLE " + tableName + " ALTER COLUMN ID RESTART WITH 1").executeUpdate();
            //테이블 ID 의 시작 pk를 다시 1로 초기화
        }
        entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate(); //다시 참조무결성 세팅
    }
}

(1) InitializingBean 의 용도는?

빈의 주입이 완료된 후(빈 생성 및 설정) 실행되는 초기화 로직을 만들 수 있다.

즉, 순서로 따지면 @SpringBootTest로 bean 생성 및 설정 -> bean 설정 끝났으니 초기화 로직 실행 -> @Test 테스트 로직 실행 순서가 되어 테스트 실행 전마다 Database를 초기화 하는 작업을 진행해주는 것이다.

* 참고로 InitializingBean 사용은 권고하지 않고 보통 다른 방법을 사용한다


(2) @PersistenceContext 와 EntityManager의 관계는?

@PersistenceContext
private EntityManager entityManager;

EntityManager는 Entity들의 라이프 사이클 , 영속성 등을 관리하고 또한 DB 처리를 위한 CRUD 작업등을 메소드로 제공한다.

이런 EntityManager를 주입할 수 있는 표준이 @PersistenceContext 이다.
(이 부분은 JPA 공부할 때 자세히 다뤄볼 예정)

여기서는 그냥 DB관련 처리하려고 가져왔다 정도로만 하고 넘어가도록 하겠다.



질문 4. 어떻게 insert 처리가 된거지?

현재 Product 클래스는 @Entity로써 products 라는 테이블과 매핑되도록 세팅되어있다.

DB로 따지면 [ products 테이블 => id (pk) , name , price , discountPolicy ]

그리고 이 Product를 JpaRepository<Product , Long>을 상속한 ProductRepository가 save()를 실행하면 insert 로 매핑된다.




이후 주문 , 결제 프로세스는 위와 비슷하므로 생략합니다.



참고자료

https://velog.io/@qwerty1434/InitializingBean-vs-PostConstruct (InitializingBean)

profile
#SQLD강사 #AI개발 #AI강사 #개발자 개발도 하고 강의도 하지만 고민을 제일 많이 합니다

0개의 댓글