SPRING 입문 [ 코드로 배우는 스프링 부트 ]

[스프링 입문] 섹션 6.5 스프링 DB 접근 기술 (JPA)

wlalsu_u 2023. 1. 18. 02:56

6.5.1  JPA 란?

 

 

앞선 섹션 6.4의 JdbcTemplate 예제에서, 중복 코드는 제거할 수 있었지만,

 

여전히 SQL 은 개발자가 작성해야했다.

 

 

JPA는 기존의 반복 코드 뿐만 아니라, 기본적인 SQL도 직접 만들어서 실행해준다!

 

 

또한, SQL과 데이터 중심의 설계에서, 객체 중심의 설계로 패러다임을 전환한다.

 

 

따라서 JPA 를 사용하여 개발하면, 개발 생산성을 크게 향상시킬 수 있다.

 

 

 


 

 

6.5.2  JPA 코드 작성 - 라이브러리

 

 

JPA 를 이용하여 DB에 접근하는 코드를 작성해보자.

 

 

먼저, build.gradle 파일의 dependencies 에 아래의 코드를 추가하고, 라이브러리를 받아준다.

 

 

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

 

 

라이브러리를 모두 받으면,

 

External Libraries 에서 jpajpa hibernate 라이브러리를 찾을 수 있다.

 

 

JPA는 표준 인터페이스만을 제공하는데, 이를 구현한 것이 hibernate 이다.

 

 

 

+ 참고 )
spring-boot-starter-data-jpa 는 JPA 뿐만 아니라, JDBC 관련 라이브러리를 포함한다.
따라서, 이전에 작성했던 jdbc implementation은 제거해도 된다.

 

 


 

6.5.3  JPA 코드 작성 - JPA 설정

 

 

 

다음으로, JPA 관련 설정을 추가해보자.

 

application.properties 파일에 다음의 코드를 추가한다.

 

 

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none

 

spring.jpa.show-sql = true

 

 

: JPA 가 생성하는 SQL 을 출력한다.

 

 

spring.jpa.hibernate.ddl-auto

 

 

회원 객체를 보고 자동으로 table 을 생성한다.

 

앞서 table 을 이미 만들었으므로, none 으로 설정해준다.

 

(create 로 값을 주면, table 자동 생성)

 

 

 


 

 

6.5.4  JPA 코드 작성 - Member 클래스

 

 

 

다음으로, Member 클래스를 다음과 같이 작성한다.

 

 

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

 

 

@Entity

 

: Entity를 mapping 하면, JPA 가 해당 코드를 관리하게 된다.

 

 

+ 추가 )
JPA는 ORM(Object, Relational, Mapping) 기술이다.
따라서, ORM 객체의 object 와 relational 데이터베이스의 table 을 mapping 하기 위해서는 어노테이션이 필요하다.

 

 

@Id @GeneratedValue(strategy = GenerationType.IDENTITY)

 

 

쿼리에 id 값을 직접 넣지 않고,

 

db 에 값을 넣으면 자동으로 id 값이 생성되도록 작성하고자 한다.

 

 

따라서, @GeneratedValue 에서 strategy IDENTITY 로 작성한다.

 

(이를 아이덴티티 전략이라고 한다.)

 

 

 


 

6.5.5  JPA 코드 작성 - Repository

 

 

다음으로 repository 를 작성해보자.

 

 

src  >  main  >  java  >  hello.hellospring  >  repository 폴더에

 

JpaMemberRepository 파일을 생성하고 MemberRepository 를 구현한다.

 

 

public class JpaMemberRepository implements  MemberRepository

 

 

이어서 아래의 코드를 작성한다.

 

 

private final EntityManager em;

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

 

 

EntityManager em

 

 

JPA 는 EntityManager 를 통해 동작하게 된다.

 

 

JPA 라이브러리를 받으면, 스프링 부트가 자동으로 EntityManager 를 생성한다.

 

이때, EntityManager 는 데이터 소스를 가지고 있어, db 통신 등을 내부에서 모두 처리한다.

 

 

 

JpaMemberRepository 생성자

 

 

JPA 를 사용하기 위해, EntityManager 를 injection 받도록 한다.

 

 


 

 

다음으로는, save 메소드를 아래와 같이 오버라이딩 한다.

 

아래와 같이 코드를 작성하면, JPA 가 자동으로 insert 쿼리를 생성하고, DB에 저장한다.

 

 

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

 

 

em.persist(member)

 

: member 를 넣어주고, 영구 저장하도록 한다.

 

 

 


 

findByName 메소드를 다음과 같이 오버라이딩한다.

 

 

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

 

 

em.find(Member.class, id)

 

 

: 조회할 타입과 식별자를 넣어 회원을 조회하도록 한다.

 

 

  return Optional.ofNullable(member);

 

 

: Optioanl 로 return 해야 하므로, 값이 null 인 경우를 위해 다음과 같이 작성한다.

 

 

 


 

 

 

 

findByName 의 경우, 객체에 제한 쿼리 언어를 사용해야 한다. (sql 과 동일)

 

 

 

이는 원래 table 을 대상으로 sql 을 보냈다면, Entity를 대상으로 Query 보내는 것이다. 

 

이후, 이는 sql 로 번역된다.

 

 

@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();
    }

 

 

em.createQuery("select m from Member m where m.name = :name", Member.class)

 

 

: Member.class 로 조회를 하고, select 대상이 Entity 가 된다. 

 

 

 


 

 

findAll 메소드를 다음과 같이 오버라이딩한다.

 

 

@Override
public List<Member> findAll() {
    return em.createQuery("select m from Member m", Member.class)
            .getResultList();
}

 

 

 


 

 

전체 코드는 다음과 같다.

 

 

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


}

 

 

 


 

6.5.6  JPA 코드 작성 - MemberService

 

 

@Transactional
public class MemberService {
    ...
}

 

 

@Transactional

 

 

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

 

 

스프링이 해당 클래스의 메서드를 실행할 때 transaction 을 시작하고,

 

메서드가 정상 종료되면 transaction을 commit한다.

 

 

(런타임 예외가 발생하면 롤백)

 

 

 


 

 

6.5.7  JPA 코드 작성 - SpringConfig

 

 

 

SpringConfig 코드를 아래와 같이 수정한다.

 

 

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

import javax.persistence.Entity;
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);
    }
}

 

 

return new JpaMemberRepository(em);

 

 

: return 값을 JpaMemerRepository 로 변경하고, Entity를 받도록 한다.

 

 

 

 

 


 

 

6.5.8  스프링 통합 테스트

 

 

MemberServiceIntegrationTest 를 실행시키면, test 가 정상적으로 작동하는 것을 확인할 수 있다.

 

 

또한 스프링 JPA 를 세팅하면, 기본적으로 Hibernate 오픈 소스 구현체가 사용이 되는 것도 확인할 수 있다.

 

 

 

 

 

 

 

다음과 같이,  Commit 어노테이션을 사용하고 test 를 실행하면,

 

실제 localhost 에서도 데이터가 저장됨을 볼 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

김영한 '스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술' 강의를 기반으로 작성하였습니다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard