SpringDataJpaNotes
Spring Data JPA 介紹
Spring-Data 概述
Spring Data 是一個資料訪問框架 ,用於簡化資料庫訪問,旨在提供一致的資料庫訪問模型,同時仍然保留不同資料庫底層資料儲存的特點,Spring Data 採用了領域驅動模型的設計思想,實現了訪問關係型資料庫、非關係型資料庫的統一的介面,只需要定義好領域模型(Entity),後續的建立表、CURD、排序操作不需要手動新增任何SQL語句,同時也支援手動擴充套件功能。
Spring Data 只要定義介面,遵循 Spring Data 的規範,就無需寫實現類。
Spring Data 提供了預設的交易處理方式,即所有的查詢均宣告為唯讀事務。
Spring Data 專案所支援 NoSQL 儲存:MongoDB (檔案資料庫)、Neo4j(圖形資料庫)、Redis(鍵/值儲存)、Hbase(列族資料庫)
Spring Data 專案所支援的關係資料儲存技術:JDBC、JPA
Spring Data JPA 概述
JPA(Java Persistence API)是 Sun 官方提出的 Java 持久化規範。
JPA主要是為了簡化現有的持久化開發工作和整合 ORM 技術, 是在充分吸收了現有 Hibernate、TopLink、JDO 等 ORM 框架的基礎上發展而來的,具有易於使用、伸縮性強等優點。
Spring Data JPA 是 Spring 基於 ORM 框架、JPA 規範的基礎上封裝的一套 JPA 應用框架,可使開發者用簡單程式碼即可實現對資料的訪問和操作。
它提供了包括增刪改查等在內的常用功能,且易於擴充套件!通常我們寫持久層,都是先寫一個介面,再寫介面對應的實現類,在實現類中進行持久層的業務邏輯處理。而現在,Spring Data JPA幫助我們自動完成了持久層的業務邏輯處理,開發者唯一要做的,就只是宣告持久層的介面,其他都交給 Spring Data JPA 來幫你完成!
注意:JPA 是一套規範,不是一套產品, Hibernate、TopLink、JDO 它們是一套產品,如果說這些產品實現了這個 JPA 規範,那麼就可以叫它們為 JPA 的實現產品。
Repository
Repository介面
Repository 介面是 Spring Data 的一個核心介面,是一個抽象的介面,使用者透過繼承該介面來實現資料的訪問,它不提供任何方法,開發者需要在自己定義的介面中宣告需要的方法
public interface Repository<T, ID> { }很重要的一點就是,Repository的實現類是在應用啟動的時候生成的,也就是Spring的應用上下文建立的時候.而不是透過程式碼生成技術產生的,也不是介面方法呼叫時才產生的 基礎的Repository提供了最基本的資料訪問功能,其幾個子介面則擴充套件了一些功能。
編寫Spring Data JPA Repository 的關鍵在於從一組介面中挑選一個進行擴充套件
EX:
public interface CustomerRepository extends Repository<CustomerEntity,Long> { }新增註解為其指定 CustomerEntity和 id 屬性。
在spring boot中如果使用了 spring-boot-starter-data-jpa ,會自動掃描所有擴充套件了Repository介面的類
CrudRepository 介面
CrudRepository 介面繼承Repository,提供對實體類(CRUD)增刪改查方法,可以直接呼叫。
CrudRepository介面實現了save、delete、count、exists等方法,繼承這個介面時需要兩個範本引數T和ID,T就是你的實體類(對應資料庫表),ID就是主鍵。
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID>
{
<S extends T> S save(S entity); //Saves the given entity (儲存給定的實體。)
Optional<T> findById(ID primaryKey); //Returns the entity identified by the given ID.(返回由給定ID標識的實體。)
Iterable<T> findAll(); //Returns all entities.(返回所有實體。)
long count();//Returns the number of entities.(查詢實體數量)
void delete(T entity); // Deletes the given entity.(刪除給定的實體。)
boolean existsById(ID primaryKey); //Indicates whether an entity with the given ID exists.(根據id判斷實體是否存在)
void delete(ID id);//根據Id刪除實體
<S extends T> Iterable<S> saveAll(Iterable<S> entities);//儲存集合
}在使用中,使用者需要繼承這個介面,CustomerEntity就是定義的實體,Long是主鍵型別
public interface CustomerRepository extends CrudRepository<CustomerEntity, Long>
{
}
package com.example.demo;
import java.util.ArrayList;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* CustomerRepositoryTest
*/
@SpringBootTest
public class CustomerRepositoryTest {
@Autowired
private UserRepository userRepository;
// CRUD 操作
// 增 save(entity), save(entities)
@Test
public void save1() {
UserEntity userEntity = new UserEntity();
userEntity.setName("肯德基20");
userEntity = userRepository.save(userEntity);
System.out.println(userEntity);
}
// save(entities)
@Test
public void saveManyTest() {
UserEntity userEntity = new UserEntity();
userEntity.setName("test21");
UserEntity userEntity2 = new UserEntity();
userEntity2.setName("test22");
ArrayList<UserEntity> userEntities = new ArrayList<UserEntity>();
userEntities.add(userEntity);
userEntities.add(userEntity2);
userRepository.saveAll(userEntities);
}
} // 刪
delete(id),delete(entity),delete(entities),deleteAll
// 查
findOne(id),findAll,exits(id)
// save***只要 id一樣,就會更新,而不是新增.PagingAndSortingRepository 介面
繼承CrudRepository,具有分頁查詢和排序功能
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort); //排序
Page<T> findAll(Pageable pageable); //分頁查詢(含排序功能)
}**example: **
package com.example.demo;
import java.util.ArrayList;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* CustomerRepositoryTest
*/
@SpringBootTest
public class CustomerRepositoryTest {
@Autowired
private CustomerRepository customerRepository;
@Test
public void findAll() {
Iterable iterable = customerRepository.findAll();
System.out.println("iterable " + iterable);
}
}JpaRepository 介面
繼承 PagingAndSortingRepository,實現一組 JPA 規範相關的方法, 該介面提供了JPA的相關功能 ,PagingAndSortingRepository介面本身已經繼承了 CrudRepository
public interface JpaRepository<T,ID> extends PagingAndSortingRepository<T,ID>, QueryByExampleExecutor<T>
{
List<T> findAll(); //查詢所有實體
List<T> findAll(Sort sort); //排序、查詢所有實體
List<T> save(Iterable<? extends T> entities);//儲存集合
T saveAndFlush(T entity);//強制執行持久化
void flush();//執行快取與資料庫同步
void deleteInBatch(Iterable<T> entities);//刪除一個實體集合
}JpaSpecificationExecutor介面
可以執行原生SQL查詢也可以自訂Repository的方法不屬於Repository體系,實現一組 JPA Criteria 查詢相關的方法 Specification:封裝 JPA Criteria 查詢準則。通常使用匿名內部類的方式來建立該介面的物件
Specification:封裝 JPA Criteria 查詢準則。通常使用匿名內部類的方式來建立該介面的物件
由於JpaSpecificationExecutor 並不繼承repository 介面,所以它不能單獨使用,只能和jpa Repository 一起用。
public interface JpaSpecificationExecutor<T> {
T findOne(Specification<T> spec);
List<T> findAll(Specification<T> spec);
Page<T> findAll(Specification<T> spec, Pageable pageable);
List<T> findAll(Specification<T> spec, Sort sort);
long count(Specification<T> spec);
} public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor {
}Spring-Data 方法定義規範
public interface ProductInfoRepository extends JpaRepository<ProductInfoEntity,String> {
//定義一個方法:根據商品名稱查詢所有的商品
List<ProductInfoEntity> findAllByProductName(String name);
}當建立 Repository 實現的時候, Spring Data會檢查 Repository 介面的所有方法,解析方法的名稱,並基於被持久化的物件來推測方法的目的,Spring Data 定義了一組小型的領域特定語言(DSL) ,在這裡持久化的細節都是透過 Repository的方法簽名來描述的
findAllByProductName(String name) 方法非常簡單,Repoditory 方法是 由一個動詞,一個可選主題,關鍵字By以及一個斷言所組成
在findAllByProductName 方法中,動詞是findAll ,斷言是 ProductName,主題並沒有指定,
暗含就是 ProductInfoEntity Repository 方法的主題是可選的,它主要是讓你命名方法的時候有很多的靈活性,findAllByProductName和findAllProductInfoEntityByProductName方法沒有什麼區別. 要查詢的物件的型別是透過如何引數化 Repository 介面來決定的,而不是方法名稱中的主題.
擴充套件查詢
按照Spring Data 的規範,查詢方法以find | read | get 開頭, 涉及條件查詢時,條件的屬性用條件關鍵字連線,要注意的是:條件屬性以首字母大寫。
find | read | get方法都會查詢資料並返回物件.而 count 則會返回匹配物件的數量,而不是物件本身.
EX:定義一個 Entity 實體類
class UserEntity{
private String firstName;
private String lastName;
}使用And條件連線時,應這樣寫:
findByLastNameAndFirstName(String lastName,String firstName);假如建立如下的查詢:findByUserDepUuid(),框架在解析該方法時,首先剔除 findBy,然後對剩下的屬性進行解析,假設查詢實體為Doc (1)先判斷 userDepUuid (根據 POJO 規範,首字母變為小寫)是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,繼續第二步; (2)從右往左擷取第一個大寫字母開頭的字串(此處為Uuid),然後檢查剩下的字串是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,則重複第二步,繼續從右往左擷取;最後假設 user 為查詢實體的一個屬性; (3)接著處理剩下部分(DepUuid),先判斷 user 所對應的型別是否有depUuid屬性,如果有,則表示該方法最終是根據 “ Doc.user.depUuid” 的取值進行查詢;否則繼續按照步驟 2 的規則從右往左擷取,最終表示根據 “Doc.user.dep.uuid” 的值進行查詢。 (4)可能會存在一種特殊情況,比如 Doc包含一個 user 的屬性,也有一個 userDep 屬性,此時會存在混淆。可以明確在屬性之間加上 “_” 以顯式表達意圖,比如 “findByUser_DepUuid()” 或者 “findByUserDep_uuid()” 特殊的引數: 還可以直接在方法的引數上加入分頁或排序的引數,比如:
Page<UserModel> findByName(String name, Pageable pageable);
List<UserModel> findByName(String name, Sort sort);如果覺得curdrepository提供的查詢不符合要求,可以繼承該介面進行擴充套件,
Spring Data JPA為此提供了一些表達條件查詢的關鍵字,大致如下:
條件的屬性名稱與個數要與引數的位置與個數一一對應 直接在介面中定義查詢方法,如果是符合規範的,可以不用寫實現,目前支援的關鍵字寫法如下:
| Keyword | Description | Sample |
|---|---|---|
| And | 等價於SQL中的and 關鍵字 | findByUsernameAndPassword(String user, Striang pwd); |
| Or | 等價於SQL中的or 關鍵字 | findByUsernameOrAddress(String user, String addr); |
| Between | 等價於SQL中的between 關鍵字 | findBySalaryBetween(int max,int min); |
| LessThan | 等價於SQL中的"<" | findBySalaryLessThan(int max); |
| GreaterThan | 等價於SQL中的">" | findBySalaryGreaterThan(intmin); |
| IsNull | 等價於SQL中的"is null" | findByUsernameIsNull(); |
| IsNotNull | 等價於SQL中的"is not null" | findByUsernameIsNotNull(); |
| NotNull | 與IsNotNull等價 | |
| Like | 等價於SQL中的"like" | findByUsernameLike(String user); |
| NotLike | 等價於SQL中的"not like" | findByUsernameNotLike(Stringuser); |
| OrderBy | 等價於SQL中的"order by" | findByUsernameOrderBySalaryAsc(String user); |
| Not | 等價於SQL中的"! =" | findByUsernameNot(String user); |
| In | 等價於SQL中的"in" | findByUsernameIn(Collection |
| NotIn | 等價於SQL中的"not in" | findByUsernameNotIn(Collection |
@Query註解
Spring Data 這個小型的DSL依舊有其侷限性,有時候透過方法名錶達預期的查詢很繁瑣,甚至無法實現.如果與呆這種情況,Spring Data能讓我們透過**@Query**註解來解決問題
這種查詢可以宣告在 Repository 方法中,擺脫像命名查詢那樣的約束,將查詢直接在相應的介面方法中宣告,結構更為清晰,這是 Spring data 的特有實現。
如果是 @Query 中有 LIKE 關鍵字,後面的引數需要前面或者後面加 %,這樣在傳遞引數值的時候就可以不加 %:
@Query註解 這種查詢可以宣告在 Repository 方法中,擺脫像命名查詢那樣的約束,將查詢直接在相應的介面方法中宣告,結構更為清晰,這是 Spring data 的特有實現。
自訂 Repository 方法 定義一個介面: 宣告要新增的, 並自實現的方法 提供該介面的實現類: 類名需在要宣告的 Repository 後新增 Impl, 並實現方法 宣告 Repository 介面, 並繼承 1) 宣告的介面 使用. 注意: 預設情況下, Spring Data 會在 base-package 中查詢 “介面名Impl” 作為實現類. 也可以透過 repository-impl-postfix 宣告尾碼
@Query("select o from UserModel o where o.name like %?1")@Query來指定本地查詢
使用**@Query來指定本地查詢,只要設定nativeQuery為true**
@Query(value="select * from tbl_user where name like %?1" ,nativeQuery=true)@Query 與 @Modifying 這兩個 annotation一起宣告,可定義個性化更新操作,例如只涉及某些欄位更新時最為常用
Springdata支援JPQL 語句對查詢進行擴充套件,
例子如下:
public interface CustomerRepository extends CrudRepository<Customer, Long>
{
@Query("select a from Customer a WHERE a.firstName = ?")
List<Customer> findByQuery(StringfirstName);
} EX:
//宣告自訂查詢
/**
* 使用JPA SQL語句
* @return
**/
@Query("select p from ProductInfoEntity p where p.productName like '%米%' ")
List<ProductInfoEntity> findProductInfo();/**
* 使用JPA SQL語句 查詢價格最高的商品
**/
@Query("select p from ProductInfoEntity p " +
"where p.productPrice=" +
"(select max(p2.productPrice) from ProductInfoEntity p2)")
List<ProductInfoEntity> findMaxPrice(); /**
* 使用JPA SQL語句 帶引數的查詢1
* @param name
* @param price
* @return
**/
@Query("select o from ProductInfoEntity o where o.productName=?1 and o.productPrice=?2")
List<ProductInfoEntity> findParam(String name, double price); /**
* 使用JPASQL語句 帶引數的查詢2
* @param name
* @param price
* @return
**/
@Query("select o from ProductInfoEntity o where o.productName=:name and o.productPrice=:price")
List<ProductInfoEntity> findParam2(@Param("name") String name, @Param("price") double price);
當然還可以使用原生SQL語句進行查詢,只需要 nativeQuery = true 即可 /**
*使用原生SQL語句 查詢
* @return
**/
@Query(nativeQuery = true,value = "select count(*) from product_info")
Integer getCount();Spring data JPA 更新及刪除操作整合事物的使用
更新操作注意事項:
/** 使用Query註解寫更新JPA語句
*新增 @Modifying 註解
**/
@Modifying
@Query("update ProductInfoEntity o set o.productPrice =:price where o.productId=:id")
Integer updatePrice(@Param("id") String id,@Param("price") double price);- 在service層新增事物 @Transactional
package com.itguang.weixinsell.service;
import com.itguang.weixinsell.repository.ProductInfoRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class ProductInfoService {
@Autowired
private ProductInfoRepository infoRepository;
public Integer updatePrice( String id,double price){
Integer i = infoRepository.updatePrice(id, price);
return i;
}
}參考
Spring Data JPA - Reference Documentation
Spring For All 社群 Spring Data JPA 從入門到進階系列教程 | Spring For All (spring4all.com)