6.2.1 순수 JDBC - 드라이버 세팅
앞서 섹션 5에서 DB를 메모리에 저장하면, 서버를 내렸을 때 정보가 모두 손실되는 문제가 있었다.
따라서, 어플리케이션에서 DB 를 연동하여,
회원 정보가 데이터베이스에 저장되도록 하고자 한다.
그중에서 첫 번째로, 고전적인 방식인 순수 JDBC 기술을 살펴보자.
먼저, build.gradle 파일에 Jdbc 와 H2 데이터 베이스 관련 라이브러리를 추가해야 한다.
다음 코드를 dependencies 안에 추가로 작성해준다.
implementation 'org.springgramework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
- Jdbc : Java는 db와 연동하려면 기본적으로 jdbc 드라이버를 꼭 필요로 한다.
- H2 : db와 연동할 때, 데이터베이스가 제공하는 클라이언트가 필요하다.
다음으로, db와 연동하기 위해서는 접속 정보를 넣어주어야 한다.
스프링 부트가 db와 연동하는 일을 모두 해주므로, 우리는 경로만 넣어주면 된다.
src > main > resources > application.properties 에 아래의 코드를 작성한다.
h2 데이터베이스에 접근할 것이므로, h2 드라이버를 넣어준다.
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
6.2.2 순수 JDBC - Jdbc Repository 연결
Jdbc API 를 사용하여 개발을 진행한다.
기존에 우리는 MemoryMemberRepository 에서 인터페이스를 구현하여 회원을 저장하였다.
이는 인터페이스 구현을 메모리에 하는 방식이었다.
하지만 구현을 Jdbc 로 하기 위해,
Jdbc 리포지토리로 바꿔 연결하는 작업을 수행하고자 한다.
먼저, repository 폴더 아래에 JdbcMemberRepository 파일을 아래와 같이 작성한다.
private final DataSource dataSource;
public JdbcMemberRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
- db 와 연동하기 위해서는 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);
}
}
String sql = "insert into member(name) values(?)";
: 나중에 파라미터를 받아야 하므로 ? 로 작성한다.
ResultSet rs = null;
: resultset 는 결과를 받는다.
conn = getConnection();
: getConnection 을 통해, 먼저 커넥션을 가져온다.
pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
: 앞서 작성한 sql 을 넣는다.
: RETURN_GENERATED_KEYS 를 통해 DB 에 회원 정보를 넣을 때, id 값을 얻을 수 있다.
pstmt.setString(1, member.getName());
: 파라미터 값으로 인덱스 1번을 넣으면, 앞선 sql 의 ?와 매칭된다.
: member 객체로 넘어온 이름 값을 넣어준다.
pstmt.executeUpdate();
: 업데이트를 해주어, DB에 값이 넘어가도록 한다.
rs = pstmt.getGeneratedKeys();
: 앞선 RETURN_GENERATED_KEYS 와 매칭된다.
if (rs.next()) { } else { }
: ResultSet에 값이 있는 경우, getLong을 통해 값을 꺼내고, 세팅한다.
: 값이 없는 경우, 조회 실패 메시지가 뜨도록 한다.
private void close(Connection conn) throws SQLException {
DataSourceUtils.releaseConnection(conn, dataSource);
}
- db 컬렉션을 사용하면, 외부 네트워크와 연결된 것이므로, 사용을 완료한 후 리소스를 모두 반환해야 한다.
localhost8080 를 실행시켜 확인해보자.
다음과 같이 이름이 Java인 회원을 등록한다.
회원 목록을 확인해보면, 제대로 db에 저장되는 것을 확인할 수 있다.
전체 코드는 다음과 같다.
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);
}
}
+ 추가 )
처음에 DataSourceUtils 에서 오류가 발생했는데,
spring 프로젝트 파일을 C 드라이브로 옮기니 제대로 실행되었다.
6.2.3 순수 JDBC - Configuration
지금까지는 MemoryMemberRepository 를 통해 메모리에 회원 정보를 저장하였다.
따라서, SpringConfig 에서 MemoryMemberRepository 를 스프링 빈에 등록하도록 하였다.
하지만 Jdbc 를 리포지토리로 사용하기 위해 SpringConfig 파일을 아래와 같이 변경한다.
private final DataSource dataSource;
public SpringConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public MemberRepository memberRepository() {
return new JdbcMemberRepository(dataSource);
}
- dataSource 객체를 통해 데이터베이스 커넥션을 가능하도록 만들고, return 값에 주입한다.
- 스프링 부트가 데이터베이스 커넥션을 바탕으로,
dataSource 를 제공하고, 자체적으로 빈도 생성해준다.
이렇게 코드를 완성하면, 회원 정보가 db 에 저장되므로,
서버를 껐다가 다시 켜도 회원 정보가 보존되는 것을 볼 수 있다.
+ ) H2 데이터베이스를 실행한 상태에서 서버를 실행해야한다.
단순히 JdbcMemberRepository 를 만들고 인터페이스를 확장함으로서,
회원 정보 저장을 메모리에서 Jdbc 로 바꿀 수 있다.
(다른 코드 수정이 필요 없다)
6.2.3 객체 지향 설계의 장점 (다형성)
앞서, MemberRepository 인터페이스를 구현하는
MemoryMemberRepository 와 JdbcMemberRepository 를 작성하였다.
기존에는 memberService 가 MemoryMemberRepository(메모리) 와 연결되어 있었지만,
단순히 SpringConfig 를 수정하여, JdbcMemberRepoisotry(Jdbc) 와 연결되도록 하였다.
이를 개방 폐쇄 원칙(OCP, Open-Closed Principle)이라고 한다.
<개방 폐쇠 원칙(OCP, Open-Closed Principle)>
확장에는 열려 있지만 , 수정/변경에는 닫혀 있다
우리는 단순히 JdbcMemberRepository 를 만들고, 인터페이스를 확장함으로서,
다른 코드 변경 없이 구현 클래스를 변경할 수 있다.
스프링의 DI (Dependencies Injection)를 통해 기존 코드 변경 없이,
간단한 설정만으로 구현 클래스를 변경할 수 있는 것이다.
김영한 '스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술' 강의를 기반으로 작성하였습니다.
'SPRING 입문 [ 코드로 배우는 스프링 부트 ]' 카테고리의 다른 글
[스프링 입문] 섹션 6.4 스프링 DB 접근 기술 (스프링 통합 Jdbc Template) (0) | 2023.01.17 |
---|---|
[스프링 입문] 섹션 6.3 스프링 DB 접근 기술 (스프링 통합 테스트) (0) | 2023.01.17 |
[스프링 입문] 섹션 6.1 스프링 DB 접근 기술 (H2 데이터베이스 설치) (0) | 2023.01.17 |
[스프링 입문] 섹션 5.3 웹 MVC 개발 (회원 웹 기능 - 조회) (0) | 2023.01.10 |
[스프링 입문] 섹션 5.2 웹 MVC 개발 (회원 웹 기능 - 등록) (0) | 2023.01.10 |