Spring&Spring Boot

[Spring Boot] 입문 - Spring Boot 프로젝트에 JPA 적용하기

on1ystar 2023. 2. 24. 22:34
728x90
반응형

스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 김영한 인프런 강의 참고

이전 포스팅

 

Spring Boot 프로젝트에 JPA 적용하기


build.gradle 파일에 JPA 관련 라이브러리 추가

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	// implementation 'org.springframework.boot:spring-boot-starter-jdbc'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	runtimeOnly 'mysql:mysql-connector-java'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

spring-boot-starter-data-jpa는 내부에 jdbc 관련 라이브러리를 포함한다. 따라서 jdbc는 제거해도 된다.

 

Spring Boot에 JPA 설정 추가

resources/application.properties

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test_db?serverTimezone=UTC&characterEncoding=UTF-8
spring.datasource.username=
spring.datasource.password=

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
  • show-sql : JPA가 생성하는 SQL을 출력한다.
  • ddl-auto : JPA는 테이블을 자동으로 생성하는 기능을 제공하는데 none을 사용하면 해당 기능을 끈다.

 

JPA 엔티티 매핑

domain/Member.java

package com.example.practice.domain;

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

@Entity
public class Member {
    
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • @Entity : 클래스가 엔티티임을 지정. JPA가 같은 이름을 가진 테이블과 매핑 시킴
  • @Id : 엔티티의 기본 키를 지정
  • @GeneratedValue : 기본 키 값에 대한 생성 전략 스펙을 제공
  • 위와 같이 strategy = GenerationType.IDENTITY을 지정해 주면 기본 키 생성을 데이터베이스에 위임하는 전략을 설정

 

JPA 회원 리포지토리

/repository/JpaMemberRepository.java

package com.example.practice.repository;

import java.util.List;
import java.util.Optional;

import com.example.practice.domain.Member;

import jakarta.persistence.EntityManager;

public class JpaMemberRepository implements MemberRepository{

    private final EntityManager em;

    public JpaMemberRepository(EntityManager em) {
        this.em = em;
    }

    @Override
    public Member save(Member member) {
        em.persist(member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        Member member = em.find(Member.class, id);
        return Optional.ofNullable(member);
    }

    @Override
    public Optional<Member> findByName(String name) {
        List<Member> result = em.createQuery("select m from Member m where m.name = :name", Member.class)
            .setParameter("name", name)
            .getResultList();
        return result.stream().findAny();
    }

    @Override
    public List<Member> findAll() {
        return em.createQuery("select m from Member m", Member.class)
            .getResultList();
    }
    
}
  • EntityManager : 데이터베이스와의 연결 정보, 테이블과 매핑된 엔티티 정보 등을 가지고 있는 인터페이스로 스프링이 생성해 주고 DI도 가능하다.
  • persist : 객체를 테이블에 삽입(저장)
  • find : 지정된 클래스 및 기본 키의 엔터티를 검색
  • createQuery : Jakarta Persistence 쿼리 언어 문을 실행하기 위한 TypedQuery 인스턴스를 생성
  • getResultList() : SELECT 쿼리를 실행하고 쿼리 결과를 List로 반환

Jakarta Persistence 쿼리 언어 문은 보통 JPQL라고 줄여서 부르며 객체지향 쿼리 언어다. JPA에서 해당 JPQL을 분석한 다음 적절한 SQL로 변환해 주어서 데이터베이스에서 데이터를 가져오게 된다.

(참고 : https://docs.spring.io/spring-data/jpa/docs/current/reference/html/)

위 예제에서 알 수 있는 점은 간단한 삽입이나 조회는 특별한 구현 없이 이미 JPA 구현체인 Hiberate에서 구현되어 있기 때문에 코드가 매우 간결해졌다는 것이다. 그리고 이 구현체는 데이터베이스 테이블이 아닌 객체와 매핑된 엔티티에 맞춰져 있기 때문에, 코드 상에서 조회의 결과 값을 그냥 객체처럼 사용할 수 있다.

또한 자신의 엔티티 스펙에 맞춰진 특정 조회 로직같은 경우는 JPQL 문을 사용하면 되기 때문에 보다 간결하고 유연하게 데이터 접근 로직을 구현할 수 있다.

(각 메서드에 대한 보다 자세한 내용은 jakarta 공식 문서 참고)

 

서비스 계층에 트랜잭션 추가

service/MemberService.java

import org.springframework.transaction.annotation.Transactional;

@Transactional
public class MemberService {
	// ...

org.springframework.transaction.annotation.Transactional를 사용

스프링은 해당 클래스의 메서드를 실행할 때 트랜잭션을 시작하고, 메서드가 정상 종료되면 트랜잭션을 커밋한다. 만약 런타임 예외가 발생하면 롤백한다.

JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.

 

JPA를 사용하도록 스프링 설정 변경

SpringConfig.java

// ...

@Configuration
public class SpringConfig {

    private final DataSource dataSource;
    private final EntityManager em;  // 추가

    public SpringConfig(DataSource dataSource, EntityManager em) {
        this.dataSource = dataSource;
        this.em = em;
    }

    @Bean
    MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    MemberRepository memberRepository() {
        // return new MemoryMemberRepository();
        // return new JdbcMemberRepository(dataSource);
        // return new JdbcTemplateMemberRepository(dataSource);
        return new JpaMemberRepository(em);
    }
}

이전과 마찬가지로 MemberRepository 구현체만 갈아 끼우면 된다.

테스트 통과
Hibernate가 자동으로 생성해 준 SQL 문을 확인할 수 있음

JPA는 이처럼 개발자에게 말도 안되는 편리함을 준다. 하지만 잘 사용하는 것은 또 다른 문제다.

JPA는 10년 이상 된 기술이기 때문에 자세히 알기 위해서는 공부해야 하는 양이 상당하다. 데이터를 접근하는 민감한 기술이기 때문에 실무에서 사용하기 위해서는 많은 공부와 연습이 필요하다.

 

Reference

728x90
반응형