Search

[강의 요약] 김영한 - 스프링 DB 1편 - 데이터 접근 핵심 원리

Last update: @2/22/2023
주의
본 포스팅은 인프런 강의를 통해 학습한 내용을 임의로 요약한 것으로 일부 내용의 오류 및 누락, 링크 숨김 등이 존재합니다.

JDBC(Java Database Connectivity)

자바에서 데이터베이스에 접근하는 방법을 표준화한 인터페이스
java.sqljavax.sql
각 DB마다 JDBC를 구현한 드라이버가 존재하고 이 드라이버를 통해 DB와 통신

JDBC 사용을 편리하게 해주는 기술

SQL Mapper : SQL 응답 결과를 객체로 편리하게 반환. SQL은 개발자가 직접 작성
종류
Jdbc Template
MyBatis
ORM(Object-Relation Mapping) : 객체를 RDBS 테이블과 매핑하는 기술. SQL을 대신 생성하고 결과도 객체로 반환
JPA(Java Persistence API) 인터페이스 - 자바 표준
Hibernate
이클립스링크

JDBC 사용

[OLD] DB로부터 커넥션(세션)을 얻고, 커넥션을 통해 SQL(Statement)을 보내고 결과(Result Set)를 받음
JDBC 4.0 부터 드라이버는 DriverManager에서 자동으로 찾아서 반환(그 이전은 Class.forName() 사용)
모든 예외처리 및 사용한 리소스(connection, statement, resultset)를 수동으로 close해줘야 함

커넥션풀(Connection Pool)

커넥션을 DB로부터 여러 개 얻어두고 Pool에 저장한 후, 커넥션이 필요할 때마다 풀에서 가져다 쓰는 방식
성능면에 큰 이점이 있어 실무에서는 항상 이 방식을 사용함
적절한 커넥션 풀 숫자는 시스템 스펙에 따라 다르고, 성능 테스트를 통해 정해야 함
여러 종류가 있지만 최근에는 오픈소스 HikariCP사용(스프링 부트 2.0 이상 default)

DataSource

커넥션을 획득하는 방법을 추상화한 인터페이스
종류
[OLD] DriverManagerDataSource(기존 DriverManager 대체)
HikariCP 커넥션 풀 (이외 대부분의 커넥션 풀은 DataSource를 지원함)
사용법
HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl(URL); dataSource.setUsername(USERNAME); dataSource.setPassword(PASSWORD); dataSource.setMaximumPoolSize(10); // default: 10 dataSource.setPoolName("MyPool"); // 생략 가능 Connection con = dataSource.getConnection();
Java
복사
커넥션 풀을 생성하는 작업은 별도의 쓰레드에서 작동

트랜잭션

DB의 여러 레코드를 수정하는 작업을 묶어서 모두 실패하거나 모두 성공하도록 하는 것
보통 레코드 하나를 수정해도 트랜잭션이 일어나지만, 보통 auto commit을 꺼놓고 작업하는 것을 트랜잭션이라고 함
ACID
Atomicity : 트랜잭션 내에서 실행한 작업들은 하나의 작업처럼 모두 성공하거나 모두 실패해야 함
Consistency : 모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야 함(무결성 제약 조건 항시 만족)
Isolation : 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리해야 함
Durability : 트랜잭션이 성공적으로 끝나면 그 결과가 항상 기록돼야 하고, 중간에 시스템에 문제가 생겨도 로그를 사용해서 성공한 트랜잭션 내용을 복구할 수 있어야 함

트랜잭션 직접 적용과 문제점

[OLD] JDBC를 통해 수동으로 적용
[OLD] JPA를 통해 수동으로 적용
트랜잭션의 문제와 해결
구현 기술마다 트랜잭션 사용 코드가 다름 트랜잭션 추상화로 해결
커넥션 동기화 문제 트랜잭션 추상화로 해결
트랜잭션 구현 기술이 Service 계층에 누수됨 트랜잭션 AOP(@Transactional)로 해결
트랜잭션 코드 반복 문제(setAutoCommit, commit, rollback, 예외처리 등)
트랜잭션 템플릿으로 해결
예외 누수 문제
throws SQLException 리포지토리에서 SQLException을 런타임 예외로 변환해서 던짐
JDBC 코드 반복 문제
Repository에 커넥션 동기화용 코드가 추가됨 트랜잭션 추상화로 해결

[프로그래밍 방식 트랜잭션 관리] 트랜잭션 추상화 - 트랜잭션 매니저 & 트랜잭션 동기화 매니저

