一、Spring中集成Hibernate

Hibernate是在开发者社区很流行的开源持久化框架。它不仅提供了基本的对象关系映射,还提供了ORM工具所应具有的所有复杂功能,比如缓存、延迟加载、预先抓取以及分布式缓存。

1.1 声明Hibernate的Session工厂


1.2 构建不依赖于Spring的Hibernate代码

spring Scheduled spring scheduled hibernate_java


为了给不使用模板的Hibernate Repository添加异常转换功能,我们只需在Spring应用上下文中添加一个PersistenceExceptionTranslationPostProcessorbean:


spring Scheduled spring scheduled hibernate_spring_02


PersistenceExceptionTranslationPostProcessor是一个bean 后置处理器(bean post-processor),它会在所有拥有@Repository注解的类上添加一个通知器(advisor),这样就会捕获任何平台相关的异常并以Spring非检查型数据访问异常的形式重新抛出。

二、Spring与Java持久化API(JPA)

2.1 配置实体管理器工厂

简单来讲,基于JPA的应用程序需要使用EntityManagerFactory的实现类来获取EntityManager实例。JPA定义了两种类型的实体管理器:

  • 应用程序管理类型(Application-managed):当应用程序向实体管理器工厂直接请求实体管理器时,工厂会创建一个实体管理器。在这种模式下,程序要负责打开或关闭实体管理器并在事务中对其进行控制。这种方式的实体管理器适合于不运行在JavaEE容器中的独立应用程序。
  • 容器管理类型(Container-managed):实体管理器由Java EE创建和管理。应用程序根本不与实体管理器工厂打交道。相反,实体管理器直接通过注入或JNDI来获取。容器负责配置实体管理器工厂。这种类型的实体管理器最适用于Java EE容器,在这种情况下会希望在persistence.xml指定的JPA配置之外保持一些自己对JPA的控制。

这两种实体管理器工厂分别由对应的Spring工厂Bean创建:

  • LocalEntityManagerFactoryBean生成应用程序管理类型的EntityManager-Factory;
  • LocalContainerEntityManagerFactoryBean生成容器管理类型的Entity-ManagerFactory。

配置应用程序管理类型的JPA

对于应用程序管理类型的实体管理器工厂来说,它绝大部分配置信息来源于一个名为persistence.xml的配置文件。这个文件必须位于类路径下的META-INF目录下。
persistence.xml的作用在于定义一个或多个持久化单元。持久化单元是同一个数据源下的一个或多个持久化类。简单来讲,persistence.xml列出了一个或多个的持久化类以及一些其他的配置如数据源和基于XML的配置文件。如下是一个典型的persistence.xml文件:

spring Scheduled spring scheduled hibernate_Data_03


因为在persistence.xml文件中包含了大量的配置信息,所以在Spring中需要配置的就很少了。可以通过以下的@Bean注解方法在Spring中声明LocalEntityManagerFactoryBean:


spring Scheduled spring scheduled hibernate_java_04

使用容器管理类型的JPA

容器管理的JPA采取了一个不同的方式。当运行在容器中时,可以使用容器(在我们的场景下是Spring)提供的信息来生成EntityManagerFactory。

你可以将数据源信息配置在Spring应用上下文中,而不是在persistence.xml中了。例如,如下的@Bean注解方法声明了在Spring中如何使用LocalContainerEntity-ManagerFactoryBean来配置容器管理类型的JPA:

spring Scheduled spring scheduled hibernate_java_05


这里,我们使用了Spring配置的数据源来设置dataSource属性。任何javax.sql.DataSource的实现都是可以的。尽管数据源还可以在persistence.xml中进行配置,但是这个属性指定的数据源具有更高的优先级。



jpaVendorAdapter属性用于指明所使用的是哪一个厂商的JPA实现。Spring提供了多个JPA厂商适配器:

  • EclipseLinkJpaVendorAdapter
  • HibernateJpaVendorAdapter
  • OpenJpaVendorAdapter

在本例中,我们使用Hibernate作为JPA实现,所以将其配置为Hibernate-JpaVendorAdapter:


spring Scheduled spring scheduled hibernate_spring_06


persistence.xml文件的主要作用就在于识别持久化单元中的实体类。但是从Spring 3.1开始,我们能够在LocalContainerEntityManagerFactoryBean中直接设置packagesToScan属性:


spring Scheduled spring scheduled hibernate_spring Scheduled_07

从JNDI获取实体管理器工厂

还有一件需要注意的事项,如果将Spring应用程序部署在应用服务器中,EntityManagerFactory可能已经创建好了并且位于JNDI中等待查询使用。在这种情况下,可以使用Spring jee命名空间下的<jee:jndi-lookup>元素来获取对EntityManagerFactory的引用:

spring Scheduled spring scheduled hibernate_spring_08


spring Scheduled spring scheduled hibernate_java_09

2.2 编写基于JPA的Repository

spring Scheduled spring scheduled hibernate_spring_10


需要注意的是EntityManagerFactory属性,它使用了@PersistenceUnit注解,因此,Spring会将EntityManagerFactory注入到Repository之中。有了EntityManagerFactory之后,JpaSpitterRepository的方法就能使用它来创建EntityManager了,然后EntityManager可以针对数据库执行操作。

