카테고리 없음

8. DB 연동

비뀨_ 2021. 11. 14. 05:55

JDBC를 사용해봤다면 DB에 연결하기 위해 반복적인 코드가 너무 많은 경험이 있을 것이다.

 

구조적인 반복을 줄이기 위해 JdbcTemplate 클래스를 사용할 수 있다.

 

JdbcTemplate를 사용하기 위해서는 pom.xml에 다음 dependency를 추가한다.


        //JdbcTemplate 등 JDBC 연동에 필요한 기능을 제공한다.
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>5.0.2.RELEASE</version>
		</dependency>
        //DB 커넥션 풀 기능을 제공한다.
		<dependency>
			<groupId>org.apache.tomcat</groupId>
			<artifactId>tomcat-jdbc</artifactId>
			<version>8.5.27</version>
		</dependency>
        //Mysql 연결에 필요한 JDBC 드라이버 제공
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.45</version>
		</dependency>

커넥션 풀 한줄 요약

DB에 접근하는 일정 연결 객체 생성해 놓고 빌려주고 반납하게 해서 자원 절약!

https://beetr.tistory.com/9

 

Java에서 DBCP의 개념과 쓰는 법

더보기 DBCP는 (Database Connection Pool) 의 약자로 웹서버에서 데이터베이스에 연결하기 위한. 라이브러리다. 아니 그럼 데이터베이스 있는데 왜 DBCP를 따로 쓰려고 함 ??? 라고 생각 할 수도 있는데,

beetr.tistory.com

 

DataSource

DataSource는 연결을 위한 팩토리라고 보면 될 것 같다.

스프링이 제공하는 DB 연동 기능은 DataSource를 사용해 DB Connection을 구한다.

DB연동에 사용할 DataSource를 스프링 빈으로 등록하고 연동 기능을 구현한 빈 객체는 DataSource를 주입받아 사용한다.

 

@Configuration
public class DbConfig {

	@Bean(destroyMethod = "close")
	public DataSource dataSource() {
    //DataSource 객체 생성 = 연결 객체 생성
		DataSource ds = new DataSource();
        //드라이버 클래스 지정
		ds.setDriverClassName("com.mysql.jdbc.Driver");
        //연결할 URL 지정.
		ds.setUrl("jdbc:mysql://localhost/spring5fs?characterEncoding=utf8");
		// id, password 입력
        ds.setUsername("spring5");
		ds.setPassword("spring5");
        // 초기 연결 크기 지정
		ds.setInitialSize(2);
        // 초기 연결 객체 10개 생성
		ds.setMaxActive(10);
		ds.setTestWhileIdle(true);
		ds.setMinEvictableIdleTimeMillis(60000 * 3);
		ds.setTimeBetweenEvictionRunsMillis(10 * 1000);
		return ds;
	}
}

물론 위에 DB설정은 내께 아니다. 

 

Tomcat JDBC의 주요 프로퍼티

 

더보기

Connection Pool 은 연결을 생성하고 유지한다.

사용자가 연결을 요청하면 해당 커넥션은 활성화 상태가 되고 , 반환하면 유휴(Idle) 상태가 된다.

연결 요청 = Connection conn = dataSource.getConnection() ;

반환  = conn.close();

