오늘은 드디어 데이터베이스에 데이터를 저장하고 관리하는 것을 직접해볼 것이다.
또한 오늘 포스팅을 통해 DB 접근 기술이 어떻게 발전되었는지 순서대로 나열해서 포스팅을 해보겠다.
실무에서는 MySQL이나 Oracle을 많이 쓰지만
교육용으로도 좋고 가볍고 용량이 작다는 장점이 있는 H2 데이터베이스라는 것을 설치해서 진행 해볼 예정이다.
H2데이터베이스 설치에 대해서는 간단하게 링크 들어가서 설치하면 됨으로 따로 포스팅을 하지 않겠다.
💻 H2 데이터베이스 설치하기
소켓을 통해 접근을 할 것인데, 파일로 접근하는 방식은 애플리케이션과 웹콘솔이 동시에 동작 시 오류가 생길 위험이 있지만 소켓을 통해 접속하게되면 여러군데에서 접속이 가능하기 때문에 소켓으로 접속하도록 하였다.
먼저 Member 테이블을 만들기 위해 아래 sql문을 작성 후 실행 해보자.
create table member(
id bigint generated by default as identity,
name varchar(255),
primary key (id)
);
sql문에서 generated by default as identity는 해 insert시 DB가 자동으로 값을 채워주는 코드이다.
회원 리포지토리에서 squence의 값을 하나씩 증가시켜 id로 저장해준 것과 같은 것으로 이해할 수 있다.
그렇다면 정상 작동되는지 insert을 진행해보자!
insert into member(name) values('spring1') 을 입력 후 실행하면 테이블에 값이 저장된 것을 확인할 수 있다.
1. 순수 JDBC
이번 시간에는 가장 오래된 방식인 JDBC 를 이용해서 진행해보겠다.
실무에서 사용되는 일은 거의 없으며, 약 20년 전에 흔히 사용되던 방법이라고 한다.
먼저 연결을 위해 환경 설정을 해주자
build.gradle 파일에 아래와 같은 라이브러리를 추가 해준다.
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
다음으로는 스프링부트가 데이터베이스를 연결할 수 있도록 설정을 추가해준다.
source/application.properties 파일에 다음과 같은 코드를 작성해준다.
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
그 다음으로는 repository 폴더에 JdbcMemberRepository 파일을 생성하고 다음과 같은 코드를 작성한다.
package hello.helloSpring.repository;
import hello.helloSpring.domain.Member;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
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 Optional<Member> findyByName(String name) {
return Optional.empty();
}
@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);
}
}
아니 이건 세미 때 했던 방법이었는데! 그때 와... 이걸 이렇게 하는구나.... 하나 다른게 있다면
String sql = "insert into member(name) values(?)";
아래 이부분에 대해서 따로 작성했던 것으로 기억하는데... 진짜
DB에 값을 저장하기 위해서 insert 쿼리 한 줄을 실행시키기 위해서 작성을 하고
연결을 통해 preparedStatement라는 값으로 정하고 쿼리를 수행 후
예외처리를 위해 try-catch하고 마지막에 그동안 사용했던 Connection / preparedStatement / ResultSet를 닫아주면 된다.
반갑지만 반갑지 않구나...이게 바로 순수 JDBC였구나..하고 넘어가자 !
근데, 여기서 중요한 것은 기존의 다른 코드를 변경하지 않고 인터페이스를 확장하여,
JdbcMemberRepository를 만들고 Config 파일만 변경하여 리포지토리 구현체를 변경했다 !
이 것은 인터페이스를 두고 손쉽게 구현체를 변경을 했다 즉 '다형성'을 활용했으며
스프링은 이런 것을 편리하게 사용할 수 있도록 지원했다는 것이 중요하다!
2.Spring JdbcTemplate
설정 자체는 순수 JDBC와 동일하게 하면 되며 JDBC에서 봤던 반복 코드를 대부분 제거를 해준다.
Spring JDBC Template은 실무에서도 쓰인다고 한다. 그럼 코드를 통해 알아보자 !
repository 폴더에 JdbcTemplateMemberRepository 파일을 생성, 다음과 같은 코드를 작성한다.
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class JdbcTemplateMemberRepository implements MemberRepository{
private final JdbcTemplate jdbcTemplate;
@Autowired
public JdbcTemplateMemberRepository(DataSource dataSource){
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Member save(Member member) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
Map<String, Object> parameters = new HashMap<>();
parameters.put("name", member.getName());
Number key = jdbcInsert.executeAndReturnKey(new
MapSqlParameterSource(parameters));
member.setId(key.longValue());
return member;
}
@Override
public Optional<Member> findById(Long id) {
List<Member> result = jdbcTemplate.query("select * from where id = ?",memberRowMapper(),id);
return result.stream().findAny();
}
@Override
public Optional<Member> findByName(String name) {
List<Member> result = jdbcTemplate.query("select * from where name = ?",memberRowMapper(),name);
return result.stream().findAny();
}
@Override
public List<Member> findAll() {
return jdbcTemplate.query("select * from member",memberRowMapper());
}
private RowMapper<Member> memberRowMapper() {
return (rs, rowNum) -> {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
};
}
}
이전에 봤던 JdbcMemberRepository 코드보다 훨씬 간단하고 짧아졌다.
연결을 설정하고 접근 연결을 종료하는 등 다양한 코드가 사라진 것을 볼 수 있다.
리포지토리를 구현했으니 이제 구현체를 사용을 위해 아래와 같이 추가해 준다.
@Bean
public MemberRepository memberRepository(){
//return new MemoryMemberRepository();
//return new JdbcMemberRepository(dataSource);
return new JdbcTemplateMemberRepository(dataSource);
}
이번 강의에서는 H2데이터베이스를 활용하여
순수 Jdbc와 JdbcTemplate과 같은 스프링의 DB 접근 기술에 대해 학습했다.
다음 강의에서는 JPA에 대해서 학습 해보자 !
'ON > 실습' 카테고리의 다른 글
[Spring Boot] 스프링 DB 접근 기술 - 스프링 데이터 JPA (1) | 2023.11.29 |
---|---|
[Spring Boot] 스프링 DB 접근 기술 - JPA (0) | 2023.11.28 |
[Spring Boot] 회원 관리 예제 - 웹 MVC 개발 ① (0) | 2023.11.24 |
[Spring Boot] 스프링 빈과 의존관계 - 자바 코드로 직접 스프링 빈 등록하기 ② (0) | 2023.11.23 |
[Spring Boot] 스프링 빈과 의존관계 - 컴포넌트 스캔과 자동 의존관계 설정 ① (0) | 2023.11.20 |