拋磚引玉
先看一段常用配置文件,看看使用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 帶給我們的便利,同時又能夠爲部分方法提供自定義實現,我們可以採用如下的方法:
- 將需要開發者手動實現的方法從持久層接口(假設爲 AccountDao )中抽取出來,獨立成一個新的接口(假設爲 AccountDaoPlus ),並讓 AccountDao 繼承 AccountDaoPlus;
- 爲 AccountDaoPlus 提供自定義實現(假設爲 AccountDaoPlusImpl );
- 將 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。
參考: