토비의 스프링 Vol.2 - 2장 데이터 액세스 기술
데이터 액세스 기술
공통 개념
DAO 패턴
데이터 액세스 계층은 DAO 패턴이라 불리는 방식으로 분리하는 것이 원칙이다.
DAO 패턴은 DTO 또는 도메인 오브젝트만을 사용하는 인터페이스를 통해 데이터 엑세스 기술을 외부에 노출하지 않도록 만드는 것이다. 따라서 DAO는 구현 기술에 대한 정보를 외부에 공개해서는 안 된다. 이를 통해 DAO를 사용하는 코드에 영향을 주지 않고 데이터 액세스 기술을 변경하거나 하나 이상의 데이터 액세스 기술을 혼합해서 사용할 수 있게 해준다. 가장 중요한 장점은 DAO를 이용하는 서비스 계층의 코드를 기술이나 환경에 종속되지 않는 순수한 POJO로 개발할 수 있다는 것이다.
DAO 인터페이스와 DI
DAO는 인터페이스를 이용해 접근하고 DI 되도록 만들어야 한다. DAO 인터페이스에는 구체적인 데이터 액세스 기술과 관련된 어떤 API나 정보도 노출하지 않는다.
- 인터페이스를 만들 때 습관적으로 DAO 클래스의 모든 public 메소드를 추가하지 않아야 한다. DAO를 사용하는 서비스 계층 코드에서 의미 있는 메소드만 인터페이스로 공개해야 한다.
- 특정 데이터 액세스 기술에서만 의미 있는 DAO 메소드의 이름은 피한다.
예외 처리
데이터 액세스 중에 발생하는 예외는 대부분 복구할 수 없다. 따라서 DAO 밖으로 던져질 때는 런타임 예외여야 한다.
- 의미 있는 예외를 처리할 때는 스프링에서 제공하는 예외 추상화를 사용한다.
- 데이터 액세스 기술의 API를 직접 사용할 때는 스프링에 내장된, AOP를 이용해 예외를 전환해준다.
템플릿과 API
스프링은 DI의 응용 패턴인 템플릿/콜백 패턴을 이용해 데이터 액세스 기술을 위한 템플릿을 제공한다. 미리 만들어진 작업 흐름을 담은 템플릿은 반복되는 코드를 제거해줄 뿐 아니라 예외 변환과 트랜잭션 동기화 기능도 함께 제공해준다.
DataSource
Connection은 모든 데이터 액세스 기술에서 사용되는 필수 리소스다. 보통 미리 정해진 개수만큼의 DB 커넥션을 풀에 준비해두고, 애플리케이션이 요청할 때마다 풀에서 꺼내 하나씩 할당해주고 다시 돌려받아서 풀에 넣는 식의 풀링 기법을 이용한다.
스프링에서는 DataSource를 하나의 독립된 빈으로 등록하도록 강력하게 권장한다. 스프링 데이터 액세스 기술의 다양한 서비스에서 DataSource를 필요로 하고 있기 때문에 공유 가능한 스프링 빈으로 등록해줘야 한다.
- SimpleDriverDataSource
- 스프링이 제공하는 가장 단순한 DataSource 구현 클래스다. getConnectioin()을 호출할 때마다 매번 DB 커넥션을 새로 마들고 따로 풀을 관리하지 않는다. 따라서 실전에서는 절대 사용하면 안 되고, 단순한 테스트용으로만 사용해야 한다.
- SingleConnectionDataSource
- 하나의 물리적인 DB 커넥션만 만들어두고 이를 계속 사용하는 DataSource다. 순차적으로 진행되는 통합테스트에서는 사용 가능하지만 동시에 두 개 이상의 스레드가 동작하는 경우에는 하나의 커넥션을 공유하게 되므로 위험하다.
- 아파치 Commons DBCP
- 가장 유명한 오픈소스 DB 커넥션 풀 라이브러리다.
- c3p0 JDBC/DataSource Resource Pool
- c3p0는 JDBC 3.0 스펙을 준수하는 Connection과 Statement 풀을 제공하는 라이브러러리다.
- 상용 DB 커넥션 풀
- 일부 상용 DB는 자체적으로 커넥션 풀 라이브러리를 제공해준다.
- JDNI/WAS DB 풀
- 대부분의 자바 서버는 자체적으로 DB 풀 서비스를 제공해준다. DB 풀 라이브러리를 사용해 애플리케이션 레벨의 전용 풀을 만드는 대신 서버가 제공하는 DB 풀을 사용해야 하는 경우에는 JNDI를 통해 서버의 DataSource에 접근해야 한다.
<jee:jndi-lookup>
태그를 이용하면 JNDI를 통해 가져온 오브젝트를 스프링의 빈으로 사용할 수 있다.
- 대부분의 자바 서버는 자체적으로 DB 풀 서비스를 제공해준다. DB 풀 라이브러리를 사용해 애플리케이션 레벨의 전용 풀을 만드는 대신 서버가 제공하는 DB 풀을 사용해야 하는 경우에는 JNDI를 통해 서버의 DataSource에 접근해야 한다.
JDBC
JDBC는 자바의 데이터 액세스 기술의 기본이 되는 로우레벨의 API다.
스프링 JDBC 기술과 동작원리
스프링의 JDBC 접근 방법
- SimpleJdbcTemplate
- JdbcTemplate과 NameParameterJdbcTemplate에서 가장 많이 사용되는 기능을 통합하고 자바 5 이상의 장점을 최대한 활용할 수 있게 만든 것이다. 방대한 템플릿 메소드와 내장된 콜백을 제공하며, JDBC의 모든 기능을 최대한 활용할 수 있는 유연성을 갖고 있다.
- SimpleJdbcInsert, SimpleJdbcCall
- DB가 제공해주는 메타정보를 활용해서 최소한의 코드만으로 단순한 JDBC 코드를 작성하게 해준다. 메타정보에서 컬럼 정보와 파라미터 정보를 가져와서 삽입용 SQL과 프로시저 호출 작업에 사용해주기 때문에 매우 편리하다.
스프링 JDBC가 해주는 작업
- DB가 제공해주는 메타정보를 활용해서 최소한의 코드만으로 단순한 JDBC 코드를 작성하게 해준다. 메타정보에서 컬럼 정보와 파라미터 정보를 가져와서 삽입용 SQL과 프로시저 호출 작업에 사용해주기 때문에 매우 편리하다.
- Connection 열기와 닫기
- Statement 준비와 닫기
- Statement 실행
- ResultSet 루프
- 예외처리와 변환
- 트랜잭션 처리
SimpleJdbcTemplate
SimpleJdbcTemplate이 제공하는 기능은 실행, 조회, 배치의 세 가지 작업으로 구분할 수 있다.
SimpleJdbcTemplate 생성
SimpleJdbcTemplate template = new SimpleJdbcTemplate(dataSource);
DataSource는 보통 빈으로 등록해두므로 SimpleJdbcTemplate이 필요한 DAO에서 DataSource 빈을 DI 받아 SimpleJdbcTemplate을 생성해두고 사용하면 된다.SQL 파라미터
- 위치 치환자 -
INSERT INTO MEMBER(ID, NAME, POINT) VALUES(?, ?, ?);
- 이름 치환자 -
INSERT INTO MEMBER(ID, NAME, POINT) VALUES(:id, :name, :point);
이름을 이용해 바인딩해주기 때문에 중간에 순서가 바뀌어도 파라미터 바인딩에는 영향을 주지 않는다. 이름 치환자를 선호한다.
뿐만아니라 이름 치환자는 맵이나 오브젝트에 담긴 내용을 키 값이나 프로퍼티 이름을 이용해 바인딩할 수 있다.
SQL 실행 메소드
update() 메소드를 호출할 때 SQL과 함께 바인딩할 파라미터를 전달하는 방법
- varargs
- Map
- SqlParameterSource
SQL 조회 메소드
- int queryForInt(String sql, [SQL 파라미터])
- long queryForLong(String sql, [SQL 파라미터])
T queryForObject(String sql, Class requiredType, [SQL 파라미터])
T queryForObject(String sql, RowMapper rm, [SQL 파라미터])
List query(String sql, RowMapper rm, [SQL 파라미터])
- Map<String, Object> queryForMap(String sql, [SQL 파라미터])
- List<Map<String, Object» queryForList(String sql, [SQL 파라미터])
SQL 배치 메소드
SQL 배치 메소드는 update()로 실행하는 SQL들을 배치 모드로 실행하게 해준다. 내부적으로 JDBC Statement의 addBatch()와 executeBatch() 메소드를 이용해 여러 개의 SQL을 한 번에 처리한다. 많은 SQL을 실행해야 하는 경우 배치 방식을 사용하면 DB 호출을 최소화할 수 있기 때문에 성능이 향상될 수 있다.
- int[] batchUpdate(String sql, Map<String, ?>[] batchValues)
- int[] batchUpdate(String sql, SqlParameterSource[] batchArgs)
- int[] batchUpdate(String sql, List<Object[]> batchArgs)
SimpleJdbcInsert
SimpleJdbcInsert 생성
SimpleJdbcInsert는 테이블별로 만들어서 사용한다.
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(dataSource);
SimpleJdbcInsert는 오브젝트를 생성한 후에 적어도 어떤 테이블을 적용할지 초기화해줘야 한다.초기화 메소드
- SimpleJcbcInsert with TableName(String tableName)
- SimpleJdbcInsert withSchemaName(String schemaName),
SimpleJdbcInsert withCatalogName(String catalogName) - SimpleJdbcInsert usingColumns(String… columnNames)
- SimpleJdbcInsert usingGeneratedKeyColumns(String… columnNames)
- SimpleJdbcInsertOperations withoutTableColumnMetaDataAccess()
SimpleJdbcInsert 실행
- int execute([이름 치환자 SQL 파라미터])
- Number executeAndReturnKey([이름 치환자 SQL 파라미터])
- keyHolder executeAndReturnKeyHolder([이름 치환자 SQL 파라미터])
SimpleJdbcCall
SimpleJdbcCall은 DB에 생성해둔 저장 프로시저 또는 저장 펑션을 호출할 때 사용한다.
SimpleJdbcCall 생성
SimpleJdbcCall은 dataSource를 이용해 생성한다. 멀티스레드 환경에 안전하게 공유 가능하므로 인스턴스 변수에 저장해두고 공유해서 사용해도 된다. 기본적으로 실행할 저장 프로시저나 저장 펑션 중의 하나로 초기화해줘야 한다.
초기화 메소드
- SimpleJdbcCallOperations withProcedureName(String procedureName)
- SimpleJdbcCallOperations withFunctionName(String functionName)
- SimpleJdbcCallOperations resurninResultSet(String parameterName, ParameterizedRowMapper rowMapper)
SimpleJdbcCall실행
-
T executeFunction(Class returnType, [SQL 파라미터])
-
T executeObject(Class returnType, [SQL 파라미터])
- Map<String, Object> execute([SQL 파라미터])
스프링 JDBC DAO
가장 권장되는 DAO 작성 방법은 DAO는 DataSource에만 의존하게 만들고 스프링 JDBC 오브젝트는 코드를 이용해 직접 생성하거나 초기화해서 DAO의 인스턴스 변수에 저장해두고 사용하는 것이다. 모든 JDBC 오브젝트는 한번 만들어지면 반복적으로 사용할 수 있기 때문에 초기에 만들어 인스턴스 변수에 저장해두는 게 좋다.
SimpleJdbcTemplate은 모든 DAO에서 동일하게 생성되는데, 이 중복은 SimpleJdbcTemplate을 독립적인 빈으로 등록하고 주입받는 방법이나, DAO의 공통 코드를 뽑아내 추상 클래스를 만들어두고 모든 DAO가 이를 상속하게 하는 방법으로 처리할 수 있다. 이미 스프링은 JdbcDaoSupport라는, JDBC를 이용한 DAO 작성에 사용할 수 있는 추상 클래스를 제공하고 있으며, 직접 만들어서 쓸 수도 있다.
iBatis SqlMaps
iBatis는 자바오브젝트와 SQL 문 사이의 자동매핑 기능을 지원하는 ORM 프레임워크다.
SqlMapClient 생성
iBatis의 핵심 API는 SqlMapClient 인터페이스에 담겨 있다. 스프링에서는 SqlMapClient를 빈으로 등록해두고 DAO에서 DI 받아 사용해야 하기 때문에 SqlMapClient를 빈으로 등록해주는 팩토리 빈의 도움이 필요하다. 스프링이 제공하는 SqlMapClient용 팩토리 빈은 SqlMapClientFactoryBean이다. 이 빈을 이용해서 DAO에서 사용할 SqlMapClient를 빈으로 등록해줘야 한다.
iBatis 설정파일과 매핑파일
- 설정파일
- 설정파일에는 데이터소스, 트랜잭션 매니저, 매핑 리소스 파일 목록, 프로퍼티, 타입별칭과 핸들러, 오브젝트 팩토리와 설정 프로퍼티 값을 넣을 수 있다.
- DB 커넥션과 트랜잭션 관리를 위한 정보는 iBatis 설정파일에 넣지 않고 스프링 빈으로 등록한 것을 사용하는 게 바람직하다.
- 매핑파일
- 매핑정보에는 사용할 SQL 문과 SQL 파라미터, 실행 결과를 어떻게 자바오브젝트로 변환하는지가 담겨 있다.
SqlMapClient를 위한 SqlMapClientFactoryBean 등록
SqlMapClient를 싱글톤 빈으로 등록해서 필요한 DAO에서 DI 받아 사용할 수 있다. SqlMapClient는 멀티스레드에서 공유해서 사용해도 안전한 오브젝트다. SqlMapClient의 구현 클래스를 직접 빈으로 등록하는 대신 SqlMapClientFactoryBean을 이용해 팩토리 빈을 생성해줘야 한다. ```
- 매핑정보에는 사용할 SQL 문과 SQL 파라미터, 실행 결과를 어떻게 자바오브젝트로 변환하는지가 담겨 있다.
#### SqlMapClientTemplate
iBatis용 DAO는 SqlMapClient 빈을 DI 받아서 iBatis 기능을 이용한다. 이 때 SqlMapClient를 직접 사용하는 대신 스프링이 제공하는 템플릿 오브젝트인 SqlMapClientTemplate을 이용하는 것이 좋다.
##### 등록, 수정 삭제
* insert()
* update()
* delete()
##### 조회
* 단일 로우 조회: queryForObject()
* 다중 로우 조회: queryForList()
* 다중 로우 조회: queryForMap()
* 스프링 JDBC의 queryForMap()과 맵에 내용이 담기는 방식이 전혀 다르다. 스프링의 queryForMap()은 하나의 로우를, 컬럼을 키로 해서 맵에 넣는다. 반면 iBatis의 queryForMap()은 맵의 엔트리마다 로우 하나의 내용이 들어간다. 맵의 키는 지정된 컬럼 값이 사용된다.
* 다중 로우 조회: queryWithRowHandler()
* 해당 로우를 매핑한 결과를 어떻게 처리할지를 스스로 결정한다.
##### SqlMapClientCallback
스프링이 제공해주는, 콜백이 내장된 템플릿 메소드를 이용하는 대신 직접 iBatis의 SqlMapExecutor의 API를 사용하고 싶다면 SqlMapClientCallback 인터페이스를 사용할 수 있다.
<hr/>
### JPA
JPA는 JavaPersistent API의 약자로 EJB 3.0과 함께 등장한 JavaEE와 JavaSE를 위한 영속성(persistence)관리와 O/R 매핑(ORM)을 위한 표준 기술이다.
ORM이란 오브젝트와 RDB 사이에 존재하는 개념과 접근 방법, 성격의 차이 때문에 요구되는 불편한 작업을 제거해줘서 자바 개발자가 오브젝트를 가지고 정보를 다루면 ORM 프레임워크가 이를 RDB에 적절한 형태로 변환해주거나 그 반대로 RDB에 저장되어 있는 정보를 자바오브젝트가 다루기 쉬운 형태로 변환해주는 기술이다.
#### EntityManagerFactory 등록
JPA 퍼시스턴스 컴텍스에 접근하고 엔티티 인스턴스를 관리하려면 JPA의 핵심 인터페이스인 EntityManager를 구현한 오브젝트가 필요하다. EntityManager는 JPA에서 두 가지 방식으로 관리된다. 하나는 애플리케이션이 관리하는 EntityManager이고, 다른 하나는 컨테이너가 관리하는 EntityManager다. 컨테이너가 관리하는 EntityManager를 위해서는 JavaEE 환경과 서버가 필요하다. 애플리케이션이 관리하는 EntityManager는 JavaEE와 JavaSE에서 모두 사용할 수 있다.
##### LocalEntityManagerFactoryBean
LocalEntityManagerFactoryBean은 JPA 스펙의 JavaSE 기동 방식을 이용해 EntityManagerFactory를 생성해준다.
> 이 방식은 JPA만을 사용하는 단순한 환경에 적용할 수는 있지만 스프링에서 본격적으로 사용하기에는 많은 제약사항이 있다.
##### JavaEE 5 서버가 제공하는 EntityManagerFactory
JPA는 JavaSE 환경보다는 JavaEE에서 서버가 제공하는 JPA 프로바이더를 통해 사용하는 것이 일반적이다. 스프링 애플리케이션에서는 JNDI를 통해 서버가 제공하는 EntityManager와 EntityManagerFactory를 제공받을 수 있다. 또 서버의 JTA를 이용해 트랜잭션 관리 기능을 활용할 수 있다.
##### LocalContainerEntityManagerFactoryBean
LocalContainerEntityManagerFactoryBean은 스프링이 직접 제공하는 컨테이너 관리 EntityManager를 위한 EntityManagerFactory를 만들어준다. 이 방법을 이용하면 JavaEE 서버에 배치하지 않아도 컨테이너에서 동작하는 JPA의 기능을 활용할 수 있을 뿐만 아니라, 스프링이 제공하는 일관성 있는 데이터 액세스 기술의 접근 방법을 적용할 수 있고 스프링의 JPA 확장 기능도 활용할 수 있다.
<bean id=”emf class=”org.springframework.orm.jpa.LacalContainerEntityManagerFactoryBean”>
</bean>
###### LocalContainerEntityManagerFactoryBean의 프로퍼티
* persistenceUnitName
* persistenceXmlLocation
* jpaProperties, jpaPropertyMap
* jpaVendorAdapter
* loadtimeWeaver
##### 트랜잭션 매니저
컨테이너가 관리하는 EntityManager 방식에서는 컨테이너가 제공하는 트랜잭션 매니저가 반드시 필요하다.
* JPA용 트랜잭션 매니저
```
JpaTransactionManager를 등록하고 EntityManagerFactory 빈을 프로퍼티에 등록해주면 된다.
EntityManager와 JpaTemplate
스프링에서는 템플릿 방식의 JpaTemplate뿐 아니라 JPA API를 직접 사용해 DAO를 작성할 수도 있다. JPA의 핵심 프로그래밍 인터페이스는 EntityManager다. EntityManager 오브젝트를 가져올 수 있으면 JPA의 모든 기능을 이용할 수 있다.
- JpaTemplate
- 템플릿을 이용하는 방법은 반복되는 작업을 줄여주고 예외 변환 같은 편리한 기능을 제공해줄 수 있다는 장점이 있는 반면에, 데이터 액세스 기술이 직접 제공하는 API를 사용하는 대신 템플릿의 메소드와 콜백을 사용하게 된다는 단점이 있다.
- 애플리케이션 관리 EntityManager와 @PersistenceUnit
- 컨테이너 대신 애플리케이션 코드가 관리하는 ENtityManager를 이용.
- 컨테이너 관리 ENtityManager와 @PersistenceContext
- 컨테이너가 제공하는 EntityManager를 직접 제공받아서 사용한다. DAO가 컨테이너로부터 EntityManager를 직접 주입받으려면 JPA의 @PersistenceContext 애노테이션을 사용해야 한다.
- @PersistenceContext와 확장된 퍼시스턴스 컨텍스트
- @PersistenceContext 애노테이션을 이용해서 컨테이너가 관리하는 EntityManager를 주입받는다. 다만 type 엘리먼트의 값이 다르다. @PersistenceContext 애노테이션을 별다른 엘리먼트 설정 없이 사용하면 디폴트 값인 PersistenceContextType.TRANSACTION이 적용되면서 트랜잭션 스코프의 퍼시스턴스 컨텍스트로 EntityManager가 만들어지고 관리된다. 하지만 이 방법은 type을 PersistenceContextType.EXTENDED로 지정하여 트랜잭션 스코프 대신 확장된 스코프를 갖는 EntityManager가 만들어진다. JPA에서 이 확장된 퍼시스턴스 컨텍스트는 상태유지 세션빈에 바인딩되는 것을 말한다.
JPA 예외 변환
JpaTemplate이 JPA API를 직접 사용하는 방법이 좀 더 나은 점은 JPA의 예외를 스프링의 데이터 액세스 예외 추상화 클래스 계층인 DataAccessException의 예외로 변환해준다는 점이다.
반대로 JPA의 API를 사용하는 방법은 자동으로 예외 변환이 일어나지 않는다. 다행이도 JPA가 런타임 예외를 발생시키기 때문에 불필요한 try/catch 블록이나 throws 선언이 필요 없다. EntityManager와 같은 리소스 관리는 기본적으로 JPA 지원 컨테이너가 알아서 해주기 때문에 역시 신경 쓰지 않아도 된다.
JPA 예외 변환 AOP
JpaTemplate을 사용하지 않고 JPA API를 직접 이용하는 경우에도 JPA 예외를 스프링의 DataAccessException 예외로 전환시킬 수는 있다. 스프링의 AOP를 이용하면 된다.
- @Repository
- 예외 변환이 필요한 DAO 클래스에 @Repository 애노테이션을 부여한다.
- PersistenceExceptionTranslationPostProcessor
- @Repository 애노테이션이 붙은 빈을 찾아서 예외 변환 기능을 가진 AOP 어드바이스를 적용해주는 후처리기가 필요하다. PersistenceExceptionTranslationPostProcessor를 빈으로 등록해주기만 하면 된다.
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
- @Repository 애노테이션이 붙은 빈을 찾아서 예외 변환 기능을 가진 AOP 어드바이스를 적용해주는 후처리기가 필요하다. PersistenceExceptionTranslationPostProcessor를 빈으로 등록해주기만 하면 된다.
하이버네이트
하이버네이트는 가장 크게 성공한 오픈소스 ORM 프레임워크다. 복잡한 엔티티빈 대신 평범한 POJO로 SQL을 직접 사용하는 전통적인 방식 못지않게 강력하고 빠르면서도 편리한 ORM 방식의 개발이 가능함을 보여준 게 하이버네이트다. 스프링과 하이버네이트는 비슷한 시기에 POJO 프로그래밍을 바탕으로 자바 엔터프라이즈 개발의 혁신을 가져온 대표적인 기술이다.
SessionFactory 등록
하이버네이트에는 핵심 엔진 역할을 하는 SessionFactory가 있다. SessionFactory는 엔티티 매핑정보와 설정 프로퍼티 등을 이용해 초기화한 뒤에 애플리케이션에서 사용해야 한다.
- LocalSessionFactoryBean
- LocalSessionFactoryBean은 빈으로 등록된 DataSource를 이용해서 스프링이 제공하는 트랜잭션 매니저와 연동할 수 있도록 설정된 SessionFactory를 만들어주는 팩토리빈이다.
- LocalSessionFactoryBean이 SessionFactory를 설정하기 위한 프로퍼티 사용
- mappingLocations
- hibernateProperties
- AnnotationSessionFactoryBean
- 하이버네이트는 엔티티 클래스에 애노테이션을 부여하고 이를 매핑정보로 사용하는 방법을 제공한다.
트랜잭션 매니저
- 하이버네이트는 엔티티 클래스에 애노테이션을 부여하고 이를 매핑정보로 사용하는 방법을 제공한다.
- HibernateTransactionManager
- 단일 DB를 사용하고 JTA를 이용할 필요가 없다면 HibernateTransactionManager 빈을 추가한다. HibernateTransactionManager를 사용하면 하이버네이트 DAO와 JDBC DAO를 같은 트랜잭션으로 묶어서 동작시킬 수 있다. 동일한 DataSource를 사용하도록 SessionFactory와 JDBC DAO를 설정해주기만 하면 된다.
- JtaTransactionManager
- 여러 개의 DB에 대한 작업을 하나의 트랜잭션으로 묶으려면 JTA를 통해서 서버가 제공하는 글로벌 트랜잭션 기능을 이용해야 한다.
Session과 HibernateTemplate
Session은 하이버네이트의 핵심 API다. Session은 SessionFactory로부터 만들어지며 보통 트랜잭션과 동일한 스코프를 갖고 있다. 하이버네이트 DAO는 스프링이 관리하는 트랜잭션과 동기화된 Session을 가져와 사용한다.
- 여러 개의 DB에 대한 작업을 하나의 트랜잭션으로 묶으려면 JTA를 통해서 서버가 제공하는 글로벌 트랜잭션 기능을 이용해야 한다.
- HibernateTemplate
- 스프링의 템플릿/콜백 패턴이 적용된 HibernateTemplate을 이용하는 방법이다. HibernateTemplate은 하이버네이트의 Session에 있는 대부분의 기능을 템플릿이 제공하는 메소드를 통해 이용하게 해준다.
- 데이터 액세스 기술의 템플릿 스타일을 특별히 선호하는 경우가 아니라면 HibernateTemplate의 사용은 그다지 권장되지 않는다.
- SessionFactory.getCurrentSession()
- 현재 트랜잭션에 연결되어 있는 하이버네이트 Session을 돌려준다. 이를 이용하면 스프링의 트랜잭션 매니저 또는 JTA의 트랜잭션에 연동되어 만들어지는 Session을 가져올 수 있다.
- 이 방법은 반드시 트랜잭션이 시작된 후에만 사용될 수 있다. 그렇지 않으면 현재 스레드에 바인딩된 Session이 없다는 하이버네이트 예외가 발생한다.
트랜잭션
트랜잭션 추상화와 동기화
스프링이 제공하는 트랜잭션 서비스는 트랜잭션 추상화와 트랜잭션 동기화 두 가지로 생각해볼 수 있다.
스프링은 데이터 액세스 기술과 트랜잭션 서비스 사이의 종속성을 제거하고 스프링이 제공하는 트랜잭션 추상 계층을 이용해서 트랜잭션 기능을 활용하도록 만들어준다. 이를 통해 트랜잭션 서비스의 종류나 환경이 바뀌더라도 트랜잭션을 사용하는 코드는 그대로 유지할 수 있는 유연성을 얻을 수 있다.
스프링의 트랜잭션 동기화는 트랜잭션을 일정 범위 안에서 유지해주고, 어디서든 자유롭게 접근할 수 있게 만들어준다.
PlatformTransactionManager
스프링 트랜잭션 추상화의 핵심 인터페이스는 PlatformTransactionManager다. PlatformTransactionManager는 트랜잭션 경계를 지정하는 데 사용한다.
트랜잭션 매니저의 종류
스프링이 제공하는 PlatformTransactionManager 구현 클래스
- DataSourceTransactionManager
- JpaTransactionManager
- HibernateTransactionManager
- JmsTransactionManager, CciTransactionManager
- JtaTransactionManager
트랜잭션 경계설정 전략
트랜잭션의 시작과 종료가 되는 경계는 보통 서비스 계층 오브젝트의 메소드다. 비즈니스 로직이 거의 없어서 서비스 계층과 데이터 액세스 계층을 통합했다면, 통합된 계층의 메소드가 트랜잭션 경계가 될 것이다.
트랜잭션 경계를 설정하는 방법은 코드에 의한 프로그램적인 방법과, AOP를 이용한 선언적인 방법으로 구분할 수 있다. 전자는 트랜잭션을 다루는 코드를 직접 만들고, 후자는 AOP를 이용해 기존 코드에 트랜잭션 경계설정 기능을 부여해준다.
코드에 의한 트랜잭션 경계설정
- PlatformTransactionManager의 메소드를 직접 사용하는 대신 템플릿/콜백 방식의 TransactionTemplate을 이용하면 편리하다.
- 트랜잭션의 기본 속성을 변경하려면 TransactionTemplate을 만들 때 TransactionDefinition 오브젝트를 만들어서 파라미터로 제공해주면 된다.
- 코드에 의한 트랜잭션 경계설정은 실제로는 많이 사용되지 않는다.
선언적 트랜잭션 경계설정
- 선언적 트랜잭션을 이용하면 코드에는 전혀 영향을 주지 않으면서 특정 메소드 실행 전후에 트랜잭션이 시작되고 종료되거나 기존 트랜잭션에 참여하도록 만들 수 있다. 간단한 설정으로 특정 부가기능을 임의의 타깃 오브젝트에 부여해줄 수 있는 프록시 AOP를 주로 활용한다.
- AOP를 이용해 트랜잭션 기능을 부여할 때는 tx 네임스페이스, @Transactional을 이용한 방법을 많이 사용한다.
프록시 모드: 인터페이스와 클래스
스프링의 AOP는 기본적으로 다이내믹 프록시 기법을 이용해 동작한다. 다이내믹 프록시를 적용하려면 인터페이스가 있어야 한다. 하지만 특별한 경우에 인터페이스를 구현하지 않은 클래스에 트랜잭션을 적용해야 할 때는 스프링이 지원하는 클래스 프록시 모드를 사용하면 된다.
- aop/tx 스키마 태그의 클래스 프록시 설정
<aop:config proxy-target-class="true">
- @Transactional의 클래스 프록시 설정
<tx:annotation-driven proxy-target-class="true" />
- @Transactional은 클래스에 부여해야 한다.
- 클래스 프록시는 일반적으로 인터페이스를 구현하지 않는 클래스에 주로 사용된다. 하지만 원한다면 인터페이스가 있는 클래스에 클래스 프록시를 강제로 적용할 수도 있다. 이때는 반드시 클래스에 @Transactional을 부여해줘야 한다.
- 클래스 프록시의 제약사항을 알아야 한다.
- 클래스 프록시는 final 클래스에는 적용할 수 없다.
- 클래스 프록시를 적용하면 클래스의 생성자가 두 번 호출된다. 이 때문에 생성자에서 리소스를 할당하는 것 같은 중요한 작업은 피해야 한다.
- 불필요한 메소드에 트랜잭션이 적용될 수 있다.
- 클래스의 모든 public 메소드에 트랜잭션이 적용된다. 따라서 수정자 같은, 클라이언트가 사용하지는 않지만 public으로 정의하는 메소드에도 트랜잭션이 적용되는 문제가 발생한다.
클래스 프록시는 코드를 함부로 손댈 수 없는 레거니 코드나, 여타 제한 때문에 인터페이스를 사용하지 못했을 경우에만 사용해야 한다.
AOP 방식: 프록시와 AspectJ
AspectJ AOP는 스프링과 달리 프록시를 타깃 오브젝트 앞에 두지 않는다. 대신 타깃 오브젝트 자체를 조작해서 부가기능을 직접 넣는 방식이다. 마치 처음부터 타깃 오브젝트의 클래스에 부가기능을 가진 소스코드가 있었던 것처럼 만들어준다. 메소드 실행 지점만 조인 포인트로 사용할 수 있는 프록시 방식의 스프링 AOP에서는 불가능한 다양한 조인 포인트와 고급 기능을 이용할 수 있다.
트랜잭션 속성
트랜잭션 전파:propagation
- 클래스의 모든 public 메소드에 트랜잭션이 적용된다. 따라서 수정자 같은, 클라이언트가 사용하지는 않지만 public으로 정의하는 메소드에도 트랜잭션이 적용되는 문제가 발생한다.
- REQUIRED
- SUPPORTS
- MANDATORY
- REQUIRES_NEW
- NOT_SUPPORTES
- NEVER
- NESTED
트랜잭션 격리수준: isolation
트랜잭션 격리수준은 동시에 여러 트랜잭션이 진행될 때에 트랜잭션의 작업 결과를 여타 트랜잭션에게 어떻게 노출할 것인지를 결정하는 기준이다.
- DEFAULT
- READ_UNCOMMITTED
- READ_COMMITTED
- REPEATABLE_READ
- SERIALIZABLE
트랜잭션 제한시간: timeout
이 속성을 이용하면 트랜잭션에 제한시간을 지정할 수 있다.
읽기전용 트랜잭션: read-only, readOnly
트랜잭션을 읽기전용으로 설정할 수 있다. 성능을 최적화하기 위해 사용할 수도 있고 특정 트랜잭션 작업 안에서 쓰기 작업이 일어나는 것을 의도적으로 방지하기 위해 사용할 수도 있다.
트랜잭션 롤백 예외: rollback-for, rollbackFor, rollbackForClassName
선언적 트랜잭션에서는 런타임 예외가 발생하면 롤백한다. 반면에 예외가 전혀 발생하지 않거나 체크 예외가 발생하면 커밋한다. 하지만 원한다면 기본 동작방식을 바꿀 수 있다.
트랜잭션 커밋 예외: no-rollback-for, noRollbackFor, noRollbackForClassName
기본적으로는 롤백 대상인 런타임 예외를 트랜잭션 커밋 대상으로 지정해준다.
데이터 액세스 기술 트랜잭션의 통합
스프링은 두 개 이상의 데이터 액세스 기술로 만든 DAO를 하나의 트랜잭션으로 묶어서 사용하는 방법을 제공한다. 물론 이때도 DB당 트랜잭션 매니저는 하나만 사용한다. 대신 하나의 트랜잭션 매니저가 여러 개의 데이터 액세스 기술의 트랜잭션 기능을 지원해주도록 만든 것이다.
트랜잭션 매니저별 조합 가능 기술
- DataSourceTransactionManager
- JDBC와 iBatis 두 가지 기술을 함께 사용할 수 있다.
- JpaTransactionManager
- JPA, JDBC, iBatis 세 가지 기술의 DAO가 하나의 트랜잭션으로 동기화된다.
- Hibernate TransactionManager
- 하이버네이트, JDBC, iBatis 세 가지 기술의 DAO를 통합해서 사용할 수 있다.
- JtaTransactionManager
- 하나 이상의 DB 또는 JMS와 같은 트랜잭션이 지원되는 서비스를 통합해서 하나의 트랜잭션으로 관리할 수 있다.
ORM과 비 ORM DAO를 함께 사용할 때의 주의사항
JPA나 하이버네이트 같은 엔티티 기반의 ORM 기술과 JDBC, iBatis 같은 SQL 기반의 비 ORM 기술을 함께 사용하고 하나의 트랜잭션으로 묶어서 사용하는 것은 기술적으로 볼때 아무런 문제가 없지만, 각 기술의 특징을 잘 이해하지 않으면 예상치 못한 오류를 만날 수 있다.
- 하나 이상의 DB 또는 JMS와 같은 트랜잭션이 지원되는 서비스를 통합해서 하나의 트랜잭션으로 관리할 수 있다.
JPA나 하이버네이트는 단순히 JDBC API를 간접적으로 실행해주는 방식이 아니다. 새로 등록된 오브젝트를 일단 엔티티 매니저나 세션에만 저장해둔다. 캐싱하는 것이다. 따라서 JPA의 저장이나 수정 작업을 한 후에는 강제로 캐시의 내용을 DB로 보내주는 EntityManager나 Session의 flush() 메소드를 사용하거나, JDBC의 DAO가 호출될 때 flush()를 호출하는 방법이 있다.
JTA를 이용한 글로벌/분산 트랜잭션
한 개 이상의 DB나 JMS의 작업을 하나의 트랜잭션 안에서 동작하게 하려면 서버가 제공하는 트랜잭션 매니저를 JTA를 통해 사용해야 한다. 스프링에서는 서버에 설정해둔 XA DataSource와 트랜잭션 매니저 그리고 UserTransaction 등을 JNDI를 통해 가져와 모든 데이터 액세스 기술에서 사용할 수 있다.
독립형 JTA 트랜잭션 매니저
JTA는 WAS가 제공하는 서비스를 이용하는 경우가 일반적이지만, 원한다면 서버의 지원 없이도 애플리케이션 안에 JTA 서비스 기능을 내장하는 독립형 JTA 방식으로 이용할 수 있다. 이 방식을 사용하면 JTA를 지원하는 WAS가 아닌 톰캣과 같은 서블릿 컨테이너에서도 JTA 기능을 이용하는 것이 가능하다.
독립형 JTA 트랜잭션 매니저는 ObjectWeb의 JTA 엔진인 JOTM과 Atomikos의 TransactionalEssentials가 대표적이다.
WAS 트랜잭션 매니저의 고급 기능 사용하기
스프링의 JtaTransactionManager는 JTA의 표준 스펙을 따르는 API를 사용해 트랜잭션을 관리한다. 그런데 WebLogic이나 OC4J, WebSphere 등의 고급 WAS에서는 표준 JTA는 지원하지 않는, WAS가 제공하는 고급 트랜잭션 기능을 활용할 수 있다.
- WAS별 전용 트랜잭션 매니저
- WebSphereUowTransactionManager
- WebSphereUowTransactionManager를 JtaTransactionManager 대신 사용하면 IBM WebSphere의 UOWManager를 통해서 WebSphere가 제공하는 트랜잭션 서비스의 기능을 최대한 활용할 수 있다.
- WebLogicJtaTransactionManager
- WebLogic 서버의 트랜잭션 서비스를 최대한 활용할 수 있게 해준다.
- OC4JJtaTransactionManager
- OC4J 서버의 트랜잭션 기능에 최적화된 트랜잭션 매니저다. tx 스키마의 jta-transaction-manager 태그를 이용하면 서버를 자동인식해서 적절한 JTA 트랜잭션을 등록해준다. 세 개의 서버 외에 배치됐을 때는 기본인 JtaTransactionManager가 사용된다.
<tx:jta-transaction-manager/>
스프링 3.1의 데이터 액세스 기술
스프링의 서브 프로젝트인 스프링 데이터(Spring Data) 프로젝트는 Hadoop, GemFire, Redis, Riak, MongoDA, CouchDB, Neo4j, HBase, Cassandra, S3 Blob등의 기술을 스프링 애플리케이션에서 손쉽게 사용할 수 있게 도와주는 모듈을 제공한다. 스프링 데이터 프로젝트는 JPA와 JDBC에 대해서도 스프링의 기본 데이터 액세스 전략 이상의 편리한 기능을 제공하는 확장 모듈을 지원하기도 한다.
persistence.xml 없이 JPA 사용하기
스프링 3.1에서는 스프링의 클래스 스캐닝 기술을 이용해 JPA 엔티티 클래스를 찾아 자동으로 등록하는 방법이 지원된다. JPA 엔티티 클래스가 담긴 패키지를 LocalContainerEntityManagerFactoryBean 빈의 packagesToScan 프로퍼티에 넣어주면 된다. 이렇게 해주면 persistence.xml 파일을 제거해도 된다.
하이버네이트 4 지원
- 스프링 3.1은 하이버네이트 4를 공식적으로 지원한다. org.springframework.orm.hibernate4 패키지 아래 클래스를 사용해야 한다.
- LocalSessionFactoryBean, LocalSessionFactoryBuilder
@EnableTransactionManager
@EnableTransactionManager는 XML의
<tx:annotation-driven/>
과 동일한 컨테이너 인프라 빈을 등록해주는 자바 코드 설정용 애노테이션이다. @Transactional 애노테이션을 이용한 트랜잭션 설정을 가능하게 해준다.
- LocalSessionFactoryBean, LocalSessionFactoryBuilder
트랜잭션 매니저가 두 개 이상 등록되어 있어서 어느 트랜잭션 매니저를 사용할지 @EnableTransactionManagement가 결정할 수 없거나 명시적으로 사용할 트랜잭션 매니저 빈을 지정하고 싶다면 TransactionManagementConfigurer 타입의 트랜잭션 관리 설정자를 이용해야 한다.
JdbcTemplate 사용 권장
스프링 3.1에서는 SimpleJdbcTemplate의 모든 기능을 JdbcTemplate과 NamedParameterJdbcTemplate이 제공하게 만들었고, SimpleJdbcTemplate은 더 이상 사용을 권장하지 않도록 @Deprecated 해버렸다.
정리
- DAO 패턴을 이용하면 데이터 액세스 계층과 서비스 계층을 깔끔하게 분리하고 데이터 액세스 기술을 자유롭게 변경해서 사용할 수 있다.
- 스프링 JDBC는 JDBC DAO를 템플릿/콜백 방식을 이용해 편리하게 작성할 수 있게 해준다.
- SQL 매핑 기능을 제공하는 iBatis로 DAO를 만들 때도 스프링의 템플릿/콜백 지원 기능을 사용할 수 있다.
- JPA와 하이버네이트를 이용하는 DAO에서는 템플릿/콜백과 자체적인 API를 선택적으로 사용할 수 있다.
- 트랜잭션 경계설정은 XML의 스키마 태그와 애노테이션을 이용해 정의할 수 있다. 또한 트랜잭션 AOP를 적용할 때는 프록시와 AspectJ를 사용할 수 있다.
- 스프링은 하나 이상의 데이터 액세스 기술로 만들어진 DAO를 같은 트랜잭션 안에서 동작하도록 만들어준다. 하나 이상의 DB를 사용할 때는 JTA 지원 기능을 활용해야 한다.
출처 : https://github.com/Masssidev/toby-vol2