JDBC 그리고 ORM
1. JDBC(Java Database Connectivity)
JDBC는 자바 애플리케이션에서 데이터베이스에 액세스 하는 방법을 명세하는 인터페이스다.
데이터베이스 제품군은 연결 방법이 제각각이다. 따라서 각 데이터베이스에 연결하기 위해서 개발자는 각각의 데이터베이스에 모두 대응해야 한다. 자바 진영에서는 해당 문제를 의존성을 역전 시키는 방법으로 해결했다. 애플리케이션의 비지니스 로직이 데이터베이스 제품군에 의존하는 게 아니라 인터페이스에 의존하도록 했다. 해당 인터페이스가 바로 JDBC다. 자바는 Java Standard Edition에 JDBC 인터페이스를 제공하고, 각 데이터베이스 개발사는 해당 인터페이스를 구현한 DB Driver를 배포한다. 따라서 개발자는 DB 제품군이 변경되더라도 코드를 수정하지 않아도 된다.
JDBC Sample code
드라비어 매니저 인스턴스로부터 커넥션을 얻고, 스테이트멘트를 생성하고, 쿼리를 날리고, 결과를 얻고, 커넥션을 닫는다.
그런데 JDBC를 사용할 때마다 무수히 반복되는 예외처리 코드를 만나게 된다. 어떤 작업을 하더라도 동일한 코드를 반복한다. 커넥션을 획득하고, 스테이트멘트를 준비하고, 쿼리를 날리고, 결과를 반복하고, 커넥션을 반납한다. 이러한 중복 작업을 대신 해주기 위해 등장한 것이 SQL Mapper다.
2. SQL Mapper
SQL 매퍼는 JDBC를 사용하기 위해 필요한 중복 코드를 콜백 패턴을 활용해서 대신 처리 해준다.
JDBC Template sample code
JdbcTemplate 객체를 사용하는 코드 예시다. JdbcTemplate 인스턴스를 생성하고 update 쿼리를 실행하는 내부 과정을 뜯어본다.
우선 JdbcTeamplate 객체 생성자에 dataSource를 아규먼트로 넘겨주면 일어나는 일을 확인 해본다.
setDataSource 메서드는 JdbcAccessor 객체에 dataSource만 넘겨준다.
다음으로 afterPropertiesSet 메서드는 getDataSource 메서드를 실행해서 데이터소스 존재 유무를 확인한다. 다음으로 LazyInit이 아닌 경우 예외를 뱉는다. (Spring JdbcTemplate을 사용하고 있으므로, 이때 최종 예외는 SQLExceptionTranslator다.)
SQLExceptionTranslator
데이터 소스에서 발생한 JDBC 예외를 Spring에서 사용하는 SQLException, DataAccessException, DataAccessResourceFailureException 등으로 변환해준다.
LazyInit
JdbcTemplate 인스턴스를 생성할 때 설정할 수 있으며 기본 옵션은 true다. 해당 옵션은 JdbcTemplate 인스턴스가 실제로 사용되기 전까지는 초기화하지 않는 옵션이다. 따라서 애플리케이션 초기화시 JdbcTemplate을 생성하지 않으므로 애플리케이션 실행 시간을 단축할 수 있다. 반대로 애플리케이션 초기화 시 디비 커넥션을 무조건 확인해야 한다면 옵션에 false를 주면 된다.
update method
update 메서드는 내부에서 UpdateStatementCallback 클래스의 인스턴스를 excute 메서드에 넘겨준다.
excute method
excute 메서드는 콜백 패턴이 적용되어 있다. StatementCallback<T> action 파라미터에 넘어오는 콜백 인스턴스를 받아서 다음 로직을 실행한다.
- DB 커넥션 풀에서 커넥션을 얻는다. (Spring은 기본적으로 HikariCP를 사용한다.)
- 스테이트멘트를 생성한다.(이 과정은 java.sql 패키지의 Statement 인터페이스의 구현체에서 확인할 수 있다.)
- ApplyStatementSetting 메서드에 2번에서 생성한 스테이트멘트 인스턴스를 태운다. (해당 메서드는 fetch 사이즈와 max row 카운트를 설정하는 메서드다.)
- 스테이트멘트 인스턴스를 콜백 인스턴스의 doInStatement 메서드에 넘기고 실행한다.
- 스테이트멘트에서 예외 경고를 확인하고, 결과를 반환한다.
위 과정이 JdbcTemplate 사용하여 DB에 쿼리를 날릴때 일어나는 일련의 과정이다. 요약 하자면 update, select 등을 위한 각각의 StatementCallback 클래스가 존재한다. excute 메서드는 콜백 패턴을 사용해서 Callback 인스턴스를 넘겨 받고 공통 로직을 처리하는 방식으로 중복 코드를 제거한다.
하지만 SQL Mapper도 단점이 존재한다. 주관적인 관점에서는 SQL 쿼리를 직접 작성해야 한다는 점이 가장 치명적이다. SQL 쿼리는 자바 컴파일러가 타입 체크를 해주지 못한다. 따라서 애플리케이션을 실행시켜서 쿼리를 날리기 전까지 신택스 에러를 잡지 못한다.
3. ORM(Object Relational Mapping)
Object는 객체를, Relational은 관계형 데이터베이스를 의미한다. 객체와 관계형 데이터베이스를 매핑해주는 프레임워크다. 자바 진영의 표준 ORM 인터페이스는 JPA다. 대표적인 구현체는 Hibernate가 있다.
ORM은 왜 등장하게 되었을까?
객체 지향 기반의 애플리케이션과 관계형 데이터베이스는 패러다임이 다르다. 객체 지향 프로그래밍은 클래스를 이용해서 객체를 생성하고 각 객체간의 책임과 역할을 나누고 협력한다. 이 과정에서 상속, 연관 관계 등이 존재한다. 반면에 디비는 상속 관계가 없다. 테이블과 관계를 이용해서 데이터를 저장한다. 이와 같은 패러다임의 불일치 문제로 객체 지향 프로그래밍 언어를 사용하는 애플리케이션에서는 디비에 객체를 저장하기 위해서 필연적으로 매핑 작업을 해주어야 한다. 객체를 디비 테이블의 패러다임에 맞추면 객체 간의 협력 관계를 만들 수 없는 치명적인 문제가 발생한다.
그렇다면 객체를 저장하면 알아서 패러다임 불일치 문제를 해결해주는 무언가가 없을까?
이 역할을 해주는 게 ORM이다. 다만 ORM은 객체와 DB 중간에 껴서 매핑 해주는 프레임워크이므로 어설프게 사용하면 블랙박스가 되어 DB를 고문할 수 있다.