본문 바로가기
[inflearn] 스프링 입문

15. 스프링 JPA (Java Persistence API)

by 슬픈 야옹이 2023. 4. 30.

JPA (Java Persistence API)는 자바 표준 인터페이스로, 자바 프로그램과 DB 사이의 연동 기능을 제공하는 ORM 기술이다.

 

ORM (Object-Relation Mapping) : 객체지향 프로그래밍에서, 객체와 DB 릴레이션(테이블)을 매핑하는 것.

 

JPA는 자바 표준 인터페이스고, 다양한 벤더해서 이를 구현한 라이브러리를 배포한다.

실습에서는 오픈소스 JPA 라이브러리인 hibernate를 이용한다.

 

JPA를 통해 개발자는 데이터 및 SQL에 대해 크게 신경쓰지 않고 객체 중심의 프로그램 설계를 할 수 있어 개발 생산성이 크게 향상된다.

 

JPA는 현재 전세계적으로 널리 쓰이는 API이기 때문에, 이를 다룰 줄 알면 많은 도움이 될 것이다.

 

 

 

 

환경 설정

build.gradle에 jpa 관련 설정을 추가한다.

data-jpa에 jdbc 라이브러리가 포함되어 있기 때문에 기존 jdbc는 없어도 된다.

 

build.gradle

// 제거
implementation 'org.springframework.boot:spring-boot-starter-jdbc'

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

 

 

 

스프링 부트에 jpa 관련 설정 추가

 

 

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 엔티티 매핑

Member.java를 다음과 같이 수정한다.

Member.java

 

Member.java

package hdxian.hdxianspring.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@Table(name = "member2")
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

- 엔티티로 지정. 이 클래스의 데이터를 바탕으로 테이블을 생성 및 관리한다.

 

@Table(name = "member2")

- 강의에는 없는 내용이지만, 나는 사용하는 테이블 이름이 다르므로 별도로 테이블 이름을 지정했다.

지정하지 않으면 member 테이블 대상으로 sql이 생성된다.

 

@Id

- 테이블의 PK로 지정

 

@GeneratedValue(strategy = GenerationType.IDENTITY)

- identity 전략 : 테이블에 insert할 때 값을 직접 정해서 넣어주는 게 아니라 자동으로 값이 정해지도록 하는 것.

 

 

 

JPA 리포지토리 생성 및 적용

JpaMemberRepository 클래스를 생성한 뒤 다음과 같이 작성한다.

 

 

 

JpaMemberRepository.java

package hdxian.hdxianspring.repository;

import hdxian.hdxianspring.domain.Member;
import org.springframework.beans.factory.annotation.Autowired;

import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;

public class JpaMemberRepository implements MemberRepository {

    private final EntityManager em;

    @Autowired // 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 em

- JPA는 EntityManager를 이용해 DB 관련 정보를 관리한다. EntityManager는 JPA 환경에서 스프링이 자동으로 생성해 관리한다.

- JPA를 사용하려면 이 EntityManager를 인젝션받아 사용한다.

 

 

em.persist()

- @Entity로 지정한 객체를 인수로 전달하면 해당 객체의 데이터를 바탕으로 sql 쿼리를 자동으로 생성해 테이블에 insert 해준다.

 

em.find()

- 객체를 바탕으로 테이블에서 데이터를 조회한다. 전달하는 첫 번째 인수는 class 정보를, 두 번째 인수로는 PK(@Id로 지정된 멤버)를 전달한다.

 

 

pk가 아닌 특정 조건으로 검색할 때는 jpql이라는 객체지향 sql을 작성해서 전달해야 한다. Member 등의 객체를 기준으로 데이터를 조회한다.

 

 

 

MemverService 클래스 선언부에 @Transactional을 추가한다.

 

 

 

MemverService.java

package hdxian.hdxianspring.service;

import hdxian.hdxianspring.domain.Member;
import hdxian.hdxianspring.repository.MemberRepository;
import hdxian.hdxianspring.repository.MemoryMemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Transactional
public class MemberService {

    // private final MemberRepository repository = new MemoryMemberRepository();
    private final MemberRepository repository;

    public MemberService(MemberRepository repository) {
        this.repository = repository;
    }

    // 회원 가입 기능
    public Long join(Member member) {

        // 중복된 이름으로는 가입 불가.
        checkNameDuplication(member);

        repository.save(member);
        return member.getId();
    }

    private void checkNameDuplication(Member member) {
        repository.findByName(member.getName()) // findbyName()의 리턴 타입은 Optional<Member>
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

    public List<Member> findMembers() {
        return repository.findAll();
    }

    public Optional<Member> findOne(Long memberId) {
        return repository.findById(memberId);
    }


}

스프링은 @Transactional이 붙은 클래스의 메서드를 실행할 때 항상 트랜잭션을 시작하고, 메서드가 정상 종료되면 실행 결과를 커밋한다. 만일 런타임 오류가 발생하면 롤백한다.

 

JPA를 통한 모든 데이터 변경은 트랜잭션 내에서 이루어져야 한다.

 

 

JpaMemberRepository를 사용하도록 SpringConfig를 변경한다.

 

SpringConfig.java

package hdxian.hdxianspring;

import hdxian.hdxianspring.repository.*;
import hdxian.hdxianspring.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 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);
    }

}

 

EntityManager를 인젝션받도록 설정하고, JpaMemberRepository를 리턴하도록 변경한다.

 

 

 

테스트 내용은 같으므로 생략한다.

 

+)

테스트 결과 일부

테스트 결과를 보면 Hibernate: ... 라고 뜨면서 실행된 sql 쿼리가 출력된다. JPA가 생성한 sql 쿼리다.

이전에 application.properties에서 show-sql=true 라고 설정했었기 때문에 지금처럼 결과에 sql이 출력된다.

 

 

 

 

 

'[inflearn] 스프링 입문' 카테고리의 다른 글

17. AOP (Aspect Oriented Programming)  (0) 2023.05.01
16. 스프링 데이터 JPA  (0) 2023.04.30
14. 스프링 JdbcTemplate  (0) 2023.04.30
13. 스프링 통합 테스트  (0) 2023.04.30
12. 순수 JDBC  (0) 2023.04.29