[Spring] 스프링 DB 접근기술 - 2편

Gerry·2022년 4월 30일
0

스프링 입문

목록 보기
8/9
post-thumbnail

스프링 JdbcTemplate

이전 포스트에서 저희는 순수 Jdbc만을 이용해 H2 데이터베이스와 프로젝트를 연동하였습니다.
이번 포스트에서는 JdbcTemplate를 이용해서 좀더 적은 양의 코드로 데이터베이스 연동을 진행해보겠습니다.

JdbcTemplate이란?

  • 기존의 Jdbc는 DB 연동에 필요한 Connection 객체, 쿼리 실행을 위한 PreparedStatement 객체를 생성하고 쿼리 실행 후에는 finally 블록에서 ResultSet , PreparedStatement , Connection 타입 객체들을 close() 메서드로 닫는 구조적인 반복이 존재했습니다.
  • 이런 구조적인 반복을 줄이기 위해서 템플릿 메서드 패턴과 전략 패턴을 함께 사용하는 API가 JdbcTemplate입니다.
    -출처: Jae Honey 티스토리

💻 JdbcTemplate 회원 리포지토리 작성

JdbcTemplate를 이용한 방식은 순수 Jdbc만을 이용했을때와 동일한 환경설정으로 진행하면 됩니다.
build.gradle 파일은 수정하지 않은 상태로 /repository 하위에 JdbcTemplateMemberRepository 를 생성해서 다음과 같이 작성합니다.

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

💻 JdbcTemplate으로 설정 변경

JdbcTemplateMemberRepository 라는 새로운 리포지토리를 작성하였기 때문에 SpringConfig 파일을 아래와 같이 수정해야 합니다.

