JDBC API를 이용하면 다음과 같이 반복되는 코드가 발생한다.
DB를 연결하려면, DB 연동에 사용할 DataSource를 스프링 빈으로 등록하고 DB 연동 기능을 구현한 빈 객체는 DataSource를 주입받아 사용한다.
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(); // DataSource 객체 생성
ds.setDriverClassName("com.mysql.jdbc.Driver"); // JDBC 드라이버 클래스를 지정한다
ds.setUrl("jdbc:mysql://localhost/spring5fs?characterEncoding=utf8"); // JDBC URL을 지정한다
// DB에 연결할 때 사용할 사용자 계정과 암호 지정
ds.setUsername("spring5");
ds.setPassword("spring5");
ds.setInitialSize(2);
ds.setMaxActive(10);
return ds;
}
}
Tomcat JDBC의 DataSource 클래스는 모듈의 커넥션 풀 기능을 제공하는 클래스다. DataSource 클래스는 커넥션을 몇 개 만들지 지정할 수 있는 메서드를 제공한다.
public class DbQuery {
private DataSource dataSource;
public DbQuery(DataSource dataSource) {
this.dataSource = dataSource;
}
public int count() {
Connection conn = null;
try {
conn = dataSource.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(conn != null)
try {
conn.close(); // 풀에 반환
} catch (SQLException e) {
}
maxActive : 활성 상태가 가능한 최대 커넥션 개수를 지정한다. 40으로 지정하면 이는 동시에 커넥션 풀에서 가져올 수 있는 커넥션 개수가 40개라는 의미이다.
활성 커넥션이 40개일 때, 커넥션 풀에 다시 커넥션을 요청하면 다른 커넥션이 반환될 때까지 대기하는데 이 대기 시간이 maxWait다. 대기 시간 내에 반환된 커넥션이 없으면 익셉션이 발생한다.
커넥션 풀을 사용하는 이유는 성능이 좋아서다. 매번 새로운 커넥션을 생성하면 매번 연결하는 데 시간을 쏟는다. 커넥션 풀을 사용하면 미리 커넥션을 생성했다가 필요할 때 꺼내 쓰므로 전체 응답 시간도 짧아진다. 그래서 필요한 최소 수준의 커넥션 풀은 미리 생성해두는 게 좋다. 이게 initialSize다.
커넥션 풀에 생성된 커넥션은 계속 재사용된다. 이때 일정 시간 이상 연결이 없는 커넥션은 자동으로 DBMS와 연결이 끊기는데, 이때 커넥션 연결은 끊기지만 풀에는 남아있다.
import org.springframework.jdbc.core.JdbcTemplate;
public class MemberDao {
private JdbcTemplate jdbcTemplate;
public MemberDao(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource); // DataSource 주입
}
위와 같이 JdbcTemplate을 생성하고 그 안에 DataSource를 주입한다.
JdbcTemplate 클래스 내에 SELECT 쿼리 실행을 위해 query() 메서드를 제공한다.
파라미터로 sql 쿼리문을 전달 받아 실행하고 RowMapper를 이용해 ResultSet의 결과를 자바 객체로 변환한다.
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);
? 는 인덱스 파라미터를 포함하며, 마지막에 작성한 email 이 인덱스 파라미터에 들어간다.
만약 인덱스 파라미터가 두 개 이상일 경우 , 로 연결한다.
RowMapper는 ResultSet에서 데이터를 읽어와 Member 객체로 변환해주는 기능을 제공한다.
결과가 1행인 경우 사용할 수 있는 메서드이다. 결과가 1행이 넘어가면 오류가 발생한다.
다음은 MEMBER 테이블의 전체 행 개수를 구하는 코드이다.
public int count() {
List<Member> results = jdbcTemplate.query(
"select * from MEMBER",
new RowMapper<Member>() {
@Override
public Member mapRow(ResultSet rs, int rowNum)
throws SQLException {
return rs.getInt(1);
});
queryForObject() 메서드는 쿼리 실행 결과 행이 한 개인 경우에 사용한다. 두 번째 파라미터는 칼럼을 읽어올 때 사용할 타입을 지정한다.
public int count() {
Integer count = jdbcTemplate.queryForObject(
"select count(*) from MEMBER", Integer.class);
return count;
}
}
INSERT, UPDATE, DELETE 쿼리는 update() 메서드를 활용한다.
update() 메서드는 쿼리 실행으로 변경된 행 개수를 리턴한다.
public void update(Member member) {
jdbcTemplate.update(
"update MEMBER set NAME = ?, PASSWORD = ? where EMAIL = ?",
member.getName(), member.getPassword(), member.getEmail());
}
PreparedStatement의 경우 set 메서드를 활용해 직접 인덱스 파라미터의 값을 설정해야 할 때도 있다.
이 경우 PreparedStatementCreator를 인자로 받는 메서드를 이용해 직접 PreparedStatement를 생성하고 설정해야 한다.
public interface PreparedStatementCreator {
PreparedStatement createPreparedStatement(Connection con) throws SQLException;
}
다음과 같이 활용한다.
jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con)
throws SQLException {
// 파라미터로 전달받은 Connection을 이용해서 PreparedStatement 생성
PreparedStatement pstmt = con.prepareStatement(
"insert into MEMBER (EMAIL, PASSWORD, NAME, REGDATE) values (?,?,?,?)";
// 인덱스 파라미터 값 결정
pstmt.setString(1, member.getEmail());
pstmt.setString(2, member.getPassword());
pstmt.setString(3, member.getName());
pstmt.setTimestamp(4, Timestamp.valueOf(member.getRegisterDateTime());
// 생성한 PreparedStatement 객체 리턴
return pstmt;
}
});
Spring 1
EDITOR: PORO
[스프링 1] 9장. 스프링 MVC 시작하기 (0) | 2022.11.17 |
---|---|
[스프링 1] 8장. DB 연동(2) (1) | 2022.11.17 |
[스프링1] 7장. AOP 프로그래밍 (0) | 2022.11.10 |
[스프링1] 6장. 빈 라이프 사이클과 범위 (0) | 2022.11.05 |
[스프링1] 5장. 컴포넌트 스캔 (0) | 2022.11.05 |