이 포스트는 김영한 이사님의 스프링 입문 강의를 듣고 작성하였습니다.
이제 드디어 JPA를 이용하여 MemberRepository를 구현할 차례이다.
강의에서 구글 트렌트를 이용하여 Mybatis와 JPA를 비교하여 설명을 하여서 강의를 듣고 정리하는 이 시점에서 다시 찾아보았다.
확실히 JPA의 빈도수가 높은 것을 확인할 수 있다.
JPA의 큰 특징은 다음과 같다.
JPA의 특징
JPA는 기존의 반복 코드는 물론이고, 기본적인 SQL도JPA가 직접 만들어서 실행해준다.JPA를 사용하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환을 할 수 있다.JPA를 사용하면 개발 생산성을 크게 높일 수 있다.
사실 위 특징은 JPA만의 특성이기 보다는 ORM의 특성이라고 보는 것이 맞는 것 같다. ORM에 관한 간단한 정보는 이 글을 참고하자.
우선 build.gradle파일의 dependencies에 다음과 같이 추가를 해주어야 한다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
//implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
}
이렇게 하고 우측 상단의 코끼리 모양을 눌러주자. 이 세팅은 전에 했던 과정과 동일하다.
그 다음 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=create
show-sql을 true로 하면 jpa에서 만든 쿼리를 보여주게 되며 hibernate.ddl-auto를 create로 하면 테이블도 자동으로 생성을 해준다.
참고로 강의에서는 기존에 테이블이 존재하기 때문에 none으로 하고 진행을 하였지만 나같은 경우는 테이블이 직접 생성되는 것을 보고 싶어서 테이블을 삭제하고 다시 진행할 예정이다.
Externel Libraries에서 jpa와 hibernate가 들어가있는 것을 확인할 수 있다. jpa는 자바진영의 표준 인터페이스이고 hibernate는 그 구현체이다.
package memberpractice.memberpractice.domain;
import jakarta.persistence.*;
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
위의 코드를 보면 기존에 우리가 작성했던 부분에서 3개의 어노테이션이 추가된 것을 확인할 수 있다. 이것에 대한 정보는 이 글을 참고하자.
간단하게 얘기하자면 클래스에 @Entity를 붙이면 JPA가 관리하는 엔티티가 되며 @Id와 @GeneratedValue는 테이블의 pk에 관한 어노테이션이다.
이제 우리가 Entity로 지정한 Member를 활용하여 JpaMemberRepository를 구현하여 보자.
package memberpractice.memberpractice.repository;
import jakarta.persistence.EntityManager;
import memberpractice.memberpractice.domain.Member;
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();
}
}
Jpa를 사용하려면 EntityManager를 주입받아서 사용하여야 한다.
이때 EntityManager는 앞에서 우리가 data-jpa라이브러리를 받았을 때 스프링부트에서 자동적으로 생성해준 것이다.
위 코드를 보면 save와 findById같은 경우는 EntityManager에서 기본제공되는 메소드로도 바로 간편하게 구현된 것을 확인할 수 있다.
findByName이나 findAll같은 기능은 순수한 쿼리문이 아닌 jpql이라는 것을 활용하여 구현하였는데 이를 통해 복잡한 조회기능도 구현할 수 있게 된다. 자세한 정보는 이 글을 참고하자.
package memberpractice.memberpractice.service;
import memberpractice.memberpractice.domain.Member;
import memberpractice.memberpractice.repository.MemberRepository;
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 {
.
.
.
}
Jpa를 사용하여 데이터를 저장하거나 변경할때는 @Transactional을 붙여주어야 한다.
package memberpractice.memberpractice;
import jakarta.persistence.EntityManager;
import memberpractice.memberpractice.repository.JdbcTemplateMemberRepository;
import memberpractice.memberpractice.repository.JpaMemberRepository;
import memberpractice.memberpractice.repository.MemberRepository;
import memberpractice.memberpractice.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig {
// private final DataSource dataSource;
//
// @Autowired
// public SpringConfig(DataSource dataSource) {
// this.dataSource = dataSource;
// }
private final EntityManager em;
@Autowired
public SpringConfig(EntityManager em) {
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);
}
}
그 후 SpringConfig에서 EntityManager를 주입받고 JpaMemberRepository로 넘겨주자.
이제 우리가 전에 작성한 스프링 통합테스트로 실행을 해보자.
회원 가입이 정상적으로 실행된 것을 확인할 수 있다. 또한 블록친 부분에서 실제로 DB에 전송된 쿼리를 확인할 수 있다.
또한, 테이블도 정상적으로 생성된 것을 확인할 수 있다.
테스트에 @Commit어노테이션도 붙여서 실제 DB에 반영시켜보자.
DB에 정상적으로 잘 반영된 것을 확인할 수 있다.
전체 테스트도 성공적으로 동작한 것을 확인할 수 있다.
기존에도 장고를 통해 ORM에 대해서 접해보고 다뤄봤었지만 JPA는 이번에 처음 다뤄보았었다. 실제 프로젝트에서 유연하게 쓰려면 앞으로 JPA에 대해서도 공부를 많이 해야겠다. 이론적인 부분에 대해서도 이번 스프링 입문 강의가 끝나고나면 자세히 다뤄보고 싶다.