在Spring环境中建立JPA
SpringJPA提供了三种方法创建JPAEntityManagerFactory:
LocalEntityManagerFactoryBean负责创建一个适合于仅使用JPA进行数据访问的环境的EntityManager。Factorybean将使用JPAPersistenceProvider类的自动检测机制(根据JPA的JavaSE启动),而在绝大多数情况下,只需要指定persistenceunit名称:
<beans><beanid="entityManagerFactory"class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<propertyname="persistenceUnitName"value="myPersistenceUnit"/>
</bean>
</beans>
这种JPA部署方式最为简单,但却最受限制。例如,不能连接到现有的JDBCDataSource,并且不支持全局事务。甚至,持久化类的织入(字节码转换)也是特定于提供者的,经常需要在启动时指定一个特定的JVM代理。总之,这种方法实际上只适用于独立的应用程序和测试环境(这正是JPA规范设计它的原因)。
仅在简单部署环境中只使用这种方式,比如独立的应用程序和集成测试。
从JNDI获取EntityManagerFactory(例如在JavaEE5环境中),仅通过修改XML配置即可实现:
<beans><jee:jndi-lookupid="entityManagerFactory"jndi-name="persistence/myPersistenceUnit"/>
</beans>
在标准的JavaEE5启动过程中,JavaEE服务器自动检测持久化单元(例如应用程序文件包中的META-INF/persistence.xml),以及JavaEE部署描述符中定义给那些持久化单元命名上下文位置的环境的persistence-unit-ref项(例如web.xml)。
在这种情况下,整个持久化单元部署,包括持久化类的织入(字码码转换)都取决于JavaEE服务器。JDBCDataSource通过在META-INF/persistence.xml文件中的JNDI位置进行定义;EntityManager事务与服务器的JTA子系统整合。Spring仅仅用获得的EntityManagerFactory,通过依赖注入将它传递给应用程序对象,并为它管理事务(一般通过JtaTransactionManager)。
注意,如果在同一个应用程序中使用了多个持久化单元,JNDI获取的这种持久化单元的bean名称应该与应用程序用来引用它们的持久化单元名称相符(例如@PersistenceUnit和@PersistenceContext注解)。
在部署到JavaEE5服务器时使用该方法。关于如何将自定义JPA提供者部署到服务器,以及允许使用服务器提供的缺省提供者之外的JPA提供者,请查看服务器文档的相关说明。
LocalContainerEntityManagerFactoryBean提供了对JPAEntityManagerFactory的全面控制,非常适合那种需要细粒度定制的环境。LocalContainerEntityManagerFactoryBean将基于persistence.xml文件创建PersistenceUnitInfo类,并提供dataSourceLookup策略和loadTimeWeaver。因此它可以在JNDI之外的用户定义的数据源之上工作,并控制织入流程。
<beans><beanid="entityManagerFactory"class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<propertyname="dataSource"ref="someDataSource"/>
<propertyname="loadTimeWeaver">
<beanclass="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
</property>
</bean>
</beans>
这是最为强大的JPA配置方式,允许在应用程序中灵活进行本地配置。它支持连接现有JDBCDataSource,支持本地事务和全局事务等等。然而,它也将需求强加到了运行时环境中,例如,如果持久化提供者需要字节码转换,则必须有织入ClassLoader的能力。
注意,这个选项可能与JavaEE5服务器内建的JPA功能相冲突。因此,当运行在完全JavaEE5环境中时,要考虑从JNDI获取EntityManagerFactory。另一种可以替代的方法是,在LocalContainerEntityManagerFactoryBean定义中通过“persistenceXmlLocation”指定相关位置,例如“META-INF/my-persistence.xml”,并且只将包含该名称的描述符放在应用程序包文件中。因为JavaEE5服务器将只查找默认的META-INF/persistence.xml文件,它会忽略这种定制的持久化单元,因而避免与前面Spring驱动的JPA配置冲突。(例如,适用于Rdsin3.1)。
在基于Spring的应用程序环境中使用该方式可获得全部JPA功能。这包括web容器,如Tomcat,以及独立的应用程序和包含复杂持久化需求的集成测试。
何时需要加载时织入?
并非所有的JPA提供者都需要JVM代理(Hibernate就是一个例子)。如果你的提供者不需要代理(agent)或者你有其他选择(例如通过自定义编译器或者ant任务在构建时进行增强),那么就不应该使用加载时织入。
LoadTimeWeaver接口由Spring提供,允许JPAClassTransformer实例能够根据环境(web容器/应用服务器)以特定的方式插入。通过Java5代理挂钩ClassTransformers经常是无效的——代理通常在整个虚拟机环境下工作,并且监控每一个被加载的类——这在生产环境下一般是不提倡的。
Spring提供了大量用于不同环境的LoadTimeWeaver实现类,允许ClassTransformer实例能够仅用于每个classloader,而不是每个虚拟机。
接下来的一节将讨论在Tomcat以及使用Spring的VM代理情况下的典型JPA织入配置。关于设置常用加载时织入的详细内容,请参见AOP一章中的第6.8.4.5节“Spring配置”一节,它涵盖了Tomcat、VM代理以及WebLogic、OC4J、GlassFish和Resin。
ApacheTomcat缺省的ClassLoader(类装载器)并不支持类的切换,但是它允许使用用户自定义的类装载器。Spring提供了TomcatInstrumentableClassLoader类(在org.springframework.instrument.classloading.tomcat包中),这个类继承自Tomcat的类装载器(WebappClassLoader)并且允许JPAClassTransformer的实例来“增强”所有由它加载的类。简单说,JPA转化器(JPAtransformer)仅仅在(使用TomcatInstrumentableClassLoader的)特定web应用程序中才能被使用。
为使用用户自定义的类装载器:
将spring-tomcat-weaver.jar复制到$CATALINA_HOME/server/lib下(其中$CATALINA_HOME表示Tomcat的安装路径)。
通过修改webapplicationcontext使Tomcat使用用户自定义的类装载器(而不是默认的类装载器):
<Contextpath="/myWebApp"docBase="/my/webApp/location"><LoaderloaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/>
</Context>
Tomcat5.0.x和5.5.x系列支持多个上下文路径(contextlocations):服务器配置文件($CATALINA_HOME/conf/server.xml),默认的上下文配置($CATALINA_HOME/conf/context.xml)会影响所有被部署的web应用程序、单独部署在Server端的web应用程序的配置($CATALINA_HOME/conf/[enginename]/[hostname]/my-webapp-context.xml)或者与web应用程序一起(your-webapp.war/META-INF/context.xml)。从效率的角度说,我们推荐在web-app的内部配置的方式,因为仅仅使用JPA的应用程序会使用用户自定义的类装载器。更多具体有关可用的上下文路径的内容请参见Tomcat5.x的文档。
注意,5.5.20之前的版本有一个XML配置解析的bug,造成server.xml中无法使用Loader标签,无论是否指定了classloader,也不管这个classloader是官方的还是自定义的。
如果你正在使用的是Tomcat5.5.20以上的版本,就可以将useSystemClassLoaderAsParent设置成 false来解决这个问题:
<Contextpath="/myWebApp"docBase="/my/webApp/location"><LoaderloaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"
useSystemClassLoaderAsParent="false"/>
</Context>
将spring-tomcat-weaver.jar复制到$CATALINA_HOME/lib(where$CATALINA_HOME表示Tomcat安装根目录的位置)。
通过编辑web应用程序上下文文件,使Tomcat使用自定义的ClassLoader(而不是默认的ClassLoader):
<Contextpath="/myWebApp"docBase="/my/webApp/location"><LoaderloaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/>
</Context>
Tomcat6.0.x(类似于5.0.x/5.5.x)系列支持几种上下文路径:(contextlocations):服务器配置文件($CATALINA_HOME/conf/server.xml),默认的上下文配置($CATALINA_HOME/conf/context.xml)会影响所有被部署的web应用程序、单独部署在Server端的web应用程序的配置($CATALINA_HOME/conf/[enginename]/[hostname]/my-webapp-context.xml)或者与web应用程序一起(your-webapp.war/META-INF/context.xml)。从效率的角度说,我们推荐在web-app的内部配置的方式,因为仅仅使用JPA的应用程序会使用用户自定义的类装载器。更多具体有关可用的上下文路径的内容请参见Tomcat5.xdocumentation。
Tomcat5.0.x/5.5.x
Tomcat6.0.x
所有Tomcat版本所需的最后一步,是在配置LocalContainerEntityManagerFactoryBean的时,使用相应的LoadTimeWeaver:
<beanid="emf"class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"><propertyname="loadTimeWeaver">
<beanclass="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
</property>
</bean>
利用这种方法,依赖工具的JPA应用程序无需代理就可以在Tomcat上运行。这在宿主应用程序依赖不同的JPA实现时尤为重要,因为JPA转化器只适用于ClassLoader级别,它们之间是彼此隔离的。
如果Tomcat使用TopLink作为JPA提供者,请将核心的toplinkjar包放在$CATALINA_HOME/shared/lib文件夹中,而不再放到war中。
对于需要类工具,同时现有的LoadTimeWeaver实现不提供这种支持的环境,JDK代理是唯一的解决方案。对于这种情况,Spring提供了需要特定于Spring(但非常常用)的VM代理(spring-agent.jar)的InstrumentationLoadTimeWeaver:
<beanid="emf"class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"><propertyname="loadTimeWeaver">
<beanclass="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
</property>
</bean>
请注意在启动虚拟机时,同时启动Spring代理,方法是提供下列JVM选项:
-javaagent:/path/to/spring-agent.jar自Spring2.5,可以使用context:load-time-weaver元素来配置上下文范围的LoadTimeWeaver了。这种“全局”织入器由所有JPALocalContainerEntityManagerFactoryBeans自动拣选。
这是配置加载时织入器的推荐方法,提供平台(ebLogic,OC4J,GlassFish,Tomcat,Resin,VMagent)的自动检测,以及织入器到所有织入器知道的bean的自动传播。
<context:load-time-weaver/><beanid="emf"class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
</bean>
关于如何建立常用加载时织入的详细内容,请参见第6.8.4.5节“Spring配置”一节,它涵盖了Tomcat、VM代理,以及WebLogic,OC4J,GlassFish和Resin。
对于那些依靠多个持久化单元位置(例如存放在classpath中的多个jar中)的应用程序,Spring提供了作为中央仓库的PersistenceUnitManager,避免了持久化单元查找过程(的潜在开销)。缺省实现允许指定多个位置(默认情况下classpath会搜索META-INF/persistence.xml文件),它们会被解析然后通过持久化单元名称被获取:
<beanid="pum"class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager"><propertyname="persistenceXmlLocation">
<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>
<propertyname="dataSources">
<map>
<entrykey="localDataSource"value-ref="local-db"/>
<entrykey="remoteDataSource"value-ref="remote-db"/>
</map>
</property>
<!--ifnodatasourceisspecified,usethisone-->
<propertyname="defaultDataSource"ref="remoteDataSource"/>
</bean>
<beanid="emf"class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<propertyname="persistenceUnitManager"ref="pum"/>
</bean>
要注意的是,缺省实现允许在将持久化单元信息传入JPAprovider之前用PersistenceUnitPostProcessor(它允许选择持久化单元)修改它们,传入的过程可以是通过属性声明式地传入(影响其中所有的单元)或编程式地传入。如果没有指定persistenceUnitManager,LocalContainerEntityManagerFactoryBean会创建一个并在内部使用它。