[Spring Boot] H2 데이터베이스 설치 1

Updated:

H2 데이터베이스 설치

jdbc : DB sql을 가지고

개발이나 텍스트 용도로 가볍고 편리한 DB, 웹 화면 제공

  • 다운로드 및 설치
  • h2 데이터베이스 버전은 스프링 부트 버전에 맞춘다.

Screen Shot 2022-04-11 at 5 14 42 PM

  • 권한 주기 : chmod 755 h2.sh
  • 실행 : ./h2/sh

Screen Shot 2022-04-11 at 5 16 56 PM

Screen Shot 2022-04-11 at 5 17 34 PM

안될 때, 뒷부분(세션 키)은 절대 건들이지 말고 localhost로 바꿔주기

최초에는 데이터베이스 파일 생성 후 연결()

  • 데이터베이스 파일 생성 방법
    • jdbc:h2:~/test (최초한번)
    • 이후부터는 jdbc:h2:tcp://localhost/~/test 로 접속

Screen Shot 2022-04-11 at 5 24 55 PM

Screen Shot 2022-04-11 at 5 24 11 PM

왼쪽 상단 아이콘을 통해 나가기

터미널에서 test.mv.db 가 생성 되었는지 확인!!

Screen Shot 2022-04-11 at 5 24 03 PM

이 이후부터 접근 시,

jdbc:h2:tcp://localhost/~/test 로 접속 ➡️ 파일에 직접 접근하는 것이 아니라 소켓을 통해 접근, 이렇게 해야 여러곳에서 접근 가능

Screen Shot 2022-04-11 at 5 28 53 PM

❗️ 문제가 있으면, 나와서 rm test.mv.db로 파일 지우고 서버 내리고 완전히 처음부터 다시

테이블 생성하기

테이블 관리를 위해 프로젝트 루트에 sql/ddl.sql 파일 생성

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

코드 작성 후 실행, 첫 줄은 필요 없으면 지워도 된다.

단축키 : command + enter : 실행

  • id = Java에서는 long, DB에서는 bigint
  • generated by default as identity : 값을 셋팅하지 않고 insert하면 db가 들어왔을 때, 자동으로 id 값을 채워줌
  • name : varchar
  • pk : id

MEMBER 테이블 생성

Screen Shot 2022-04-11 at 5 35 56 PM


MEMBER 테이블 조회

select * from member;

Screen Shot 2022-04-11 at 5 36 07 PM

이름을 넣어보자

insert into member(name) values('spring')
insert into member(name) values('spring2')

Screen Shot 2022-04-11 at 5 41 03 PM

조회 하면 pk 값인 id가 자동으로 생성된 것을 볼 수 있다.

Screen Shot 2022-04-11 at 5 41 59 PM

❗️ ddl 관리를 위한 디렉토리와 파일 생성

Screen Shot 2022-04-11 at 5 49 14 PM

❗️ 도중에 터미널에서 ./h2.sh 끄면 X

순수 JDBC

고대의 방식… 20년 전 방식! 편안하게 듣기

환경설정 build.gradle의 dependencies에 jdbc, h2 db 관련 라이브러리를 추가

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

스프링부트 데이터베이스 연결 설정 추가

src/main/resources/application.properties

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

빨간줄이 뜨면 build.gradle에서 코끼리 눌러주기

❗️ 스프링부트 2.4 부터 spring.datasource.username=sa를 추가 하지 않으면 Wrong user name or password 오류 발생

Screen Shot 2022-04-11 at 7 08 52 PM

Jdbc 레포지토리 구현 src/main/java/com.example.memberproject/repository/ 에 JdbcMemberRepository.java 생성 후 코드 작성

MemberRepository를 implements 하고 메소드들도 구현

Screen Shot 2022-04-11 at 6 05 09 PM

단축키 : option + command + L : 자동 정렬

JdbcMemberRepository.java

package com.example.memberproject.repository;

import com.example.memberproject.domain.Member;

import java.util.List;
import java.util.Optional;

import org.springframework.jdbc.datasource.DataSourceUtils;

import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;

// db로 연동해서 jdbc로 할거야
public class JdbcMemberRepository implements MemberRepository{

    private final DataSource dataSource; // db에 붙기 위해

