스프링 DB 접근 기술 - JPA

Chooooo·2022년 8월 15일
0

JPA

이전까지는 스프링의 DB 접근 기술 중 H2 데이터베이스의 사용을 통해 순수 Jdbc와 JdbcTemplate에 대해 알아봤다.
이번 강의 에서는 스프링 JPA에 대해 알아보자!
JPA : Java Persistence API의 약자.
JPA는 '자바의 표준 인터페이스'이고 그 구현체로 보통 hibernate를 사용한다.

JPA의 장점, 사용 이유

JPA를 사용하면 기본적인 SQL문을 JPA가 직접 만들어 실행해준다. --> 스프링 JdbcTemplate를 사용해 반복 코드는 줄었지만, 여전히 SQL문을 직접 작성해야 한다는 단점이 있었다. JPA를 사용하면 SQL문을 직접 작성하지 않아도 된다!
특히 기본 CRUD 기능의 SQL문을 직접 작성할 필요가 없다.
이렇게 JPA를 사용하면 SQL과 데이터 중심 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있고, 개발 생산성을 크게 향상시킬 수 있다.

현재 JPA와 MyBatis라는 API를 비교할 수 있는데, 아직까지 국내에서는 MyBatis를 더 쓰지만 전 세계적인 추세로는 JPA를 더 많이 쓴다. 국내 도입이 늦어진 것.
스프링 기술도 매우 거대하지만 JPA도 그만큼 거대한 기술이다. 스프링만큼 공부가 필요해!!!

스프링과 JPA가 비슷한 시기에 나와서 스프링이 JPA를 지원하는 부분이 많아!


코드에서 한번 보자

1. 라이브러리 추가

build.gradle 파일에 아래 코드 추가하여 JPA를 사용하기 위한 라이브러리를 추가
(build.gradle파일에 JPA, H2 데이터베이스 관련 라이브러리 추가)

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 'com.h2database:h2'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}

기본 코드에서 'implementation 'org.springframework.boot:spring-boot-starter-data-jpa''부분이 추가되었는데 이 부분이 Jdbc 관련 라이브러리를 포함하기 때문에 Jdbc는 제거해도 된다.

2. JPA를 사용과 관련된 설정

resources/application.properties 파일에 아래 코드 추가하여 스프링 부트가 JPA를 사용하는 것에 있어서 관련된 설정을 해주자!

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none

주의!

스프링부트 2.4부터는 spring.datasource.username=sa를 꼭 추가해주어야 한다. 그렇지 않으면 오류가 발생한다.

  • spring.jpa.show-sql = true : 이 줄은 JPA가 생성하는 sql을 출력한다.
  • spring.jpa.hibernate.ddq-auto=none : JPA는 원래 객체를 보고 DB를 자동으로 만들지만, 우리는 H2에 이미 만들어 놓았고 만들어진 것을 사용할 것이므로 해당 기능을 끄자.(이번 강의에서는 이미 생성된 Member 테이블을 사용할 것이기에)
    'none'대신에 'create'로 해당 기능을 사용할 수 있어 이때는 JPA가 엔티티 정보를 바탕으로 테이블을 직접 생성!

3. JPA Entity 맵핑

  • 참고로 JPA에 대해 조금 깊이 들어가면 JPA는 인터페이스로, hibernate와 같은 구현체를 우리가 사용한다.
    JPA는 ORM기술로 (Object Relational Mapping)을 의미하며, 객체와 관계형 데이터를 맵핑하는 기술
    이를 사용하기 위해 기존의 클래스에 어노테이션 추가(JPA의 맵핑 과정은 어노테이션을 통해 이루어진다.)
    즉 자바 코드에 선언한 어노테이션을 보고, 관계형 데이터베이스의 요소와 비교한다.
package hello.hellospring.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.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;
 }
}

객체(Member)와 DB를 연동하기 위해 @Entity 어노테이션을 추가하였다.

javax.persistence.Entity를 import
@Entity를 선언하면 해당 클래스 객체는 JPA가 관리하는 Entity라고 표현한다.

그리고 PK(Primary Key)를 맵핑해 주어야 한다.
PK를 설정하기 위해 id에 @Id 어노테이션으로 PK설정을 하고, @GeneratedValue(strategy = GenerationType.IDENTITY)를 사용하면 기존에 우리가 설정하였던 것처럼 id값을 자동으로(알아서) 생성하여 pk값으로 지정해준다.
(SQL에 선언한 generated by default as identity처럼, DB가 자동으로 생성해주는 값에 선언해주자)

주석처리된 @Column 어노테이션을 사용하면 name 멤버 변수를 DB의 'username'과 맵핑할 수도 있어.
(만약 DB에 컬럼명이 username이라면 이렇게 @Column(name = "username") 써주면 맵핑해줘)
이처럼 각 어노테이션을 사용하여 DB와 맵핑을 시도하고 이를 바탕으로 자동으로 SQL문을 작성한다.

4. JPA를 사용하여 회원 repository 작성

이제 JPA를 사용하여 회원 리포지토리를 작성하자
repository폴더에 JpaMemberRepository 파일 생성하고 MemberRepository를 구현(implements)하여 아래 코드 작성.

