[Spring Boot] 입문 - Spring Boot 프로젝트에 JPA 적용하기
스프링 입문 - 코드로 배우는 스프링 부트, 웹 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
구현체만 갈아 끼우면 된다.
JPA는 이처럼 개발자에게 말도 안되는 편리함을 준다. 하지만 잘 사용하는 것은 또 다른 문제다.
JPA는 10년 이상 된 기술이기 때문에 자세히 알기 위해서는 공부해야 하는 양이 상당하다. 데이터를 접근하는 민감한 기술이기 때문에 실무에서 사용하기 위해서는 많은 공부와 연습이 필요하다.