JPA는 스프링부트에서 DB에 접근하는 방법을 알 수 있는 중요한 기술 중 하나이다. 그래서 JPA가 어떤 것인지 기초부터 심화까지 알아보았고 그 내용을 정리하고자 한다.
JPA
JPA란, Java 객체를 데이터베이스 테이블에 매핑(ORM) 해주는 표준 "인터페이스"이다.
즉, SQL을 직접 쓰지 않아도 DB와 소통할 수 있게 도와주는 도구라고 볼 수 있다.
JPA를 구현한 대표적인 오픈소스로는 "Hibernate"가 있다.
JPA의 장점은 다음과 같다.
- JPA는 기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
- JPA를 사용하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있다.
구현방법
구현방법에 대해 알아보기 전, 이해를 위해 전체적인 다이어그램을 먼저 보여주고 가겠다.
MemberService와 MemberRepository는 사전에 작성이 되어 있다고 가정을 할 것이다.
1️⃣설정 추가
우선, JPA 관련 라이브러리를 추가해 준다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
}
그 다음 스프링부트에 JPA 설정을 추가해 준다.
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
- show-sql: JPA가 생성하는 SQL을 출력한다.
- ddl-auto: JPA는 테이블을 자동으로 생성하는 기능을 제공하는데, none을 사용하면 해당 기능을 끈다.
📍여기서 잠깐, ddl-auto에 대해 짚고 넘어가자📍
ddl-auto란 Hibernate와 같은 JPA 구현체를 사용할 때 데이터베이스 스키마 관리를 제어하는 설정이다. 각 옵션에 대해 간략히 적어두겠다.
- none: 데이터베이스 스키마와 관련된 어떠한 작업도 수행하지 않는다.
- validate: 애플리케이션이 시작될 때, 엔티티 매핑이 데이터베이스 스키마와 일치하는지 검증한다.
- update: 엔티티 매핑과 데이터베이스 스키마를 비교하여 필요한 경우 스키마를 업데이트한다.
- create: 애플리케이션이 시작될 때 기존 스키마를 삭제하고 새로 생성한다.
- create-drop: 은 create와 유사하지만, 애플리케이션이 종료될 때 스키마를 삭제한다는 점이 다르다.
2️⃣JPA 엔티티 매핑
그다음 DB의 테이블에 매핑되는 클래스를 정의해 준다.
- @Entity: 데이터베이스 테이블에 매핑되는 JPA 엔티티 클래스임을 명시한다.
- @Id: 해당 테이블의 기본 키(PK)이다.
@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;
}
}
3️⃣JPA 레포지토리
아래에 나오는 JpaMemberRepository는 미리 정의되어 있던 MemberRepository 인터페이스를 구현한 것이다.
이 클래스는 직접 EntityManager를 사용해서 DB 조작을 한다.
📍여기서 잠깐, 엔티티 메니저에 대해 짚고 넘어가자📍
EntityManager는 엔티티를 저장하고, 수정하고, 삭제하고 조회하는 등 엔티티와 관련된 모든 일을 처리한다.
- 영속성 컨텍스트: 데이터베이스와 에플리케이션 사이에 엔티티를 영구 저장하는 환경으로 1차 캐싱, 쓰기 지연, 변경 감지를 통해 영속 로직을 효율적으로 할 수 있게 해준다.
- 4가지 상태: 엔티티는 비영속, 영속, 준영속, 삭제 상태를 가질 수 있는데, 엔티티 매니저는 persist, merge, remove, close 메서드를 이용하여 엔티티의 상태를 변경할 수 있다.
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); // ⭐️ JPA(Hibernate)가 내부적으로 SQL을 생성해서 실행한다!
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() {
List<Member> result = em.createQuery("select m from Member m", Member.class)
.getResultList();
return result;
}
}
❓그럼 EntityManager는 어떻게 주입되는지 궁금할 것이다.
스프링에서 생성자 주입으로 EntityManager를 자동으로 넣어주면 된다.
@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
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new JpaMemberRepository(em);
}
}
4️⃣서비스 계층에 트렌잭션 추가
스프링은 해당 클래스의 메서드를 실행할 때 트랜잭션을 시작하고, 메서드가 정상 종료되면 트랜잭션을 커밋한다. 만약 런타임 예외가 발생하면 롤백한다.
JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행돼야 한다.
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class MemberService { }
🚨추가로, JPA에서 발생할 수 있는 N + 1 문제에 대해 알아보자🚨
- N + 1 문제: 연관 관계가 설정된 엔티티를 조회할 경우에, 조회된 데이터 개수(N)만큼 연관관계의 조회 쿼리가 추가로 발생하는 현상을 의미한다.
- 해결방법: fetch join, @EntityGraph를 사용할 수 있다. fetch join은 연관 관계에 있는 엔티티를 한번에 즉시 로딩하는 구문이다. @EntityGraph도 비슷한 효과를 만들어내며, 쿼리 메서드에 해당 어노테이션을 추가해 사용할 수 있다.
Spring Data JPA
Spring Data JPA는, JPA를 한 단계 추상화시킨 Repository라는 인터페이스를 제공함으로써 이루어진다.
스프링 데이터 JPA가 JPA를 추상화했다는 말은, 스프링 데이터 JPA의 Repository의 구현에서 JPA를 사용하고 있다는 것이다.
Repository 인터페이스의 기본 구현체인 SimpleJpaRepository의 코드를 보면 내부적으로 EntityManager을 사용하고 있다.
스프링 데이터 JPA의 장점은 다음과 같다.
- 스프링 데이터 JPA를 활용하면 레포지토리에 구현 클래스 없이 인터페이스 만으로 개발을 할 수 있다.
- 반복 개발해온 기본 CRUD 기능도 스프링 데이터 JPA가 모두 제공한다.
1️⃣스프링 데이터 JPA 레포지토리
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
@Override
Optional<Member> findByName(String name);
}
2️⃣스프링 데이터 JPA 회원 레포지토리를 사용하도록 스프링 설정 변경
스프링 데이터 JPA가 SpringDataJpaMemberRepository를 스프링 빈으로 자동 등록해 준다.
@Configuration
public class SpringConfig {
private final MemberRepository memberRepository;
@Autowired
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository);
}
}
'Develop > Server' 카테고리의 다른 글
[Server] Kotlin SpringBoot 프로젝트에 ktlint 적용하기 (0) | 2025.05.05 |
---|---|
[IT 인프라] Ch4 기본 이론 (큐, 배타적 제어, 상태 저장/비저장) (0) | 2025.05.03 |
[IT 인프라] Ch3 3계층형 시스템 (웹 데이터, 가상화) (0) | 2025.04.12 |
[Cloud Native] Ch1 클라우드 네이티브 소개 (1) | 2024.06.16 |