이번 시간에는 가볍고 심플한 H2 데이터베이스를 설치하여 서버와 DB를 연결하겠습니다.
개발이나 테스트 용도로 가볍고 편리한 DB, 웹 화면 제공
h2 데이터베이스는 꼭 다음 링크에 들어가서 1.4.200 버전을 설치해주세요.
최근에 나온 2.0.206 버전을 설치하면 일부 기능이 정상 동작하지 않습니다.
저는 윈도우 사용자이기 때문에 Windows Installer를 다운로드 했습니다.
다운로드 후 설치까지 진행하면 H2 콘솔로 이동할 수 있습니다.
데이터베이스 최초 파일을 생성하기 위해 그대로 연결을 한 번 시켜줍니다.
에러가 나신 분들은 IP를 localhost로 변경하시면 생성이 됩니다.
하지만 저는 생성이 되지 않아서 다음 링크를 보고 참고하여 직접 test.mv.db 파일을 생성하여 진행했습니다.
한 번 최초 실행을 했으면 그 후로부터는 jdbc:h2:tcp://localhost/~/test 로 연결해줍니다.
성공적으로 연결되면 이런 화면이 나타납니다.
create table member
(
id bigint generated by default as identity,
name varchar(255),
primary key (id)
);
id bigint generated by default as identity : id 값이 null로 들어왔을 때 자동으로 채워줍니다.
sql문을 작성한 후 '실행'을 누르면 테이블이 생성됩니다.
이제 데이터를 삽입해 봅시다.
insert into member(name) values('spring');
삽입 후 다시 조회해보면 잘 삽입한 것을 확인할 수 있습니다.
이제 회원 가입 웹과 연결을 해보겠습니다.
[ build.gradle 파일에 jdbc, h2 데이터베이스 관련 라이브러리 추가 ]
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
각각 JDBC 드라이버와 데이터베이스 클라이언트입니다.
[ 스프링 부트 데이터베이스 연결 설정 추가 ( resources/application.properties ) ]
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
추가한 후, 다시 gradle 파일로 가서
코끼리 아이콘을 눌러줍니다.
😺 : 스프링부트 2.4부터는 spring.datasource.username=sa 를 꼭 추가해주어야 합니다.
인텔리J 커뮤니티(무료) 버전의 경우 application.properties 파일의 왼쪽이 다음 그림과 같이 회색으로 나옵니다.
실제로 동작하는 데 문제 없습니다.
[ Jdbc 회원 리포지토리 ]
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.jdbc.datasource.DataSourceUtils;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class JdbcMemberRepository implements MemberRepository {
private final DataSource dataSource;
public JdbcMemberRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
@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();
pstmt = conn.prepareStatement(sql,
Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, member.getName());
pstmt.executeUpdate();
rs = pstmt.getGeneratedKeys();
if (rs.next()) {
member.setId(rs.getLong(1));
} else {
throw new SQLException("id 조회 실패");
}
return member;
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
@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();
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);
}
}
DataSource
데이터베이스 커넥션을 획득할 때 사용하는 객체입니다.
스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 DataSource를 생성하고 스프링 빈으로 만들어둡니다.
그래서 DI를 받을 수 있습니다.
[ SpringConfig 수정 ]
package hello.hellospring;
import hello.hellospring.repository.JdbcMemberRepository;
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 {
@Autowired
DataSource dataSource;
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new JdbcMemberRepository(dataSource);
// return new MemoryMemberRepository();
}
}
[ 구현 클래스 이미지 ]
[ 스프링 설정 이미지 ]
개방-폐쇄 원칙(OCP, Open-Closed Principle) : 확장에는 열려있고, 수정, 변경에는 닫혀있다.
스프링의 DI (Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스를 변경할 수 있습니다.
이제 회원을 등록하고 DB에 결과가 잘 입력되는지 확인하겠습니다.
😺 : 꼭 H2 콘솔 열어둔 상태로 실행하셔야 합니다!
[ 결과 ]
아까 넣었던 spring이 잘 출력되어 있습니다.
데이터를 DB에 저장하므로 스프링 서버를 다시 실행해도 데이터가 안전하게 저장됩니다.
학습중인 스프링 강의 : https://inf.run/pcut
스프링 실습 코드 저장소 : https://github.com/0pyaq0/Spring_Study.git
본 글은 2022년도에 작성한 기존 티스토리 블로그 글을 재업로드한 글입니다
기존 티스토리 블로그 : https://develop-about-leejin.tistory.com/