상세 컨텐츠

본문 제목

[스프링1] 8장. DB 연동

22-23/22-23 Spring 1

by YUZ 유즈 2022. 11. 10. 10:01

본문

728x90

❗ JDBC 프로그래밍의 단점을 보완하는 스프링

JDBC API를 이용하면 다음과 같이 반복되는 코드가 발생한다.

❗ DataSource

📌 DB에 연결하기

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의 주요 프로퍼티

Tomcat JDBC의 DataSource 클래스는 모듈의 커넥션 풀 기능을 제공하는 클래스다. DataSource 클래스는 커넥션을 몇 개 만들지 지정할 수 있는 메서드를 제공한다.

  • setInitialSize(int) : 커넥션 풀을 초기화할 때 생성할 초기 커넥션 개수를 지정한다. 기본값은 10이다.
  • setMaxActive(int) : 커넥션 풀에서 가져올 수 있는 최대 커넥션 개수를 지정한다. 기본값은 100이다.
  • setMaxIdle(int) : 커넥션 풀에 유지할 수 있는 최대 커넥션 개수를 지정한다. 
  • setMindle(int) : 커넥션 풀에 유지할 최소 커넥션 개수를 지정한다. 기본값은 initialSize에서 가져온다.
  • setMaxWait(int) : 커넥션 풀에서 커넥션을 가져올 때 대기할 최대 시간을 밀리초 단위로 지정한다.
  • setMaxAge(long) : 최초 커넥션 연결 후 커넥션의 최대 유효 시간을 밀리초 단위로 지정한다.
  • setValidationQuery(String) : 커넥션이 유효한지 검사할 때 사용할 쿼리를 지정한다. 언제 검사할지는 별도 설정으로 지정한다. 기본값은 null이다. null이면 검사를 하지 않는다. "select 1"이나 "select 1 from dual"과 같은 쿼리를 주로 사용한다.
  • setValidationQueryTimeout(int) : 검사 쿼리의 최대 실행 시간을 초 단위로 지정한다. 이 시간을 초과하면 검사에 실패한 것으로 간주한다. 0 이하로 지정하면 비활성화한다. 기본값은 -1이다.
  • setTestOnBorrow(boolean) : 풀에서 커넥션을 가져올 때 검사 여부를 지정한다. 기본값은 false이다.
  • setTestOnReturn(boolean) : 풀에 커넥션을 반환할 때 검사 여부를 지정한다. 기본값은 false이다.
  • setTestWhileldle(boolean) : 커넥션이 풀에 유휴 상태로 있는 동안에 검사할 지 여부를 결정한다. 기본값은 false이다.
  • setMinEvictableldleTimeMillis(int) : 커넥션 풀에 유휴 상태로 유지할 최소 시간을 밀리초 단위로 지정한다. testWhileldle이 true이면 유휴 시간이 이 값을 초과한 커넥션 풀에서 제거한다.
  • setTimeBetweenEvictionRunsMillis(int) : 커넥션 풀의 유휴 커넥션을 검사할 주기를 밀리초 단위로 지정한다.

 

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와 연결이 끊기는데, 이때 커넥션 연결은 끊기지만 풀에는 남아있다.

 

❗ JDBC Template

📌 JdbcTemplate 객체 생성하기

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를 이용하여 조회 쿼리 실행하기

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 객체로 변환해주는 기능을 제공한다.

 

📌 queryForObject() 메서드

결과가 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;
	}
}

 

📌 JdbcTemplate을 이용한 변경 쿼리 실행

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());
    }

 

📌 PreparedStatementCreator를 이용한 쿼리 실행

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

728x90

관련글 더보기