8장의 키워드
# JDBC API
# DataSource
# JDBC Template
# 커넥션 풀
자바에서 제공하는 표준 SQL 인터페이스 API로써, 여러 개의 관계형 데이터베이스에 접근하여 SQL 문을 수행하여 처리
템플릿 메서드 패턴, 전략 패턴 사용하여 구조적 반복 줄이기
# 템플릿 메서드 패턴: 특정 작업을 처리하는 일부분을 서브 클래스로 캡슐화하여 전체적인 구조는 바꾸지 않으면서 특정 단계에서 수행하는 내용을 바꾸는 패턴
# 전략 패턴: 같은 문제를 해결하는 여러 알고리즘이 클래스별로 캡슐화되어 있고 이들이 필요할 때 교체할 수 있도록 함으로써 동일한 문제를 다른 알고리즘으로 해결
⇒ 템플릿 메서드 패턴과 전략 패턴을 엮은 JdbcTemplete 클래스 제공
스프링이 제공하는 장점: 트랜잭션 관리가 쉽다!
트랜잭션을 적용하고 싶은 메서드에 @Transactional 애노테이션을 붙인다.
# 트랜잭션: DB의 상태를 변화하는 작업을 수행하는 최소 단위
@Transactional
public void insert(Member member){
...
}
pom.xml 의존 추가
# 트랜잭션 기능을 사용하기 위해 Spring-tx 모듈 필요, Spring-jdbc가 포함하고 있어 추가하지 않았다.
✏️ 커넥션 풀(Connection Pool)?
1. 자바 프로그램에서 DBMS로 커넥션을 생성하는 시간이 매우 길다.
2. 응답 시간, 부하를 줄이기 위해 커넥션 풀을 사용
JDBC API는 Datasource를 이용하여 DB 연결을 구하는 방법을 정의한다.
@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 지정
ds.setUsername("spring5");
ds.setPassword("spring5");
ds.setInitialSize(2); // 생성한 초기 커넥션 개수
ds.setMaxActive(10); // 커넥션 풀에서 가져올 수 있는 최대 커넥션 개수
ds.setTestWhileIdle(true); // 유휴 커넥션 검사
ds.setMinEvictableIdleTimeMillis(60000 * 3); // 최소 유휴 시간 = 3분
return ds;
}
// DbQuery.java
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) {
}
}
}
}
커넥션 풀에서 커넥션을 가져오면 conn은 활성 상태, conn.close()에서 커넥션을 반환하면 유휴 상태
MaxActive가 10으로 설정, 활성 상태의 커넥션 = 10개라고 가정하면, 다른 커넥션이 반환될 때까지 대기한다.
대기 시간 내에 풀에 반환된 커넥션이 있으면 해당 커넥션을 사용, 없으면 익셉션 발생
커넥션 풀을 사용하는 이유: 성능
커넥션 풀에 생성된 커넥션은 지속적으로 재사용, 영구적인 것은 아님
일정 시간 내에 쿼리를 실행하지 않으면 커넥션을 끊는다.
예) 커넥션 특정 커넥션이 5분 이상 유휴 상태로 존재하면 DBMS는 해당 커넥션 연결을 끊는다. 하지만 커넥션은 여전히 커넥션 풀 속에 남아있다. DBMS와의 연결이 끊긴 커넥션을 사용하면 익셉션이 발생한다.
⇒ 커넥션 풀의 커넥션이 유효한지 주기적인 검사가 필요
// MemberDao.java
public class MemberDao {
private JdbcTemplate jdbcTemplate;
public MemberDao(DataSource dataSource) { // dataSource 주입 받음
this.jdbcTemplate = new JdbcTemplate(dataSource); // jdbcTemplate 생성
}
// AppCtx.java
@Configuration
public class AppCtx {
@Bean(destroyMethod = "close")
public DataSource dataSource() {
DataSource ds = new DataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
...
}
@Bean
public MemberDao memberDao() { // 빈 객체 등록
return new MemberDao(dataSource());
}
Jdbc Templete 클래스는 SELECT 쿼리 실행을 위한 query() 메서드 제공
sql 파라미터로 전달받은 쿼리 실행, RowMapper을 이용하여 ResultSet의 결과를 자바 객체로 변환
// p192 MemberDao.java
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);
}
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);
});
}
return results.get(0);
)}
// Memberdao.java
public int count() {
Integer count = jdbcTemplate.queryForObject(
"select count(*) from MEMBER", Integer.class);
return count;
}
두 번째 파라미터는 칼럼을 읽어올 때 사용할 타입 지정
INSERT, DELETE, UPDATE ⇒ update() 메서드 사용
쿼리 실행 결과로 변경된 행의 개수 리턴
public void update(Member member) {
jdbcTemplate.update(
"update MEMBER set NAME = ?, PASSWORD = ? where EMAIL = ?",
member.getName(), member.getPassword(), member.getEmail());
}
지금까지는 위 4번의 방법으로 쿼리에서 사용할 값을 인자로 전달
PreparedStatement에서 파라미터의 값을 직접 설정할 때가 있다.
public interface PreparedStatementCreator {
PreparedStatement createPreparedStatement(Connection con) throws SQLException;
}
// 200p
jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con) // Connection 파라미터
throws SQLException {
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())
);
return pstmt;
}
});
파라미터로 전달받는 Connection을 이용하여 prepareStatement 객체를 생성하고 인덱스 파라미터를 알맞게 설정한 뒤 prepareStatement 객체를 리턴한다.
update() 메서드는 PreparedStatement를 실행한 후 자동으로 생성된 키 값을 keyHolder에 보관한다.
getKey() 메서드를 이용하여 keyHolder에 보관된 Key 값을 구한다.
public void insert(Member member) {
KeyHolder keyHolder = new GeneratedKeyHolder(); // 자동 생성된 키값 구해줌
jdbcTemplate.update(new PreparedStatementCreator() { // 파라미터: PreparedStatementCreator, keyHolder
@Override
public PreparedStatement createPreparedStatement(Connection con)
throws SQLException {
// 파라미터로 전달받은 Connection을 이용해서 PreparedStatement 생성
PreparedStatement pstmt = con.prepareStatement(
"insert into MEMBER (EMAIL, PASSWORD, NAME, REGDATE) " +
"values (?, ?, ?, ?)",
new String[] { "ID" });
// 인덱스 파라미터 값 설정
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;
}
}, keyHolder);
Number keyValue = keyHolder.getKey();
member.setId(keyValue.longValue());
}
1. 웹 애플리케이션은 데이터 보관을 위해 DBMS를 사용한다. DB 연동을 위해서 JAVA에서는 (JDBC API), (JPA), (MyBatis) 기술을 지원한다.
2. JDBC API를 사용하여 발생하는 구조적인 문제를 해결하기 위해 템플릿 메서드 패턴, 전략 패턴을 엮은 (JdbcTemplete 클래스)을/를 사용한다.
3. JDBC API로 트랜잭션을 처리할 때 트랜잭션을 적용하고 싶은 메서드에 (@Transactional) 애노테이션 을/를 붙여준다.
4. JAVA에서 DBMS로 커넥션을 생성하는데 시간이 걸리기 때문에 커넥션을 (커넥션 풀)에 미리 생성하여 저장한다.
5. 커넥션 풀에서 커넥션을 가져오면 해당 커넥션은 (활성 상태), 커넥션을 커넥션 풀이 반환하면 해당 커넥션은 (유휴 상태)가 된다.
6. JDBC API는 (DataSource) 클래스를 사용하여 DB와의 연동을 정의한다.
7. Jdbc Templete에서 SELECT 쿼리를 수행하기 위해 (query()) 메서드 사용, INSERT, UPDATE, DELETE 쿼리를 수행하기 위해 (update()) 메서드를 사용한다.
1. QUERY 구문을 작성한다.
public int count() {
Connection conn = null;
try {
conn = /* 채우기 */
try (Statement stmt = /* 채우기 */
ResultSet rs = /* 채우기 */) {
rs.next();
return rs.getInt(1);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (conn != null)
try {
conn.close();
} catch (SQLException e) {
}
}
}
2. 결과가 1행일 때 사용할 수 있는 메서드를 이용하여 다음 코드를 작성하자.
MEMBER의 급여(sal)의 평균을 구한다고 가정, 반환 타입은 Double
힌트: select AVG(sal) from MEMBER
public int average() {
Integer average = jdbcTemplate.?어떤 메서드일까요?(/* 채우기 */);
return average;
}
Corner Spring #2
Editor : Otcr
[스프링2] 9장. 스프링 MVC 시작하기 (0) | 2022.11.24 |
---|---|
[스프링2] 8장. DB연동 (2) (0) | 2022.11.17 |
[스프링2] 7장. AOP 프로그래밍 (0) | 2022.11.10 |
[스프링2] 6장. 빈 라이프사이클과 범위 (0) | 2022.10.13 |
[스프링2] 5장. 컴포넌트 스캔 (0) | 2022.10.13 |