天天看點

對ORM的支援 之 8.4 內建JPA ——跟我學spring3

       JPA全稱為Java持久性API(Java Persistence API),JPA是Java EE 5标準之一,是一個ORM規範,由廠商來實作該規範,目前有Hibernate、OpenJPA、TopLink、EclipseJPA等實作。

       Spring目前提供內建Hibernate、OpenJPA、TopLink、EclipseJPA四個JPA标準實作。

       Spring通過使用如下Bean進行內建JPA(EntityManagerFactory):

LocalEntityManagerFactoryBean:适用于那些僅使用JPA進行資料通路的項目,該FactoryBean将根據JPA PersistenceProvider自動檢測配置檔案進行工作,一般從“META-INF/persistence.xml”讀取配置資訊,這種方式最簡單,但不能設定Spring中定義的DataSource,且不支援Spring管理的全局事務,而且JPA 實作商可能在JVM啟動時依賴于VM agent進而允許它們進行持久化類位元組碼轉換(不同的實作廠商要求不同,需要時閱讀其文檔),不建議使用這種方式;

         persistenceUnitName:指定持久化單元的名稱;

         使用方式:

java代碼:

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">  

    <property name="persistenceUnitName" value="persistenceUnit"/>  

</bean>  

從JNDI中擷取:用于從Java EE伺服器擷取指定的EntityManagerFactory,這種方式在進行Spring事務管理時一般要使用JTA事務管理;

      使用方式:

<beans xmlns="http://www.springframework.org/schema/beans"  

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  

    xmlns:jee="http://www.springframework.org/schema/jee"  

    xsi:schemaLocation="  

       http://www.springframework.org/schema/beans  

       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  

       http://www.springframework.org/schema/jee  

       http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">  

  <jee:jndi-lookup id="entityManagerFactory"  jndi-name="persistence/persistenceUnit"/>  

</beans>  

此處需要使用“jee”命名标簽,且使用<jee:jndi-lookup>标簽進行JNDI查找,“jndi-name”屬性用于指定JNDI名字。

LocalContainerEntityManagerFactoryBean:适用于所有環境的FactoryBean,能全面控制EntityManagerFactory配置,如指定Spring定義的DataSource等等。

         persistenceUnitManager:用于擷取JPA持久化單元,預設實作DefaultPersistenceUnitManager用于解決多配置檔案情況

         dataSource:用于指定Spring定義的資料源;

         persistenceXmlLocation:用于指定JPA配置檔案,對于對配置檔案情況請選擇設定persistenceUnitManager屬性來解決;

         persistenceUnitName:用于指定持久化單元名字;

         persistenceProvider:用于指定持久化實作廠商類;如Hibernate為org.hibernate.ejb.HibernatePersistence類;

         jpaVendorAdapter:用于設定實作廠商JPA實作的特定屬性,如設定Hibernate的是否自動生成DDL的屬性generateDdl;這些屬性是廠商特定的,是以最好在這裡設定;目前Spring提供HibernateJpaVendorAdapter、OpenJpaVendorAdapter、EclipseLinkJpaVendorAdapter、TopLinkJpaVendorAdapter、OpenJpaVendorAdapter四個實作。其中最重要的屬性是“database”,用來指定使用的資料庫類型,進而能根據資料庫類型來決定比如如何将資料庫特定異常轉換為Spring的一緻性異常,目前支援如下資料庫(DB2、DERBY、H2、HSQL、INFORMIX、MYSQL、ORACLE、POSTGRESQL、SQL_SERVER、SYBASE)。

         jpaDialect:用于指定一些進階特性,如事務管理,擷取具有事務功能的連接配接對象等,目前Spring提供HibernateJpaDialect、OpenJpaDialect 、EclipseLinkJpaDialect、TopLinkJpaDialect、和DefaultJpaDialect實作,注意DefaultJpaDialect不提供任何功能,是以在使用特定實作廠商JPA實作時需要指定JpaDialect實作,如使用Hibernate就使用HibernateJpaDialect。當指定jpaVendorAdapter屬性時可以不指定jpaDialect,會自動設定相應的JpaDialect實作;

         jpaProperties和jpaPropertyMap:指定JPA屬性;如Hibernate中指定是否顯示SQL的“hibernate.show_sql”屬性,對于jpaProperties設定的屬性自動會放進jpaPropertyMap中;

         loadTimeWeaver:用于指定LoadTimeWeaver實作,進而允許JPA 加載時修改相應的類檔案。具體使用得參考相應的JPA規範實作廠商文檔,如Hibernate就不需要指定loadTimeWeaver。

接下來學習一下Spring如何內建JPA吧:

1、準備jar包,從下載下傳的hibernate-distribution-3.6.0.Final包中擷取如下Hibernate需要的jar包進而支援JPA:

