Spring DB 1
JDBC
등장 이유
보통 애플리케이션은 클라이언트에서 요청이 들어오면 서버에서 DB에서 요청에 맞는 데이터를 알맞게 처리한다.
이런 과정에서 애플리케이션 서버와 DB는 서로 통신을 해야한다.
커넥션 연결
- TCP/IP를 사용SQL 전달
결과 응답
위와 같은 과정을 통해 DB와 통신하는데 여기서 문제가 발생한다.
위 그림에서 Mysql -> Oracle DB로 바뀔 시 각각 데이터 베이스마다 케넥션 연결과 같은
사용 방법이 다 다르다
는 점이다.그렇기 때문에 DB를 바꾸면 애플리케이션의 모든 DB를 다 고쳐야되는 문제가 발생하고 이런 문제를 해결하기 위해
JDBC라는 자바 표준
이 등장한다.
JDBC 표준 인터페이스
JDBC(Java Database Connectivity)는 자바에서 데이터베이스에 접속할 수 있도록 하는 자바 API
대표 기능
java.sql.Connection
- 연결java.sql.Statement
- SQL을 담은 내용java.sql.ResultSet
- SQL 요청 응답
자바는 위와 같은 표준 인터페이스를 정의해두었다.
개발자는 이런 인터페이스를 사용해 개발하고 각 DB 벤더(회사)에서 제공하는 JDBC 드라이버만 갈아끼우면 된다.
하지만 이럼에도 일부 사용법의 차이는 해결하지 못했다. 대표적으로 페이징 SQL은 DB마다 문법의 차이가 있다. 이런 부분은 JPA로 해결 가능하다.
커넥션 풀
- DB와 애플리케이션이 연결되기 위해선 Connection을 생성하고 해당 커넥션에 연결하는 작업이 필요하다.
- 하지만 매 요청마다 커넥션을 만드는 것은 TCP/IP로 매번 DB와 통신해야되며 커넥션을 만드는 과정도 리소스를 많이 잡아먹는다.
- 이런 문제를 해결하기 위해 DB는 커넥션 풀이라는 개념을 도입했다.
- 커넥션 풀이란 미리 애플리케이션 로딩 시점에 커넥션을 미리 만들어 놓고 보관한 뒤 풀에서 꺼내서 쓰는 것을 말한다.
- 이런 방법을 통해 다양한 이점을 얻을 수 있다. 그렇기 때문에 스프링에서는 hikariCP라는 오픈 소스를 사용해 커넥션 풀을 관리한다.
DataSource
- 커넥션을 얻는 방법은 매우 다양하다.
- 직접 JDBC DriverManager를 사용하거나, 커넥션 풀인 hikariCP를 사용하는 등 매우 다양하기 때문에 변경을 쉽게 하기 위해 스프링에서는
DataSource라는 이름으로 추상화
했다. - DataSource를 사용하면 커넥션을 얻어오는 방식을 바꾸더라도 기존 코드를 변경하지 않고 사용이 가능하다.
Transaction
ACID
- 원자성(Automicity) - 트랜잭션 내에서 실행된 작업들은 마치 하나의 작업인 것처럼 모두 성공하거나 실패해야함
- 일관성(Consistency) - 모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야함. 예를 들어 db에서 정한 무결성 제약 조건을 항상 만족해야함
- 격리성(Isolation) - 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리해야함. 격리성은 동시성과 관련된 성능 이슈로 격리 수준을 선택 가능
- 지속성(Durability) - 트랜잭션을 성공적으로 끝내면 그 결과가 항상 기록되어야함. 중간에 시스템에 문제가 발생해도 데이터베이스 로그 등을 사용해 성공한 트랜잭션 내용을 복구해야함
DB 연결과 DB 세션
애플리케이션에서 DB에 접근할 때 DB 서버에 연결을 요청하고 커넥션을 맺는다.
이때 DB 서버는 내부에서 세션이라는 것을 만들어 해당 요청을 실행하게 된다.
DB의 모든 기능은 이 세션을 통해 실행되며, 커넥션을 닫거나, 세션을 강제로 종료하면 그때 세션이 종료된다.
이와 같이 10개의 커넥션을 생성하면 세션도 10개 만들어진다.
DB 락
- 만약 세션 1이 트랜잭션을 시작하고 데이터를 수정한 뒤 커밋을 수행하지 않았는데 다른 세션 2가 해당 데이터에 접근해 다른 값으로 데이터를 수정한다면 어떻게 될까?
- 이런 일이 발생한다면 트랜잭션의 원자성이 깨지게 되는 것이다. 이런 문제를 해결하기 위해 DB는 트랜잭션이 시작되면 다른 세션에서 접근할 수 없게 막는 DB 락이라는 개념을 제공한다.
- DB 락은 서로 다른 세션이 같은 데이터에 접근할 때 락을 획득한 사람만이 데이터를 변경할 수 있게 해주는 장치이다.
- DB 락은 조회 시 사용하지 않는다. 하지만 만약 조회 시에도 락을 획득하고 다른 세션이 데이터에 접근하는 것을 막을 수 있는데 select for update 를 사용하면 조회 시에도 락을 획득하게 된다.
트랜잭션 추상화
우리의 애플리케이션은 보통 프리젠테이션 계층과 서비스 계층, 데이터 접근 계층으로 나뉜다.
이 중 프리젠테이션 계층은 웹과 관련된 계층이고 서비스 계층은 비즈니스 로직과 관련된 계층, 데이터 접근 계층은 DB와 관련된 계층이다.
이때 서비스 계층은 중요한 점이 있는데 순수한 자바 코드로만 작성해야 된다는 점이다.
만약 서비스 계층이 특정 DB에 종속적으로 구현한다면 데이터 접근 계층에서 적용한 기술을 다른 기술로 바꿀 때 서비스 계층 또한 코드를 다 바꿔야한다.
또 트랜잭션의 경우도 서비스 계층에서 시작하는게 일반적인데 각 DB 접근 기술마다 트랜잭션을 시작하는 방법이 다른다는 문제도 있다.
이런 문제를 해결하기 위해 스프링은 트랜잭션 기술을 추상화한 트랜잭션 매니저를 제공해준다.
트랜잭션 동기화
스프링의 트랜잭션 매니저는 크게 두 가지 역할을 한다
- 트랜잭션 추상화
- 리소스 동기화
여기서 리소스 동기화란 트랜잭션이 시작하고 끝까지 같은 DB 커넥션을 유지해야한다. 서비스 계층에서 시작된 커넥션을 데이터 접근 계층까지 유지해야하는건데 이런 부분을 트랜잭션 매니저가 도와준다.
트랜잭션 매니저는 트랜잭션 동기화 매니저를 제공한다. 동기화 매니저는 쓰레드 로컬을 사용해 커넥션을 동기화 해준다.
트랜잭션 템플릿
트랜잭션 매니저를 사용하면 많은 부분 추상화가 가능해진다.
하지만 트랜잭션 매니저만으로는 아직 해결하지 못한 부분이 있는데 바로 트랜잭션 안에서 로직 성공시 커밋하고 실패시 롤백하는 부분은 아직 남아있다.
이런 공통 부분을 스프링에서는 템플릿 콜백 패턴을 적용해 해결했다.
트랜잭션 템플릿 사용 로직
1 2 3 4 5 6 7 8
txTemplate.executeWithoutResult((status) -> { try { //비즈니스 로직 bizLogic(fromId, toId, money); } catch (SQLException e) { throw new IllegalStateException(e); } })
트랜잭션 AOP
하지만 이럼에도 아직 서비스 계층에 비즈니스 로직을 제외한 try catch 문이 들어있는 등 완벽히 비즈니스 로직만 남기지 못했다.
이런 부분은 스프링은 AOP를 사용해 해결했다.
@Transaction 어노테이션이 바로 AOP를 적용하는 어노테이션이다.
참고) 스프링의 트랜잭션 AOP를 위한 포인트컷, 어드바이스, 어드바이저
- 어드바이저: BeanFactoryTransactionAttributeSourceAdvisor
- 포인트컷: TransactionAttributeSourcePointcut
- 어드바이스: TransactionInterceptor
예외
체크 예외를 언체크 예외로 변환할 때는 꼭 기존 예외를 포함시켜야한다.
언체크 예외의 경우 개발자가 놓칠 수 있기 때문에 문서화가 중요하다.
DB 관련 예외들은 DB 접근 기술마다 던지는 예외의 종류가 다르다.
그렇기 때문에 DB 접근 기술 관련 예외는 스프링이 추상화해서 제공한다.
위와 같이 언체크 예외를 상속 받은 예외들을 제공하는데 크게 2가지로 구분할 수 있다.
- Transient - 일시적이라는 뜻으로, 다시 시도했을 때 성공 가능성이 있음, 쿼리 타임아웃이나 락과 관련된 오류
- NonTransient - 성공 가능성 X, SQL 문법 오류 등
스프링은 예외 변환기를 통해 SQLException의 ErrorCode에 맞는 적절한 스프링 데이터 접근 예외로 변환해준다.
출처)
[스프링 DB 1편 - 데이터 접근 핵심 원리 강의 - 대시보드 | 인프런 (inflearn.com)](https://www.inflearn.com/course/스프링-db-1/dashboard) |