淺談JPA三:開始使用Spring-Data-JPA

拋磚引玉

先看一段常用配置文件,看看使用Spring-Data-JPA需要使用到哪些東西吧!

<?xml version="1.0" encoding="UTF-8"?>
<!-- beans節點屬性含有各種xmlns,此處爲節省篇幅而省略 -->
<beans>
    <!-- 數據庫連接 -->
    <context:property-placeholder location="classpath:your-config.properties" ignore-unresolvable="true" />
    <!-- service包 -->
    <context:component-scan base-package="your service package" />
    <!-- 使用cglib進行動態代理 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />
    <!-- 支持註解方式聲明式事務 -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
    <!-- dao -->
    <jpa:repositories base-package="your dao package" repository-impl-postfix="Impl" entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager" />
    <!-- 實體管理器 -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="your entity package" />
        <property name="persistenceProvider">
            <bean class="org.hibernate.ejb.HibernatePersistence" />
        </property>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="generateDdl" value="false" />
                <property name="database" value="MYSQL" />
                <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
                <!-- <property name="showSql" value="true" /> -->
            </bean>
        </property>
        <property name="jpaDialect">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
        </property>
        <property name="jpaPropertyMap">
            <map>
                <entry key="hibernate.query.substitutions" value="true 1, false 0" />
                <entry key="hibernate.default_batch_fetch_size" value="16" />
                <entry key="hibernate.max_fetch_depth" value="2" />
                <entry key="hibernate.generate_statistics" value="true" />
                <entry key="hibernate.bytecode.use_reflection_optimizer" value="true" />
                <entry key="hibernate.cache.use_second_level_cache" value="false" />
                <entry key="hibernate.cache.use_query_cache" value="false" />
            </map>
        </property>
    </bean>

    <!-- 事務管理器 -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <!-- 數據源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${driver}" />
        <property name="url" value="${url}" />
        <property name="username" value="${userName}" />
        <property name="password" value="${password}" />
        <property name="initialSize" value="${druid.initialSize}" />
        <property name="maxActive" value="${druid.maxActive}" />
        <property name="maxIdle" value="${druid.maxIdle}" />
        <property name="minIdle" value="${druid.minIdle}" />
        <property name="maxWait" value="${druid.maxWait}" />
        <property name="removeAbandoned" value="${druid.removeAbandoned}" />
        <property name="removeAbandonedTimeout" value="${druid.removeAbandonedTimeout}" />
        <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />
        <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />
        <property name="validationQuery" value="${druid.validationQuery}" />
        <property name="testWhileIdle" value="${druid.testWhileIdle}" />
        <property name="testOnBorrow" value="${druid.testOnBorrow}" />
        <property name="testOnReturn" value="${druid.testOnReturn}" />
        <property name="poolPreparedStatements" value="${druid.poolPreparedStatements}" />
        <property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}" />
        <property name="filters" value="${druid.filters}" />
    </bean>
    
    <!-- 事務 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" />
            <tx:method name="get*" read-only="true" />
            <tx:method name="find*" read-only="true" />
            <tx:method name="select*" read-only="true" />
            <tx:method name="delete*" propagation="REQUIRED" />
            <tx:method name="update*" propagation="REQUIRED" />
            <tx:method name="add*" propagation="REQUIRED" />
            <tx:method name="insert*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>
    <!-- 事務入口 -->
    <aop:config>
        <aop:pointcut id="allServiceMethod" expression="execution(* your service implements package.*.*(..))" />
        <aop:advisor pointcut-ref="allServiceMethod" advice-ref="txAdvice" />
    </aop:config>
</beans>

介紹

由上面的配置文件可以看出,Spring-Data-JPA並不是一個ORM框架,也不是一個規範,而是一個遵守JPA規範、且藉助Hibernate作爲實現的框架。它對Hibernate進行進一步封裝簡化,使開發者通過聲明接口即可完成CRUD操作。

這裏和Hibernate、Mybatis做個對比:

