본문 바로가기
Book/스프링 부트와 AWS 웹 서비스

[Spring Boot] JPA로 데이터베이스 다루기

by 달의 조각 2023. 2. 9.
이 글은 이동욱 님의 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 책을 읽으며 정리한 글입니다.

 

  JPA는 인터페이스로서 자바 표준 명세서이다. 인터페이스 JPA를 사용하려면 Hibernate, EclipseLink 등의 구현체가 필요하다. Spring에서 JPA를 사용할 때에는 이 구현체를 직접 다루지 않고, 구현체를 쉽게 사용하고자 추상화시킨 Spring Data JPA 모듈을 이용한다.

  • JPA ← Hibernate ← Spring Data JPA

 


 

🍊 Entity

// domain/posts
@Getter
@NoArgsConstructor
@Entity
public class Posts extends BaseTimeEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    ...

    @Builder
    public Posts(String title, String content, String author) {
        this.title = title;
        this.content = content;
        this.author = author;
    }
}

Entity 클래스에서는 절대 Setter 메소드를 만들지 않는다

해당 필드의 값 변경이 필요하면 그 목적과 의도를 나타낼 수 있는 메소드를 추가해야 한다. 기본적으로 생성자를 통해 값을 채우고, 값 변경이 필요하면 해당 이벤트에 맞는 public 메소드를 호출하여 변경한다.

@Builder

위의 코드에서는 생성자 대신 빌더 클래스를 이용했다. 생성자는 값을 채울 때 지금 채워야 할 필드가 무엇인지 명확히 지정할 수 없다. 에를 들어서 new Example(b, a)처럼 a와 b의 값을 변경해도 코드를 실행하기 전까지 문제를 찾을 수 없다. 빌더를 사용하면 어느 필더에 어떤 값을 채워야 할지 명확하게 인지할 수 있다.

 

// domain/posts
public interface PostsRepository extends JpaRepository<Posts, Long> {

}

Entity 클래스와 기본 Entity Repository는 함께 위치해야 한다. 둘은 아주 밀접한 관계이고, Entity 클래스는 기본 Repository 없이는 제대로 역할을 할 수가 없다. 프로젝트 규모가 커져서 도메인별로 프로젝트를 분리해야 할 때에도 함께 움직여야 하므로 도메인 패키지에서 함께 관리한다.

 

실제 쿼리 확인하기

// src/main/resources/application.properties
spring.jpa.show_sql=true

// 로그 쿼리 MySQL 버전으로 변경
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
spring.jpa.properties.hibernate.dialect.storage_engine=innodb
spring.datasource.hikari.jdbc-url=jdbc:h2:mem://localhost/~/testdb;MODE=MYSQL
spring.h2.console.enabled=true

 


 

spring 웹 계층

  • Web Layer: 컨트롤러, JSP/Freemarket 등의 뷰 템플릿 영역이다. 필터, 인터셉터, 컨트롤러 어드바이스 등 외부 요청과 응답에 대한 전반적인 영역을 말한다.
  • Service Layer: 일반적으로 Controller와 Dao의 중간 영역에서 사용된다. @Transactional이 사용되어야 하는 영역이다. 비즈니스 로직을 처리하는 곳이 아니라 트랜잭션, 도메인 간 순서 보장의 역할을 한다.
  • Repository Layer: 데이터 저장소에 접근한다.
  • Dtos: 계층 간에 데이터 교환을 위한 객체를 말한다. 뷰 템플릿 엔진에서 사용될 객체나 Repository Layer에서 결과로 넘겨 준 객체 등이다.
  • Domain Model: 도메인이라고 불리는 개발 대상을 모든 사람이 동일한 관점에서 이해할 수 있고 공유할 수 있도록 단순화시킨 것이다. 비즈니스 처리를 담당하는 곳이다.

 

Bean을 주입받는 방법

  • @Authwired
  • setter
  • 생성자: 권장!

 

DTO 클래스를 사용해야 하는 이유

  Entity 클래스를 Request / Response 클래스로 사용해서는 안 된다.

엔티티 클래스는 데이터베이스와 맞닿은 핵심 클래스이다. 이를 기준으로 테이블이 생성되며, 스키마가 변경된다. 사소한 화면 변경을 위해 테이블과 연결된 엔티티 클래스를 변경하는 것은 너무 큰 변경이다.

수많은 서비스 클래스나 비즈니스 로직이 엔티티 클래스를 기준으로 동작한다. 엔티티 클래스가 변경되면 여러 클래스에 영향을 끼치지만 Request, Response용 Dto는 View를 위한 클래스라 정말 자주 변경이 필요하다.

View Layer와 DB Layer의 역할 분리를 철저하게 하는 게 좋다. Controller에서 결과값으로 여러 테이블을 조인해서 줘야 할 경우도 빈번하므로 엔티티 클래스만으로 표현하기 어려운 경우가 많다.

꼭 엔티티 클래스와 Controller에서 쓸 Dto는 분리해서 사용해야 한다.

@Getter
public class PostsResponseDto {

    private Long id;
    private String title;
    private String content;
    private String author;

    public PostsResponseDto(Posts entity) { // 필요한 필드의 값만 가져온다
        this.id = entity.getId();
        this.title = entity.getTitle();
        this.content = entity.getContent();
        this.author = entity.getAuthor();
    }
}

 

🍊 JPA 기능과 함께 Api Controller 테스트 하기

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private PostsRepository postsRepository;
    
    ...
}
  • @WebMvcTest는 JPA 기능이 작동하지 않는다. JPA 기능까지 한 번에 테스트하려면 @SpringBootTest와 @TestRestTemplate를 사용한다.

 

🍊 JPA Auditing으로 생성 시간 / 수정 시간 자동화하기

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {

    @CreatedDate // 엔티티가 생성되어 저장될 때 시간이 자동 저장된다
    private LocalDateTime createdDate;

    @LastModifiedDate // 조회한 엔티티의 값을 변경할 때 시간이 자동 저장된다
    private LocalDateTime modifiedDate;
}
  • @MappedSuperclass: JPA 엔티티 클래스들이 이를 상속할 경우 필드들도 칼럼으로 인식하도록 한다.
  • @EntityListeners(AuditingEntityListener.class): Auditing 기능을 포함시킨다.
@Getter
@NoArgsConstructor
@Entity
public class Posts extends BaseTimeEntity { // 상속
	...
}
@EnableJpaAuditing // JPA Auditing 어노테이션들을 모두 활성화
@SpringBootApplication
public class Application {
	...
}

댓글