package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
public class JpaMemberRepository implements MemberRepository {
 private final EntityManager em;
 public JpaMemberRepository(EntityManager em) {
 this.em = em;
 }
 public Member save(Member member) {
 em.persist(member);
 return member;
 }
 public Optional<Member> findById(Long id) {
 Member member = em.find(Member.class, id);
 return Optional.ofNullable(member);
 }
 public List<Member> findAll() {
 return em.createQuery("select m from Member m", Member.class)
 .getResultList();
 }
 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();
 }
}
  • EntityManager
    JPA는 EntityManager로 동작. 반드시 선언해야 하는 요소이다.
    build.gradle에서 data-jpa를 설정해두면, 스프링부트가 자동으로 EntityManager라는 것을 만들어(현재 데이터베이스와 연결까지 해서) 설정한 DB와의 통신을 편하게 해준다.
    생성자에서 이 만들어진 EntityManager를 주입받는 것!!
    (이전 강의에서 dataSource를 주입받은 것처럼 생성된 manager를 주입받으면 된다)

*Persist(e)
인자로 회원 객체를 넘겨주기만 하면 JPA가 insert query를 자동으로 작성해서 DB에 넣고 id를 생성, 이를 받아 pk로 세팅한다. id를 부여받아 DB에 저장된 회원 객체를 반환하기만 하면 된다!!

*find(조회할 타입, PK)
인자로 조회할 타입, 조회 시 식별할 pk값이 필요해. 반환된 회원 객체를 Optional로 감싸 반환한다. 해당 findbyId() 메서드를 사용 시 자동으로 select query를 작성, 실행한다.

findByName() 함수는 PK기반으로 검색하는 함수가 아니기 때문에 다른 방법 사용.
createQuery("JPQL쿼리문", 타입)
JPQL

일반 쿼리문은 테이블을 대상으로 쿼리문을 보내지만, JPQL은 객체를 대상으로 쿼리문을 보낸다. JPQL로 보낸 쿼리문이 SQL로 번역되어 보내진다.
select m from Member m : Member(m) 자체를 조회하고 있다.
다음 시간에 배울 스프링 데이터 JPA를 사용하면 JPQL도 직접 짜지 않아도 된다.

기존에 작성했던 SQL처럼 테이블 대상으로 회원의 id, name을 검색하는 것이 아니라, 회원 객체(entity)를 대상으로 쿼리를 보내 해당하는 객체 자체를 검색.
기존에는 id, name을 select하여 새롭게 회원 객체를 생성해 이를 세팅하고 세팅된 객체를 반환하였다고 한다면, 이제는 검색하여 반환된 회원 객체 자체를 반환할 수 있다!!
findAll()함수 또한 createQuery()를 사용하여 검색된 모든 결과를 리스트 형태로 받아 반환.


JPA를 사용하여 리포지토리를 구현하였더니 새로 회원 객체를 생성하고 검색 결과를 이에 세팅하여 반환하는 것과 같은 중복된 과정이 삭제되고, SQL을 직접 작성하는 과정 또한 축소되었다.

5. JPA를 사용할 때 주의점

데이터 저장 및 변경할 때는 반드시 해당 클래스/메서드에 @Transactional 어노테이션을 선언해야 한다.
JPA는 모든 데이터 저장/변경이 트랜잭션 안에서 실행되어야 한다!!!!
이를 위해 회원 서비스에 추가해줘야지.
MemberService.java
*클래스 선언부 위에 @Transcational 추가

import org.springframework.transaction.annotation.Transactional;

@Transactional
public class MemberService {}

회원 서비스 안의 join()함수는 데이터를 추가하는 기능이므로 데이터 변경 시 트랜잭션이 필요하고, 이를 @Transactional 어노테이션을 추가하여 해결할 수 있다!
(이때 스프링은 해당 클래스 메서드를 실행할 때 트랜잭션을 시작하고, 메서드가 정상종료되면 트랜잭션을 커밋, 런타임 예외가 발생한다면 롤백을 수행한다)

SpringConfig 수정

이제 생성한 JpaMemberRepository를 사용하도록 SpringConfig를 수정하자

package hello.hellospring;

import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

@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 MemoryMemberRepository();
        //return new JdbcMemberRepository(dataSource);
        //return new JdbcTemplateMemberRepository(dataSource);
        return new JpaMemberRepository(em);
    }
}

기존에 JdbcMemberRepository를 사용하던 코드를 주석처리하고 JpaMemberRepository를 사용하도록 해당 리포지토리를 생성, 반환한다. 이때 스프링에서 생성되는 EntityManager를 주입해준다!

통합테스트

JPA를 사용한 리포지토리가 정상적으로 동작하는지 통합테스트 실행
이전에 작성했던 MemberServiceIntegrationTest를 통해 테스트!

로그를 통해 JPA가 생성한 SQL또한 확인 가능하다.
즉 결국 DB에 전달되는 것은 SQL문인데, 직접 작성할 필요가 없게 편리성을 더한 것.
@Commit어노테이션을 추가 후 테스트 수행 시 H2 데이터베이스에 데이터가 저장되는 것 또한 확인 가능하다.

요즘 JPA는 큰 스타트업 등에서도 사용할 만큼 자주 사용해. JPA는 스프링만큼 공부할게 많다. 실무에서 사용할 정도가 되려면 깊이 있게 공부하자!

이 글은 강의 : 김영한 - "스프링 입문-코드로 배우는 스프링 부트, 웹 MVC, DB접근기술"을 듣고 정리한 내용(필기)입니다.

profile
back-end, 지속 성장 가능한 개발자를 향하여

0개의 댓글