이 프로젝트의 개발 환경
- 개발 언어 및 개발 환경
- OpenJDK 12
- Spring: spring-context 5.0.2.RELEASE
- Spring: spring-jdbc: 5.0.2.RELEASE
- Tomcat: tomcat-jdbc: 8.5.27
- MySQL: mysql-connector-java: 8.0.22
- Gradle 7.3
- 기타 환경
- macOS Sonoma 14.1.1
- IntelliJ IDEA 2020.3 Ultimate Edition
- MySQL 8.2.0
자바의 JDBC와 스프링의 JdbcTemplate
자바에서 JDBC 프로그래밍을 사용하면 DB 연동에 필요한 Connection을 구한 다음 PreparedStatement를 생성하고, 쿼리 결과로 ResultSet을 처리합니다.
이 코드는 사실상 데이터 처리와는 무관하지만 JDBC 사용을 위해서는 반복해서 작성해야합니다.
Member member;
Connection conn = null;
PreparedStatement pstmt = null;
ResultSets rs = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost/spring5fs", "spring5", "spring5");
...
} catch (SQLException e) {
throw e;
} finally {
if (null != rs) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != pstmt) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != conn) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
구조적으로 반복되는 코드를 줄이기 위해 템플릿 메소드 패턴과 전력 패턴을 함께 사용 할 수 있습니다.
스프링이 제공하는 JdbcTemplate에서는 이 두 가지 패턴을 모두 제공합니다.
List<Member> results = jdbcTemplate.query(
"select * from MEMBER where EMAIL = ?",
new RowMapper<Member>() {
@Override
public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
Member member = new Member(
rs.getString("EMAIL"),
rs.getString("PASSWORD"),
rs.getString("NAME"),
rs.getTimestamp("REGDATE"));
member.setId(rs.getLong("ID"));
return member;
}
}, email);
return results.isEmpty() ? null : results.get(0);
JDBC에서 필요로했던 SQL을 위한 준비 코드가 상당히 줄어든 것을 볼 수 있습니다.
여기서 자바 8부터 지원하는 람다 구문을 사용하면 추가적으로 코드를 줄일 수 있습니다.
List<Member> results = jdbcTemplate.query(
"select * from MEMBER where EMAIL = ?",
(ResultSet rs, int rowNum) -> {
Member member = new Member(
rs.getString("EMAIL"),
rs.getString("PASSWORD"),
rs.getString("NAME"),
rs.getTimestamp("REGDATE"));
member.setId(rs.getLong("ID"));
return member;
}, email);
return results.isEmpty() ? null : results.get(0);
스프링의 JdbcTemplate이 제공하는 또 다른 장점은 트랜잭션 관리가 쉽다는 것입니다.
JDBC를 사용할 때는 트랜잭션 관리를 위해 Connection을 setAutoCommit(false)로 지정하고 commit() 또는 rollback()을 사용해서 트랜잭션을 종료해야합니다.
Member member;
Connection conn = null;
PreparedStatement pstmt = null;
ResultSets rs = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost/spring5fs", "spring5", "spring5");
conn.setAutoCommit(false);
...
conn.commit();
} catch (SQLException e) {
throw e;
} finally {
if (null != rs) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != pstmt) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != conn) {
try {
conn.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
반면 스프링에서는 트랜잭션을 적용하고 싶은 함수에서 @Transcational 어노테이션을 등록하면 됩니다.
커밋과 롤백은 스프링에서 자동으로 처리됩니다.
@Transcational
public void insert(Member member)
{
}
예제 프로젝트 작성
sp5-chap08 프로젝트를 생성하고 chap08 패키지를 추가합니다. 이 예제에서는 chap03 패키지의 소스 코드를 복사하여 사용합니다.
build.gradle 파일에서는 spring-jdbc tomcat-jdbc mysql-connect-java 의존성을 추가합니다.
dependencies {
implementation group: 'org.springframework', name: 'spring-context', version: '5.0.2.RELEASE'
implementation group: 'org.springframework', name: 'spring-jdbc', version: '5.0.2.RELEASE'
implementation group: 'org.apache.tomcat', name: 'tomcat-jcbc', version: '8.5.27'
implementation group: 'mysql', name: 'mysql-connector-java', version: '5.1.45'
}
코드 | 비고 | |
JdbcTemplate 등 JDBC 연동에 필요한 스프링 API를 제공합니다. | ||
DB Connection pool을 제공합니다. | ||
MySQL 연결에 필요한 JDBC 드라이버를 제공합니다. |
예제에서는 DBMS로 MySQL을 사용합니다. MySQL 설치와 관련해서는 이 블로그의 문서: Docker에서 MySQL 서버 실행하기를 참고합니다.
DB 테이블 생성
로컬 호스트에서 실행 중인 MySQL 서버에 접속하고 다음 SQL을 모두 실행합니다.
create user 'spring5'@'localhost' identified by 'spring5';
create database spring5fs character set=utf8;
grant all privileges on sspring5fs.* to 'spring5'@'localhost';
use spring5fs;
create table MEMBER(
ID int auto_increment primary key,
EMAIL varchar(255),
PASSWORD varchar(100),
NAME varchar(100),
REGDATE datetime,
unique key(EMAIL)
) engine=InnoDB character set=utf8;
코드 | 비고 | |
엑세스 비밀번호는 |
||
Datasource 설정
스프링이 제공하는 DB 연동은 DataSource를 사용해서 DB Connection을 구합니다.
예제에서는 Datasource를 Bean으로 등록하고 DB 연동 기능을 구현한 Bean 객체는 DataSource를 주입 받아서 사용합니다.
chap08 패키지에서 DbConifg 클래스를 생성합니다.
package chap08;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DbConfig
{
@Bean(destroyMethod = "close")
public DataSource dataSource()
{
DataSource ds = new DataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost/spring5ffs?characterEncoding=utf8");
ds.setUsername("spring5");
ds.setPassword("spring5");
ds.setInitialSize(2);
ds.setMaxActive(10);
return ds;
}
}
코드 | 비고 | |
tomcat의 DataSource는 |
||
MySQL 연동을 위해 |
chap08 패키지에서 DbQuery 클래스를 생성합니다.
package chap08;
import org.apache.tomcat.jdbc.pool.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class DbQuery
{
private DataSource ds;
public DbQuery(DataSource ds)
{
this.ds = ds;
}
public int count()
{
Connection conn = null;
try {
conn = ds.getConnection();
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select count(*) from MEMBER")) {
rs.next();
return rs.getInt(1);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (null != conn) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
코드 | 비고 | |
Connection을 |
JdbcTemplate을 사용한 쿼리 실행
스프링의 JdbcTemplate을 사용하면 DataSource Connection Statement ResultSet을 직접 사용하지 않더라도 쿼리를 실행 할 수 있습니다.
chap08 패키지에서 MemberDao 클래스의 함수를 JdbcTemplate으로 구현합니다.
package chap08;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
public class MemberDao
{
private JdbcTemplate jdbcTemplate;
public MemberDao(DataSource ds)
{
this.jdbcTemplate = new JdbcTemplate(ds);
}
public Member selectByEmail(String email)
{
List<Member> results = jdbcTemplate.query(
"select * from MEMBER where EMAIL = ?",
new RowMapper<Member>() {
@Override
public Member mapRow(ResultSet rs, int rowNum)
throws SQLException
{
Member member = new Member(
rs.getString("EMAIL"),
rs.getString("PASSWORD"),
rs.getString("NAME"),
rs.getTimestamp("REGDATE").toLocalDateTime());
member.setId(rs.getLong("ID"));
return member;
}
}, email);
return results.isEmpty() ? null : results.get(0);
}
public void insert(Member member)
{
}
public void update(Member member)
{
}
public List<Member> selectAll()
{
List<Member> results = jdbcTemplate.query(
"select * from MEMBER",
new RowMapper<Member>() {
@Override
public Member mapRow(ResultSet rs, int rowNum)
throws SQLException
{
Member member = new Member(
rs.getString("EMAIL"),
rs.getString("PASSWORD"),
rs.getString("NAME"),
rs.getTimestamp("REGDATE").toLocalDateTime());
member.setId(rs.getLong("ID"));
return member;
}
});
return results;
}
}
코드 | 비고 | |
DataSource를 의존 주입하기 위해 chap08 패키지에서 AppConfig 클래스를 수정합니다.
package chap08;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(DbConfig.class)
public class AppConfig
{
@Autowired
public DataSource dataSource;
@Bean
public MemberDao memberDao()
{
return new MemberDao(dataSource);
}
}
예제 프로젝트 실행 및 테스트
예제 프로젝트를 실행하고 멤버 추가를 위해 list 명령문을 사용합니다. list를 실행하면 MemberDao의 selectAll() 함수가 실행됩니다.
public List<Member> selectAll()
{
List<Member> results = jdbcTemplate.query(
"select * from MEMBER",
new RowMapper<Member>() {
@Override
public Member mapRow(ResultSet rs, int rowNum)
throws SQLException
{
Member member = new Member(
rs.getString("EMAIL"),
rs.getString("PASSWORD"),
rs.getString("NAME"),
rs.getTimestamp("REGDATE").toLocalDateTime());
member.setId(rs.getLong("ID"));
return member;
}
});
return results;
}
아직 데이터베이스에 멤버를 추가하는 코드는 작성하지 않았으므로 명령문의 실행 결과로 아무것도 표시되지 않습니다.
명령문이 오류 없이 실행된다면 JdbcTemplate이 MySQL에 연결하고 SQL을 실행하고 있는 상태입니다.
정리 및 복습
- 스프링의
JdbcTemplate을 사용해 MySQL에 연결하고 SQL을 실행합니다. - JdbcTemplate에서 MySQL에 연결하기 위해 tomcat의
DataSource를 생성하고 연결 정보를 입력합니다. - 스프링에서 트랜잭션을 적용하려면
@Transcational어노테이션을 등록합니다.