설정 메서드 설명
setInitialSize( 숫자 ) 커넥션 풀 생성시 초기 커넥션 갯수. 10개가 기본 값.
setMaxActive( 숫자 ) 최대 커넥션 갯수. 기본값 : 100개
setMaxIdle( 숫자 ) 유지할 수 있는 최대 커넥션 갯수. 
setMinIdle( 숫자 ) 최소 커넥션 갯수. 기본값은 initialSize에서 가져옴.
setMaxWait( 숫자 ) 대기할 최대시간. 기본값 : 30초
setMaxAge( 숫자 ) 최초 연결 후 커넥션의 유효시간. 기본값 : 0 ( 무한 ) 
setValidationQuery( 문자 ) 커넥션 유효한지 검사할 때 쓸 쿼리. 기본값 : null (검사 안함)
setValidationQueryTimeout ( 숫자 ) 검사 쿼리 최대 실행시간. 기본값 : -1 ( 비활성화 ) 
setTestOnBorrow( boolean )  커넥션 반환할 때 검사 여부 . 기본 : false 
setTestWhileIdle( boolean )  커넥션이 풀에 있을 때 검사할지 여부. 기본 : false
setMinEvictableIdleTimeMillis(int) 커넥션 풀에 유휴 상태로 유지할 최소시간.
기본값 : 60000 Millisecond ( 1분 ) 
setTimeBetweenEvictionRunsMillis( int )  커넥션 풀의 유휴 커넥션을 검사할 주기. 기본 : 5000 Millisecond ( 5초 ) . 1초 이하로 설정하면 안됨.

 

JdbcTemplate 생성과 사용

1. JdbcTemplate 생성하기

public class MemberDao {
	//JdbcTemplate 선언
	private JdbcTemplate jdbcTemplate;
	//DataSource 의존성 주입 ( DI )
	public MemberDao(DataSource dataSource) {
		this.jdbcTemplate = new JdbcTemplate(dataSource);
	}

2. JdbcTemplate를 쓰는 MemberDao 빈 등록

	@Bean
	public MemberDao memberDao() {
		return new MemberDao(dataSource());
	}

 

1. 조회 쿼리 실행

더보기

List<T> query( String sql , RowMapper<T> rowMapper)

List<T> query( String sql , Object[] args , RowMapper<T> rowMapper)

List<T> query( String sql , RowMapper<T> rowMapper , Object.. args)

 

sql = 전달 받은 쿼리 . 

RowMapper<T> = ResultSet의 결과 자바 객체로 변환.

args = 인덱스 기반 파라미터 가진 쿼리면  args 파라미터로 각 인덱스 파라미터의 값 지정.

	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,name); // 인덱스 파라미터가 여러개인 경우.  콤마로 구분한다.

		return results.isEmpty() ? null : results.get(0);
	}

인덱스 파라미터가 여러개인 경우란 ?

위에서는 파라미터로 String email만 받았지만 , email과 name을 받는다고 쳤을 때 ,

"select * from member where email = ? and name= ? " 이렇게  위치홀더( 물음표 : ? )가 있을 때 

해당하는 값을 순서대로 넣어주기 위한 것 .

 

그리고 위처럼 RowMapper를 일회용으로 선언해서 작성해도 된다.

반대로 , 여러 곳에서 동일한 RowMapper를 사용할 때에는 따로 RowMapper를 구현한 클래스를

만들어서 사용하면 코드의 중복을 막을 수 있다. 

 

결과가 한개일 때 

 

	public int count() {
		Integer count = jdbcTemplate.queryForObject(
        	    //첫번째 인자 : sql 문   , 두번째 인자 : 칼럼을 읽을 때 사용할 타입
				"select count(*) from MEMBER", Integer.class);
		return count;
	}

위와 같이 결과 값이 select의 결과 값이 회원의 숫자의 갯수인 경우 queryForObject를 사용해서 

RowMapper 대신 Integer 클래스를 사용할 수 있다. 

 

2.  JdbcTemplate을 이용한 변경 쿼리 실행

조회를 제외한 Insert , Update , Delete 쿼리는 update() 메서드를 사용한다.

더보기

int update( String sql ) 

int update( String sql , Object... args ) 

매개 변수는 위와 비슷하게  질의문과 , ?에 넣을 인자들을 넣어주면 된다.

 

PrepareStatement를 사용해서 ? 에 넣을 인자들을 넣어 줄 수 있는데 , 

PrepareStatementCreator 를 인자로 받는 메서드를 이용해서 직접 PrepareStatement를 생성, 설정해야 한다.

