Last update: @2/22/2023
주의
본 포스팅은 인프런 강의를 통해 학습한 내용을 임의로 요약한 것으로 일부 내용의 오류 및 누락, 링크 숨김 등이 존재합니다.
JDBC(Java Database Connectivity)
•
자바에서 데이터베이스에 접근하는 방법을 표준화한 인터페이스
◦
java.sql 및 javax.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 인터페이스로 트랜잭션 추상화
•
TransactionManager는 TransactionSynchronizationManager를 통해 쓰레드 로컬에 커넥션 보관
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 기능 대부분 지원)
•
자동 등록 빈 이름: dataSource 및 transactionManager
•
만약 개발자가 직접 등록하는 게 있으면 자동 등록은 되지 않음
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
복사