hibernate mybatis spring-data-jpa
需寫Dao接口 需寫Mapper接口 需寫Repository接口
需寫Dao實現(可使用HQL或Criteria) 不寫Mapper實現(由XML實現:通過ID屬性關聯接口方法) 不寫Repository實現(根據其接口命名自動生成、@Query註解自寫jpql語句或動態查詢Predicate)

注:

  • 接口命名自動生成規範詳見網絡。
  • @Query是Spring-data-jpa帶有的註解,動態查詢Predicate底層即是使用JPA的Criteria。

提供的查詢API

Spring data項目所支持的NoSql存儲:

  • MongoDB 文檔數據庫
  • Neoj4 圖形數據庫
  • Redis 鍵/值數據庫
  • Hbase 列族數據庫

Spring data項目所支持的關係數據存儲:

  • Jdbc
  • JPA

根據屬性名定義查詢方法

Spring Data JPA 支持通過定義在Repository 接口中的方法名來定義查詢,而方法名是根據實體類的屬性名來確定的。

根據屬性名來定義查詢方法,示例如下:

從代碼可以看出,這裏使用了findBy 、Like 、And 這樣的關鍵字。其中findBy 可以用find 、read、readBy 、query 、queryBy 、get、getBy 來代替。而Like 和and 這類查詢關鍵字,如表所示:

限制結果數量。結果數量是用top 和first 關鍵字來實現的:

使用JPA的NamedQuery查詢

Spring Data JPA 支持用 JPA 的NameQuery 來定義查詢方法,即一個名稱映射一個查詢語句。定義如下:

使用@Query查詢

Spring Data JPA 還支持用@Query 註解在接口的方法上實現查詢。

  • 使用參數索引,例如:

  • 使用命名參數。上面的例子是使用參數的索引號來查詢的,在Spring Data JPA 裏還支持在語句裏用名稱來匹配查詢參數,例如:

  • 更新查詢。Spring Data JPA 支持@Modifying 和@Query 註解組合來實現更新查詢,其中返回值int 表示更新語句影響的行數

Specification

JPA 提供了基於準則查詢的方式,即Criteria 查詢。而Spring Data JPA 提供了一個Specification (規範)接口讓我們可以更方便地構造準則查詢, Specification 接口定義了一個toPredicate 方法用來構造查詢條件。
我們的接口類必需實現JpaSpecificationExecutor 接口,代碼如下:

然後需要定義Criterial查詢,代碼如下:

Spring Data JPA 充分考慮了在實際開發中所必需的排序和分頁的場景,爲我們提供了Sort類以及Page 接口和Pageable 接口。

自定義Repository的實現

Spring Data JPA提供了CrudRepository 、PagingAndSortingRepository,Spring Data JPA 也提供了JpaRepository。我們也可以自定義Repository的實現。爲了享受 Spring Data JPA 帶給我們的便利,同時又能夠爲部分方法提供自定義實現,我們可以採用如下的方法:

  1. 將需要開發者手動實現的方法從持久層接口(假設爲 AccountDao )中抽取出來,獨立成一個新的接口(假設爲 AccountDaoPlus ),並讓 AccountDao 繼承 AccountDaoPlus;
  2. 爲 AccountDaoPlus 提供自定義實現(假設爲 AccountDaoPlusImpl );
  3. 將 AccountDaoPlusImpl 配置爲 Spring Bean:
<jpa:repositories base-package="footmark.springdata.jpa.dao">
	<jpa:repository id="accountDao" repository-impl-ref=" accountDaoPlus " />
</jpa:repositories>
<bean id="accountDaoPlus" class="......."/>

此外,<jpa:repositories > 提供了一個 repository-impl-postfix 屬性,用以指定實現類的後綴。設置自動查找時默認的自定義實現類命名規則:

<jpa:repositories base-package="footmark.springdata.jpa.dao" repository-impl-postfix="Impl"/>

