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 에서 jpa와 jpa 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 접근 기술' 강의를 기반으로 작성하였습니다.
'SPRING 입문 [ 코드로 배우는 스프링 부트 ]' 카테고리의 다른 글
[스프링 입문] 섹션 7 AOP (+ 강의를 마치며) (0) | 2023.01.18 |
---|---|
[스프링 입문] 섹션 6.6 스프링 DB 접근 기술 (스프링 데이터 JPA) (0) | 2023.01.18 |
[스프링 입문] 섹션 6.4 스프링 DB 접근 기술 (스프링 통합 Jdbc Template) (0) | 2023.01.17 |
[스프링 입문] 섹션 6.3 스프링 DB 접근 기술 (스프링 통합 테스트) (0) | 2023.01.17 |
[스프링 입문] 섹션 6.2 스프링 DB 접근 기술 (순수 JDBC) (0) | 2023.01.17 |