스프링 입문 - Ch 6. 스프링 DB 접근 기술(2)

seren-dev·2022년 3월 31일
0

스프링 입문

목록 보기
10/11

JPA

Jdbc -> JdbcTemplate
개발해야 할 반복 코드 줄음
그러나 sql문은 개발자가 직접 작성

JPA: sql문 작성도 JPA가 자동으로 작성

  • JPA는 기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
  • JPA를 사용하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환을 할 수 있다.
  • JPA를 사용하면 개발 생산성을 크게 높일 수 있다.

build.gradle 파일에 JPA, h2 데이터베이스 관련 라이브러리 추가

bulild.gradle

//implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  • gradle 버튼 눌러서 리프레시해야 함
  • spring-boot-starter-data-jpa 는 내부에 jdbc 관련 라이브러리를 포함한다. 따라서 jdbc는 제거해도 된다.

스프링 부트에 JPA 설정 추가

resources/application.properties

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
  • show-sql : JPA가 생성하는 SQL을 출력한다.
  • ddl-auto : JPA는 테이블을 자동으로 생성하는 기능을 제공하는데 none를 사용하면 해당 기능을 끈다.
    create를 사용하면 엔티티 정보를 바탕으로 테이블도 직접 생성해준다.

JPA는 자바 표준 인터페이스이고 구현은 여러 업체가 있다.
구현체로 hibernate...
JPA는 ORM: 객체와 관계형 DB를 mapping

JPA 엔티티 매핑

member.java

package hello.hellospring.domain;

import javax.persistence.*;

@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;
    }
}
  • IDENTITY: 쿼리에 아이디를 넣는게 아니라 DB가 아이디를 자동으로 생성
  • @Column(name="username") private String name;: DB 컬럼명 username과 매핑

JPA 리포지토리 생성

  • EntityManager로 모든 걸 동작
  • build.gradle에서 data-jpa 라이브러리를 받으면 스프링부트가 EntityManager를 자동으로 생성하고 현재 DB랑 연결
  • 그래서 바로 injection 받으면 됨

-> JPA를 사용하려면 EntityManager 주입 받아야 함

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;
    }

    @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();
    }
}
  • em.persist(member) 하면 insert 쿼리 만들어서 DB에 넣고 setId까지 다 해줌
  • em.createQuery: 테이블이 아니라 객체(멤버 엔티티)를 대상으로 쿼리를 날림
    select m from Member as m
    객체 자체를 select
  • 저장하고 조회하고 업데이트하는 것은 쿼리문을 작성할 필요가 없지만, pk기반이 아니면 createQuery메서드로 JPQL(Java Persistence Query Language)를 작성해야 함

[JPA] 객체지향 쿼리, JPQL
JPQL은 엔티티 객체를 대상으로 쿼리를 질의하고
SQL은 데이터베이스 테이블을 대상으로 쿼리를 질의한다.

주의: Service 객체에 @Transactional 추가

join 메서드에만 추가해도 됨

  • JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.
    @Transactional은 기본적으로 해당 메서드가 실행될 때 트랜잭션을 시작하고 해당 메서드가 끝날 때 커밋을 해서 데이터베이스에 전달된 내용을 확정합니다.
    그런데 테스트 코드에서 @Transactional을 사용하면 커밋을 하는게 아니라 데이터베이스에 롤백을 해서 데이터베이스에 전달된 데이터를 모두 삭제됩니다.
    참고: [Java]@Transactional Annotation 알고 쓰자

Service클래스에서의 @Transactional을 사용해도 되고, Repository클래스에서의 @Transactional를 사용해도 됩니다.
하지만 트랜잭션은 보통 비즈니스 로직을 실행할 때 사용하는 것이 가장 적절합니다. 왜냐하면 비즈니스 로직에 문제가 생기면 한번에 해당 비즈니스 로직을 롤백할 수 있기 때문이지요. 그래서 보통 비즈니스 로직 단위로 트랜잭션을 사용합니다.

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

@Configuration
public class SpringConfig {


    private EntityManager em;

    @Autowired
    public SpringConfig(EntityManager em) {
        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);
    }
}

회원가입 메서드 테스트하면 성공하면서

Hibernate: select member0_.id as id1_0_, member0_.name as name2_0_ from member member0_ where member0_.name=?
Hibernate: insert into member (id, name) values (default, ?)

@Test @Commit void 회원가입() 하면 DB에 반영 됨