則在框架掃描到 AccountDao 接口時,它將嘗試在相同的包目錄下查找 AccountDaoImpl.java,如果找到,便將其中的實現方法作爲最終生成的代理類中相應方法的實現。

應該繼承哪個接口?

持久層接口繼承 Repository 並不是唯一選擇。Repository 接口是 Spring Data 的一個核心接口,它不提供任何方法,開發者需要在自己定義的接口中聲明需要的方法。與繼承 Repository 等價的一種方式,就是在持久層接口上使用 @RepositoryDefinition 註解,併爲其指定 domainClass 和 idClass 屬性。如下兩種方式是完全等價的:

public interface UserDao extends Repository<AccountInfo, Long> { …… }
<script src="https://localhost01.cn/js/jquery-2.0.0.min.js"></script>
@RepositoryDefinition(domainClass = AccountInfo.class, idClass = Long.class) public interface UserDao { …… }
CrudRepository

如果持久層接口較多,且每一個接口都需要聲明相似的增刪改查方法,直接繼承Repository就顯得有些囉嗦,這時可以繼承CrudRepository,它會自動爲域對象創建增刪改查方法,供業務層直接使用。開發者只是多寫了 “Crud” 四個字母,即刻便爲域對象提供了開箱即用的十個增刪改查方法。

但是,使用 CrudRepository 也有副作用,它可能暴露了你不希望暴露給業務層的方法。比如某些接口你只希望提供增加的操作而不希望提供刪除的方法。針對這種情況,開發者只能退回到 Repository 接口,然後到** CrudRepository 中把希望保留的方法聲明覆制到自定義的接口**中即可。

PagingAndSortingRepository

分頁查詢和排序是持久層常用的功能,Spring Data 爲此提供了 PagingAndSortingRepository 接口,它繼承自 CrudRepository 接口,在 CrudRepository 基礎上新增了兩個與分頁有關的方法。但是,我們很少會將自定義的持久層接口直接繼承自 PagingAndSortingRepository,而是在繼承 Repository 或 CrudRepository 的基礎上,在自己聲明的方法參數列表最後增加一個 Pageable 或 Sort 類型的參數,用於指定分頁或排序信息即可,這比直接使用 PagingAndSortingRepository 提供了更大的靈活性。

JpaRepository

JpaRepository 是繼承自 PagingAndSortingRepository 的針對 JPA 技術提供的接口,它在父接口的基礎上,提供了其他一些方法,比如 flush(),saveAndFlush(),deleteInBatch() 等。如果有這樣的需求,則可以繼承該接口。

總結

上述四個接口,開發者到底該如何選擇?其實依據很簡單,根據具體的業務需求,選擇其中之一。筆者建議在通常情況下優先選擇 Repository 接口。因爲 Repository 接口已經能滿足日常需求,其他接口能做到的在 Repository 中也能做到,彼此之間並不存在功能強弱的問題。只是 Repository 需要顯示聲明需要的方法,而其他則可能已經提供了相關的方法,不需要再顯式聲明,但如果對 Spring Data JPA 不熟悉,別人在檢視代碼或者接手相關代碼時會有疑惑,他們不明白爲什麼明明在持久層接口中聲明瞭三個方法,而在業務層使用該接口時,卻發現有七八個方法可用,從這個角度而言,應該優先考慮使用 Repository 接口。

其他

JPA配置文件

我們知道原生的jpa的配置信息是必須放在META-INF目錄下面的,並且名字必須叫做persistence.xml,這個叫做persistence-unit,就叫做持久化單元,放在這下面我們感覺不方便,不好,於是Spring提供了org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
這樣一個類,可以讓你的隨心所欲的起這個配置文件的名字,也可以隨心所欲的修改這個文件的位置,只需要在這裏指向這個位置就行。然而更加方便的做法是,直接把配置信息就寫在這裏更好,於是就有了這實體管理器這個bean。使用

<property name="packagesToScan" value="your entity package" />

來加載我們的entity。

參考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章