jdbcTemplate.update( new PreparedStatementCreator(){
	@Override
    public PreparedStatement createPreparedStatement(Connection con) throws SQLException{
    
        //파라미터로 전달받은 Connection을 이용해 PreparedStatement 생성
        PreparedStatement ps = con.prepareStatement(
        " insert into Member ( email , password , name , regdate ) values( ? , ? , ? , ?)");
        //인덱스 파라미터 선택
        ps.setString(1,member.getEmail());
        ps.setString(2,member.getPassword());
        ps.setString(3,member.getName());
        ps.setTimestamp(4, Timestamp.valueOf(member.getRegisterDateTime()));
        return ps ; 
    }
});

 

KeyHolder 이용해서 자동 생성 ( Auto_Increment ) 값 구하기

 

public void insert(Member member) {
		KeyHolder keyHolder = new GeneratedKeyHolder();
		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 (?, ?, ?, ?)",
						new String[] { "ID" });
            // 후략 ... 바로 위에 있는 코드가 들어가 있음.
            
		}, keyHolder);
		Number keyValue = keyHolder.getKey();
		member.setId(keyValue.longValue());
	}
더보기

GeneratedKeyHolder : 자동 생성된 키 값을 구해주는 KeyHolder 구현 클래스

Number keyValue  :  createPreparedStatement 두번째 인자로 자동 생성된 키 값 객체를 전달 받음.

 

update 메서드는 PrepareStatement를 실행 후 자동 생성된 키 값을 KeyHolder에 보관함.

.getKey() 메서드로 구하고  intValue , LongValue() 메서드로 변환해서 사용.

 

트랜잭션 처리

 

트랜잭션 : 0 or 100 . 완전히 안되거나 , 완전히 되거나 둘 중 하나.  은행 등에서 이체를 생각하면 쉬움.

 

JDBC에서의 처리 

Connection conn = null;

try{
	conn = DriverManager.getConnection(jdbcUrl , user , pw );
    conn.setAutoCommit(false);  // 자동 커밋 취소 . ( 트랜잭션 범위 시작 ) 
    
    // ..... 트랜잭션 구문 있다고 상상
    
    conn.commit();  // 트랜잭선 범위 끝. 커밋함.
}catch(SQLException ex){
if(conn==null)
	// 롤백.
	try{conn.rollback(); } catch( SQLException ex ){}
}

자동 커밋을 지우고 , 트랜잭션 범위를 돌리고 , 이상없다면 커밋 , 이상있다면 롤백을 통해서 처리했었다.

 

Spring에서 @Transactional 을 이용한 트랜잭션 처리

 

설정

  • 플랫폼 트랜잭션 매니저 빈 설정
  • @Transactional 어노테이션 달기.

 

@Configuration
@EnableTransactionManagement
public class AppCtx {
	
    //.. 중략
    
    @Bean
	public PlatformTransactionManager transactionManager() {
		DataSourceTransactionManager tm = new DataSourceTransactionManager();
		tm.setDataSource(dataSource());
		return tm;
	}
    
    //.. 후략

 

PlatformTransactionManager는 스프링이 제공하는 트랜잭션 매니저 인터페이스다.

DataSourceTransactionManager 는 PlatfromTransactionManager 인터페이스를 상속받은 것.

트랜잭션을 사용할 객체를 만들었다면

@EnableTransactionManagement 를 사용하여   @Transactional 어노테이션이 붙은 메서드를 트랜잭션으로 관리한다.

 

 

사용 .

public class ChangePasswordService {

	private MemberDao memberDao;

	@Transactional
	public void changePassword(String email, String oldPwd, String newPwd) {
		Member member = memberDao.selectByEmail(email);
		if (member == null)
			throw new MemberNotFoundException();

		member.changePassword(oldPwd, newPwd);

		memberDao.update(member);
	}

	public void setMemberDao(MemberDao memberDao) {
		this.memberDao = memberDao;
	}

}

트랜잭션 처리할 구문에 @Transactional 어노테이션만 붙이면 됨.

selectByEmail 구문과  changePassword 구문은 한 트랜잭션 안에 묶이게 된다.