Loading...
Spring Framework Reference Documentation 7.0.2의 Hibernate의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
Spring 환경에서 Hibernate를 다루는 것부터 시작하며, 이를 사용하여 Spring이 OR mapper를 통합하는 방식에 대해 설명합니다. 이 섹션은 많은 이슈들을 자세히 다루고 DAO 구현과 transaction 경계 설정의 다양한 변형을 보여줍니다. 이러한 패턴의 대부분은 지원되는 다른 모든 ORM 도구로 직접 변환될 수 있습니다. 이 장의 후반 섹션에서는 다른 ORM 기술들을 다루고 간단한 예제를 보여줍니다.
Spring Framework 7.0부터 Spring은 Spring의
HibernateJpaVendorAdapter를 위해 Hibernate ORM 7.x를 요구합니다.org.springframework.orm.jpa.hibernatepackage는 이전의orm.hibernate5를 대체합니다: 이제 Hibernate ORM 7.1+에서 사용되며,HibernateJpaVendorAdapter와 긴밀하게 통합되고 Hibernate의 nativeSessionFactory.getCurrentSession()스타일도 지원합니다.
SessionFactory Setup in a Spring Container애플리케이션 객체를 하드 코딩된 리소스 조회에 묶지 않기 위해,
JDBC DataSource나 Hibernate SessionFactory와 같은 리소스를
Spring 컨테이너 안의 빈으로 정의할 수 있습니다. 리소스에 접근해야 하는
애플리케이션 객체는 다음 섹션에 있는 DAO 정의에 나와 있는 것처럼
빈 reference를 통해 이러한 미리 정의된 인스턴스에 대한 reference를 받습니다.
다음 XML 애플리케이션 컨텍스트 정의의 발췌는 JDBC DataSource와 그 위에 Hibernate
SessionFactory를 설정하는 방법을 보여줍니다:
1<beans> 2 3 <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 4 <property name="driverClassName" value="org.hsqldb.jdbcDriver"/> 5 <property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/> 6 <property name="username" value="sa"/> 7 <property name="password" value=""/> 8 </bean> 9 10 <bean id="mySessionFactory" class="org.springframework.orm.jpa.hibernate.LocalSessionFactoryBean"> 11 <property name="dataSource" ref="myDataSource"/> 12 <property name="mappingResources"> 13 <list> 14 <value>product.hbm.xml</value> 15 </list> 16 </property> 17 <property name="hibernateProperties"> 18 <value> 19 hibernate.dialect=org.hibernate.dialect.HSQLDialect 20 </value> 21 </property> 22 </bean> 23 24</beans>
local Jakarta Commons DBCP BasicDataSource에서 JNDI에 위치한
DataSource(일반적으로 애플리케이션 서버에 의해 관리됨)로 전환하는 것은
다음 예제에서 보듯이 설정의 문제일 뿐입니다:
1<beans> 2 <jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/> 3</beans>
Spring의 JndiObjectFactoryBean / <jee:jndi-lookup>을 사용하여
JNDI에 위치한 SessionFactory에 접근하고 이를 노출할 수도 있습니다.
그러나 이는 일반적으로 EJB 컨텍스트 밖에서는 흔하지 않습니다.
Spring은 또한
@Bean스타일 설정 및 프로그래밍 방식 설정과 원활하게 통합되는LocalSessionFactoryBuildervariant를 제공합니다 (FactoryBean은 사용되지 않음).LocalSessionFactoryBean과LocalSessionFactoryBuilder모두 background 부트스트래핑을 지원하며, Hibernate 초기화가 주어진 부트스트랩 executor (예:SimpleAsyncTaskExecutor)에서 애플리케이션 부트스트랩 스레드와 병렬로 실행됩니다.LocalSessionFactoryBean에서는bootstrapExecutor프로퍼티를 통해 이를 사용할 수 있습니다. 프로그래밍 방식의LocalSessionFactoryBuilder에서는 부트스트랩 executor argument를 받는 오버로드된buildSessionFactory메서드가 있습니다. 이러한 native Hibernate setup은 native Hibernate access 옆에서 표준 JPA 상호 작용을 위한 JPAEntityManagerFactory를 노출할 수도 있습니다. 자세한 내용은 Native Hibernate Setup for JPA를 참조하세요.
Hibernate에는 contextual session이라는 기능이 있으며, 여기서 Hibernate 자체가
트랜잭션당 하나의 current Session을 관리합니다. 이는 대략적으로 Spring의
트랜잭션당 하나의 Hibernate Session 동기화와 동일합니다. 해당 DAO 구현은
plain Hibernate API를 기반으로 한 다음 예제와 유사합니다:
1public class ProductDaoImpl implements ProductDao { 2 3 private SessionFactory sessionFactory; 4 5 public void setSessionFactory(SessionFactory sessionFactory) { 6 this.sessionFactory = sessionFactory; 7 } 8 9 public Collection loadProductsByCategory(String category) { 10 return this.sessionFactory.getCurrentSession() 11 .createQuery("from test.Product product where product.category=?") 12 .setParameter(0, category) 13 .list(); 14 } 15}
1class ProductDaoImpl(private val sessionFactory: SessionFactory) : ProductDao { 2 3 fun loadProductsByCategory(category: String): Collection<*> { 4 return sessionFactory.currentSession 5 .createQuery("from test.Product product where product.category=?") 6 .setParameter(0, category) 7 .list() 8 } 9}
이 스타일은 instance 변수에 SessionFactory를 보관하는 것을 제외하면
Hibernate reference documentation과 예제의 스타일과 유사합니다. Hibernate의
CaveatEmptor sample 애플리케이션에 있는 구식 static HibernateUtil class보다
이러한 instance 기반 setup을 강력히 권장합니다. (일반적으로, 절대적으로 필요하지
않은 한 어떤 리소스도 static 변수에 보관하지 마십시오.)
앞의 DAO 예제는 의존성 주입 패턴을 따릅니다. 이는 Spring의
HibernateTemplate에 대해 작성된 경우와 마찬가지로 Spring IoC 컨테이너에
잘 맞습니다. 또한 이러한 DAO를 plain Java(예: 단위 테스트에서)에서 설정할 수도
있습니다. 이를 위해 DAO를 인스턴스화하고 원하는 팩터리 reference로
setSessionFactory(..)를 호출하면 됩니다. Spring 빈 정의로서 DAO는
다음과 같습니다:
1<beans> 2 3 <bean id="myProductDao" class="product.ProductDaoImpl"> 4 <property name="sessionFactory" ref="mySessionFactory"/> 5 </bean> 6 7</beans>
이러한 DAO 스타일의 주요 장점은 Hibernate API에만 의존한다는 것입니다. 어떤 Spring class도 import할 필요가 없습니다. 이는 비침투성 관점에서 매력적이며 Hibernate 개발자에게 더 자연스럽게 느껴질 수 있습니다.
그러나 DAO는 plain HibernateException(unchecked이므로 선언하거나
catch할 필요가 없음)을 throw하므로, 호출자는 Hibernate 자신의 exception 계층에
의존하지 않는 한 예외를 일반적으로 치명적인 것으로만 취급할 수 있습니다.
(optimistic locking failure와 같은) 특정 원인을 catch하는 것은 호출자를
구현 전략에 묶지 않고는 불가능합니다. 이 절충점은 Hibernate 기반이 강하고
특별한 exception 처리 필요가 없거나 둘 다인 애플리케이션에는 허용될 수 있습니다.
다행히도 Spring의 LocalSessionFactoryBean은 어떤 Spring 트랜잭션 strategy에
대해서도 Hibernate의 SessionFactory.getCurrentSession() 메서드를 지원하며,
HibernateTransactionManager와 함께 사용하더라도 현재 Spring이 관리하는
트랜잭션 Session을 반환합니다. 이 메서드의 표준 동작은 여전히
진행 중인 JTA 트랜잭션과 연관된 현재 Session(있는 경우)을 반환하는 것입니다.
이 동작은 Spring의 JtaTransactionManager, EJB 컨테이너 관리 트랜잭션(CMT),
또는 JTA를 사용하는지 여부와 관계없이 적용됩니다.
요약하면, plain Hibernate API를 기반으로 DAO를 구현하면서도 Spring이 관리하는 트랜잭션에 참여할 수 있습니다.
Java 코드에서 명시적인 트랜잭션 경계 설정 API 호출을 AOP 트랜잭션 인터셉터로 대체할 수 있는 Spring의 선언적 트랜잭션 지원을 사용하는 것이 좋습니다. 이 트랜잭션 인터셉터는 Java 어노테이션이나 XML을 사용하여 Spring 컨테이너에서 설정할 수 있습니다. 이 선언적 트랜잭션 기능을 사용하면 비즈니스 서비스를 반복적인 트랜잭션 경계 설정 코드에서 자유롭게 유지하고 애플리케이션의 실제 가치인 비즈니스 로직 추가에 집중할 수 있습니다.
계속 진행하기 전에, 아직 읽지 않았다면 Declarative Transaction Management를 읽을 것을 강력히 권장합니다.
서비스 레이어에 @Transactional 어노테이션을 달고 Spring 컨테이너에
이러한 어노테이션을 찾아서 어노테이션된 메서드에 대해 트랜잭션 semantic을
제공하도록 지시할 수 있습니다. 다음 예제는 이를 수행하는 방법을 보여줍니다:
1public class ProductServiceImpl implements ProductService { 2 3 private ProductDao productDao; 4 5 public void setProductDao(ProductDao productDao) { 6 this.productDao = productDao; 7 } 8 9 @Transactional 10 public void increasePriceOfAllProductsInCategory(final String category) { 11 List productsToChange = this.productDao.loadProductsByCategory(category); 12 // ... 13 } 14 15 @Transactional(readOnly = true) 16 public List<Product> findAllProducts() { 17 return this.productDao.findAllProducts(); 18 } 19}
1class ProductServiceImpl(private val productDao: ProductDao) : ProductService { 2 3 @Transactional 4 fun increasePriceOfAllProductsInCategory(category: String) { 5 val productsToChange = productDao.loadProductsByCategory(category) 6 // ... 7 } 8 9 @Transactional(readOnly = true) 10 fun findAllProducts() = productDao.findAllProducts() 11}
컨테이너에서는 PlatformTransactionManager 구현체를 (빈으로) 설정하고
런타임에서 @Transactional 처리를 선택하기 위해 <tx:annotation-driven/>
entry를 설정해야 합니다. 다음 예제는 이를 수행하는 방법을 보여줍니다:
1<?xml version="1.0" encoding="UTF-8"?> 2<beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:aop="http://www.springframework.org/schema/aop" 5 xmlns:tx="http://www.springframework.org/schema/tx" 6 xsi:schemaLocation=" 7 http://www.springframework.org/schema/beans 8 https://www.springframework.org/schema/beans/spring-beans.xsd 9 http://www.springframework.org/schema/tx 10 https://www.springframework.org/schema/tx/spring-tx.xsd 11 http://www.springframework.org/schema/aop 12 https://www.springframework.org/schema/aop/spring-aop.xsd"> 13 14 <!-- SessionFactory, DataSource, etc. omitted --> 15 16 <bean id="transactionManager" 17 class="org.springframework.orm.jpa.hibernate.HibernateTransactionManager"> 18 <property name="sessionFactory" ref="sessionFactory"/> 19 </bean> 20 21 <tx:annotation-driven/> 22 23 <bean id="myProductService" class="product.SimpleProductService"> 24 <property name="productDao" ref="myProductDao"/> 25 </bean> 26 27</beans>
트랜잭션을 애플리케이션의 더 높은 레벨에서 경계 설정할 수 있으며,
어떤 수의 operation에 걸쳐 있는 하위 레벨 data access 서비스 위에서 수행할 수
있습니다. 주변 비즈니스 서비스의 구현에는 제한이 없습니다. Spring
PlatformTransactionManager만 있으면 됩니다. 이 역시 어디에서든 올 수 있지만,
가능하면 setTransactionManager(..) 메서드를 통한 빈 reference로 제공하는 것이
좋습니다. 또한 productDAO는 setProductDao(..) 메서드에 의해 설정되어야 합니다.
다음의 snippet 쌍은 Spring 애플리케이션 컨텍스트에서 트랜잭션 매니저와
비즈니스 서비스 정의 및 비즈니스 메서드 구현 예제를 보여줍니다:
1<beans> 2 3 <bean id="myTxManager" class="org.springframework.orm.jpa.hibernate.HibernateTransactionManager"> 4 <property name="sessionFactory" ref="mySessionFactory"/> 5 </bean> 6 7 <bean id="myProductService" class="product.ProductServiceImpl"> 8 <property name="transactionManager" ref="myTxManager"/> 9 <property name="productDao" ref="myProductDao"/> 10 </bean> 11 12</beans>
1public class ProductServiceImpl implements ProductService { 2 3 private TransactionTemplate transactionTemplate; 4 private ProductDao productDao; 5 6 public void setTransactionManager(PlatformTransactionManager transactionManager) { 7 this.transactionTemplate = new TransactionTemplate(transactionManager); 8 } 9 10 public void setProductDao(ProductDao productDao) { 11 this.productDao = productDao; 12 } 13 14 public void increasePriceOfAllProductsInCategory(final String category) { 15 this.transactionTemplate.execute(new TransactionCallbackWithoutResult() { 16 public void doInTransactionWithoutResult(TransactionStatus status) { 17 List productsToChange = ProductServiceImpl.this.productDao.loadProductsByCategory(category); 18 // do the price increase... 19 } 20 }); 21 } 22}
1class ProductServiceImpl(transactionManager: PlatformTransactionManager, 2 private val productDao: ProductDao) : ProductService { 3 4 private val transactionTemplate = TransactionTemplate(transactionManager) 5 6 fun increasePriceOfAllProductsInCategory(category: String) { 7 transactionTemplate.execute { 8 val productsToChange = productDao.loadProductsByCategory(category) 9 // do the price increase... 10 } 11 } 12}
Spring의 TransactionInterceptor는 callback 코드 내에서 어떤 checked 애플리케이션
exception도 throw될 수 있도록 허용하는 반면, TransactionTemplate은 callback 내에서
unchecked exception으로 제한됩니다. TransactionTemplate은 unchecked 애플리케이션
exception이 발생하거나 애플리케이션이 (TransactionStatus를 설정하여) 트랜잭션을
rollback-only로 표시한 경우 rollback을 트리거합니다. 기본적으로
TransactionInterceptor는 동일한 방식으로 동작하지만 메서드별로 구성 가능한
rollback policy를 허용합니다.
TransactionTemplate과 TransactionInterceptor 모두 실제 트랜잭션 처리를
PlatformTransactionManager 인스턴스에 위임합니다. 이는 Hibernate 애플리케이션의
경우 ThreadLocal Session을 내부적으로 사용하는 단일 Hibernate SessionFactory에
대한 HibernateTransactionManager이거나, 컨테이너의 JTA subsystem에 위임하는
JtaTransactionManager일 수 있습니다. custom PlatformTransactionManager
구현체를 사용할 수도 있습니다. 애플리케이션의 특정 배포에서 분산 트랜잭션
요구 사항에 직면했을 때와 같이 native Hibernate 트랜잭션 관리에서
JTA로 전환하는 것은 설정의 문제일 뿐입니다. Hibernate 트랜잭션 매니저를
Spring의 JTA 트랜잭션 구현으로 교체할 수 있습니다. 트랜잭션 경계 설정과
data access 코드는 generic 트랜잭션 관리 API를 사용하므로 변경 없이
작동합니다.
여러 Hibernate session factory에 걸친 분산 트랜잭션의 경우, 트랜잭션 strategy로
JtaTransactionManager를 여러 LocalSessionFactoryBean 정의와 결합할 수 있습니다.
각 DAO는 해당 빈 프로퍼티로 전달되는 특정 SessionFactory reference를 하나씩
받습니다. 모든 하위 JDBC data source가 트랜잭션 컨테이너 data source라면,
비즈니스 서비스는 strategy로 JtaTransactionManager를 사용하는 한 특별한 고려 없이
어떤 수의 DAO와 어떤 수의 session factory에 걸쳐 트랜잭션을 경계 설정할 수
있습니다.
HibernateTransactionManager와 JtaTransactionManager 모두 컨테이너별
트랜잭션 매니저 조회나 JCA 커넥터(만약 트랜잭션을 EJB로 시작하지 않는
경우) 없이 Hibernate와 함께 proper JVM-level cache handling을 허용합니다.
HibernateTransactionManager는 특정 DataSource에 대해 Hibernate JDBC
Connection을 plain JDBC access 코드에 export할 수 있습니다. 이 기능은 하나의
데이터베이스에만 접근하는 경우 JTA 없이 혼합된 Hibernate와 JDBC data access에 대해
고수준 트랜잭션 경계 설정을 완전히 가능하게 합니다.
HibernateTransactionManager는 전달된 SessionFactory를
LocalSessionFactoryBean class의 dataSource 프로퍼티를 통해 DataSource와
함께 설정한 경우 Hibernate 트랜잭션을 JDBC 트랜잭션으로 자동 노출합니다.
또는 HibernateTransactionManager class의 dataSource 프로퍼티를 통해
트랜잭션이 노출되어야 하는 DataSource를 명시적으로 지정할 수 있습니다.
실제 리소스 connection의 JTA 스타일 lazy retrieval을 위해, Spring은 target
connection pool에 대한 해당 DataSource proxy class를 제공합니다:
LazyConnectionDataSourceProxy를
참조하세요. 이는 데이터베이스를 직접 hit하는 대신 local cache에서 처리할 수 있는 경우가
많은 Hibernate read-only 트랜잭션에 특히 유용합니다.
컨테이너 관리 JNDI SessionFactory와 local로 정의된 SessionFactory 사이를
애플리케이션 코드의 한 줄도 변경하지 않고 전환할 수 있습니다. 리소스 정의를
컨테이너에 유지할지 애플리케이션 내부에 local로 둘지는 주로 사용하는 트랜잭션
strategy의 문제입니다. Spring이 정의한 local SessionFactory와 비교할 때 수동으로
등록된 JNDI SessionFactory는 어떤 이점도 제공하지 않습니다. Hibernate의
JCA 커넥터를 통해 SessionFactory를 배포하는 것은 Jakarta EE 서버의
management infrastructure에 참여한다는 부가 가치를 제공하지만, 그 이상의 실제
가치를 추가하지는 않습니다.
Spring의 트랜잭션 지원은 컨테이너에 묶여 있지 않습니다. JTA 이외의 어떤 strategy로 설정된 경우 stand-alone 또는 테스트 환경에서도 트랜잭션 지원이 동작합니다. 특히 단일 데이터베이스 트랜잭션의 전형적인 경우, Spring의 단일 리소스 local 트랜잭션 지원은 JTA에 대한 lightweight하고 강력한 대안입니다. local EJB stateless session bean을 사용하여 트랜잭션을 구동하는 경우, 단일 데이터베이스에만 접근하고 선언적 트랜잭션을 제공하기 위해 stateless session bean만 사용하더라도 EJB 컨테이너와 JTA 모두에 의존하게 됩니다. JTA를 프로그래밍 방식으로 직접 사용하는 것도 Jakarta EE 환경을 필요로 합니다.
Spring이 구동하는 트랜잭션은 단일 데이터베이스에 접근하는 한 local JDBC
DataSource와 마찬가지로 local로 정의된 Hibernate SessionFactory와도 잘
작동합니다. 따라서 분산 트랜잭션 요구 사항이 있을 때만 Spring의 JTA
트랜잭션 strategy를 사용하면 됩니다. JCA 커넥터는 컨테이너별 배포
단계를 필요로 하며, (당연히) 우선 JCA 지원이 필요합니다. 이 설정은
local 리소스 정의 및 Spring이 구동하는 트랜잭션이 있는 단순 web 애플리케이션을
배포하는 것보다 더 많은 작업을 요구합니다.
종합적으로, EJB를 사용하지 않는다면 local SessionFactory setup과 Spring의
HibernateTransactionManager 또는 JtaTransactionManager를 고수하십시오. 컨테이너
배포의 불편함 없이 proper transactional JVM-level caching 및 분산 트랜잭션을
포함한 모든 이점을 얻을 수 있습니다. JCA 커넥터를 통한 Hibernate
SessionFactory의 JNDI 등록은 EJB와 함께 사용하는 경우에만 가치를 더합니다.
일부 매우 엄격한 XADataSource 구현이 있는 JTA 환경(현재 일부 WebLogic Server 및
WebSphere 버전)에서는, 해당 환경의 JTA 트랜잭션 매니저를 고려하지 않고
Hibernate를 설정한 경우 애플리케이션 서버 log에 잘못된 warning 또는
exception이 나타날 수 있습니다. 이러한 warning이나 exception은 접근 중인
connection이 더 이상 유효하지 않거나 JDBC access가 더 이상 유효하지 않음을
나타내며, 이는 트랜잭션이 더 이상 활성 상태가 아니기 때문일 수 있습니다.
예를 들어, 다음은 WebLogic의 실제 exception입니다:
1java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No 2further JDBC access is allowed within this transaction.
또 다른 일반적인 문제는 JTA 트랜잭션 이후 connection leak으로, Hibernate session (및 잠재적으로 하위 JDBC connection)이 제대로 close되지 않는 것입니다.
이러한 문제는 Hibernate가 JTA 트랜잭션 매니저를 인식하도록 하여 해결할 수 있으며, Hibernate는 Spring과 함께 JTA 트랜잭션 매니저에 동기화됩니다. 이를 수행하는 두 가지 옵션이 있습니다:
Spring JtaTransactionManager 빈을 Hibernate setup에 전달합니다. 가장 쉬운
방법은 LocalSessionFactoryBean 빈의 jtaTransactionManager 프로퍼티에 대한
빈 reference를 제공하는 것입니다
(Hibernate Transaction Setup 참조).
그러면 Spring은 해당 JTA strategy를 Hibernate에 제공하게 됩니다.
Hibernate의 JTA 관련 프로퍼티를 명시적으로 설정할 수도 있습니다. 특히
"hibernate.transaction.coordinator_class", "hibernate.connection.handling_mode" 및
필요한 경우 "hibernate.transaction.jta.platform"을 LocalSessionFactoryBean
위의 "hibernateProperties"에서 설정할 수 있습니다(이러한 프로퍼티에 대한 자세한
내용은 Hibernate manual을 참조하십시오).
이 섹션의 나머지 부분에서는 Hibernate가 JTA PlatformTransactionManager를 인식하는
경우와 인식하지 못하는 경우에 발생하는 event sequence를 설명합니다.
Hibernate가 JTA 트랜잭션 매니저에 대한 인식 없이 설정된 경우, JTA 트랜잭션이 commit될 때 다음 event가 발생합니다:
JtaTransactionManager는 JTA 트랜잭션에 동기화되어 있으므로
JTA 트랜잭션 매니저에 의해 afterCompletion callback을 통해 호출됩니다.afterTransactionCompletion callback을 통해 수행되고, 이어서 Hibernate session에
대한 명시적인 close() 호출이 발생하여 Hibernate가 JDBC Connection에 대한
close()를 시도하게 됩니다.Connection을 사용 가능한 것으로 간주하지 않아 이 Connection.close()
호출이 warning 또는 error를 트리거합니다.Hibernate가 JTA 트랜잭션 매니저를 인식하도록 설정된 경우, JTA 트랜잭션이 commit될 때 다음 event가 발생합니다:
JtaTransactionManager는 JTA 트랜잭션에 동기화되어 있으므로
트랜잭션은 JTA 트랜잭션 매니저에 의해 beforeCompletion callback을 통해
호출됩니다.afterCompletion callback을 통해 호출되고 cache를 적절히
지울 수 있습니다.General ORM Integration Considerations
JPA