https://docs.spring.io/spring-framework/reference/data-access/orm.html
Spring中的ORM简介
Spring框架支持与Java持久化API(JPA)集成,并支持原生Hibernate进行资源管理、数据访问对象(DAO)实现和事务策略。例如,对于Hibernate,有一流的支持,包括多个方便的IoC特性,解决了许多典型的Hibernate集成问题。可以通过依赖注入配置所有支持的OR(对象关系)映射工具的功能。它们可以参与Spring的资源和事务管理,并遵守Spring的通用事务和DAO异常层次结构。推荐集成方式是针对简单的Hibernate或JPA API编写DAO代码。
在创建数据访问应用程序时,Spring会显著增强你选择的ORM层。可以根据需要利用尽可能多的集成支持,并且应该将这种集成努力与在内部构建类似基础设施的成本和风险进行比较。可以像使用库一样使用大部分ORM支持,而不考虑技术,因为一切都是设计为一组可重用的JavaBeans。在Spring IoC容器中的ORM便于配置和部署。因此,本节中的大多数示例展示了Spring容器内的配置。
使用Spring框架创建ORM DAO的好处包括:
- 更容易测试。Spring的IoC方法使得替换Hibernate
SessionFactory
实例、JDBCDataSource
实例、事务管理器和映射对象实现(如果需要)的实现和配置位置变得容易。这反过来又使得更容易独立地测试每一块与持久性相关的代码。 - 通用数据访问异常。Spring可以包装来自ORM工具的异常,将它们从专有(可能是受检)异常转换为通用运行时
DataAccessException
层次结构。这个特性允许你只在适当的层处理大多数不可恢复的持久化异常,无需烦人的模板代码catch
、throw
和异常声明。你仍然可以根据需要捕获和处理异常。JDBC异常(包括特定于数据库的方言)也转换为相同的层次结构,这意味着可以在一致的编程模型中使用JDBC执行一些操作。 - 通用资源管理。Spring应用程序上下文可以处理Hibernate
SessionFactory
实例、JPAEntityManagerFactory
实例、JDBCDataSource
实例以及其他相关资源的位置和配置。这使得这些值易于管理和更改。Spring提供高效、简便且安全的持久化资源处理。例如,使用Hibernate的相关代码通常需要使用相同的HibernateSession
以确保效率和正确的事务处理。Spring通过HibernateSessionFactory
暴露当前Session
,可以轻松地创建并将Session
绑定到当前线程透明地。因此,Spring解决了典型Hibernate使用中的许多长期问题,适用于任何本地或JTA事务环境。 - 集成事务管理。可以通过
@Transactional
注解或在XML配置文件中显式配置事务AOP通知(advice),使用声明式、面向切面编程(AOP)风格的方法拦截器来包装你的ORM代码。在这两种情况下,事务语义和异常处理(回滚等)都由你来处理。还可以更换各种事务管理器,而不影响ORM相关代码。例如,可以在本地事务和JTA之间切换,两种情况下都有相同的完整服务(如声明式事务)。此外,与JDBC相关的代码可以与用于进行ORM的代码完全集成事务。这对于不适合ORM的数据访问(如批处理和BLOB流)但仍然需要与ORM操作共享公共事务的情况很有用。
通用ORM集成考虑因素
本节强调适用于所有ORM技术的考虑因素。
Spring的ORM集成的主要目标是清晰的应用程序分层(适用于任何数据访问和事务技术)以及应用程序对象的松散耦合——不再有业务服务对数据访问或事务策略的依赖,不再有硬编码的资源查找,不再难以替换的单例,不再有自定义的服务注册表。目标是有一种简单一致的方法来连接应用程序对象,保持它们的可重用性,并尽可能避免容器依赖。所有单独的数据访问特性都可以独立使用,但与Spring的应用程序上下文概念很好地集成,提供基于XML的配置和对不需要Spring感知的普通JavaBean实例的交叉引用。在一个典型的Spring应用程序中,许多重要的对象都是JavaBeans:数据访问模板、数据访问对象、事务管理器、使用数据访问对象和事务管理器的业务服务、Web视图解析器、使用业务服务的Web控制器等。
资源和事务管理
Spring倡导简单有效的资源处理方法,即在JDBC的情况下通过模板进行IoC,在ORM技术中应用AOP拦截器。
基础设施提供了适当的资源处理和将特定API异常转换为非检查的基础设施异常层次结构。Spring引入了适用于任何数据访问策略的DAO异常层次结构。
在事务管理方面,JdbcTemplate
类与Spring事务支持相集成,并通过相应的Spring事务管理器支持JTA(Java事务API)和JDBC事务。对于支持的ORM(对象关系映射)技术,Spring通过Hibernate和JPA(Java持久化API)事务管理器以及JTA支持提供Hibernate和JPA支持。
异常转换(Exception Translation)
当在DAO中使用Hibernate或JPA时,必须决定如何处理持久化技术的本地异常类。DAO抛出的是一个HibernateException
或PersistenceException
的子类,具体取决于技术。这些异常都是运行时异常,不必声明或捕获。可能还需要处理IllegalArgumentException
和IllegalStateException
。这意味着调用者只能将异常视为通常是致命的,除非他们想依赖于持久化技术自己的异常结构。没有将调用者绑定到实现策略的情况下,捕获特定原因(如乐观锁定失败)是不可能的。这种权衡对于强烈基于ORM的应用程序或者不需要任何特殊异常处理(或两者兼有)的应用程序来说可能是可以接受的。然而,Spring允许通过@Repository
注解透明地应用异常转换。以下示例(一个用于Java配置,另一个用于XML配置)展示了如何做到这一点:
@Repository
public class ProductDaoImpl implements ProductDao {
// class body here...
}
<beans>
<!-- Exception translation bean post processor -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
后处理器(postprocessor )自动寻找所有异常转换器(PersistenceExceptionTranslator
接口的实现),并通知所有使用@Repository
注解标记的bean,以便发现的转换器可以拦截并对抛出的异常应用适当的转换。
总之,可以基于简单的持久化技术的API和注解实现DAO,同时仍然从Spring管理的事务、依赖注入和透明异常转换(如果需要)中受益,转换为Spring的自定义异常层次结构。
Hibernate
我们从在Spring环境中使用Hibernate 5开始,用它来展示Spring集成OR映射器的方法。本节详细讨论了许多问题,并展示了DAO实现和事务划分的不同变体。这些模式中的大多数可以直接转换到所有其他支持的ORM工具。
截至Spring框架6.0,Spring要求Hibernate ORM 5.5+用于Spring的HibernateJpaVendorAdapter
以及本地Hibernate SessionFactory
设置。推荐使用Hibernate ORM 5.6作为该版本Hibernate的最后一个功能分支。
Hibernate ORM 6.x仅作为JPA提供者(HibernateJpaVendorAdapter
)得到支持。不再支持使用orm.hibernate5
包的普通SessionFactory
设置。推荐在新的开发项目中使用Hibernate ORM 6.1/6.2与JPA风格的设置。
在Spring容器中设置SessionFactory
为了避免将应用程序对象与硬编码的资源查找绑定,可以在Spring容器中将资源(如JDBC DataSource
或Hibernate SessionFactory
)定义为bean。需要访问资源的应用程序对象通过bean引用接收对这些预定义实例的引用。
以下摘自XML应用程序上下文定义的片段展示了如何设置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>
从本地的Jakarta Commons DBCP BasicDataSource
切换到JNDI定位的DataSource
(通常由应用服务器管理)只是配置的问题,如下例所示:
<beans>
<jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
</beans>
还可以使用Spring的JndiObjectFactoryBean
/ <jee:jndi-lookup>
访问JNDI定位的SessionFactory
,以检索并暴露它。然而,这在EJB上下文之外通常不常见。
Spring还提供了一个LocalSessionFactoryBuilder
变体,与@Bean
风格的配置和编程式设置(不涉及FactoryBean
)无缝集成。
LocalSessionFactoryBean
和LocalSessionFactoryBuilder
都支持后台引导,Hibernate初始化在给定的引导执行器(如SimpleAsyncTaskExecutor
)上与应用程序引导线程并行运行。在LocalSessionFactoryBean
上,这可以通过bootstrapExecutor
属性获得。在编程式的LocalSessionFactoryBuilder
上,有一个重载的buildSessionFactory
方法,它接受一个引导执行器参数。
截至Spring框架5.1,这种原生Hibernate设置也可以暴露一个JPA EntityManagerFactory
,用于标准JPA交互以及原生Hibernate访问。
基于原生Hibernate API实现DAO
Hibernate有一个称为上下文会话的特性,其中Hibernate本身管理每个事务的一个当前Session
。这大致相当于Spring的同步每个事务一个Hibernate 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();
}
}
这种风格类似于Hibernate参考文档和示例中的风格,除了在实例变量中持有SessionFactory
。推荐这种基于实例的设置,而不是来自Hibernate的CaveatEmptor示例应用程序的老派静态HibernateUtil
类。(通常情况下,除非绝对必要,否则不要在任何静态变量中保留任何资源)
前面的DAO示例遵循依赖注入模式。它很好地适应了Spring IoC容器,就像它是针对Spring的HibernateTemplate
编写的一样。也可以在纯Java中设置这样的DAO(例如,在单元测试中)。为此,实例化它并通过所需的工厂引用调用setSessionFactory(..)
。作为Spring bean定义,DAO将类似于以下内容:
<beans>
<bean id="myProductDao" class="product.ProductDaoImpl">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
</beans>
这种DAO风格的主要优势是它仅依赖于Hibernate API。不需要导入任何Spring类。从非侵入性的角度来看,这是很吸引人的,并且对于Hibernate开发人员来说可能感觉更自然。
然而,DAO抛出的是原生的HibernateException
(这是未检查的,因此不必声明或捕获),这意味着调用者只能将异常视为通常是致命的——除非它们想依赖Hibernate自己的异常层次结构。如果不将调用者与实现策略绑定,就不可能捕获特定的原因(如乐观锁定失败)。对于那些强烈基于Hibernate、不需要任何特殊异常处理或者两者都需要的应用程序来说,这种权衡可能是可以接受的。
幸运的是,Spring的LocalSessionFactoryBean
支持Hibernate的SessionFactory.getCurrentSession()
方法,用于任何Spring事务策略,即使在使用HibernateTransactionManager
的情况下,也返回当前由Spring管理的事务性Session
。如果没有正在进行的JTA事务,该方法的标准行为仍然是返回与当前的Session
相关联的JTA事务。无论你是使用Spring的JtaTransactionManager
、EJB容器管理的事务(CMTs)还是JTA,这种行为都适用。
总之,可以基于纯Hibernate API实现DAOs,同时仍然能够参与由Spring管理的事务。
声明式事务界定(Declarative Transaction Demarcation)
建议使用Spring的声明式事务支持,它允许用AOP事务拦截器替换Java代码中的显式事务划分API调用。可以在Spring容器中通过使用Java注解或XML配置此事务拦截器。这种声明式事务功能使你的业务服务不受重复的事务划分代码的影响,并专注于添加业务逻辑,这是应用程序的真正价值所在。
可以使用@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();
}
}
在容器中,需要设置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>
编程式事务划分
可以在应用程序的更高级别划分事务,位于跨越任意数量操作的低级别数据访问服务之上。周围的业务服务的实现也没有限制。它只需要一个Spring PlatformTransactionManager
。同样,后者可以来自任何地方,但最好是通过setTransactionManager(..)
方法作为bean引用。此外,productDAO
应该通过setProductDao(..)
方法设置。以下一对代码片段展示了在Spring应用程序上下文中的事务管理器和业务服务定义,以及业务方法实现的示例:
<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的TransactionInterceptor
允许任何已检查的应用程序异常与回调代码一起抛出,而TransactionTemplate
仅限于在回调中未检查的异常。如果发生未检查的应用程序异常或者应用程序通过设置TransactionStatus
将事务标记为仅回滚,TransactionTemplate
会触发回滚。默认情况下,TransactionInterceptor
的行为相同,但允许为每个方法配置可定制的回滚策略。
事务管理策略
TransactionTemplate
和TransactionInterceptor
都将实际的事务处理委托给一个PlatformTransactionManager
实例(它可以是一个HibernateTransactionManager
(用于单个Hibernate SessionFactory
),在底层使用ThreadLocal
Session
)或一个JtaTransactionManager
(委托给容器的JTA子系统)用于Hibernate应用程序。甚至可以使用自定义的PlatformTransactionManager
实现。从原生Hibernate事务管理切换到JTA(例如,当应用程序的某些部署面临分布式事务要求时)只是配置问题。可以用Spring的JTA事务实现替换Hibernate事务管理器。事务划分和数据访问代码无需更改,因为它们使用的是通用的事务管理API。
对于跨越多个Hibernate会话工厂的分布式事务,可以将JtaTransactionManager
作为事务策略与多个LocalSessionFactoryBean
定义相结合。然后,每个DAO都会得到一个特定的SessionFactory
引用,并将其传递给相应的bean属性。如果所有底层的JDBC数据源都是事务性容器数据源,那么只要业务服务使用JtaTransactionManager
作为策略,它就可以在任何数量的DAO和任何数量的会话工厂之间界定事务,而无需特别关注。
HibernateTransactionManager
和JtaTransactionManager
都允许与Hibernate进行适当的JVM级别的缓存处理,无需特定于容器的事务管理器查找或JCA连接器(如果不使用EJB来启动事务)。
HibernateTransactionManager
可以将Hibernate的JDBC连接导出到特定数据源的普通JDBC访问代码。这种能力允许在完全不使用JTA的情况下进行混合Hibernate和JDBC数据访问的高级事务划分,前提是只访问一个数据库。如果已经通过LocalSessionFactoryBean
类的dataSource
属性为传入的SessionFactory
设置了DataSource
,HibernateTransactionManager
会自动将Hibernate事务暴露为JDBC事务。或者,可以通过HibernateTransactionManager
类的dataSource
属性明确指定应该暴露事务的DataSource
。
为了实现JTA风格的实际资源连接的延迟检索,Spring为目标连接池提供了一个相应的DataSource
代理类:LazyConnectionDataSourceProxy
。这对于Hibernate只读事务特别有用,因为它们通常可以从本地缓存处理,而不是访问数据库。
比较容器管理和本地定义的资源
可以在容器管理的JNDI SessionFactory
和本地定义的SessionFactory
之间切换,而无需更改任何应用程序代码。是否将资源定义保留在容器中还是在应用程序内部本地化,主要是使用的事务策略的问题。与Spring定义的本地SessionFactory
相比,手动注册的JNDI SessionFactory
没有任何优势。通过Hibernate的JCA连接器部署SessionFactory
提供了参与Jakarta EE服务器管理基础设施的附加价值,但除此之外并没有增加实际价值。
Spring的事务支持并不绑定于任何容器。当配置使用除JTA之外的任何策略时,事务支持也适用于独立或测试环境。尤其是在典型的单一数据库事务场景中,Spring的单资源本地事务支持是一种轻量级且强大的替代方案,可以替代JTA。当使用本地EJB无状态会话bean来驱动事务时,即使只访问一个数据库并且仅使用无状态会话bean通过容器管理的事务提供声明式事务,也依赖于EJB容器和JTA。直接以编程方式使用JTA也需要Jakarta EE环境。
Spring驱动的事务可以与本地定义的Hibernate SessionFactory
一起工作,就像它们与本地JDBC DataSource
一样,前提是它们访问的是单一数据库。因此,只需要在有分布式事务要求时使用Spring的JTA事务策略。JCA连接器需要特定于容器的部署步骤,并且(显然)首先需要JCA支持。这种配置比部署具有本地资源定义和Spring驱动事务的简单web应用程序需要更多的工作。
总而言之,如果不使用EJB,那么坚持使用本地SessionFactory
设置和Spring的HibernateTransactionManager
或JtaTransactionManager
。将获得所有好处,包括适当的事务性JVM级缓存和分布式事务,而无需忍受容器部署的不便。通过JCA连接器进行的Hibernate SessionFactory
的JNDI注册,只有在与EJB结合使用时才会增加价值。
Hibernate引发的虚假应用服务器警告
在某些具有非常严格的XADataSource
实现的JTA环境中(目前包括一些WebLogic Server和WebSphere版本),当Hibernate配置未考虑到该环境的JTA事务管理器时,应用服务器日志中可能会出现虚假警告或异常。这些警告或异常表明正在访问的连接不再有效,或者JDBC访问不再有效,可能是因为事务不再活跃。例如,以下是一个来自WebLogic的实际异常:
java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No
further JDBC access is allowed within this transaction.
另一个常见问题是在使用Hibernate会话(可能是底层的JDBC连接)后,JTA事务出现连接泄漏,没有正确关闭。
可以通过让Hibernate意识到JTA事务管理器(与Spring一起同步)来解决这些问题。为此,有两个选项:
将Spring JtaTransactionManager
bean传递给Hibernate设置。最简单的方法是将bean引用传递到LocalSessionFactoryBean
bean的jtaTransactionManager
属性中。然后,Spring会向Hibernate提供相应的JTA策略。
还可以在LocalSessionFactoryBean
上的“hibernateProperties
”中显式配置Hibernate的JTA相关属性,特别是“hibernate.transaction.coordinator_class
”、“hibernate.connection.handling_mode
”和可能的“hibernate.transaction.jta.platform
”。
当Hibernate没有配置对JTA事务管理器的任何意识时,以下事件将在JTA事务提交时发生:
- JTA事务提交。
- Spring的
JtaTransactionManager
与JTA事务同步,因此通过afterCompletion
回调被JTA事务管理器调用回来。 - 在其它活动中,这种同步可以触发Spring通过Hibernate的
afterTransactionCompletion
回调(用于清除Hibernate缓存)对Hibernate的回调,然后是对Hibernate会话的显式close()
调用,这会导致Hibernate尝试关闭JDBC连接。 - 在某些环境中,这个
Connection.close()
调用随后会触发警告或错误,因为应用服务器不再认为连接是可用的,因为事务已经提交。
当Hibernate配置了对JTA事务管理器的意识时,以下事件将在JTA事务提交时发生:
- JTA事务准备提交。
- Spring的
JtaTransactionManager
与JTA事务同步,因此事务通过beforeCompletion
回调被JTA事务管理器调用回来。 - Spring意识到Hibernate本身与JTA事务同步,并且与前面的情况有所不同。特别是,它与Hibernate的事务资源管理保持一致。
- JTA事务提交。
- Hibernate与JTA事务同步,因此事务通过
afterCompletion
回调被JTA事务管理器调用回来,并可以正确清除其缓存。
JPA
Spring JPA,位于org.springframework.orm.jpa
包下,为Java持久化API提供了全面的支持,其方式与集成Hibernate类似,同时它还了解底层实现以便提供额外的功能。
在Spring环境中设置JPA的三种选项
Spring JPA支持提供了三种设置应用程序用来获取实体管理器的JPA EntityManagerFactory
的方式:
- 使用
LocalEntityManagerFactoryBean
- 从JNDI获取
EntityManagerFactory
- 使用
LocalContainerEntityManagerFactoryBean
使用LocalEntityManagerFactoryBean
只能在简单的部署环境中使用此选项,例如独立应用程序和集成测试。
LocalEntityManagerFactoryBean
创建一个适用于简单部署环境的EntityManagerFactory
,在这种环境中应用程序仅使用JPA进行数据访问。该工厂bean使用JPA PersistenceProvider
的自动检测机制(根据JPA的Java SE引导),在大多数情况下,只需指定持久化单元名称。以下XML示例配置了这样一个bean:
<beans>
<bean id="myEmf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="myPersistenceUnit"/>
</bean>
</beans>
这种形式的JPA部署是最简单和最受限的。不能引用现有的JDBC DataSource
bean定义,也不存在对全局事务的支持。此外,持久化类的编织(字节码转换)是特定于提供者的,通常需要在启动时指定特定的JVM代理。这个选项只适用于独立应用程序和测试环境,这是JPA规范的设计目标。
从JNDI获取EntityManagerFactory
在部署到Jakarta EE服务器时,可以使用此选项。请查阅服务器的文档,了解如何将自定义JPA提供程序部署到你的服务器中,以便使用与服务器默认提供程序不同的提供程序。
从JNDI获取EntityManagerFactory
(例如在Jakarta EE环境中),只需更改XML配置即可,如下例所示:
<beans>
<jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
</beans>
此操作假定使用标准的Jakarta EE引导。Jakarta EE服务器自动检测持久化单元(实际上,应用程序jar中的META-INF/persistence.xml
文件)和Jakarta EE部署描述符(例如web.xml
)中的persistence-unit-ref
条目,并为这些持久化单元定义环境命名上下文位置。
在这种情况下,整个持久化单元的部署,包括持久化类的编织(字节码转换),都由Jakarta EE服务器负责。JDBC DataSource
通过META-INF/persistence.xml
文件中的JNDI位置定义。EntityManager
事务与服务器的JTA子系统集成。Spring只是使用获得的EntityManagerFactory
,并通过依赖注入将其传递给应用程序对象,同时管理持久化单元的事务(通常通过JtaTransactionManager
)。
如果在同一应用程序中使用多个持久化单元,这些通过JNDI检索到的持久化单元的bean名称应与应用程序引用它们的持久化单元名称相匹配(例如,在@PersistenceUnit
和@PersistenceContext
注解中)。
使用LocalContainerEntityManagerFactoryBean
可以在基于Spring的应用程序环境中使用此选项来获得完整的JPA功能。这包括像Tomcat这样的Web容器、独立应用程序,以及具有复杂持久化需求的集成测试。
LocalContainerEntityManagerFactoryBean
允许完全控制EntityManagerFactory
的配置,适用于需要细粒度自定义的环境。LocalContainerEntityManagerFactoryBean
基于persistence.xml
文件、提供的dataSourceLookup
策略和指定的loadTimeWeaver
创建一个PersistenceUnitInfo
实例。因此,可以使用JNDI之外的自定义数据源,并控制编织过程。以下示例显示了LocalContainerEntityManagerFactoryBean
的典型bean定义:
<beans>
<bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="someDataSource"/>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
</property>
</bean>
</beans>
以下示例显示了一个典型的persistence.xml
文件:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="myUnit" transaction-type="RESOURCE_LOCAL">
<mapping-file>META-INF/orm.xml</mapping-file>
<exclude-unlisted-classes/>
</persistence-unit>
</persistence>
快捷方式<exclude-unlisted-classes/>
表示不应该发生对注解实体类的扫描。显式的’true’值(<exclude-unlisted-classes>true</exclude-unlisted-classes/>
)也表示不进行扫描。<exclude-unlisted-classes>false</exclude-unlisted-classes/>
会触发扫描。然而,如果希望发生实体类扫描,建议省略exclude-unlisted-classes
元素。
使用LocalContainerEntityManagerFactoryBean
是功能最强大的JPA设置选项,允许在应用程序中进行灵活的本地配置。它支持链接到现有的JDBC DataSource
,同时支持本地和全局事务等。然而,它也对运行时环境提出了要求,例如如果持久化提供者需要字节码转换,就需要一个能够进行编织的类加载器。
这个选项可能与Jakarta EE服务器的内置JPA功能发生冲突。在完整的Jakarta EE环境中,考虑从JNDI获取EntityManagerFactory
。或者,在LocalContainerEntityManagerFactoryBean
定义上指定一个自定义的persistenceXmlLocation
(例如,META-INF/my-persistence.xml
),并在应用程序jar文件中只包含具有该名称的描述符。因为Jakarta EE服务器只会查找默认的META-INF/persistence.xml
文件,它会忽略这些自定义持久化单元,从而避免与Spring驱动的JPA设置预先发生冲突。(例如,这适用于Resin 3.1)
何时需要加载时编织?
并非所有JPA提供者都需要JVM代理。Hibernate就是不需要的一个示例。如果提供者不需要代理,或者有其他替代方案,例如通过自定义编译器或Ant任务在构建时应用增强,那么不应该使用加载时编织器。
LoadTimeWeaver
接口是Spring提供的类,它允许JPA ClassTransformer
实例以特定方式插入,这取决于环境是Web容器还是应用服务器。通过代理钩入ClassTransformer
通常不太高效。代理针对整个虚拟机工作,并检查加载的每个类,这在生产服务器环境中通常是不期望的。
Spring为各种环境提供了多个LoadTimeWeaver
实现,使得ClassTransformer
实例仅应用于每个类加载器,而不是每个虚拟机。
可以使用@EnableLoadTimeWeaving
注解或context:load-time-weaver
XML元素来配置上下文范围的LoadTimeWeaver
。这样的全局编织器会被所有JPA LocalContainerEntityManagerFactoryBean
实例自动捕获。以下示例显示了设置加载时编织器的推荐方式,提供平台自动检测(例如,Tomcat的可编织类加载器或Spring的JVM代理)以及将编织器自动传播到所有支持编织的beans:
<context:load-time-weaver/>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
</bean>
但是,如果需要,可以通过loadTimeWeaver
属性手动指定一个专用的编织器,如下例所示:
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
</property>
</bean>
无论LTW如何配置,使用这种技术,依赖插桩(或监控代码)的JPA应用程序都可以在目标平台(例如Tomcat)上运行,而不需要代理。当托管应用程序依赖于不同的JPA实现时,这一点尤其重要,因为JPA转换器仅在类加载器级别应用,因此彼此隔离。
处理多个持久化单元
对于依赖多个持久化单元位置的应用程序(例如,存储在类路径中的各种JARS中),Spring提供了PersistenceUnitManager
来充当中央仓库,并避免了可能代价高昂的持久化单元发现过程。默认实现允许指定多个位置。这些位置被解析,稍后通过持久化单元名称检索。(默认情况下,搜索类路径中的META-INF/persistence.xml
文件。)以下示例配置了多个位置:
<bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
<property name="persistenceXmlLocations">
<list>
<value>org/springframework/orm/jpa/domain/persistence-multi.xml</value>
<value>classpath:/my/package/**/custom-persistence.xml</value>
<value>classpath*:META-INF/persistence.xml</value>
</list>
</property>
<property name="dataSources">
<map>
<entry key="localDataSource" value-ref="local-db"/>
<entry key="remoteDataSource" value-ref="remote-db"/>
</map>
</property>
<!-- if no datasource is specified, use this one -->
<property name="defaultDataSource" ref="remoteDataSource"/>
</bean>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitManager" ref="pum"/>
<property name="persistenceUnitName" value="myCustomUnit"/>
</bean>
默认实现允许在将PersistenceUnitInfo
实例提供给JPA提供者之前对其进行自定义,可以通过声明性方式(通过其属性,影响所有托管单元)或编程方式(通过PersistenceUnitPostProcessor
,允许选择持久化单元)。如果没有指定PersistenceUnitManager
,LocalContainerEntityManagerFactoryBean
将创建一个并在内部使用。
后台引导
LocalContainerEntityManagerFactoryBean
通过 bootstrapExecutor
属性支持后台引导,如下所示:
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="bootstrapExecutor">
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
</property>
</bean>
实际的JPA提供者引导被交给指定的执行器,然后并行运行于应用程序引导线程。暴露的EntityManagerFactory
代理可以被注入到其他应用程序组件中,并且甚至能够响应EntityManagerFactoryInfo
配置检查。然而,一旦其他组件访问实际的JPA提供者(例如,调用createEntityManager
),这些调用会阻塞,直到后台引导完成。特别是在使用Spring Data JPA时,确保为其仓库设置延迟引导。
基于JPA实现DAO:EntityManagerFactory和EntityManager
虽然EntityManagerFactory
实例是线程安全的,但EntityManager
实例不是。注入的JPA EntityManager
的行为就像从应用服务器的JNDI环境获取的EntityManager
一样,如JPA规范所定义。如果有当前事务性的EntityManager
,它会委托所有调用给该EntityManager
。否则,它会回退到为每个操作新创建的EntityManager
,实际上使其使用变得线程安全。
通过使用注入的EntityManagerFactory
或EntityManager
,可以编写不依赖任何Spring依赖项的纯JPA代码。如果启用了PersistenceAnnotationBeanPostProcessor
,Spring可以理解字段和方法级别的@PersistenceUnit
和@PersistenceContext
注解。以下示例显示了一个使用@PersistenceUnit
注解的纯JPA DAO实现:
public class ProductDaoImpl implements ProductDao {
private EntityManagerFactory emf;
@PersistenceUnit
public void setEntityManagerFactory(EntityManagerFactory emf) {
this.emf = emf;
}
public Collection loadProductsByCategory(String category) {
EntityManager em = this.emf.createEntityManager();
try {
Query query = em.createQuery("from Product as p where p.category = ?1");
query.setParameter(1, category);
return query.getResultList();
}
finally {
if (em != null) {
em.close();
}
}
}
}
前面的DAO没有依赖Spring,仍然可以很好地适应Spring应用程序上下文。此外,DAO利用注解来要求注入默认的EntityManagerFactory
,如下例bean定义所示:
<beans>
<!-- bean post-processor for JPA annotations -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
作为明确定义PersistenceAnnotationBeanPostProcessor
的替代方法,可以考虑在应用程序上下文配置中使用Spring的context:annotation-config
XML元素。这样做会自动注册所有基于注解配置的Spring标准后处理器,包括CommonAnnotationBeanPostProcessor
等。
考虑以下示例:
<beans>
<!-- post-processors for all standard config annotations -->
<context:annotation-config/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
这样的DAO的主要问题是它总是通过工厂创建一个新的EntityManager
。可以通过请求注入一个事务性的EntityManager
(也称为“共享EntityManager
”,因为它是实际事务性EntityManager
的共享、线程安全的代理)来避免这种情况。以下示例展示了如何做到这一点:
public class ProductDaoImpl implements ProductDao {
@PersistenceContext
private EntityManager em;
public Collection loadProductsByCategory(String category) {
Query query = em.createQuery("from Product as p where p.category = :category");
query.setParameter("category", category);
return query.getResultList();
}
}
@PersistenceContext
注解有一个可选的属性type
,默认值为PersistenceContextType.TRANSACTION
。可以使用这个默认值来接收一个共享的EntityManager代理。另一种选择,PersistenceContextType.EXTENDED
,则是完全不同的事情。这会导致所谓的扩展EntityManager
,它不是线程安全的,因此不能在并发访问的组件中使用,比如Spring管理的单例bean。扩展EntityManager
实例只应该在有状态的组件中使用,例如驻留在会话中的组件,其生命周期不依赖于当前事务,而完全由应用程序控制。
方法和字段级别的注入
可以在类内的字段或方法上应用表示依赖注入的注解(如@PersistenceUnit
和@PersistenceContext
)——因此有“方法级注入”和“字段级注入”的说法。字段级注解简洁且易于使用,而方法级注解允许对注入的依赖进行进一步处理。在这两种情况下,成员的可见性(公有、受保护或私有)并不重要。
类级别的注解呢?
在Jakarta EE平台上,它们用于依赖声明而不是资源注入。
注入的EntityManager
由Spring管理(知道正在进行的事务)。尽管新的DAO实现使用EntityManager
的方法级注入而不是EntityManagerFactory
,但由于使用了注解,bean定义中不需要做任何更改。
这种DAO风格的主要优势是它仅依赖于Java持久化API。不需要导入任何Spring类。此外,由于理解了JPA注解,注入是由Spring容器自动应用的。从非侵入性的角度来看,这很有吸引力,对JPA开发人员来说可能感觉更自然。
基于@Autowired实现DAO(通常使用基于构造函数的注入)
@PersistenceUnit
和@PersistenceContext
只能声明在方法和字段上。那么通过构造函数和其他@Autowired
注入点提供JPA资源呢?
只要目标被定义为bean,例如通过LocalContainerEntityManagerFactoryBean
,EntityManagerFactory
可以轻松地通过构造函数和@Autowired
字段/方法注入。注入点与原始的EntityManagerFactory
定义按类型完全匹配。
然而,@PersistenceContext
风格的共享EntityManager
引用并不是默认可用的。为了使其能够根据@Autowired
所需的类型匹配,考虑定义一个SharedEntityManagerBean
作为EntityManagerFactory
定义的伴随bean:
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
</bean>
<bean id="em" class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
<property name="entityManagerFactory" ref="emf"/>
</bean>
或者,可以基于SharedEntityManagerCreator
定义一个@Bean
方法:
@Bean("em")
public static EntityManager sharedEntityManager(EntityManagerFactory emf) {
return SharedEntityManagerCreator.createSharedEntityManager(emf);
}
在有多个持久化单元的情况下,每个EntityManagerFactory
定义都需要有一个相应的EntityManager
bean定义来伴随,理想情况下带有与不同的EntityManagerFactory
定义匹配的限定符,以便通过@Autowired @Qualifier("…")
区分持久化单元。
Spring驱动的JPA事务
对于JPA,推荐的策略是通过JPA的本地事务支持进行本地事务。Spring的JpaTransactionManager
提供了许多已知的本地JDBC事务功能(如事务特定的隔离级别和资源级别的只读优化),这些功能适用于任何常规的JDBC连接池,无需JTA事务协调器和支持XA的资源。
Spring JPA还允许配置的JpaTransactionManager
将JPA事务暴露给访问相同DataSource
的JDBC访问代码,只要注册的JpaDialect
支持检索底层的JDBC Connection
。Spring为EclipseLink和Hibernate JPA实现提供了方言。
对于JTA风格的实际资源连接的延迟检索,Spring为目标连接池提供了一个相应的DataSource
代理类:LazyConnectionDataSourceProxy
。这对于通常可以从本地缓存处理而不是访问数据库的JPA只读事务特别有用。
理解JpaDialect 和 JpaVendorAdapter
作为一个高级特性,JpaTransactionManager
和AbstractEntityManagerFactoryBean
的子类允许将自定义的JpaDialect
传递给jpaDialect
bean属性。JpaDialect
实现可以启用Spring支持的以下高级功能,通常以特定于供应商的方式:
- 应用特定的事务语义(如自定义隔离级别或事务超时)
- 检索事务性的JDBC
Connection
(以便暴露给基于JDBC的DAO) - 将
PersistenceException
高级转换为Spring的DataAccessException
这对于特殊的事务语义和异常的高级转换特别有价值。默认实现(DefaultJpaDialect
)不提供任何特殊能力,如果需要前面列出的功能,必须指定适当的方言。
作为更广泛的提供者适配设施,主要用于Spring的全功能LocalContainerEntityManagerFactoryBean
设置,JpaVendorAdapter
结合了JpaDialect
的功能和其他特定于提供者的默认设置。指定HibernateJpaVendorAdapter
或EclipseLinkJpaVendorAdapter
是自动配置Hibernate或EclipseLink的EntityManagerFactory
设置的最方便方式。请注意,这些提供者适配器主要设计用于与Spring驱动的事务管理(即与JpaTransactionManager
一起使用)。
设置JPA与JTA事务管理
作为JpaTransactionManager
的替代方案,Spring还允许通过JTA进行多资源事务协调,无论是在Jakarta EE环境中还是在独立的事务协调器(如Atomikos)中。除了选择Spring的JtaTransactionManager
而不是JpaTransactionManager
之外,还需要采取一些进一步的步骤:
- 底层的JDBC连接池需要支持XA,并与事务协调器集成。在Jakarta EE环境中,这通常是直接的,通过JNDI暴露不同类型的
DataSource
。同样,独立的事务协调器通常也带有特殊的XA集成DataSource
变体。 - JPA
EntityManagerFactory
设置需要配置为JTA。这是特定于提供者的,通常通过在LocalContainerEntityManagerFactoryBean
上指定jpaProperties
的特殊属性来实现。在Hibernate的情况下,这些属性甚至是版本特定的。 - Spring的
HibernateJpaVendorAdapter
强制执行某些面向Spring的默认设置,例如连接释放模式为on-close
,这与Hibernate 5.0自己的默认设置相匹配,但在Hibernate 5.1+中不再是这样。对于JTA设置,请确保将持久化单元事务类型声明为“JTA”。或者,设置Hibernate 5.2的hibernate.connection.handling_mode
属性为DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
以恢复Hibernate自己的默认设置。 - 或者,考虑从应用程序服务器本身获取
EntityManagerFactory
(即通过JNDI查找而不是本地声明的LocalContainerEntityManagerFactoryBean
)。服务器提供的EntityManagerFactory
可能需要在服务器配置中进行特殊定义(使得部署不太可移植),但它是为服务器的JTA环境设置的。
原生Hibernate设置和与JPA交互的原生Hibernate事务
原生的LocalSessionFactoryBean
设置与HibernateTransactionManager
结合使用,允许与@PersistenceContext
和其他JPA访问代码进行交互。现在,Hibernate SessionFactory
原生实现了JPA的EntityManagerFactory
接口,而Hibernate Session
的处理原生地就是JPA EntityManager
。Spring的JPA支持设施会自动检测原生Hibernate会话。
因此,在许多场景中,这样的原生Hibernate设置可以作为标准JPA LocalContainerEntityManagerFactoryBean
和JpaTransactionManager
组合的替代品,允许与SessionFactory.getCurrentSession()
(以及HibernateTemplate
)进行交互,而在同一个本地事务中也可以使用@PersistenceContext
EntityManager
。这样的设置还提供了更强的Hibernate集成和更多的配置灵活性,因为它不受JPA引导合同的限制。
在这种情况下,不需要HibernateJpaVendorAdapter
配置,因为Spring的原生Hibernate设置提供了更多的功能(例如,自定义Hibernate Integrator设置、Hibernate 5.3 bean容器集成以及对只读事务的更强优化)。最后但同样重要的是,还可以通过LocalSessionFactoryBuilder
表达原生Hibernate设置,与@Bean
风格的配置无缝集成(不涉及FactoryBean
)。
LocalSessionFactoryBean
和LocalSessionFactoryBuilder
支持后台引导,就像JPA的LocalContainerEntityManagerFactoryBean
一样。
在LocalSessionFactoryBean
上,这可以通过bootstrapExecutor
属性进行配置。对于程序化的LocalSessionFactoryBuilder
,一个重载的buildSessionFactory
方法接受一个引导执行器参数。