lib\jpa\hibernate-jpa-2.0-api-1.0.0.Final.jar //用于支援JPA

2、對象模型定義,此處使用UserModel2:

package cn.javass.spring.chapter8;  

//省略import  

@Entity  

@Table(name = "test")  

public class UserModel2 {  

    @Id @GeneratedValue(strategy = GenerationType.AUTO)  

    private int id;  

    @Column(name = "name")  

    private String myName;  

    //省略getter和setter  

}  

注意此處使用的所有注解都是位于javax.persistence包下,如使用@org.hibernate.annotations.Entity 而非@javax.persistence. Entity将導緻JPA不能正常工作。

1、 JPA配置定義(chapter8/persistence.xml),定義對象和資料庫之間的映射:

<?xml version="1.0" encoding="UTF-8"?>  

<persistence version="1.0"  

    xmlns="http://java.sun.com/xml/ns/persistence"  

    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence                      http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">  

    <persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL"/>  

</persistence>  

      在JPA配置檔案中,我們指定要持久化單元名字,和事務類型,其他都将在Spring中配置。

2、 資料源定義,此處使用第7章的配置檔案,即“chapter7/ applicationContext-resources.xml”檔案。

3、 EntityManagerFactory配置定義(chapter8/applicationContext-jpa.xml):

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">  

  <property name="dataSource" ref="dataSource"/>  

  <property name="persistenceXmlLocation" value="chapter8/persistence.xml"/>  

  <property name="persistenceUnitName" value="persistenceUnit"/>  

  <property name="persistenceProvider" ref="persistenceProvider"/>  

  <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>  

  <property name="jpaDialect" ref="jpaDialect"/>  

  <property name="jpaProperties">  

      <props>  

          <prop key="hibernate.show_sql">true</prop>  

      </props>  

  </property>  

<bean id="persistenceProvider" class="org.hibernate.ejb.HibernatePersistence"/>  

<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">  

   <property name="generateDdl" value="false" />  

   <property name="database" value="HSQL"/>  

<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>  

LocalContainerEntityManagerFactoryBean:指定使用本地容器管理EntityManagerFactory,進而進行細粒度控制;

dataSource屬性指定使用Spring定義的資料源;

persistenceXmlLocation指定JPA配置檔案為chapter8/persistence.xml,且該配置檔案非常簡單,具體配置完全在Spring中進行;

persistenceUnitName指定持久化單元名字,即JPA配置檔案中指定的;

persistenceProvider:指定JPA持久化提供商,此處使用Hibernate實作HibernatePersistence類;

jpaVendorAdapter:指定實作廠商專用特性,即generateDdl= false表示不自動生成DDL,database= HSQL表示使用hsqldb資料庫;

jpaDialect:如果指定jpaVendorAdapter此屬性可選,此處為HibernateJpaDialect;

 jpaProperties:此處指定“hibernate.show_sql =true”表示在日志系統debug級别下将列印所有生成的SQL。

4、 擷取EntityManagerFactory:

public class JPATest {  

    private static EntityManagerFactory entityManagerFactory;  

    @BeforeClass  

