Spring 1-7. 스프링 DB 접근 기술 (2)

JG's Development Blog·2023년 1월 31일
0

Spring

목록 보기
9/10

스프링 JdbcTemplate

  • 스프링에서 제공하는 이 라이브러리는 JDBC API에서 본 반복적인 코드를 대부분 제거해준다.
  • 그러나 SQL은 직접 작성해야 한다.
  • 현재 실무에서도 종종 사용한다.
  • JdbcTemplateMemberRepository 라는 새로운 repository를 만들고 다음 코드를 넣는다.
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class JdbcTemplateMemberRepository implements MemberRepository {
 private final JdbcTemplate jdbcTemplate;
 public JdbcTemplateMemberRepository(DataSource dataSource) {
 jdbcTemplate = new JdbcTemplate(dataSource);
 }
 @Override
 public Member save(Member member) {
 SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
 jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
 Map<String, Object> parameters = new HashMap<>();
 parameters.put("name", member.getName());
 Number key = jdbcInsert.executeAndReturnKey(new
MapSqlParameterSource(parameters));
 member.setId(key.longValue());
 return member;
 }
 @Override
 public Optional<Member> findById(Long id) {
 List<Member> result = jdbcTemplate.query("select * from member where id 
= ?", memberRowMapper(), id);
 return result.stream().findAny();
 }
 @Override
 public List<Member> findAll() {
 return jdbcTemplate.query("select * from member", memberRowMapper());
 }
 @Override
 public Optional<Member> findByName(String name) {
 List<Member> result = jdbcTemplate.query("select * from member where 
name = ?", memberRowMapper(), name);
 return result.stream().findAny();
 }
 private RowMapper<Member> memberRowMapper() {
 return (rs, rowNum) -> {
 Member member = new Member();
 member.setId(rs.getLong("id"));
 member.setName(rs.getString("name"));
 return member;
 };
 }
}
  • 생성자가 한개라면 @Autowired 생략이 가능하다.(위 코드에서도 생략 가능)
  • jdbcMemberRepository와 비교를 해보면 findById 함수가 memberRowMapper를 이용하여 두줄만에 짜여진 것을 볼 수 있다.
  • save는 jdbcTemplate을 인자로 받는 SimpleJdbcInsert를 이용하여 query를 짤 필요 없이 쉽게 구현이 되었다.
  • 현재는 자세한 설명 없이 전체적인 흐름만 보았지만 추후에 jdbcTemplate의 다양한 기능들을 배울 예정이다.

  • 구현한 jdbcTemplate 연결(다음 코드 변경)
    @Bean
    public MemberRepository memberRepository(){
        //return new MemoryMemberRepository();
        //return new JdbcMemberRepository(dataSource);
        return new JdbcTemplateMemberRepository(dataSource);
    }
  • 테스트 파일을 돌려보면 오류 없이 잘 작동한다.

JPA

  • JPA는 기존의 반복 코드는 물론, 기본적인 SQL도 만들어주기 때문에 개발 생산성을 크게 향상시킬 수 있다.
  • JPA는 Spring 만큼 기술적인 깊이도 깊고 스프링 자체 환경에서 JPA를 기본으로 하는 경우가 많기 때문에 배우면 도움이 될 것이다.
    (JPA 또한 굉장히 방대하다.)

  • JPA를 사용하기 위해 build.gradle에 다음 이미지처럼 ---data-jpa를 추가하고 gradle refresh 버튼을 눌러준다.(코끼리 버튼)

  • application.properties에 다음을 추가한다.

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
  • show-sql=true
    jpa가 날린 코드들을 볼 수 있게 해준다.
  • hibernate.ddl-auto=none
    jpa는 회원이라는 객체를 인식하여 자동으로 테이블을 만들어주는데 우리는 만들어놓은 테이블을 이용할 것이기 때문에 위 기능을 꺼준다.
  • JPA는 결국 인터페이스와 같아서 여러 업체에서 구현체를 만들어 연결을 시키는 형태라고 볼 수 있다.
    우리는 JPA의 hibernate 기능을 쓸 것이다.

  • 도메인 파일의 Member 클래스에 @Entity를 붙여 이 클래스는 JPA가 관리한다라는 표시를 해준다.

  • DB에 insert into member(name) values('spring1'); 를 실행할 때 id값을 우리가 설정하는 것이 아닌 DB에서 자동으로 생성해주는데 이것을 IDENTITY 전략이라고 한다.
    -> id 위쪽에 이 기능을 추가해준다.


  • JPA는 EntityManager로 모든 것을 관리한다.
  • JPA를 이용한 새로운 repository JpaMemberRepository 를 생성하고 다음 코드를 넣는다.
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();
    }
}

단축키 팁
windows 기준 ctrl+alt+n을 누르면 하나의 라인으로 합쳐진다.
ex)

List<Member> result = parallel;
 return result
 ->
 return parallel;

※ JPA를 사용할 때 항상 @Transactional이 있어야 한다.
-> MemberService에 @Transactional을 넣어준다.

  • SpringConfig에 JpaMemberRepository 를 연결시켜 준다.
    원래 있던 DataSource 대신 EntityManager를 만들어 넣어준다.
package hello.hellospring;

import hello.hellospring.repository.*;
import hello.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 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);
    }
}
  • 확인하기 위해 MemberServiceIntegrationTest의 회원가입 부분만 실행시켜 본다.
  • @Transactional은 실행 후 롤백시켜주므로 @Commit을 대신 넣어주면 DB에 값이 추가되는 것을 확인할 수 있다.

스프링 데이터 JPA

  • 스프링 데이터 JPA를 사용하면 리포지토리에 구현 클래스 없이 인터ㅔ이스 만으로 개발을 할 수 있게 된다.

※ 스프링 데이터 JPA는 JPA를 편리하게 사용할 수 있도록 도와주는 도구이기 때문에 반드시 JPA 선행학습이 필요하다.


  • 리포지토리에 SpringDataJpaMemberRepository 인터페이스를 생성하고 다음 코드를 넣는다.
package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {

    @Override
    Optional<Member> findByName(String name);
}

인터페이스가 다른 인터페이스를 가져올 때는 implements가 아닌 extends이다.

  • 스프링이 JpaRepository를 발견하면 spring bean에 자동으로 등록해준다.
    -> 구현체를 만들어서 등록해준다.

  • springconfig 파일을 다음과 같이 변경해준다.

package hello.hellospring;

import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {

    private final MemberRepository memberRepository;

    public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Bean
    public MemberService memberService()
    {
        return new MemberService(memberRepository);
    }

}
  • 서비스 통합테스트를 돌려보면 정상적으로 작동이 되는 것을 확인할 수 있다.

  • 기본적인 save(), findAll() 함수 등은 스프링 데이터 JPA에서 제공을 해주기 때문에 따로 짤 필요가 없다.

  • 위와 같은 공통적인 부분은 쓸 수 있지만 비지니스 모델이 다른 경우 등에는 따로 써야한다.
    이것은 스프링 데이터 JPA 리포지토리에 findByName을 통해 확인할 수 있다.

  • 실무에서는 대부분 JPA, 스프링 데이터 JPA, Querydsl(복잡한 동적 쿼리 처리 라이브러리) 의 조합으로 사용한다.

  • 이 조합으로 해결하기 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리를 사용하거나, 스프링 JdbcTemplate를 사용하면 된다.

강의 사이트
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8

profile
왕왕왕초보

0개의 댓글