
Intro
JPA는 Java Persistence Api의 약자로, SQL을 직접 작성하지 않고도 객체를 통해 DB를 구성해준다.
JPA를 사용하면 SQL과 데이터 중심 설계에서 객체 중심 설계로 패러다임 전환이 가능하기 때문에
개발 생산성을 크게 높일 수 있다.
JPA 테스트케이스 작성
JPA를 사용하기 위해 Dependency를 추가해주자.
JPA에서 JDBC를 포함하기 때문에 원래 있던 JDBC Dependency를 지우고 아래의 코드를 넣어주면 된다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
//implementation 'org.springframework.boot:spring-boot-starter-jdbc'
}
application.yml에 아래의 코드를 추가한다.
spring:
...
jpa:
show-sql: true
hibernate:
ddl-auto: none
spring.jpa.show-sql을 true로 설정하면 JPA가 생성하는 SQL을 출력해준다.
spring.jpa.hibernate.ddl-auto는 none, create, create-drop 등이 있는데,
Entity를 테이블로 자동 생성해주는 기능을 켜고 끌 수 있다.
이제 Member 도메인을 JPA에서 사용할 수 있게 Entity로 등록해줘야 한다.
Member.java
package com.devjaewoo.hellospring.domain;
import javax.persistence.*;
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
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;
}
}
그다음 MemberRepository를 구현하는 JpaMemberRepository를 만들자.
JpaMemberRepository.java
package com.devjaewoo.hellospring.repository;
import com.devjaewoo.hellospring.domain.Member;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
public class JpaMemberRepository implements MemberRepository {
private final EntityManager entityManager;
public JpaMemberRepository(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Override
public Member save(Member member) {
return null;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.empty();
}
@Override
public Optional<Member> findByName(String name) {
return Optional.empty();
}
@Override
public List<Member> findAll() {
return null;
}
}
JPA는 Datasource가 아닌 EntityManager를 통해 DB와 연결한다.
이 EntityManager도 Datasource처럼 우리가 application.yml에 입력한 정보를 모아 의존성 주입을 해준다.
Repository에 대한 Bean이 선언되는 SpringConfig 클래스에서 EntityManager을 주입받고 JpaMemberRepository에 생성자로 넘겨주자.
SpringConfig.java
package com.devjaewoo.hellospring;
import com.devjaewoo.hellospring.repository.JdbcTemplateMemberRepository;
import com.devjaewoo.hellospring.repository.JpaMemberRepository;
import com.devjaewoo.hellospring.repository.MemberRepository;
import com.devjaewoo.hellospring.repository.MemoryMemberRepository;
import com.devjaewoo.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 DataSource dataSource;
private final EntityManager entityManager;
@Autowired
public SpringConfig(DataSource dataSource, EntityManager entityManager) {
this.dataSource = dataSource;
this.entityManager = entityManager;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
//return new MemoryMemberRepository();
//return new JdbcTemplateMemberRepository(dataSource);
return new JpaMemberRepository(entityManager);
}
}
이제 MemberRepository의 함수들을 구현하자.
package com.devjaewoo.hellospring.repository;
import com.devjaewoo.hellospring.domain.Member;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
public class JpaMemberRepository implements MemberRepository {
private final EntityManager entityManager;
public JpaMemberRepository(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Override
public Member save(Member member) {
entityManager.persist(member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
Member member = entityManager.find(Member.class, id);
return Optional.ofNullable(member);
}
@Override
public Optional<Member> findByName(String name) {
return entityManager.createQuery("SELECT m FROM Member m WHERE m.name = :name", Member.class)
.setParameter("name", name)
.getResultList()
.stream().findAny();
}
@Override
public List<Member> findAll() {
return entityManager.createQuery("SELECT m FROM Member m", Member.class).getResultList();
}
}
이전에 비해 확연히 코드 양이 줄어들었다.
JPA는 실제 SQL 쿼리가 아닌 JPQL이라는 객체를 대상으로 검색하는 쿼리를 쓴다고 한다.
findByName, findAll에 들어가는 쿼리를 보면 SELECT를 컬럼에 대해 하는 게 아니라 Member에 대해서 하는 것을 볼 수 있다.
또한 JPA는 INSERT, UPDATE가 항상 @Transaction 안에서 실행되어야 한다.
지금 코드 상에 @Transaction이 없으므로 Service에 추가해줘야 한다.
MemberService.java
@Transactional
public class MemberService {
...
}
테스트 결과
이제 이전에 작성했던 통합 테스트들을 다시 실행시켜보자.

잘 된다. Repository 구현 방식을 계속 바꾸고 있는데 한번 만들어놓은 테스트를 수정하지 않고 사용할 수 있다는 점에서 되게 생산성 있는 것 같다.
로그를 보면 SELECT, INSERT 구문을 볼 수 있고, Begin Transaction / Rolled Back Transaction 등의 로그도 볼 수 있다.

출처
'Study > Spring Boot' 카테고리의 다른 글
[Spring Boot] AOP 적용해보기 (0) | 2022.02.15 |
---|---|
[Spring Boot] Spring Data JPA 써보기 (0) | 2022.02.15 |
[Spring Boot] 스프링 통합 테스트 케이스 만들기 (0) | 2022.02.15 |
[Spring Boot] JdbcTemplate로 MemoryRepository 대체하기 (0) | 2022.02.14 |
[Spring Boot] h2 Database 설정하기 (0) | 2022.02.14 |