[Spring Boot] H2 데이터베이스 설치 1
Updated:
H2 데이터베이스 설치
jdbc : DB sql을 가지고
개발이나 텍스트 용도로 가볍고 편리한 DB, 웹 화면 제공
- 다운로드 및 설치
- h2 데이터베이스 버전은 스프링 부트 버전에 맞춘다.
- 권한 주기 : chmod 755 h2.sh
- 실행 : ./h2/sh
안될 때, 뒷부분(세션 키)은 절대 건들이지 말고 localhost로 바꿔주기
최초에는 데이터베이스 파일 생성 후 연결()
- 데이터베이스 파일 생성 방법
- jdbc:h2:~/test (최초한번)
- 이후부터는 jdbc:h2:tcp://localhost/~/test 로 접속
왼쪽 상단 아이콘을 통해 나가기
터미널에서 test.mv.db 가 생성 되었는지 확인!!
이 이후부터 접근 시,
jdbc:h2:tcp://localhost/~/test 로 접속 ➡️ 파일에 직접 접근하는 것이 아니라 소켓을 통해 접근, 이렇게 해야 여러곳에서 접근 가능
❗️ 문제가 있으면, 나와서 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 테이블 생성
MEMBER 테이블 조회
select * from member;
이름을 넣어보자
insert into member(name) values('spring')
insert into member(name) values('spring2')
조회 하면 pk 값인 id가 자동으로 생성된 것을 볼 수 있다.
❗️ ddl 관리를 위한 디렉토리와 파일 생성
❗️ 도중에 터미널에서 ./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 오류 발생
Jdbc 레포지토리 구현 src/main/java/com.example.memberproject/repository/ 에 JdbcMemberRepository.java 생성 후 코드 작성
MemberRepository를 implements 하고 메소드들도 구현
단축키 : 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 테이블에 넣었던 데이터가 잘 나온다!
회원 가입을 하고 목록 조회를 해보자
잘 나온다!!
DB Console 에서 확인해 봐도 잘 들어가 있음
설명
- 객체지향적인 설계가 왜 좋을까
- 다형성을 활용할 수 있음 ➡️ 인터페이스를 두고 구현체를 바꿔 끼우기 할 수 있다.
- 스프링을 왜 쓰냐 ➡️ 이런걸 스프링은 굉장히 편리하게 되도록 지원해줌, 스프링 컨테이너가 지원, DI 덕분에 굉장히 편리함
- 이번 실습에서 확인할 수 있듯이 기존의 코드는 하나도 수정하지 않고, 어플리케이션을 설정하는 코드만 수정하면 된다.
구현 클래스 추가 이미지
✔️ MemberService는 MemberRepository에 의존하고 있다.
✔️ MemberRepository는 구현체로 MemoryMemberRepository, JdbcMemberRepository가 있다.
스프링 설정 이미지
✔️ 기존에는 메모리 버전의 MemoryMemberRepository로 스프링 빈을 등록 했다면, 이번에는 MemoryMemberRepository를 빼고 jdbc 버전의 JdbcMemberRepository를 등록했다.
✔️ 나머지는 손 댈게 하나도 없음, 구현체만 JdbcMemberRepository로 바뀜
🟡🟡🟡
- 개방-폐쇄 원칙(OCP, Open-Closed Principle)
- 확장에는 열려있고, 수정, 변경에는 닫혀있다.
- 객체지향의 다형성 개념을 잘 활용하면 이렇게 기능을 완전히 변경해도 애플리케이션 전체를 수정할 필요가 없음
- 스프링의 DI(Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스를 변경할 수 있다.
- 회원을 등록하고 DB에 결과가 잘 입력되는지 확인하자
- 데이터를 DB에 저장하므로 스프링 서버를 다시 실행해도 데이터가 안전하게 저장된다.
Leave a comment