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