@Configuration
  public class SpringConfig {
  
      private final DataSource dataSource;
  
      public SpringConfig(DataSource dataSource) {
          this.dataSource = dataSource;
	  }
  
      @Bean
      public MemberService memberService() {
          return new MemberService(memberRepository());
      }
  
      @Bean
      public MemberRepository memberRepository() {
        //return new MemoryMemberRepository();
        //return new JdbcMemberRepository(dataSource);
        return new JdbcTemplateMemberRepository(dataSource);
      }
  • 이전과 같이 기존의 JdbcMemberRepository 로 연결된 부분을 주석처리하고 JdbcTemplateMemberRepository(dataSource) 를 넣어줍니다.

👉 스프링 통합테스트 진행

H2 데이터베이스를 켜둔 상태로 이전에 작성해두었던 스프링 통합테스트를 진행하면 다음과 같이 테스트를 통과하는걸 확인할 수 있습니다.



JPA

위에서의 JdbcTemplate을 이용한 방법은 코드의 양은 줄일 수 있었지만 결국, SQL문은 직접 작성해줘야 했습니다.
하지만 JPA를 사용하면 기존의 반복 코드는 물론이고, SQL도 JPA가 직접 만들어서 실행해줍니다.
이번에는 이 JPA를 사용해 데이터베이스와 연동을 시켜보겠습니다.

JPA란?

  • Java 진영에서 ORM 기술 표준으로 사용하는 인터페이스 모음
  • 자바 애플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스
  • 인터페이스임으로 Hibernate, OpenJPA 등이 JPA를 구현함
    -출처: 티스토리 - 어제보다 더 나은 내일을

💻 JPA 관련 라이브러리 추가

build.gradle 파일에 기존의 Jdbc 라이브러리를 주석처리하고 아래의 코드를 작성해 JPA 라이브러리를 추가해줍니다.

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  • pring-boot-starter-data-jpa 는 내부에 Jdbc 관련 라이브러리를 포함하고 있습니다.

💻 스프링부트에 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 : create 로 설정하면 엔티티 정보를 바탕으로 테이블도 자동으로 생성해줍니다.

💻 JPA 엔티티 매핑

JPA는 애플리케이션 클래스와 관계형 데이터베이스의 테이블을 매핑해줘야되므로 /domain/Member 파일에 다음과 같은 어노테이션을 작성해줍니다.

@Entity
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 어노테이션을 작성해줌으로써 JPA가 매핑하는 엔티티가 됩니다.
  • DB가 알아서 ID를 생성해주는 방식을 IDENTITY 전략이라고 부릅니다.

💻 JPA 회원 리포지토리 작성

/repository 하위에 JpaMemberRepository 를 만들어 다음과 같이 작성해줍니다.

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 가 생성되고 이를 주입받아서 사용하게됩니다.
  • findByNamefindAll 은 위와 같이 JPQL이라는 객체지향 쿼리문으로 작성해줘야합니다. (엔티티를 대상으로 쿼리를 날림)

💻 서비스 계층에 트랙잭션 추가

JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 하므로 MemberService 에 아래와 같이 @Transactional 어노테이션을 작성해줍니다.

import org.springframework.transaction.annotation.Transactional

@Transactional
public class MemberService {
	...
}

💻 JPA로 설정 변경

JdbcTemplateMemberRepository 에서 JpaMemberRepository 로 아래와 같이 SpringConfig 설정을 변경해줍니다.

@Configuration
public class SpringConfig {

    private EntityManager em;

    public SpringConfig(EntityManager em) {
        this.em = em;
    }

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

    @Bean
    public MemberRepository memberRepository() {
		//return new MemoryMemberRepository();
		//return new JdbcTemplateMemberRepository(dataSource);
        return new JpaMemberRepository(em);
    }
}
  • 엔티티 관리자를 주입시켜줍니다.

👉 스프링 통합테스트 진행

스프링 통합테스트를 진행하면 다음과 같은 결과를 확인할 수 있습니다.



스프링 데이터 JPA

스프링 데이터 JPA를 사용하면 기존의 방식을 넘어서서 인터페이스만으로 개발을 완료할 수 있게 해줍니다.
실무에서는 JPA와 스프링 데이터 JPA를 기본으로 사용하고, 복잡한 동적 쿼리는 Querydsl 라이브러리를 사용해 개발합니다.

스프링 데이터 JPA

  • 스프링 프레임워크에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트입니다.
  • CRUD 처리를 위한 공통 인터페이스 제공합니다.
  • 리포지토리 개발 시 인터페이스만 작성하면 실행 시점에 스프링 데이터 JPA가 구현 객체를 동적으로 생성해서 주입해줍니다.
    -출처: 티스토리 - Aaron

👀 스프링 데이터 JPA 제공 클래스


💻 스프링 데이터 JPA 회원 리포지토리 작성

스프링 데이터 JPA는 인터페이스만으로 리포지토리 개발이 가능하기때문에 /repository 하위에 SpringDataJpaMemberRepository 라는 인터페이스를 생성하고 다음과 같이 작성해줍니다.

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

    @Override
    Optional<Member> findByName(String name);
}
  • 인터페이스가 인테페이스를 상속받을때는 extends 를 사용하며 JpaRepositoryMemberRepository 를 다중상속받습니다.

💻 스프링 데이터 JPA로 설정 변경

SpringConfig 파일을 아래와 같이 변경해줍니다.

@Configuration
public class SpringConfig {

    private final MemberRepository memberRepository;

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

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository);
    }
}
  • SpringDataJpaMemberRepository 는 스프링 데이터 JPA가 스프링 빈으로 자동 등록해줍니다.

👉 스프링 통합테스트 진행

스프링 데이터 JPA를 이용해 데이터베이스와 연결한 방식도 테스트를 진행하면 다음과 같이 테스트를 통과하는걸 확인할 수 있습니다.

🙏 이 포스트는 김영한 개발자님의 <스프링 입문 강의> 를 듣고 공부한 내용을 바탕으로 작성되었습니다.

1개의 댓글

comment-user-thumbnail
2022년 9월 2일

C😊😊L

답글 달기