공부/Spring - 김영한

[ spring ] 예제 - 회원관리 : 스프링DB 접근기술

haena02 2023. 2. 8. 16:40
반응형

서버를 껐다 켜도 데이터들이 저장되기 위해서는 데이터베이스를 활용해야한다.

 

먼저 가장 간단한 H2데이터베이스를 활용해보자

실무에서는 mySQL이나 오라클을 많이 사용한다. 

H2는 교육용으로 사용하기 좋은 가벼운 프로그램이다. 

 

1. H2 데이터베이스 설치

 

다운로드 화면

나는 1.4.200 의 window버전을 다운받았다!

모두다 다운을 받고 consol를 실행시켜 보면 아래와 같은 화면이 뜬다!

H2콘솔

경로를 잘 확인하고 >>연결<<을 누르면!

 

H2

짠 하고 멋있는 화면이 뜬다

그러고 내 파일에 "~/test.mv.db" 라는 파일이 있는지 확인해준다.

난 잘 있었다. 

그 이후로 부터는 연결하기 전에 경로를 "jdbc:h2:tcp://localhost/~/test"으로 바꿔주고 연결해야 파일에 접근할 때

소켓을 통해 접급하게 되기 때문에 여러군데에서 접근할 수 있다. 

 

* 혹시 앞으로 진행하다가 문제가 생기면

"~/test.mv.db" 를 삭제하고 h2.sh 서버를 내리고 아예 처음했던 것부터 다시 시작하며된다. 

 

2. 테이블 만들기

 

연결을 마쳤으니 테이블을 만들어보자

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

 위 코드를 치고 ctrl + enter를 치면 멤버 테이블이 생긴다. 

Member테이블 생성

전에 만들었던 member class를 보면 id와 member변수가 있다.

그래서 데이터 베이스에도 id와 name을 만들어주었다. 

 

bigint generated by default as identity라는 구문의 의미는 아무값도 안들어왔을 때 알아서 값을 채워주라는 의미이다. 

 

데이터관리를 위해 프로젝트에 sql 파일을 만들자.

sql관리

 

3. Jdbc 환경설정

 

이제는 프로그램에서 데이터가 저장이되면

데이터베이스에 쿼리문을 날려서 데이터가 데이터베이스에 저장이되도록 해보자

 

순수 Jdbc은 아주 고대의 방법이고 매우 힘든방법이다. 

하지만 요즘에는 템플릿이 나와서  이를 많이 쓴다!

 

먼저 환결설정을 해보자!

 

1. build.gradle 파일에 jdbc, h2 데이터베이스 관련 라이브러리 추가해야한다. 

implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'

 

2. 스프링 부트 데이터베이스 연결 설정 추가해야한다. 

 

'resources/application.properties'에 아래와 같은 코드를 추가해주자.

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa

맨 윗줄은 우리가 콘솔에서 설정해준 경로를 적어주면된다.

두번 째 줄은 드라이버를 설정해주면 된다 . 우리는 H2를 사용하니까 h2.Driver로 해주자

세번째 줄에는 유저 네임을 적어준다. 

 

이렇게 기본적인 설정은 마쳤다!!

 

4. Jdbc 템플릿 사용하기

 

이 템플릿은 순수 Jdbc에 비해 반복코드를 많이 제거해준다.

하지만 SQL은 직접 작성해야한다.

 

아래 코드는 jdbc 템플릿을 이용하여 리포지토리를 만든 것이다. 

package hello.hellospring.repository;

import hello.hellospring.domain.member;

import org.springframework.beans.factory.annotation.Autowired;
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 JdbcMemberRepository implements MemberRepository{

    private final JdbcTemplate jdbcTemplate;

    @Autowired //생성자가 하나일 때는 생략가능
    public JdbcMemberRepository(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 number 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 new RowMapper<member>(){
            @Override
            public member mapRow(ResultSet rs, int rowNum) throws SQLException {
                member member = new member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return member;
            }
        };
    }
}

 

이를 연결해주기 위해 config파일도 아래와 같이 수정해주자!

 

//자바코드로 스프링빈 등록하기
package hello.hellospring.service;

import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@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 JdbcMemberRepository(dataSource);
    }
}

 

5. 테스트 케이스 만들어서 테스트 해보기

 

이제는 DB까지 다 연결해서 테스트하는 통합테스트를 진행해야한다. 

 

이전까지한 테스트는 순수 자바로만 한 테스트였다.

하지만 DB가 들어온 이상 그럴 수 없다! 

 

이제는 객체를 직접 만들고 테스트하지 않고

스프링 컨테이너에게 멤버서비스, 멤버리포지토리 내놔!해야한다.

 

그전에 만들어놨던 @BeforeEach와 @AfterEach도 다 필요 없어졌다. 

@ Transactional 를 추가하면, 테스트 시작전에 트랜잭션을 시작하고 테스트 완료 후에 항상 롤백한다.

이렇게 하면 DB에 데이터가 남지 않아서 다음테스트 할 때 용이하다!

이는 @test와 함께 있을 때만 이렇게 동작한다. 

 

그렇다면 편리한 스프링 컨데이너를 이용해서 테스트를 하면 되지 않을까?

하지만 이는 매우 느리다! 

순수 자바 코드로 단위테스트를 진행하는 것이 가장 좋은 테스트라고 볼 수 있다. 

반응형