반응형
이번에는 이제 메모리 객체에 저장하지 않고, JDBC를 사용해 진짜 어플리케이션과 DB를 연동해서 직접 DB에 저장하는 방법을 알아보겠습니다.
스프링 DB 접근 기술에 대해 알아보자
순수 JDBC
일단 시작하기 전에 관련 설정을 하고 넘어가도록 하겠습니다.
1. build.gradle 파일에 jdbc, h2 데이터베이스 관련 라이브러리 관련 코드를 dependencies에 추가합니다.
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
2. 그리고 스프링 부트 데이터베이스 연결 설정을 application.properties에 추가합니다.
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
- 스프링 부트 2.4부터는 spring.datasource.username=sa를 꼭 추가해주어야 합니다. 그렇지 않으면 Wrong user name or password 오류가 발생합니다. 그리고 마지막에 공백이 들어가면 같은 오류가 발생하기 때문에, 작성한 코드들 사이의 공백은 모두 제거해야 합니다.
추가로, 인텔리 J 커뮤니티(무료) 버전의 경우 application.properties 파일의 왼쪽이 회색으로 나오는데, 이는 엔터프라이즈(유료) 버전에서 제공하는 스프링의 소스 코드를 연결해주는 편의 기능이 빠진 것으로, 실제 동작하는 데는 아무런 문제가 없습니다.
3. JDBC 리포지토리 구현하기
회원 리포지토리를 JDBC API로 직접 작성해 보겠습니다. (이렇게 직접 코딩하는 것은 오래된 이야기로, 참고만 하고 넘어가도 무방합니다. 그러나 테스트를 위해서는 필요.)
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);
}
}
4. 스프링 설정 변경하기
기존에 실제 데이터베이스를 대신해 서버가 기동된 경우에만 MemoryMemberRepository를 일종의 가상(?) 데이터베이스로 사용했었습니다. 이제 H2 데이터베이스 및 JDBC 관련 설정을 통해 실제로 설치한 H2 데이터베이스를 사용할 것입니다. MemberRepository의 빈을 생성할 때, 이제 JdbcMemberRepository의 dataSource로 바꿔치기해서 생성해주도록 하겠습니다.
코드는 아래와 같이 작성합니다.
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 {
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);
}
}
- DataSource는 데이터베이스 커넥션을 획득할 때 사용하는 객체로, 스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 DataSource를 생성하고 스프링 빈으로 만들어 둡니다. 그래서 DI를 받을 수 있습니다.
구현 클래스에 대한 이미지는 아래와 같습니다. 추후에 데이터베이스를 추가할 목적으로 인터페이스를 작성해뒀었고, 이제 기존의 MemoryMemberRepository를 H2 데이터베이스를 바라보도록 바꿔치기 한 것 입니다.
바꿔치기 한 후의 스프일 설정 흐름은 아래와 같습니다.

- 개방-폐쇄 원칙(OCP, Open-Closed Principle) 확장에는 열려있고, 수정, 변경에는 닫혀있다.
- 스프링의 DI (Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스를 변경할 수 있습니다.
이제 데이터를 MemoryMemberRepository 객체가 아닌 DB에 저장하므로 스프링 서버를 다시 실행해도 데이터가 안전하게 저장됩니다. 직접 데이터를 저장하고 확인해 보세요!
반응형
'dev-ing > spring' 카테고리의 다른 글
[Springboot] 스프링부트 기초 (14) JPA 적용하기 JPA H2 데이터베이스 (0) | 2022.10.31 |
---|---|
[Springboot] 스프링부트 기초 (13) JDBC 스프링 컨테이너와 DB연동해서 통합테스트 하기 JDBC Template (0) | 2022.10.27 |
[Springboot] 스프링부트 기초 (11) H2 데이터베이스 설치하기 (0) | 2022.10.25 |
[Springboot] 스프링부트 기초 (10) 회원예제 웹 MVC 개발하기 홈 화면 등록 조회기능 (0) | 2022.10.25 |
[Springboot] 스프링부트 기초 (9) 스프링 빈과 의존관계 설정하기 자바코드로 스프링 빈 등록하기 (0) | 2022.10.24 |
댓글