[스프링입문]Section6 - 스프링 DB 접근 기술

JiMin LEE·2022년 9월 29일

[Spring]입문

목록 보기
6/7
post-thumbnail

1. H2 데이터베이스 설치

💡 H2 Database 설치 & 사용하는 방법
  1. h2 database 다운로드 하기

  2. 터미널에서 h2/bin/ 으로 들어가기

  3. Mac은 ‘Chmod 755 h2.sh’ 명령을 통해 권한 설정하기

  4. ./h2.sh

  5. 웹페이지 통해 db 페이지 열림


  6. 1.처음 연결할 경우 아무것도 건들이지 말고 접속

  1. 이미 연결한 적 있을 경우

    이 경우 파일에 직접 접근하는 것이 아니라 socket을 통해 접근하게 됨


7 . Table 생성하기

create table member
(
  id   bigint generated by default as identity,
  name varchar(255),
  primary key (id)
);

8. Tuple(튜플) 추가하기
INSERT INTO member(name) values('spring')

3. 스프링 통합 테스트

💡

/test/…/service/MemberServiceIntegrationTest

package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

@SpringBootTest
@Transactional // test 파일에 넣어주면 테스트를 실행할 때 먼저 transaction을 실행하고 DB의 데이터를 insert query를 하고 다 넣은 다음에 테스트가 끝나면 롤백을 해준다.
              // -> DB에 넣었던 데이터가 다시 없어지게 된다.

class MemberServiceIntegrationTest {
    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    @Test
    public void 회원가입() throws Exception {

        //Given
        Member member = new Member();
        member.setName("hello");

        //When
        Long saveId = memberService.join(member);

        //Then
        Member findMember = memberRepository.findById(saveId).get();
        assertEquals(member.getName(), findMember.getName());
    }

    @Test
    public void 중복_회원_예외() throws Exception {

        //Given
        Member member1 = new Member();
        member1.setName("spring");
        Member member2 = new Member();
        member2.setName("spring");

        //When
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class,
                () -> memberService.join(member2));//예외가 발생해야 한다.

         assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
    }
}



@SpringBootTest : 스프링 컨테이너와 테스트를 함께 실행한다.

@Transactional : 테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 transaction을 시작하고, 테스트 완료 후에 항상 롤백한다. 이렇게 하면 DB에 데이터가 남지 않는다.

  • Transaction
    데이터베이스의 상태를 변화시키기 해서 수행하는 작업의 단위
    
    ex) 질의어
    SELECT
    INSERT
    DELETE
    UPDATE
    
    **작업의 단위 != 하나의 질의어**
    
    ex) 게시물을 올린 후 자신의 게시물이 업데이트된 게시판을 본다고 가정했을 때,
    		게시물 올리기(INSERT) + 게시물 목록(SELECT)가 합쳐진 하나의 작업 단위이다.
  • Transaction의 commit & rollback
    commit : 하나의 트랜잭션이 성공적으로 끝났고, 데이터베이스가 일관성있는 상태에 있을 때, 
    				 하나의 트랜잭션이 끝났다라는 것을 알려주기 위해 사용하는 연산이다.
    				- commit 할 시 수행했던 transaction이 로그에 저장되고, 후에 rollback
    					연산을 수행했었던 trancsaction 단위를 하는 것을 도와준다.
    
    rollback : 하나의 transaction 처리가 비정상적으로 종료되어 트랜잭션의 
    					원자성(트랜잭션이 데이터베이스에 모두 반영되던가, 아니면 전혀 반영되지 않아야 
    					한다는 것)이 깨진경우, 트랜잭션을 처음부터 다시 시작하거나, 트랜잭션의 부분적으로만 
    					연산된 결과를 다시 취소시킨다.


4. 스프링 JdbcTemplate

`/repository/JdbcTemplatesMemberRepository`
package hello.hellospring.repository;

import com.sun.source.tree.ReturnTree;
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 javax.sql.RowSet;
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 Optional<Member> findByName(String name) {
        List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name);
        return result.stream().findAny();
    }

    @Override
    public List<Member> findAll() {
        return jdbcTemplate.query("select * from member", memberRowMapper());
    }

    private RowMapper<Member> memberRowMapper(){
        return (rs, rowNum) -> {
            Member member = new Member();
            member.setId(rs.getLong("id"));
            member.setName(rs.getString("name"));
            return member;
        };
    }
}




/service/Springconfig.java

package hello.hellospring.service;
import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.JdbcTemplateMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
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.sql.DataSource;

@Configuration
public class SpringConfig {

    private DataSource dataSource;

    @Autowired
    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);
    }
}


5. JPA

💡 JPA
  • JPA를 사용하면 반복 코드, 기본적인 SQL도 직접 만들어서 실행해준다
  • JPA를 사용하려면 항상 transaction이 있어야 한다
    → memberservice에 @Transactional 추가하기

/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();
    }
}



/service/Springconfig.java

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

import javax.persistence.EntityManager;

@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 JdbcMemberRepository(dataSource);
//        return new JdbcTemplateMemberRepository(dataSource);
        return new JpaMemberRepository(em);
    }
}


6. 스프링 데이터 JPA

  • Repository에 구현 클래스 없이 Interface만으로 개발 완료 가능!
  • CRUD 기능도 제공한다~.ᐟ.ᐟ
  • 실무에서 관계형 데이터베이스를 사용한다면 JPA는 필수~.ᐟ.ᐟ
  • 스프링 데이터 JPA가 SpringDataJpaMemberRepository 를 스프링 빈으로 자동 등록 해준다.

/repository/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);
}



/service/Springconfig.java

package hello.hellospring.service;
import hello.hellospring.repository.*;
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);
    }

}

❗️스프링 데이터 JPA 제공 기능

1. Interface를 통한 기본적인 CRUD
2. findByName() , findByEmail() 처럼 메소드 이름만으로 조회 기능 제공
3. 페이징 기능 자동 제공

0개의 댓글