将EntityManager的代理注入到Repository之中


三、借助Spring Data实现自动化的JPARepository
为了要求Spring Data创建SpitterRepository的实现,我们需要在Spring配置中添加一个元素。如下的程序清单展现了在XML配置中启用Spring Data JPA所需要添加的内容:

spring Scheduled spring scheduled hibernate_spring Scheduled_11


<jpa:repositories>元素掌握了Spring Data JPA的所有魔力。就像<context:component-scan>元素一样,<jpa:repositories>元素也需要指定一个要进行扫描的base-package。不过,<context:component-scan>会扫描包(及其子包)来查找带有@Component注解的类,而<jpa:repositories>会扫描它的基础包来查找扩展自SpringData JPA Repository接口的所有接口。如果发现了扩展自Repository的接口,它会自动生成(在应用启动的时候)这个接口的实现。

如果要使用Java配置的话,那就不需要使用<jpa:repositories>元素了,而是要在Java配置类上添加@EnableJpaRepositories注解。如下就是一个Java配置类,它使用了@EnableJpaRepositories注解,并且会扫描com.habuma.spittr.db包:

spring Scheduled spring scheduled hibernate_spring_12

3.1 定义查询方法

spring Scheduled spring scheduled hibernate_java_13


我们并不需要实现findByUsername()。方法签名已经告诉Spring Data JPA足够的信息来创建这个方法的实现了。


当创建Repository实现的时候,Spring Data会检查Repository接口的所有方法,解析方法的名称,并基于被持久化的对象来试图推测方法的目的。本质上,

Spring Data定义了一组小型的领域特定语言

(domain-specific language ,DSL),在这里,持久化的细节都是通过Repository方法的签名来描述的。

Repository方法是由一个动词、一个可选的主题(Subject)、关键词By以及一个断言所组成。 示例:

spring Scheduled spring scheduled hibernate_JPA_14


我们可以看到,这里的动词是read,与之前样例中的find有所差别。Spring Data允许在方法名中使用四种动词:get、read、find和count。其中,动词get、read和find是同义的,这三个动词对应的Repository方法都会查询数据并返回对象。而动词count则会返回匹配对象的数量,而不是对象本身。

3.2 声明自定义查询

如果所需的数据无法通过方法名称进行恰当地描述,那么我们可以使用@Query注解,为Spring Data提供要执行的查询。对于findAllGmailSpitters()方法,我们可以按照如下的方式来使用@Query注解:

spring Scheduled spring scheduled hibernate_Data_15


3.3 混合自定义的功能

有些时候,我们需要Repository所提供的功能是无法用Spring Data的方法命名约定来描述的,甚至无法用@Query注解设置查询来实现。尽管Spring Data JPA非常棒,但是它依然有其局限性,可能需要我们按照传统的方式来编写Repository方法:也就是直接使用EntityManager。

如果你需要做的事情无法通过Spring Data JPA来实现,那就必须要在一个比Spring Data JPA更低的层级上使用JPA。好消息是我们没有必要完全放弃Spring Data JPA。我们只需在必须使用较低层级JPA的方法上,才使用这种传统的方式即可,而对于Spring Data JPA知道该如何处理的功能,我们依然可以通过它来实现。

当Spring Data JPA为Repository接口生成实现的时候,它还会查找名字与接口相同,并且添加了Impl后缀的一个类。如果这个类存在的话,Spring Data JPA将会把它的方法与Spring Data JPA所生成的方法合并在一起。对于SpitterRepository接口而言,要查找的类名为SpitterRepositoryImpl。

为了阐述该功能,假设我们需要在SpitterRepository中添加一个方法,发表Spittle数量在10,000及以上的Spitter将会更新为Elite状态。使用Spring Data JPA的方法命名约定或使用@Query均没有办法声明这样的方法。最为可行的方案是使用如下的eliteSweep()方法。

spring Scheduled spring scheduled hibernate_Data_16


SpitterRepositoryImpl没有什么特殊之处,它使用被注入的EntityManager来完成预期的任务。


注意,SpitterRepositoryImpl并没有实现SpitterRepository接口。Spring Data JPA负责实现这个接口。SpitterRepositoryImpl(将它与Spring Data的Repository关联起来的是它的名字)实现了SpitterSweeper接口,它如下所示:


spring Scheduled spring scheduled hibernate_spring_17


我们还需要确保eliteSweep()方法会被声明在SpitterRepository接口中。要实现这一点,避免代码重复的简单方式就是修改SpitterRepository,让它扩展SpitterSweeper:


spring Scheduled spring scheduled hibernate_JPA_18


如前所述,Spring Data JPA将实现类与接口关联起来是基于接口的名称。但是,Impl后缀只是默认的做法,如果你想使用其他后缀的话,只需在配置@EnableJpa-Repositories的时候,设置repositoryImplementationPostfix属性即可:


spring Scheduled spring scheduled hibernate_java_19


如果在XML中使用<jpa:repositories>元素来配置Spring DataJPA的话,我们可以借助repository-impl-postfix属性指定后缀:


spring Scheduled spring scheduled hibernate_java_20


我们将后缀设置成了Helper,Spring Data JPA将会查找名为SpitterRepository-Helper的类,用它来匹配SpitterRepository接口。