PlatformTransactionManager 인터페이스로 트랜잭션 추상화
TransactionManagerTransactionSynchronizationManager를 통해 쓰레드 로컬에 커넥션 보관
package org.springframework.transaction; public interface PlatformTransactionManager extends TransactionManager { TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
Java
복사
쓰레드 로컬에 저장된 커넥션을 Repository에서 사용하는법 - DataSourceUtils
Connection con = DataSourceUtils.getConnection(dataSource); // 커넥션 획득 시 DataSourceUtils.releaseConnection(con, dataSource); // 커넥션 반환 시
Java
복사
getConnection 시 커넥션이 있으면 주워오고 아니면 새로 만듦
release 시 트랜잭션이 진행중일 때는 커넥션을 닫지 않음
사용법(수동)
DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD); PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { bizLogic(); transactionManager.commit(status); } catch (Exception e) { transactionManager.rollback(status); throw e; }
Java
복사
리포지토리 계층에서는 아래처럼 Datasource를 받아 씀
private Connection getConnection() throws SQLException { //주의! 트랜잭션 동기화를 사용하려면 DatasourceUtils를 사용해야 함 Connection con = DataSourceUtils.getConnection(dataSource); log.info("getConnection() = {}, class={}", con, con.getClass()); return con; }
Java
복사
트랜잭션 동기화 매니저가 관리하는 dataSource가 있으면 그것을 받아 쓰고, 없으면 새로 만듦
여기선 DataSource로 DriverManagerDataSource(JDBC)를 썼기 때문에 DataSourceTransactionManager를 사용
DefaultTransactionDefinition()은 트랜잭션 관련된 옵션을 지정하는 객체

[프로그래밍 방식 트랜잭션 관리] 트랜잭션 템플릿

트랜잭션 코드 반복 문제(setAutoCommit, commit, rollback, 예외처리 등) 해결
템플릿 콜백 패턴 이용
사용법(수동)
DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD); PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); TransactionTemplate txTemplate = new TransactionTemplate(transactionManager); txTemplate.executeWithoutResult((status) -> { try { bizLogic(); } catch (SQLException e) { throw new IllegalStateException(e); } });
Java
복사

[선언적 트랜잭션 관리] 트랜잭션 AOP - @Transactional

스프링 AOP를 통해 프록시를 도입해서 서비스계층의 트랜잭션 코드 제거
클래스 또는 메서드에 @Transactional 어노테이션 표시
클래스에 붙이면 public 메서드에 적용
메서드의 어노테이션은 클래스의 어노테이션을 오버라이드
트랜잭션 AOP는 빈으로 등록된 트랜잭션 매니저를 찾아 사용하기 때문에 등록해두어야 함. 아래 항목들을 빈으로 등록해야 함 (스프링 부트는 자동 등록되니 예외)
DataSource 구현체
TransactionManager 구현체(DataSource 의존)
실무에서는 선언적 트랜잭션 관리 방법을 사용함

스프링 부트의 데이터소스 자동 등록

스프링 부트가 application.properties에 있는 속성을 사용해 DataSource 구현체를 스프링 빈으로 자동 등록함
spring.datasource.url=jdbc:h2:tcp://localhost/~/test spring.datasource.username=sa spring.datasource.password=
Java
복사
스프링 부트는 HikariDataSource를 기본 데이터 소스로 등록함
스프링 부트는 등록된 라이브러리를 참고해서 적절한 PlatformTransactionManger 구현체를 스프링 빈으로 자동 등록함
JDBC를 사용하면 DataSourceTransactionManager를, JPA를 사용하면 JpaTransactionManager를 등록
둘 다 사용하는 경우 JpaTransactionManager 등록(DataSourceTransactionManager 기능 대부분 지원)
자동 등록 빈 이름: dataSourcetransactionManager
만약 개발자가 직접 등록하는 게 있으면 자동 등록은 되지 않음

DB 예외처리

직접 SQLException을 런타임 예외로 바꾸어 처리
Repository 내의 try-catch문에서 런타임 예외로 변경
catch (SQLException e) { throw new MyDbException(e); }
Java
복사
SQLException에는 DB에서 넘어온 ErrorCode가 있어서 에러 종류를 구분할 수 있음
스프링 데이터 접근 예외 계층 사용
Transient: 일시적인 예외. 쿼리 타임아웃, 락 등과 관련된 예외로 다시 시도하면 성공할 수 있음
NonTransient: 비 일시적인 예외. SQL 문법, DB 제약조건 위배 등과 관련된 예외로 다시 실행해도 실패
스프링 예외 변환기 SQLErrorCodeSQLExceptionTranslator 사용
SQLErrorCodeSQLExceptionTranslator exTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource); DataAccessException resultEx = exTranslator.translate("select", sql, e);
Java
복사
예외 매핑은 `org.springframework.jdbc.support.sql-error-codes.xml 파일에 정의됨
스프링 트랜잭션 예외처리 기본 설정 - 트랜잭션이 진행중인 쓰레드에서 예외가 발생할 경우
런타임 예외 - 롤백
체크드 예외 - 커밋

JdbcTemplate - JDBC 반복 문제 해결

JDBC 반복 문제
커넥션 조회, 커넥션 동기화
PreparedStatement 생성 및 파라미터 바인딩
쿼리 실행
결과 바인딩
예외 발생 시 스프링 예외 변환기 실행
리소스 종료
위 문제를 템플릿 콜백 패턴으로 해결한 것이 JdbcTemplate
사용법 예시
private final JdbcTemplate template; template.update(sql, member.getMemberId(), member.getMoney()); template.queryForObject(sql, memberRowMapper(), memberId); ... private RowMapper<Member> memberRowMapper() { return (rs, rowNum) -> { Member member = new Member(); member.setMemberId(rs.getString("member_id")); member.setMoney(rs.getInt("money")); return member; }; }
Java
복사