본문 바로가기
Study/Spring Boot

[Spring Boot] JPA로 JdbcTemplate 대체하기

by DevJaewoo 2022. 2. 15.
반응형

Spring LOGO

Intro

JPAJava 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-sqltrue로 설정하면 JPA가 생성하는 SQL을 출력해준다.

spring.jpa.hibernate.ddl-autonone, 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;
    }
}

 

JPADatasource가 아닌 EntityManager를 통해 DB와 연결한다.

EntityManagerDatasource처럼 우리가 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에 대해서 하는 것을 볼 수 있다.

 

또한 JPAINSERT, UPDATE가 항상 @Transaction 안에서 실행되어야 한다.

지금 코드 상에 @Transaction이 없으므로 Service에 추가해줘야 한다.

 

MemberService.java

@Transactional
public class MemberService {
	...
}

테스트 결과

이제 이전에 작성했던 통합 테스트들을 다시 실행시켜보자.

테스트 성공

 

잘 된다. Repository 구현 방식을 계속 바꾸고 있는데 한번 만들어놓은 테스트를 수정하지 않고 사용할 수 있다는 점에서 되게 생산성 있는 것 같다.

 

로그를 보면 SELECT, INSERT 구문을 볼 수 있고, Begin Transaction / Rolled Back Transaction 등의 로그도 볼 수 있다.

테스트 로그


출처

반응형