    public JdbcMemberRepository(DataSource dataSource) { // spring 한테 주입 받아야 함
        this.dataSource = dataSource;
        // dataSource.getConnection(); // db 커넥션을 얻을 수 있음
    }
    @Override
    public Member save(Member member) {
        String sql = "insert into member(name) values(?)";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null; // 결과를 받는 변수

        try {
            conn = getConnection(); // connection 가져오기
            pstmt = conn.prepareStatement(sql,
                    Statement.RETURN_GENERATED_KEYS); // db에 insert 하면 1,2 ,... id 값을 얻을 수 있었다
            pstmt.setString(1, member.getName());
            pstmt.executeUpdate(); // db에 실제 쿼리 값이 날아감
            rs = pstmt.getGeneratedKeys(); // 33번 줄이랑 매칭, key가 1이면 1반환, 2면 2반환
            if (rs.next()) { // re에 값이 있음
                member.setId(rs.getLong(1)); // 값이 있으면 값을 꺼냄
            } else {
                throw new SQLException("id 조회 실패");
            }
            return member;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs); // 자원 release 해줘야 함
        }
    }

    @Override
    public Optional<Member> findById(Long id) { // 조회
        String sql = "select * from member where id = ?";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setLong(1, id);
            rs = pstmt.executeQuery(); // 조회는 executeQuery

            if (rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return Optional.of(member);
            } else {
                return Optional.empty();
            }
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public List<Member> findAll() {
        String sql = "select * from member";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery();
            List<Member> members = new ArrayList<>();
            while (rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                members.add(member);
            }

            return members;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public Optional<Member> findByName(String name) {
        String sql = "select * from member where name = ?";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, name);
            rs = pstmt.executeQuery();
            if (rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return Optional.of(member);
            }
            return Optional.empty();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource); // 
    }

    private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (pstmt != null) {
                pstmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null) {
                close(conn);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void close(Connection conn) throws SQLException {
        DataSourceUtils.releaseConnection(conn, dataSource);
    }
}


SpringConfig.java

package com.example.memberproject;

import com.example.memberproject.repository.JdbcMemberRepository;
import com.example.memberproject.repository.MemberRepository;
import com.example.memberproject.repository.MemoryMemberRepository;
import com.example.memberproject.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 {

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

➡️ JdbcMemberRepository 라는 클래스를 만들고 인터이스를 구현체를 만들어 확장했음

➡️ 어떤 코드도 손 대지 않고 SpringConfig만 수정

➡️ DataSource는 데이터베이스 커넥션을 획득할 때 사용하는 객체다. 스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 DataSource를 생성하고 스프링 빈으로 만들어둔다.

➡️ 그래서 DI를 받는 것이 가능

❗️주의 : h2 db 실행 해놔야함


결과

h2 DB member 테이블에 넣었던 데이터가 잘 나온다!

Screen Shot 2022-04-11 at 7 09 40 PM

회원 가입을 하고 목록 조회를 해보자

Screen Shot 2022-04-11 at 7 10 33 PM

잘 나온다!!

Screen Shot 2022-04-11 at 7 11 02 PM

DB Console 에서 확인해 봐도 잘 들어가 있음

Screen Shot 2022-04-11 at 7 11 39 PM

설명

  • 객체지향적인 설계가 왜 좋을까
  • 다형성을 활용할 수 있음 ➡️ 인터페이스를 두고 구현체를 바꿔 끼우기 할 수 있다.
  • 스프링을 왜 쓰냐 ➡️ 이런걸 스프링은 굉장히 편리하게 되도록 지원해줌, 스프링 컨테이너가 지원, DI 덕분에 굉장히 편리함
  • 이번 실습에서 확인할 수 있듯이 기존의 코드는 하나도 수정하지 않고, 어플리케이션을 설정하는 코드만 수정하면 된다.


구현 클래스 추가 이미지

image

✔️ MemberService는 MemberRepository에 의존하고 있다.

✔️ MemberRepository는 구현체로 MemoryMemberRepository, JdbcMemberRepository가 있다.


스프링 설정 이미지

Screen Shot 2022-04-11 at 7 19 14 PM

✔️ 기존에는 메모리 버전의 MemoryMemberRepository로 스프링 빈을 등록 했다면, 이번에는 MemoryMemberRepository를 빼고 jdbc 버전의 JdbcMemberRepository를 등록했다.

✔️ 나머지는 손 댈게 하나도 없음, 구현체만 JdbcMemberRepository로 바뀜

🟡🟡🟡

  • 개방-폐쇄 원칙(OCP, Open-Closed Principle)
    • 확장에는 열려있고, 수정, 변경에는 닫혀있다.
  • 객체지향의 다형성 개념을 잘 활용하면 이렇게 기능을 완전히 변경해도 애플리케이션 전체를 수정할 필요가 없음
  • 스프링의 DI(Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스를 변경할 수 있다.
  • 회원을 등록하고 DB에 결과가 잘 입력되는지 확인하자
  • 데이터를 DB에 저장하므로 스프링 서버를 다시 실행해도 데이터가 안전하게 저장된다.

참고

스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술

YoonkyungH.log

Leave a comment