spring data jpa——學習總結
JPA Spring Data 概述
-
JPA Spring Data : 致力於減少資料訪問層 (DAO) 的開發量. 開發者唯一要做的,就只是宣告持久層的介面,其他都交給 Spring Data JPA 來幫你完成!
-
框架怎麼可能代替開發者實現業務邏輯呢?比如:當有一個 UserDao.findUserById() 這樣一個方法宣告,大致應該能判斷出這是根據給定條件的 ID 查詢出滿足條件的 User 物件。Spring Data JPA 做的便是規範方法的名字,根據符合規範的名字來確定方法需要實現什麼樣的邏輯。
使用 Spring Data JPA 進行持久層開發需要的四個步驟:
-
配置 Spring 整合 JPA
-
在 Spring 配置檔案中配置 Spring Data,讓 Spring 為宣告的介面建立代理物件。配置了 <jpa:repositories> 後,Spring 初始化容器時將會掃描 base-package 指定的包目錄及其子目錄,為繼承 Repository 或其子介面的介面建立代理物件,並將代理物件註冊為 Spring Bean,業務層便可以通過 Spring 自動封裝的特性來直接使用該物件。
-
宣告持久層的介面,該介面繼承 Repository,Repository 是一個標記型介面,它不包含任何方法,如必要,Spring Data 可實現 Repository 其他子介面,其中定義了一些常用的增刪改查,以及分頁相關的方法。
-
在介面中宣告需要的方法。Spring Data 將根據給定的策略(具體策略稍後講解)來為其生成實現程式碼。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpahttp://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 配置自動掃描的包 --> <context:component-scan base-package="com.atguigu.springdata"></context:component-scan> <!-- 1. 配置資料來源 --> <context:property-placeholder location="classpath:db.properties"/> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <!-- 配置其他屬性 --> </bean> <!-- 2. 配置 JPA 的 EntityManagerFactory --> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"></property> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean> </property> <property name="packagesToScan" value="com.atguigu.springdata"></property> <property name="jpaProperties"> <props> <!-- 二級快取相關 --> <!-- <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop> <prop key="net.sf.ehcache.configurationResourceName">ehcache-hibernate.xml</prop> --> <!-- 生成的資料表的列的對映策略 --> <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop> <!-- hibernate 基本屬性 --> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.hbm2ddl.auto">update</prop> </props> </property> </bean> <!-- 3. 配置事務管理器 --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"></property> </bean> <!-- 4. 配置支援註解的事務 --> <tx:annotation-driven transaction-manager="transactionManager"/> <!-- 5. 配置 SpringData --> <!-- 加入 jpa 的名稱空間 --> <!-- base-package: 掃描 Repository Bean 所在的 package --> <jpa:repositories base-package="com.atguigu.springdata" entity-manager-factory-ref="entityManagerFactory"></jpa:repositories> </beans>
Repository 介面概述
-
Repository 介面是 Spring Data 的一個核心介面,它不提供任何方法,開發者需要在自己定義的介面中宣告需要的方法
public interface Repository<T, ID extends Serializable> { }
-
Spring Data可以讓我們只定義介面,只要遵循 Spring Data的規範,就無需寫實現類。
-
與繼承 Repository 等價的一種方式,就是在持久層介面上使用 @RepositoryDefinition 註解,併為其指定 domainClass 和 idClass 屬性。如下兩種方式是完全等價的
public interface PersonRepository extends Repository<Person, Integer>
與
@RepositoryDefinition(domainClass=Person.class, idClass=Integer.class)
public interface PersonRepository
Repository 的子介面
基礎的 Repository 提供了最基本的資料訪問功能,其幾個子介面則擴充套件了一些功能。它們的繼承關係如下:
-
Repository: 僅僅是一個標識,表明任何繼承它的均為倉庫介面類
-
CrudRepository: 繼承 Repository,實現了一組 CRUD 相關的方法
-
PagingAndSortingRepository: 繼承 CrudRepository,實現了一組分頁排序相關的方法
-
JpaRepository: 繼承 PagingAndSortingRepository,實現一組 JPA 規範相關的方法
-
自定義的 XxxxRepository 需要繼承 JpaRepository,這樣的 XxxxRepository 介面就具備了通用的資料訪問控制層的能力。
-
JpaSpecificationExecutor: 不屬於Repository體系,實現一組 JPA Criteria 查詢相關的方法
簡單條件查詢
-
簡單條件查詢: 查詢某一個實體類或者集合
-
按照 Spring Data 的規範,查詢方法以 find | read | get 開頭, 涉及條件查詢時,條件的屬性用條件關鍵字連線,要注意的是:條件屬性以首字母大寫。
-
例如:定義一個 Entity 實體類 class User{ private String firstName; private String lastName; } 使用And條件連線時,應這樣寫: findByLastNameAndFirstName(String lastName,String firstName); 條件的屬性名稱與個數要與引數的位置與個數一一對應
支援的關鍵字:
【直接在介面中定義查詢方法,如果是符合規範的,可以不用寫實現,目前支援的關鍵字寫法如下:】
查詢方法解析流程:
假如建立如下的查詢:findByUserDepUuid(),框架在解析該方法時,首先剔除 findBy,然後對剩下的屬性進行解析,假設查詢實體為Doc
-
先判斷 userDepUuid (根據 POJO 規範,首字母變為小寫)是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,繼續第二步;
-
從右往左擷取第一個大寫字母開頭的字串(此處為Uuid),然後檢查剩下的字串是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,則重複第二步,繼續從右往左擷取;最後假設 user 為查詢實體的一個屬性;
-
接著處理剩下部分(DepUuid),先判斷 user 所對應的型別是否有depUuid屬性,如果有,則表示該方法最終是根據 “ Doc.user.depUuid” 的取值進行查詢;否則繼續按照步驟 2 的規則從右往左擷取,最終表示根據 “Doc.user.dep.uuid” 的值進行查詢。
-
可能會存在一種特殊情況,比如 Doc包含一個 user 的屬性,也有一個 userDep 屬性,此時會存在混淆。可以明確在屬性之間加上 "_" 以顯式表達意圖,比如 "findByUser_DepUuid()" 或者 "findByUserDep_uuid()"
特殊的引數: 還可以直接在方法的引數上加入分頁或排序的引數,比如:
-
Page<UserModel> findByName(String name, Pageable pageable);
-
List<UserModel> findByName(String name, Sort sort);
//WHERE a.id > ?
List<Person> getByAddress_IdGreaterThan(Integer id);
//WHERE lastName LIKE ?% AND id < ?
List<Person> getByLastNameStartingWithAndIdLessThan(String lastName, Integer id);
//WHERE lastName LIKE %? AND id < ?
List<Person> getByLastNameEndingWithAndIdLessThan(String lastName, Integer id);
//WHERE email IN (?, ?, ?) OR birth < ?
List<Person> getByEmailInAndBirthLessThan(List<String> emails, Date birth);
//WHERE a.id > ?
List<Person> getByAddress_IdGreaterThan(Integer id);
使用@Query自定義查詢
這種查詢可以宣告在 Repository 方法中,擺脫像命名查詢那樣的約束,將查詢直接在相應的介面方法中宣告,結構更為清晰,這是 Spring data 的特有實現。
//查詢 id 值最大的那個 Person
//使用 @Query 註解可以自定義 JPQL 語句以實現更靈活的查詢
@Query("SELECT p FROM Person p WHERE p.id = (SELECT max(p2.id) FROM Person p2)")
Person getMaxIdPerson();
索引引數與命名引數:
-
索引引數如下所示,索引值從1開始,查詢中 ”?X” 個數需要與方法定義的引數個數相一致,並且順序也要一致
//為 @Query 註解傳遞引數的方式1: 使用佔位符.
@Query("SELECT p FROM Person p WHERE p.lastName = ?1 AND p.email = ?2")
List<Person> testQueryAnnotationParams1(String lastName, String email);
-
命名引數(推薦使用這種方式):可以定義好引數名,賦值時採用@Param("引數名"),而不用管順序。
//為 @Query 註解傳遞引數的方式1: 命名引數的方式.
@Query("SELECT p FROM Person p WHERE p.lastName = :lastName AND p.email = :email")
List<Person> testQueryAnnotationParams2(@Param("email") String email, @Param("lastName") String lastName);
如果是 @Query 中有 LIKE 關鍵字,後面的引數需要前面或者後面加 %,這樣在傳遞引數值的時候就可以不加 %
//SpringData 允許在佔位符上新增 %%.
@Query("SELECT p FROM Person p WHERE p.lastName LIKE %?1% OR p.email LIKE %?2%")
List<Person> testQueryAnnotationLikeParam(String lastName, String email);
//SpringData 允許在佔位符上新增 %%.
@Query("SELECT p FROM Person p WHERE p.lastName LIKE %:lastName% OR p.email LIKE %:email%")
List<Person> testQueryAnnotationLikeParam2(@Param("email") String email, @Param("lastName") String lastName);
@Modifying 註解和事務
@Query 與 @Modifying 執行更新操作:
-
@Query 與 @Modifying 這兩個 annotation一起宣告,可定義個性化更新操作,例如只涉及某些欄位更新時最為常用,示例如下:
//可以通過自定義的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支援使用 INSERT
//在 @Query 註解中編寫 JPQL 語句, 但必須使用 @Modifying 進行修飾. 以通知 SpringData, 這是一個 UPDATE 或 DELETE 操作
//UPDATE 或 DELETE 操作需要使用事務, 此時需要定義 Service 層. 在 Service 層的方法上新增事務操作.
//預設情況下, SpringData 的每個方法上有事務, 但都是一個只讀事務. 他們不能完成修改操作!
@Modifying
@Query("UPDATE Person p SET p.email = :email WHERE id = :id")
void updatePersonEmail(@Param("id") Integer id, @Param("email") String email);
-
注意:
-- 方法的返回值應該是 int,表示更新語句所影響的行數
-- 在呼叫的地方必須加事務,沒有事務不能正常執行
事務:
-
Spring Data 提供了預設的事務處理方式,即所有的查詢均宣告為只讀事務。
-
對於自定義的方法,如需改變 Spring Data 提供的事務預設方式,可以在方法上註解 @Transactional 宣告
-
進行多個 Repository 操作時,也應該使它們在同一個事務中處理,按照分層架構的思想,這部分屬於業務邏輯層,因此,需要在 Service 層實現對多個 Repository 的呼叫,並在相應的方法上宣告事務。
CrudRepository介面
CrudRepository 介面提供了最基本的對實體類的添刪改查操作 :
-
T save(T entity);//儲存單個實體
-
Iterable<T> save(Iterable<? extends T> entities);//儲存集合
-
T findOne(ID id);//根據id查詢實體
-
boolean exists(ID id);//根據id判斷實體是否存在
-
Iterable<T> findAll();//查詢所有實體,不用或慎用!
-
long count();//查詢實體數量
-
void delete(ID id);//根據Id刪除實體
-
void delete(T entity);//刪除一個實體
-
void delete(Iterable<? extends T> entities);//刪除一個實體的集合
-
void deleteAll();//刪除所有實體,不用或慎用!
PagingAndSortingRepository介面
該介面提供了分頁與排序功能:【不提供條件分頁查詢】
-
Iterable<T> findAll(Sort sort); //排序
-
Page<T> findAll(Pageable pageable); //分頁查詢(含排序功能)
@Test
public void testPagingAndSortingRespository(){
//pageNo 從 0 開始.
int pageNo = 6 - 1;
int pageSize = 5;
//Pageable 介面通常使用的其 PageRequest 實現類. 其中封裝了需要分頁的資訊
//排序相關的. Sort 封裝了排序的資訊
//Order 是具體針對於某一個屬性進行升序還是降序.
Order order1 = new Order(Direction.DESC, "id");
Order order2 = new Order(Direction.ASC, "email");
Sort sort = new Sort(order1, order2);
PageRequest pageable = new PageRequest(pageNo, pageSize, sort);
Page<Person> page = personRepsotory.findAll(pageable);
System.out.println("總記錄數: " + page.getTotalElements());
System.out.println("當前第幾頁: " + (page.getNumber() + 1));
System.out.println("總頁數: " + page.getTotalPages());
System.out.println("當前頁面的 List: " + page.getContent());
System.out.println("當前頁面的記錄數: " + page.getNumberOfElements());
}
JpaRepository介面
該介面提供了JPA的相關功能:
-
List<T> findAll(); //查詢所有實體
-
List<T> findAll(Sort sort); //排序、查詢所有實體
-
List<T> save(Iterable<? extends T> entities);//儲存集合
-
void flush();//執行快取與資料庫同步
-
T saveAndFlush(T entity);//強制執行持久化【類似於jpa的merge】
-
void deleteInBatch(Iterable<T> entities);//刪除一個實體集合
JpaSpecificationExecutor介面
不屬於Repository體系,實現一組 JPA Criteria 查詢相關的方法 :
-
findOne(Specification<T>) // 返回一個匹配給定的結果
-
findAll(Specification<T>) //返回所有匹配給定的結果
-
findAll(Specification<T>, Pageable) // 返回帶分頁的所有匹配給定的結果
-
findAll(Specification<T>, Sort) //返回帶排序的所有匹配給定的結果
-
count(Specification<T>) //返回總數
Specification:封裝 JPA Criteria 查詢條件。通常使用匿名內部類的方式來建立該介面的物件
/**
* 目標: 實現帶查詢條件的分頁. id > 5 的條件
*
* 呼叫 JpaSpecificationExecutor 的 Page<T> findAll(Specification<T> spec, Pageable pageable);
* Specification: 封裝了 JPA Criteria 查詢的查詢條件
* Pageable: 封裝了請求分頁的資訊: 例如 pageNo, pageSize, Sort
*/
@Test
public void testJpaSpecificationExecutor(){
int pageNo = 3 - 1;
int pageSize = 5;
PageRequest pageable = new PageRequest(pageNo, pageSize);
//通常使用 Specification 的匿名內部類
Specification<Person> specification = new Specification<Person>() {
/**
* @param *root: 代表查詢的實體類.
* @param query: 可以從中可到 Root 物件, 即告知 JPA Criteria 查詢要查詢哪一個實體類. 還可以
* 來新增查詢條件, 還可以結合 EntityManager 物件得到最終查詢的 TypedQuery 物件.
* @param *cb: CriteriaBuilder 物件. 用於建立 Criteria 相關物件的工廠. 當然可以從中獲取到 Predicate 物件
* @return: *Predicate 型別, 代表一個查詢條件.
*/
@Override
public Predicate toPredicate(Root<Person> root,
CriteriaQuery<?> query, CriteriaBuilder cb) {
Path path = root.get("id"); // 通過實體類找到對應屬性的路徑【可以進行級聯查詢】
Predicate predicate = cb.gt(path, 5);
return predicate;
}
};
Page<Person> page = personRepsotory.findAll(specification, pageable);
System.out.println("總記錄數: " + page.getTotalElements());
System.out.println("當前第幾頁: " + (page.getNumber() + 1));
System.out.println("總頁數: " + page.getTotalPages());
System.out.println("當前頁面的 List: " + page.getContent());
System.out.println("當前頁面的記錄數: " + page.getNumberOfElements());
}
自定義 Repository 方法
為某一個 Repository 上新增自定義方法:
-
定義一個介面: 宣告要新增的, 並自實現的方法
-
提供該介面的實現類: 類名需在要宣告的 Repository 後新增 Impl, 並實現方法
-
宣告 Repository 介面, 並繼承 1) 宣告的介面
-
使用.
-
注意: 預設情況下, Spring Data 會在 base-package 中查詢 "介面名Impl" 作為實現類. 也可以通過 repository-impl-postfix 聲明後綴.
為所有的 Repository 都新增自實現的方法:
-
宣告一個介面, 在該介面中宣告需要自定義的方法, 且該介面需要繼承 Spring Data 的 Repository.
-
提供 1) 所宣告的介面的實現類. 且繼承 SimpleJpaRepository, 並提供方法的實現
-
定義 JpaRepositoryFactoryBean 的實現類, 使其生成 1) 定義的介面實現類的物件
-
修改 <jpa:repositories /> 節點的 factory-class 屬性指向 3) 的全類名
-
注意: 全域性的擴充套件實現類不要用 Imp 作為字尾名, 或為全域性擴充套件介面新增 @NoRepositoryBean 註解告知 Spring Data: Spring Data 不把其作為 Repository