    public static void setUpClass() {  

        String[] configLocations = new String[] {  

                "classpath:chapter7/applicationContext-resources.xml",  

                "classpath:chapter8/applicationContext-jpa.xml"};  

        ApplicationContext ctx = new ClassPathXmlApplicationContext(configLocations);  

        entityManagerFactory = ctx.getBean(EntityManagerFactory.class);  

    }  

此處我們使用了chapter7/applicationContext-resources.xml定義的“dataSource”資料源,通過ctx.getBean(EntityManagerFactory.class)擷取EntityManagerFactory。

5、  通過EntityManagerFactory擷取EntityManager進行建立和删除表:

@Before  

public void setUp() throws SQLException {  

   //id自增主鍵從0開始  

   String createTableSql = "create memory table test" + "(id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, " + "name varchar(100))";  

   executeSql(createTableSql);  

@After  

public void tearDown() throws SQLException {  

    String dropTableSql = "drop table test";  

    executeSql(dropTableSql);  

private void executeSql(String sql) throws SQLException {  

  EntityManager em = entityManagerFactory.createEntityManager();  

  beginTransaction(em);  

  em.createNativeQuery(sql).executeUpdate();  

  commitTransaction(em);  

  closeEntityManager(em);  

private void closeEntityManager(EntityManager em) {  

  em.close();  

private void rollbackTransacrion(EntityManager em) throws SQLException {  

  if(em != null) {  

     em.getTransaction().rollback();  

  }           

private void commitTransaction(EntityManager em) throws SQLException {  

   em.getTransaction().commit();  

private void beginTransaction(EntityManager em) throws SQLException {  

   em.getTransaction().begin();  

使用EntityManagerFactory建立EntityManager,然後通過EntityManager對象的createNativeQuery建立本地SQL執行建立和删除表。

6、 使用EntityManagerFactory擷取EntityManager對象進行持久化資料:

@Test  

public void testFirst() throws SQLException {  

    UserModel2 model = new UserModel2();  

    model.setMyName("test");  

    EntityManager em = null;  

    try {  

        em = entityManagerFactory.createEntityManager();  

        beginTransaction(em);  

        em.persist(model);  

        commitTransaction(em);  

    } catch (SQLException e) {  

        rollbackTransacrion(em);  

        throw e;  

    } finally {  

      closeEntityManager(em);  

使用EntityManagerFactory擷取EntityManager進行操作,看到這還能忍受冗長的代碼和事務管理嗎?Spring同樣提供JpaTemplate模闆類來簡化這些操作。

大家有沒有注意到此處的模型對象能自動映射到資料庫,這是因為Hibernate JPA實作預設自動掃描類路徑中的@Entity注解類及*.hbm.xml映射檔案,可以通過更改Hibernate JPA屬性“hibernate.ejb.resource_scanner”,并指定org.hibernate.ejb.packaging.Scanner接口實作來定制新的掃描政策。

JpaTemplate模闆類用于簡化事務管理及常見操作,類似于JdbcTemplate模闆類,對于複雜操作通過提供JpaCallback回調接口來允許更複雜的操作。

       接下來示例一下JpaTemplate的使用:

1、修改Spring配置檔案(chapter8/applicationContext-jpa.xml),添加JPA事務管理器:

<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">  

    <property name="entityManagerFactory" ref="entityManagerFactory"/>  

txManager:指定事務管理器,JPA使用JpaTransactionManager事務管理器實作,通過entityManagerFactory指定EntityManagerFactory;用于支援Java SE環境的JPA擴充的持久化上下文(EXTENDED Persistence Context)。

2、修改JPATest類,添加類變量ctx,用于後邊使用其擷取事務管理器使用:

    private static ApplicationContext ctx;  

    public static void beforeClass() {  

        ctx = new ClassPathXmlApplicationContext(configLocations);  

3)JpaTemplate模闆類使用:

public void testJpaTemplate() {  

final JpaTemplate jpaTemplate = new JpaTemplate(entityManagerFactory);  

  final UserModel2 model = new UserModel2();  

  model.setMyName("test1");  

  PlatformTransactionManager txManager = ctx.getBean(PlatformTransactionManager.class);  

  new TransactionTemplate(txManager).execute(  

    new TransactionCallback<Void>() {  

      @Override  

      public Void doInTransaction(TransactionStatus status) {  

        jpaTemplate.persist(model);  

        return null;  

      }  

  });  

  String COUNT_ALL = "select count(*) from UserModel";  

  Number count = (Number) jpaTemplate.find(COUNT_ALL).get(0);  

  Assert.assertEquals(1, count.intValue());  

jpaTemplate:可通過new JpaTemplate(entityManagerFactory)方式建立;

txManager:通過ctx.getBean(PlatformTransactionManager.class)擷取事務管理器;

TransactionTemplate:通過new TransactionTemplate(txManager)建立事務模闆對象,并通過execute方法執行TransactionCallback回調中的doInTransaction方法中定義需要執行的操作,進而将由模闆類通過txManager事務管理器來進行事務管理,此處是調用jpaTemplate對象的persist方法進行持久化;

jpaTemplate.persist():根據JPA規範,在JPA擴充的持久化上下文,該操作必須運作在事務環境,還有persist()、 merge()、remove()操作也必須運作在事務環境;

jpaTemplate.find():根據JPA規範,該操作無需運作在事務環境,還有find()、getReference()、 refresh()、detach()和查詢操作都無需運作在事務環境。

此執行個體與Hibernate和Ibatis有所差別,通過JpaTemplate模闆類進行如持久化等操作時必須有運作在事務環境中,否則可能抛出如下異常或警告:

“javax.persistence.TransactionRequiredException:Executing an update/delete query”:表示沒有事務支援,不能執行更新或删除操作;

警告“delaying identity-insert due to no transaction in progress”:需要在日志系統啟動debug模式才能看到,表示在無事務環境中無法進行持久化,而選擇了延遲辨別插入。

以上異常和警告是沒有事務造成的,也是最讓人困惑的問題,需要大家注意。

       類似于JdbcDaoSupport類,Spring對JPA也提供了JpaDaoSupport類來支援一緻的資料庫通路。JpaDaoSupport也是DaoSupport實作:

       接下來示例一下Spring內建JPA的最佳實踐:

1、 定義Dao接口,此處使用cn.javass.spring.chapter7.dao. IUserDao:

2、 定義Dao接口實作,此處是JPA實作:

package cn.javass.spring.chapter8.dao.jpa;  

@Transactional(propagation = Propagation.REQUIRED)  

public class UserJpaDaoImpl extends JpaDaoSupport implements IUserDao {  

    private static final String COUNT_ALL_JPAQL = "select count(*) from UserModel";  

    @Override  

    public void save(UserModel model) {  

        getJpaTemplate().persist(model);  

    public int countAll() {  

        Number count =  

           (Number) getJpaTemplate().find(COUNT_ALL_JPAQL).get(0);  

        return count.intValue();  

此處注意首先JPA實作放在dao.jpa包裡,其次實作類命名如UserJpaDaoImpl,即×××JpaDaoImpl,當然如果自己有更好的命名規範可以遵循自己的,此處隻是提個建議。

另外在類上添加了@Transactional注解表示該類的所有方法将在調用時需要事務支援,propagation傳播屬性為Propagation.REQUIRED表示事務是必需的,如果執行該類的方法沒有開啟事務,将開啟一個新的事務。

3、進行資源配置,使用resources/chapter7/applicationContext-resources.xml:

4、dao定義配置,在chapter8/applicationContext-jpa.xml中添加如下配置:

4.1、首先添加tx命名空間用于支援事務:

    xmlns:tx="http://www.springframework.org/schema/tx"  

       http://www.springframework.org/schema/tx  

       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">  

4.2、為@Transactional注解事務開啟事務支援:

<tx:annotation-driven transaction-manager="txManager"/>  

隻為類添加@Transactional 注解是不能支援事務的,需要通過<tx:annotation-driven>标簽來開啟事務支援,其中txManager屬性指定事務管理器。

4.3、配置DAO Bean:

<bean id="abstractDao" abstract="true">  

</bean>     

<bean id="userDao"  

      class="cn.javass.spring.chapter8.dao.jpa.UserJpaDaoImpl"  

      parent="abstractDao"/>   

首先定義抽象的abstractDao,其有一個entityManagerFactory屬性,進而可以讓繼承的子類自動繼承entityManagerFactory屬性注入;然後定義userDao,且繼承abstractDao,進而繼承entityManagerFactory注入;我們在此給配置檔案命名為applicationContext-jpa.xml表示JPA實作。

5、最後測試一下吧(cn.javass.spring.chapter8. JPATest):

public void testBestPractice() {  

    String[] configLocations = new String[] {  

            "classpath:chapter7/applicationContext-resources.xml",  

            "classpath:chapter8/applicationContext-jpa.xml"};  

    ApplicationContext ctx = new ClassPathXmlApplicationContext(configLocations);  

    IUserDao userDao = ctx.getBean(IUserDao.class);  

    UserModel model = new UserModel();  

    userDao.save(model);  

    Assert.assertEquals(1, userDao.countAll());  

和Spring JDBC架構的最佳實踐完全一樣,除了使用applicationContext-jpa.xml代替了applicationContext-jdbc.xml,其他完全一樣。也就是說,DAO層的實作替換可以透明化。

還有與內建其他ORM架構不同的是JPA在進行持久化或更新資料庫操作時需要事務支援。

Spring+JPA CRUD(增删改查)也相當簡單,讓我們直接看具體示例吧:

public void testCRUD() {  

    PlatformTransactionManager txManager = ctx.getBean(PlatformTransactionManager.class);  

    final JpaTemplate jpaTemplate = new JpaTemplate(entityManagerFactory);  

    TransactionTemplate tansactionTemplate = new TransactionTemplate(txManager);  

    tansactionTemplate.execute(new TransactionCallback<Void>() {  

        @Override  

        public Void doInTransaction(TransactionStatus status) {  

            UserModel model = new UserModel();  

            model.setMyName("test");  

            //新增  

            jpaTemplate.persist(model);  

            //修改  

            model.setMyName("test2");  

            jpaTemplate.flush();//可選  

            //查詢  

            String sql = "from UserModel where myName=?";  

            List result = jpaTemplate.find(sql, "test2");  

            Assert.assertEquals(1, result.size());  

            //删除  

            jpaTemplate.remove(model);  

            return null;  

        }  

    });  

對于增删改必須運作在事務環境,是以我們使用TransactionTemplate事務模闆類來支援事務。

持久化:使用JpaTemplate 類的persist方法持久化模型對象;

更新:對于持久化狀态的模型對象直接修改屬性,調用flush方法即可更新到資料庫,在一些場合時flush方法調用可選,如執行一個查詢操作等,具體請參考相關文檔;

查詢:可以使用find方法執行JPA QL查詢;

删除:使用remove方法删除一個持久化狀态的模型對象。

       Spring內建JPA進行增删改查也相當簡單,但本文介紹的稍微複雜一點,因為牽扯到程式設計式事務,如果采用聲明式事務将和內建Hibernate方式一樣簡潔。