[Spring Docs] Hibernate
이번글은 공식 문서에서 소개하는 Hibernate에 대해 정리했습니다.
We start with a coverage of Hibernate 5 in a Spring environment, using it to demonstrate the approach that Spring takes towards integrating OR mappers. This section covers many issues in detail and shows different variations of DAO implementations and transaction demarcation. Most of these patterns can be directly translated to all other supported ORM tools. The later sections in this chapter then cover the other ORM technologies and show brief examples.
이 장은 Spring 환경에서 Hibernate 5를 사용하는 내용으로 시작하며, Spring이 ORM 매퍼들을 어떻게 통합하는지를 보여주는 예시로 Hibernate를 활용합니다. 이 부분에서는 다양한 이슈를 깊이 있게 다루고, DAO 구현 방식과 트랜잭션 경계 설정의 여러 가지 변형 예시를 소개합니다. 이러한 패턴들은 대부분 다른 ORM 도구에도 그대로 적용할 수 있습니다. 장의 후반부에서는 Hibernate 외의 다른 ORM 기술들도 간단한 예제를 통해 소개합니다.
As of Spring Framework 6.0, Spring requires Hibernate ORM 5.5+ for Spring’s HibernateJpaVendorAdapter as well as for a native Hibernate SessionFactory setup. We recommend Hibernate ORM 5.6 as the last feature branch in that Hibernate generation.
Hibernate ORM 6.x is only supported as a JPA provider (HibernateJpaVendorAdapter). Plain SessionFactory setup with the orm.hibernate5 package is not supported anymore. We recommend Hibernate ORM 6.1/6.2 with JPA-style setup for new development projects.
Spring Framework 6.0부터는, HibernateJpaVendorAdapter나 기존의 Hibernate SessionFactory 설정을 사용할 경우 Hibernate ORM 5.5 이상이 필요합니다. 이 중에서도 Hibernate 5 세대에서는 기능적으로 완성된 마지막 버전인 5.6 사용을 권장합니다.
Hibernate ORM 6.x 버전은 JPA 제공자(JPA provider)로서만 지원되며, 예전 방식인 SessionFactory를 직접 설정하는 방식(orm.hibernate5 패키지 사용)은 더 이상 지원되지 않습니다.
새로운 프로젝트에서는 Hibernate ORM 6.1 또는 6.2를 JPA 방식으로 설정하여 사용하는 것을 권장합니다.
SessionFactory Setup in a Spring Container
To avoid tying application objects to hard-coded resource lookups, you can define resources (such as a JDBC DataSource or a Hibernate SessionFactory) as beans in the Spring container. Application objects that need to access resources receive references to such predefined instances through bean references, as illustrated in the DAO definition in the next section.
The following excerpt from an XML application context definition shows how to set up a JDBC DataSource and a Hibernate SessionFactory on top of it:
애플리케이션 객체가 리소스를 직접 하드코딩해서 참조하지 않도록 하기 위해, JDBC DataSource나 Hibernate SessionFactory 같은 리소스를 Spring 컨테이너의 빈으로 정의할 수 있습니다. 이렇게 하면, 리소스가 필요한 애플리케이션 객체들은 Spring이 미리 정의한 인스턴스에 대한 참조를 빈 주입(bean reference)을 통해 받게 됩니다. 다음 섹션에서 소개될 DAO 정의는 이러한 방식을 보여줍니다.
다음은 XML 기반의 Spring 애플리케이션 컨텍스트 설정 일부로, JDBC DataSource와 그 위에 구성된 Hibernate SessionFactory를 설정하는 예시입니다.
<beans>
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="mappingResources">
<list>
<value>product.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.HSQLDialect
</value>
</property>
</bean>
</beans>
Switching from a local Jakarta Commons DBCP BasicDataSource to a JNDI-located DataSource (usually managed by an application server) is only a matter of configuration, as the following example shows:
로컬에서 사용하는 Jakarta Commons DBCP의 BasicDataSource를 JNDI에 등록된 DataSource(일반적으로 애플리케이션 서버가 관리)를 사용하는 방식으로 전환하는 것은 구현 코드를 바꿀 필요 없이 설정만 변경하면 됩니다. 아래 예시는 이러한 설정 변경이 얼마나 간단한지를 보여줍니다.
<beans>
<jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
</beans>
You can also access a JNDI-located SessionFactory, using Spring’s JndiObjectFactoryBean / <jee:jndi-lookup> to retrieve and expose it. However, that is typically not common outside of an EJB context.
Spring의 JndiObjectFactoryBean이나 <jee:jndi-lookup> 태그를 사용하면 JNDI에 등록된 SessionFactory에도 접근하여 이를 사용할 수 있습니다. 하지만 이 방식은 일반적으로 EJB 환경 외에서는 잘 사용되지 않습니다.
Spring also provides a LocalSessionFactoryBuilder variant, seamlessly integrating with @Bean style configuration and programmatic setup (no FactoryBean involved).
Both LocalSessionFactoryBean and LocalSessionFactoryBuilder support background bootstrapping, with Hibernate initialization running in parallel to the application bootstrap thread on a given bootstrap executor (such as a SimpleAsyncTaskExecutor). On LocalSessionFactoryBean, this is available through the bootstrapExecutor property. On the programmatic LocalSessionFactoryBuilder, there is an overloaded buildSessionFactory method that takes a bootstrap executor argument.
Such a native Hibernate setup can also expose a JPA EntityManagerFactory for standard JPA interaction next to native Hibernate access. See Native Hibernate Setup for JPA for details.
Spring은 @Bean 스타일의 설정이나 프로그래밍 방식 설정과 자연스럽게 통합되는 LocalSessionFactoryBuilder도 제공하는데, 이는 FactoryBean을 사용하지 않고도 Hibernate 설정을 구성할 수 있도록 해줍니다.
LocalSessionFactoryBean과 LocalSessionFactoryBuilder 모두 백그라운드에서 Hibernate 초기화를 비동기적으로 처리할 수 있는 기능을 지원합니다. 이때 초기화 작업은 SimpleAsyncTaskExecutor와 같은 실행기를 사용하여, 애플리케이션의 부트스트랩 스레드와 병렬로 진행됩니다.
LocalSessionFactoryBean에서는 bootstrapExecutor 프로퍼티를 통해 이 기능을 설정할 수 있고, LocalSessionFactoryBuilder에서는 bootstrapExecutor를 인자로 받는 buildSessionFactory 메서드 오버로드 버전을 사용합니다.
이와 같은 Hibernate 고유 설정(native setup)은 JPA 표준 방식의 EntityManagerFactory도 함께 노출시킬 수 있어서, Hibernate API와 JPA API를 동시에 사용할 수 있는 구조를 만들 수 있습니다. 자세한 내용은 Native Hibernate Setup for JPA 항목을 참고하면 됩니다.
Implementing DAOs Based on the Plain Hibernate API
Hibernate has a feature called contextual sessions, wherein Hibernate itself manages one current Session per transaction. This is roughly equivalent to Spring’s synchronization of one Hibernate Session per transaction. A corresponding DAO implementation resembles the following example, based on the plain Hibernate API:
Hibernate에는 Contextual Session이라는 기능이 있는데, 이는 Hibernate가 트랜잭션마다 하나의 현재 세션(Session)을 직접 관리해주는 방식입니다. 이 방식은 Spring이 트랜잭션마다 하나의 Hibernate 세션을 동기화해주는 방식과 대략적으로 유사하다고 보실 수 있습니다.
이러한 Contextual Session을 사용할 경우의 DAO 구현 예시는, 일반 Hibernate API를 기반으로 다음과 같은 형태를 갖습니다.
public class ProductDaoImpl implements ProductDao {
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public Collection loadProductsByCategory(String category) {
return this.sessionFactory.getCurrentSession()
.createQuery("from test.Product product where product.category=?")
.setParameter(0, category)
.list();
}
}
This style is similar to that of the Hibernate reference documentation and examples, except for holding the SessionFactory in an instance variable. We strongly recommend such an instance-based setup over the old-school static HibernateUtil class from Hibernate’s CaveatEmptor sample application. (In general, do not keep any resources in static variables unless absolutely necessary.)
이 스타일은 Hibernate 공식 문서나 예제와 유사하지만, SessionFactory를 인스턴스 변수로 보관한다는 점에서 차이가 있습니다. Hibernate의 CaveatEmptor 샘플 애플리케이션에서 사용된 예전 방식인 정적(static) HibernateUtil 클래스를 사용하는 것보다는 이러한 인스턴스 기반의 설정 방식을 강력히 권장드립니다.
(일반적으로, 꼭 필요한 경우가 아니라면 정적 변수에 리소스를 보관하는 것은 피하는 것이 좋습니다.)
The preceding DAO example follows the dependency injection pattern. It fits nicely into a Spring IoC container, as it would if coded against Spring’s HibernateTemplate. You can also set up such a DAO in plain Java (for example, in unit tests). To do so, instantiate it and call setSessionFactory(..) with the desired factory reference. As a Spring bean definition, the DAO would resemble the following:
앞서 나온 DAO 예제는 의존성 주입(Dependency Injection) 패턴을 따르고 있습니다. 이 방식은 Spring의 HibernateTemplate을 사용할 때와 마찬가지로, Spring IoC 컨테이너에 자연스럽게 통합될 수 있습니다.
또한 이 DAO는 단위 테스트 등에서 일반 자바 코드로도 설정하실 수 있습니다. 이 경우에는 DAO 객체를 직접 생성한 후, setSessionFactory(..) 메서드를 호출하여 원하는 SessionFactory를 주입해주면 됩니다.
Spring 빈으로 정의할 경우, 이 DAO는 다음과 같은 형태로 설정될 수 있습니다.
<beans>
<bean id="myProductDao" class="product.ProductDaoImpl">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
</beans>
The main advantage of this DAO style is that it depends on Hibernate API only. No import of any Spring class is required. This is appealing from a non-invasiveness perspective and may feel more natural to Hibernate developers.
이 DAO 스타일의 가장 큰 장점은 Hibernate API에만 의존한다는 점입니다. 즉, Spring 관련 클래스를 전혀 import하지 않아도 되며,
이는 Spring에 대한 침투성이 낮고(non-invasive), Hibernate를 주로 사용하는 개발자에게는 더 자연스럽고 익숙하게 느껴질 수 있습니다.
침투성이 낮다는 말은 프레임워크의 코드나 개념이 비즈니스 로직 코드에 깊게 스며들지 않는다는 뜻입니다.
However, the DAO throws plain HibernateException (which is unchecked, so it does not have to be declared or caught), which means that callers can treat exceptions only as being generally fatal — unless they want to depend on Hibernate’s own exception hierarchy. Catching specific causes (such as an optimistic locking failure) is not possible without tying the caller to the implementation strategy. This trade off might be acceptable to applications that are strongly Hibernate-based, do not need any special exception treatment, or both.
하지만 이 DAO는 HibernateException 같은 일반적인 예외만 던지며, 이는 체크 예외가 아니라서 명시적으로 선언하거나 catch 하지 않아도 됩니다.
이 말은 호출하는 쪽에서 예외를 처리할 때, 예외의 구체적인 원인을 파악하기보다는 그냥 ’치명적인 오류(fatal)’로만 간주해야 한다는 의미입니다. 물론, Hibernate의 예외 계층 구조에 의존한다면 좀 더 구체적인 예외 처리가 가능하겠지만, 그렇게 되면 호출하는 쪽이 Hibernate 구현에 종속되게 됩니다.
낙관적 락 실패(optimistic locking failure)와 같이 특정 원인에 대한 예외를 구분해 처리하려면, 결국 호출하는 코드도 Hibernate에 묶이게 됩니다.
이러한 트레이드오프는 다음과 같은 경우에는 수용 가능할 수 있습니다:
애플리케이션이 Hibernate 중심으로 강하게 설계되어 있는 경우, 별도의 예외 처리가 굳이 필요 없는 경우 또는 이 둘 모두 해당되는 경우
Fortunately, Spring’s LocalSessionFactoryBean supports Hibernate’s SessionFactory.getCurrentSession() method for any Spring transaction strategy, returning the current Spring-managed transactional Session, even with HibernateTransactionManager. The standard behavior of that method remains to return the current Session associated with the ongoing JTA transaction, if any. This behavior applies regardless of whether you use Spring’s JtaTransactionManager, EJB container managed transactions (CMTs), or JTA.
다행히도, Spring의 LocalSessionFactoryBean은 Hibernate의 SessionFactory.getCurrentSession() 메서드를 Spring의 어떤 트랜잭션 전략을 사용하더라도 지원합니다.
즉, HibernateTransactionManager를 사용하더라도, 이 메서드를 통해 Spring이 관리하는 현재 트랜잭션 범위의 Session을 반환받을 수 있습니다.
기본적으로 이 메서드는 현재 진행 중인 JTA 트랜잭션에 연결된 Session을 반환하는 것이 표준 동작이며, 이 동작은 다음과 같은 상황에서도 동일하게 적용됩니다:
- Spring의 JtaTransactionManager를 사용할 때
- EJB 컨테이너의 트랜잭션(CMT, Container-Managed Transaction)을 사용할 때
- 일반적인 JTA 환경을 사용할 때
즉, 어떤 방식의 트랜잭션 전략을 사용하든 getCurrentSession() 메서드를 통해 일관되게 트랜잭션 범위의 세션을 활용할 수 있습니다.
In summary, you can implement DAOs based on the plain Hibernate API, while still being able to participate in Spring-managed transactions.
정리하자면, 순수 Hibernate API만을 사용하여 DAO를 구현하더라도, Spring이 관리하는 트랜잭션에 문제없이 참여할 수 있습니다.
Declarative Transaction Demarcation
We recommend that you use Spring’s declarative transaction support, which lets you replace explicit transaction demarcation API calls in your Java code with an AOP transaction interceptor. You can configure this transaction interceptor in a Spring container by using either Java annotations or XML. This declarative transaction capability lets you keep business services free of repetitive transaction demarcation code and focus on adding business logic, which is the real value of your application.
Spring에서는 명시적인 트랜잭션 처리 코드를 직접 작성하기보다는, 선언적 트랜잭션 방식(declarative transaction)을 사용하는 것을 권장합니다. 선언적 트랜잭션은 AOP 기반의 트랜잭션 인터셉터를 통해 트랜잭션을 자동으로 처리해주며, 이를 Java 애너테이션(@Transactional) 또는 XML 설정을 통해 구성할 수 있습니다.
이러한 방식은 서비스 로직에 트랜잭션 시작/커밋/롤백과 같은 반복적인 코드 작성을 없애고, 오직 비즈니스 로직 구현에만 집중할 수 있도록 도와줍니다. 즉, 트랜잭션 관리는 Spring이 처리해주고, 개발자는 핵심 로직에 집중할 수 있게 해준다는 뜻입니다.
You can annotate the service layer with @Transactional annotations and instruct the Spring container to find these annotations and provide transactional semantics for these annotated methods. The following example shows how to do so:
서비스 레이어에 @Transactional 애너테이션을 붙이면, Spring 컨테이너가 이를 감지하여 해당 메서드에 트랜잭션 기능을 적용하도록 설정할 수 있습니다.
즉, 개발자가 메서드에 @Transactional을 붙이기만 하면, Spring이 자동으로 트랜잭션 시작, 커밋, 롤백을 관리해줍니다.
그리고 이어지는 예제에서는 이런 설정을 어떻게 적용하는지를 보여줍니다.
public class ProductServiceImpl implements ProductService {
private ProductDao productDao;
public void setProductDao(ProductDao productDao) {
this.productDao = productDao;
}
@Transactional
public void increasePriceOfAllProductsInCategory(final String category) {
List productsToChange = this.productDao.loadProductsByCategory(category);
// ...
}
@Transactional(readOnly = true)
public List<Product> findAllProducts() {
return this.productDao.findAllProducts();
}
}
In the container, you need to set up the PlatformTransactionManager implementation (as a bean) and a <tx:annotation-driven/> entry, opting into @Transactional processing at runtime. The following example shows how to do so:
Spring 컨테이너에서 @Transactional 애너테이션을 제대로 동작하게 하려면, PlatformTransactionManager 구현체를 Bean으로 등록하고, <tx:annotation-driven/> 설정을 통해 @Transactional을 처리하겠다고 선언해야 합니다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- SessionFactory, DataSource, etc. omitted -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:annotation-driven/>
<bean id="myProductService" class="product.SimpleProductService">
<property name="productDao" ref="myProductDao"/>
</bean>
</beans>
Programmatic Transaction Demarcation
You can demarcate transactions in a higher level of the application, on top of lower-level data access services that span any number of operations. Nor do restrictions exist on the implementation of the surrounding business service. It needs only a Spring PlatformTransactionManager. Again, the latter can come from anywhere, but preferably as a bean reference through a setTransactionManager(..) method. Also, the productDAO should be set by a setProductDao(..) method. The following pair of snippets show a transaction manager and a business service definition in a Spring application context and an example for a business method implementation:
트랜잭션 처리는 애플리케이션의 상위 계층, 즉 여러 하위 데이터 접근 로직들을 감싸는 서비스 계층에서 설정할 수 있습니다. 이때 비즈니스 로직이 어떻게 구현되어 있는지는 중요하지 않으며, 필요한 건 단지 Spring이 제공하는 PlatformTransactionManager입니다. 이 트랜잭션 매니저는 외부에서 주입될 수 있지만, 보통은 빈으로 등록한 후 setTransactionManager() 같은 메서드를 통해 주입합니다. 마찬가지로 DAO 객체도 setProductDao() 같은 메서드를 통해 주입받습니다.
이후 예제에서는 스프링 설정 파일에서 트랜잭션 매니저와 서비스 빈을 등록하고, 트랜잭션 처리를 적용하는 방법과 비즈니스 메서드의 예시를 보여줍니다.
<beans>
<bean id="myTxManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
<bean id="myProductService" class="product.ProductServiceImpl">
<property name="transactionManager" ref="myTxManager"/>
<property name="productDao" ref="myProductDao"/>
</bean>
</beans>
public class ProductServiceImpl implements ProductService {
private TransactionTemplate transactionTemplate;
private ProductDao productDao;
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public void setProductDao(ProductDao productDao) {
this.productDao = productDao;
}
public void increasePriceOfAllProductsInCategory(final String category) {
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
List productsToChange = this.productDao.loadProductsByCategory(category);
// do the price increase...
}
});
}
}
Spring’s TransactionInterceptor lets any checked application exception be thrown with the callback code, while TransactionTemplate is restricted to unchecked exceptions within the callback. TransactionTemplate triggers a rollback in case of an unchecked application exception or if the transaction is marked rollback-only by the application (by setting TransactionStatus). By default, TransactionInterceptor behaves the same way but allows configurable rollback policies per method.
Spring에서는 트랜잭션 처리를 위해 두 가지 주요 방식이 있는데, 하나는 TransactionInterceptor (AOP 기반)이고, 다른 하나는 TransactionTemplate (프로그래밍 방식)입니다.
TransactionInterceptor를 사용할 경우 체크 예외(Checked Exception) 도 그대로 밖으로 던질 수 있고, 트랜잭션 롤백 여부는 설정을 통해 메서드 단위로 세밀하게 제어할 수 있습니다.
반면 TransactionTemplate은 기본적으로 언체크 예외(RuntimeException 등) 가 발생해야만 자동으로 롤백이 됩니다. 혹은 개발자가 직접 TransactionStatus를 이용해 롤백을 명시적으로 설정해야 합니다.
Transaction Management Strategies
Both TransactionTemplate and TransactionInterceptor delegate the actual transaction handling to a PlatformTransactionManager instance (which can be a HibernateTransactionManager (for a single Hibernate SessionFactory) by using a ThreadLocal Session under the hood) or a JtaTransactionManager (delegating to the JTA subsystem of the container) for Hibernate applications. You can even use a custom PlatformTransactionManager implementation. Switching from native Hibernate transaction management to JTA (such as when facing distributed transaction requirements for certain deployments of your application) is only a matter of configuration. You can replace the Hibernate transaction manager with Spring’s JTA transaction implementation. Both transaction demarcation and data access code work without changes, because they use the generic transaction management APIs.
TransactionTemplate이든 TransactionInterceptor이든, 실제 트랜잭션 처리는 내부적으로 PlatformTransactionManager에 위임됩니다.
예를 들어 Hibernate 기반 애플리케이션이라면, HibernateTransactionManager를 사용할 수 있으며, 이 경우 내부적으로 ThreadLocal 기반의 Session을 활용합니다. 또는 컨테이너의 JTA 시스템에 위임하는 JtaTransactionManager도 사용할 수 있습니다.
Spring에서는 직접 구현한 사용자 정의 PlatformTransactionManager도 사용 가능합니다.
그리고 만약 특정 배포 환경에서 분산 트랜잭션이 필요해져서 기존의 Hibernate 트랜잭션에서 JTA 방식으로 전환해야 하더라도, 단순히 설정만 바꾸면 됩니다.
트랜잭션을 시작하고 종료하는 코드나, 데이터 액세스 로직은 모두 Spring의 추상화된 트랜잭션 API를 사용하고 있기 때문에 코드 변경 없이도 동작합니다.
For distributed transactions across multiple Hibernate session factories, you can combine JtaTransactionManager as a transaction strategy with multiple LocalSessionFactoryBean definitions. Each DAO then gets one specific SessionFactory reference passed into its corresponding bean property. If all underlying JDBC data sources are transactional container ones, a business service can demarcate transactions across any number of DAOs and any number of session factories without special regard, as long as it uses JtaTransactionManager as the strategy.
여러 개의 Hibernate 세션 팩토리(SessionFactory) 를 사용하는 분산 트랜잭션을 처리하려면, 트랜잭션 전략으로 JtaTransactionManager를 사용할 수 있습니다. 이때, 각각의 DAO는 자신에게 해당하는 특정 SessionFactory를 주입받게 됩니다.
모든 JDBC 데이터소스가 컨테이너에서 제공하는 트랜잭션 지원이 되는 데이터소스라면, 비즈니스 서비스는 여러 DAO와 여러 SessionFactory를 걸쳐 트랜잭션을 묶을 수 있습니다. 이때 특별한 처리가 필요 없이, 트랜잭션 전략으로 JtaTransactionManager만 사용하면 됩니다.
Both HibernateTransactionManager and JtaTransactionManager allow for proper JVM-level cache handling with Hibernate, without container-specific transaction manager lookup or a JCA connector (if you do not use EJB to initiate transactions).
HibernateTransactionManager와 JtaTransactionManager 모두 Hibernate에서 JVM 수준의 캐시 처리를 제대로 지원합니다. EJB를 사용해서 트랜잭션을 시작하지 않는다면, 컨테이너에 특화된 트랜잭션 매니저 조회나 JCA 커넥터가 없어도 괜찮습니다.
HibernateTransactionManager can export the Hibernate JDBC Connection to plain JDBC access code for a specific DataSource. This ability allows for high-level transaction demarcation with mixed Hibernate and JDBC data access completely without JTA, provided you access only one database. HibernateTransactionManager automatically exposes the Hibernate transaction as a JDBC transaction if you have set up the passed-in SessionFactory with a DataSource through the dataSource property of the LocalSessionFactoryBean class. Alternatively, you can specify explicitly the DataSource for which the transactions are supposed to be exposed through the dataSource property of the HibernateTransactionManager class.
HibernateTransactionManager는 Hibernate가 사용하는 JDBC Connection을 일반 JDBC 코드에 노출할 수 있습니다.
이 기능 덕분에 JTA 없이도 Hibernate와 JDBC를 섞어 사용할 수 있으며, 단 하나의 데이터베이스만 접근하는 경우라면 고수준 트랜잭션 관리가 가능합니다.
HibernateTransactionManager는 LocalSessionFactoryBean에서 dataSource를 설정하면, Hibernate 트랜잭션을 JDBC 트랜잭션으로 자동 연결시켜 줍니다.
또는, HibernateTransactionManager 클래스의 dataSource 속성을 명시적으로 설정해, 어떤 DataSource에 대해 트랜잭션을 노출할지 지정할 수도 있습니다.
For JTA-style lazy retrieval of actual resource connections, Spring provides a corresponding DataSource proxy class for the target connection pool: see LazyConnectionDataSourceProxy. This is particularly useful for Hibernate read-only transactions which can often be processed from a local cache rather than hitting the database.
Spring은 JTA 방식처럼 실제 DB 연결을 지연해서 가져오도록 하기 위해 LazyConnectionDataSourceProxy라는 DataSource 프록시 클래스를 제공합니다.
이 프록시는 연결 풀(connection pool)에 위임하는 구조로, 특히 Hibernate에서 읽기 전용 트랜잭션(read-only transaction) 을 사용할 때 유용합니다.
왜냐하면 읽기 전용 트랜잭션은 종종 데이터베이스를 직접 조회하지 않고 Hibernate의 1차 캐시로도 처리할 수 있기 때문입니다.
Comparing Container-managed and Locally Defined Resources
You can switch between a container-managed JNDI SessionFactory and a locally defined one without having to change a single line of application code. Whether to keep resource definitions in the container or locally within the application is mainly a matter of the transaction strategy that you use. Compared to a Spring-defined local SessionFactory, a manually registered JNDI SessionFactory does not provide any benefits. Deploying a SessionFactory through Hibernate’s JCA connector provides the added value of participating in the Jakarta EE server’s management infrastructure, but does not add actual value beyond that.
애플리케이션 코드를 전혀 변경하지 않고도 컨테이너에서 관리하는 JNDI 기반의 SessionFactory와 로컬에서 정의한 SessionFactory를 자유롭게 전환할 수 있습니다.
어떤 방식으로 리소스를 정의할지는 주로 사용하는 트랜잭션 전략에 따라 결정됩니다.
Spring이 직접 관리하는 로컬 SessionFactory에 비해, 수동으로 등록한 JNDI 기반 SessionFactory는 기능적 이점이 없습니다.
Hibernate의 JCA 커넥터를 통해 SessionFactory를 배포하면 Jakarta EE 서버의 관리 기능에 참여할 수 있는 운영상 이점은 있으나, 기능 자체에서 특별한 이득은 없습니다.
Spring’s transaction support is not bound to a container. When configured with any strategy other than JTA, transaction support also works in a stand-alone or test environment. Especially in the typical case of single-database transactions, Spring’s single-resource local transaction support is a lightweight and powerful alternative to JTA. When you use local EJB stateless session beans to drive transactions, you depend both on an EJB container and on JTA, even if you access only a single database and use only stateless session beans to provide declarative transactions through container-managed transactions. Direct use of JTA programmatically also requires a Jakarta EE environment.
Spring의 트랜잭션 기능은 JTA 같은 전략만 사용하지 않는다면, 독립 실행형 애플리케이션이나 테스트 환경에서도 동작합니다.
특히 대부분의 경우처럼 단일 데이터베이스 트랜잭션이라면, Spring이 제공하는 단일 리소스(Local) 트랜잭션 지원은 JTA보다 가볍고 효율적인 대안이 됩니다.
반면에, EJB의 Stateless Session Bean을 사용해서 트랜잭션을 제어할 경우에는, EJB 컨테이너에 의존해야 하고 JTA도 필요합니다.
이는 단일 DB만 접근하더라도, 단순히 선언형 트랜잭션을 사용하겠다는 이유로 복잡한 Jakarta EE 환경에 종속되는 것입니다.
또한, JTA를 직접 코드에서 사용하는 경우에도 반드시 Jakarta EE 환경이 필요합니다.
Spring-driven transactions can work as well with a locally defined Hibernate SessionFactory as they do with a local JDBC DataSource, provided they access a single database. Thus, you need only use Spring’s JTA transaction strategy when you have distributed transaction requirements. A JCA connector requires container-specific deployment steps, and (obviously) JCA support in the first place. This configuration requires more work than deploying a simple web application with local resource definitions and Spring-driven transactions.
Spring의 트랜잭션은 Hibernate의 SessionFactory나 JDBC DataSource가 로컬에 정의되어 있어도 잘 동작합니다. 단일 데이터베이스만 접근한다면 JTA 트랜잭션 전략을 사용할 필요는 없습니다.
JCA 커넥터를 사용하는 설정은 Jakarta EE 컨테이너의 특수한 배포 방식이 필요하고, JCA 자체를 지원하는 환경이 요구됩다. 따라서 단순한 웹 애플리케이션에서는 로컬 리소스를 정의하고 Spring 트랜잭션 기능을 사용하는 것이 훨씬 간단하고 실용적입니다.
All things considered, if you do not use EJBs, stick with local SessionFactory setup and Spring’s HibernateTransactionManager or JtaTransactionManager. You get all of the benefits, including proper transactional JVM-level caching and distributed transactions, without the inconvenience of container deployment. JNDI registration of a Hibernate SessionFactory through the JCA connector adds value only when used in conjunction with EJBs.
결론적으로, EJB를 사용하지 않는다면 로컬 SessionFactory 설정과 Spring의 HibernateTransactionManager 또는JtaTransactionManager를 사용하는 것이 가장 좋습니다. 이렇게 하면 컨테이너에 배포하지 않고도 트랜잭션 처리, JVM 수준의 캐싱, 분산 트랜잭션 등의 모든 이점을 누릴 수 있습니다.
Hibernate SessionFactory를 JCA 커넥터를 통해 JNDI에 등록하는 방식은 EJB와 함께 사용할 때만 의미가 있습니다.
Spurious Application Server Warnings with Hibernate
In some JTA environments with very strict XADataSource implementations (currently some WebLogic Server and WebSphere versions), when Hibernate is configured without regard to the JTA transaction manager for that environment, spurious warnings or exceptions can show up in the application server log. These warnings or exceptions indicate that the connection being accessed is no longer valid or JDBC access is no longer valid, possibly because the transaction is no longer active. As an example, here is an actual exception from WebLogic:
JTA 환경에서 Hibernate를 사용할 때, 트랜잭션 매니저 설정을 제대로 하지 않으면 애플리케이션 서버(WebLogic, WebSphere 등)에서 다음과 같은 문제가 발생할 수 있습니다.
Hibernate가 JTA 트랜잭션 매니저와 연동되지 않은 상태로 설정되면, 트랜잭션이 종료된 이후에도 JDBC 연결을 사용하려는 코드가 실행될 수 있습니다. 이 경우, 서버는 더 이상 유효하지 않은 연결에 접근하려 한다고 판단하여 경고나 예외를 발생시킵니다.
특히, XADataSource 구현이 엄격한 서버에서는 이런 문제가 더 자주 나타날 수 있습니다.
따라서 JTA 환경에서는 Hibernate 설정 시 트랜잭션 매니저와의 연결을 명확히 해야 하며, 그렇지 않으면 트랜잭션 종료 이후의 JDBC 접근으로 인해 서버 로그에 불필요한 경고나 예외가 출력될 수 있습니다.
java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No
further JDBC access is allowed within this transaction.
Another common problem is a connection leak after JTA transactions, with Hibernate sessions (and potentially underlying JDBC connections) not getting closed properly.
JTA 트랜잭션 이후에 자주 발생하는 일반적인 문제 중 하나는 커넥션 누수입니다. 이 경우 Hibernate 세션(그리고 그에 연결된 JDBC 커넥션)이 제대로 닫히지 않는 일이 발생할 수 있습니다.
You can resolve such issues by making Hibernate aware of the JTA transaction manager, to which it synchronizes (along with Spring). You have two options for doing this:
- Pass your Spring JtaTransactionManager bean to your Hibernate setup. The easiest way is a bean reference into the jtaTransactionManager property for your LocalSessionFactoryBean bean (see Hibernate Transaction Setup). Spring then makes the corresponding JTA strategies available to Hibernate.
- You may also configure Hibernate’s JTA-related properties explicitly, in particular "hibernate.transaction.coordinator_class", "hibernate.connection.handling_mode" and potentially "hibernate.transaction.jta.platform" in your "hibernateProperties" on LocalSessionFactoryBean (see Hibernate’s manual for details on those properties).
이러한 문제는 Hibernate가 JTA 트랜잭션 관리자를 인식하게 하여, (Spring과 함께) 동기화되도록 함으로써 해결할 수 있습니다. 이를 위한 두 가지 방법이 있습니다:
- Spring의 JtaTransactionManager 빈을 Hibernate 설정에 전달하는 방법입니다.이 방법을 사용하면 Spring은 Hibernate에 해당하는 JTA 전략을 사용할 수 있게 합니다. 가장 쉬운 방법은 LocalSessionFactoryBean 빈에 있는 jtaTransactionManager 속성에 해당 빈을 참조로 넘겨주는 것입니다 (Hibernate 트랜잭션 설정 참조).
- Hibernate의 JTA 관련 속성들을 명시적으로 설정하는 방법도 있습니다.(해당 속성들에 대한 자세한 내용은 Hibernate 매뉴얼을 참조하십시오.) 특히 "hibernate.transaction.coordinator_class", "hibernate.connection.handling_mode", 그리고 필요하다면 "hibernate.transaction.jta.platform" 속성을 LocalSessionFactoryBean의 "hibernateProperties" 항목에 지정할 수 있습니다.
The remainder of this section describes the sequence of events that occur with and without Hibernate’s awareness of the JTA PlatformTransactionManager.
When Hibernate is not configured with any awareness of the JTA transaction manager, the following events occur when a JTA transaction commits:
- The JTA transaction commits.
- Spring’s JtaTransactionManager is synchronized to the JTA transaction, so it is called back through an afterCompletion callback by the JTA transaction manager.
- Among other activities, this synchronization can trigger a callback by Spring to Hibernate, through Hibernate’s afterTransactionCompletion callback (used to clear the Hibernate cache), followed by an explicit close() call on the Hibernate session, which causes Hibernate to attempt to close() the JDBC Connection.
- In some environments, this Connection.close() call then triggers the warning or error, as the application server no longer considers the Connection to be usable, because the transaction has already been committed.
이 문단은 Hibernate가 JTA 트랜잭션 관리자를 인식하는지 여부에 따라 트랜잭션 커밋 시 발생하는 동작의 차이를 설명합니다.
Hibernate가 JTA 관리자를 알지 못하는 상태에서는 트랜잭션 커밋 시 다음과 같은 흐름이 발생합니다:
- 트랜잭션이 커밋되면 Spring의 JtaTransactionManager가 JTA 트랜잭션의 종료 이벤트를 감지합니다.
- 이 이벤트는 Spring 내부에서 Hibernate의 캐시 정리와 세션 종료를 유도하게 되며, 이때 Hibernate는 JDBC 커넥션을 닫으려고 시도합니다.
- 하지만 이미 트랜잭션이 커밋된 상태이므로, 애플리케이션 서버는 이 커넥션을 더 이상 유효하지 않다고 판단해 경고나 오류를 발생시킬 수 있습니다.
즉, Hibernate가 JTA 트랜잭션 관리자를 명확히 인식하지 못하면 커밋 이후 커넥션 정리에 있어서 불필요한 에러가 발생할 수 있다는 의미입니다.
참조