EntityManager는 수동으로 관리하기 보단, 스프링이 관리하도록 위임하고 사용하는것이 일반적입니다. 그래서 스프링이 제공하는 EntityManager를 사용하기 위해선 @Autowired 혹은 @PersistenceContext 애노테이션을 붙여주셔야 합니다.
그러면 스프링 애플리케이션이 실행될 때, 컴포넌트 스캔을 하게 되고, @Configuration 을 발견하겠죠? 이때 필드에 @PersistenceContext 애노테이션이 붙은 EntityManager를 발견하게 되고, 스프링이 관리하는 EntityManager를 여기에 주입해줍니다. 그래서 이떄는 null이 아닌 값이 JpaMemberRepository에 전달되는것 입니다.


스프링 데이터 JPA

스프링 부트와 JPA만 사용해도 개발 생산성이 정말 많이 증가하고, 개발해야할 코드도 확연히 줄어듭니다.
여기에 스프링 데이터 JPA를 사용하면, 기존의 한계를 넘어 마치 마법처럼, 리포지토리에 구현 클래스 없이 인터페이스만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 스프링 데이터 JPA가 모두 제공합니다.
스프링 부트와 JPA라는 기반 위에, 스프링 데이터 JPA라는 환상적인 프레임워크를 더하면 개발이 정말 즐거워집니다. 지금까지 조금이라도 단순하고 반복이라 생각했던 개발 코드들이 확연하게 줄어듭니다.
따라서 개발자는 핵심 비즈니스 로직을 개발하는데, 집중할 수 있습니다.
실무에서 관계형 데이터베이스를 사용한다면 스프링 데이터 JPA는 이제 선택이 아니라 필수 입니다.

주의: 스프링 데이터 JPA는 JPA를 편리하게 사용하도록 도와주는 기술입니다. 따라서 JPA를 먼저 학습한 후에 스프링 데이터 JPA를 학습해야 합니다.

  • 앞의 JPA 설정을 그대로 사용한다.

스프링 데이터 JPA 회원 리포지토리

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {

    @Override
    Optional<Member> findByName(String name);
}
  • 인터페이스가 인터페이스를 받을 때는 extends
  • JpaRepository<Member, Long> Long(id)
  • 인터페이스만 있지만 스프링 데이터 JPA가 JpaRepository를 보고 구현체를 자동으로 만들고 스프링 빈으로 등록
  • 스프링 데이터 JPA가 객체를 생성해서 SpringDataJpaMemberRepository를 스프링 빈으로 자동 등록해준다.

스프링 데이터 JPA 회원 리포지토리를 사용하도록 스프링 설정 변경

package hello.hellospring;

import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
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 MemberRepository memberRepository;

    //@Autowired
    public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @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);
//    }
}
  • 생성자 함수에서 스프링 데이터 JPA가 만들어놓은 구현체가 등록
  • MemberService 함수도 수정

MemberServiceIntegrationTest 수행

SpringDataJpaMemberRepository는 스프링이 자동으로 스프링 빈으로 등록해준다.MemberRepository는 인터페이스이고, SpringDataJpaMemberRepositoryMemberRepository 인터페이스를 구현하고 있기 때문에 스프링은 MemberRepository를 주입받으면 등록된 SpringDataJpaMemberRepository를 찾아서 주입해준다.

실용적인 관점에서 SpringDataJpaMemberRepository를 바로 주입받아서 사용하기도 한다. 이렇게 하면 스프링 데이터 JPA의 기능을 모두 편리하게 사용할 수 있다. 다만 단점은 스프링 데이터 JPA가 제공하는 모든 기능에 의존하는 설계가 되어서 향후 리포리토리 변경이 어렵다.
유연성이냐 아니면 유연성은 떨어지지만 실용성이냐의 트레이드 오프이다. 그리고 이것도 하나의 정답이 있다기 보다는 현재 나의 애플리케이션이 처한 상황과 환경에 따라서 각각 더 나은 선택지가 있을 뿐이다.


스프링 데이터 JPA 제공 클래스

스프링 데이터 JPA 제공 기능

  • 인터페이스를 통한 기본적인 CRUD
    • JpaRepository와 상위 클래스에 save, findById, findAll 이 이미 있음
  • findByName() , findByEmail() 처럼 메서드 이름만으로 조회 기능 제공
    findBy...이라고 하면 알아서 JPQL 생성
//JPQL select m from Member m where m.name = ?
    @Override
    Optional<Member> findByName(String name);
  • 페이징 기능 자동 제공

참고: 실무에서는 JPA와 스프링 데이터 JPA를 기본으로 사용하고, 복잡한 동적 쿼리는 Querydsl이라는 라이브러리를 사용하면 된다. Querydsl을 사용하면 쿼리도 자바 코드로 안전하게 작성할 수 있고, 동적 쿼리도 편리하게 작성할 수 있다. 이 조합으로 해결하기 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리(직접 쿼리 작성)를 사용하거나, 앞서 학습한 스프링 JdbcTemplate를 사용하면 된다.

0개의 댓글