一、持久化Entity
首先我們創建一個UserRepository,繼承接口JpaRepository
(該接口集成了所有常用接口方法),進行基礎CURD的操作測試。
注意:
<User, Integer>
,分別代表:實體類以及實體類主鍵屬性封裝類型
UserRepository也就是對應常規開發模式下的DAO接口
public interface UserRepository extends JpaRepository<User, Integer> {
// 保存(添加)和更新
@Override
<S extends User> S save(S s);
// 根據Id刪除
@Override
void deleteById(Integer integer);
// 根據Id查詢
@Override
Optional<User> findById(Integer integer);
}
Service層實現
-
save:保存(添加)、更新
// 添加和更新實現 public Integer addUser(User user){ user.setName("張三"); userDao.save(user); user.setName(user.getName() + "_"); userDao.save(user); return user.getId(); }
測試結果:
Hibernate: insert into user (create_time, department_id, name) values (?, ?, ?)
Hibernate: update user set create_time=?, department_id=?, name=? where id=?
數據庫返回結果:
分析:
在定義User Entity時,對id成員變量進行了@Id、@GeneratedValue(strategy=GenerationType.IDENTITY)標註,表名該成員變量對應數據庫表中的主鍵,並且採用了主鍵自增的策略。
通過addUser方法save User對象時,沒有setId(),主鍵爲空情況下,插入user對象到數據庫。看到的SQL語句打印也並沒有Id字段,是因爲主鍵自增策略的緣故,插入後主鍵Id賦值爲4。當第二個save方法執行的時候,User對象已經有主鍵了。因此執行了更新操作,Id是通過user.getId獲取的,因此最終結果name字段變爲了 '張三_'
-
findById:根據Id進行查詢
public User findUser(int id) { Optional<User> user = userDao.findById(id); return user; }
測試結果:
Hibernate: select user0_.id as id1_1_0_, user0_.create_time as create_t2_1_0_, user0_.department_id as departme4_1_0_, user0_.name as name3_1_0_, department1_.id as id1_0_1_, department1_.name as name2_0_1_ from user user0_ left outer join department department1_ on user0_.department_id=department1_.id where user0_.id=?
-
deleteById:根據Id進行刪除
public boolean deleteUser(int id) { userDao.deleteById(id); return userDao.existsById(id); }
測試結果:false
Hibernate: delete from user where id=? Hibernate: select count(*) as col_0_0_ from user user0_ where user0_.id=?
總結下:通過簡單的CURD測試發現,基本的CURD功能可以實現,也無需使用XML配置SQL。但是打印的執行SQL着實有點複雜,難道這就是自動生成的SQL該有的樣子?當然針對非過多複雜查詢的業務場景下是可以的,牽扯複雜查詢之後,避免不了SQL語句的自定義。
二、Sort 排序
測試內置排序方法:
List findAll(Sort sort); 返回所有實體,按照指定排序(默認升序)返回。
Sort
對象的構造方法:
方法 | 說明 |
---|---|
public Sort(String… properties) | 按照指定的屬性列表升序排序 |
public Sort(Direction direction, String… properties) | 按照指定屬性列表排序,排序方式由Direction指定,Direction是枚舉類,有Direction.ASC和Direction.DESC兩類 |
public Sort(Order… orders) | Order 可以通過Order靜態方法來創建 public static Order asc(String propertyName); public static Order desc(String propertyName); |
-
getAllUserList:返回用戶實體集合
public List<User> getAllUserList() { // 按照Id降序查詢 Sort sort = new Sort(Sort.Direction.DESC, "id"); List<User> all = userDao.findAll(sort); return all; }
測試結果:
Hibernate: select user0_.id as id1_1_, user0_.create_time as create_t2_1_, user0_.department_id as departme4_1_, user0_.name as name3_1_ from user user0_ order by user0_.id desc
返回截圖:
三、Page和Pageable
Pageable
接口用於構造翻頁查詢,PageRequest
是其實現類
測試內置分頁方法(含排序):
Page findAll(Pageable pageable); 返回實體列表,實體列表的offset、limit通過方法參數 Pageable 控制。
這裏採用PageRequest中的如下構造方法實例化Pageable接口:
public PageRequest(int page, int size,
Direction direction, String... properties) {
this(page, size, Sort.by(direction, properties));
}
注意:
page 總是從0
開始,標識查詢頁,size標識每頁期望的行數。
Spring Data 分頁返回Page對象,Page對象提供了以下常用方法:
方法 | 說明 |
---|---|
int getTotalPages() | 總頁數 |
long getTotalElements() | 返回元素總數 |
List getContent() | 返回此次查詢的結果集 |
-
getAllUser(int page, int size):返回分頁實體列表
public List<User> getAllUser(int page, int size) { PageRequest pageable = PageRequest.of(page, size, Sort.Direction.DESC, "id"); Page<User> pageObject = userDao.findAll(pageable); int totalPage = pageObject.getTotalPages(); long count = pageObject.getTotalElements(); return pageObject.getContent(); }
這裏按照
size = 2
處理,一共5條數據,理論分3頁。
測試前數據:
測試page0&size=2時的請求:
四、JPQL
Spring Data
可以通過查詢的方法名和參數名自動構造 JPA OQL
查詢。
JPA OQL
簡稱 JPQL
:[Java Persistence Query Language] Java持久化查詢語言,旨在以面向對象表達式
語言的表達式,將SQL語法
和簡單查詢語義
綁定在一起。這種語言編寫的查詢是可移植的
,可以被編譯成主流數據庫服務器
上的SQL
。
其特徵與原生SQL語句
類似,並且完全面向對象
,通過類名
和屬性
訪問,而不是表名
和表的屬性
。
-
基於方法名字查詢
public interface UserRepository extends JpaRepository<User, Integer> { public User findByName(String name); }
注意:方法名和參數名需要遵循一定的規則,
Spring Data JPA
才能自動轉化爲JPQL
。- 方法名通常包含多個實體屬性(成員變量)用於查詢,屬性(成員變量)之間可以使用
AND
和OR
連接,也支持Between
、LessThan
、GreaterThan
、Like
; - 方法名可以以
findBy
、getBy
、queryBy
開頭; - 查詢結果可以排序,方法名需要包含
OrderBy+屬性+Asc(Desc)
; - 可以通過
Top
、First
來限定查詢結果集; - 一些特殊的參數可以出現在參數列表(簽名)中,如:
Pageable
、Sort
。
舉堆栗子:
-
根據名字查詢,結果升序,且分頁
Page<User> findByLastnameOrderByFirstNameAsc(String lastname, Pageable pageable);
-
查詢滿足條件的前10個用戶
List<User> findFirst10ByLastname(String lastname, Sort sort);
-
使用And聯合查詢
List<User> findByLastnameAndFirstname(String lastname, String firstname);
-
使用Or查詢
List<User> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
-
使用like查詢,name中必須包含like中的
%
或者?
User findByNameLike(String name);
- 方法名通常包含多個實體屬性(成員變量)用於查詢,屬性(成員變量)之間可以使用
-
Spring Data
支持的關鍵字關鍵字 例子 轉化的JPQL片段 And findByLastnameAndFirstname …where x.lastname = ?1 and x.firstname = ?2 Or findByLastnameOrFirstname …where x.lastname = ?1 or x.firstname = ?2 Between findByStartDateBetween …where x.startDate between ?1 and ?2 LessThan findByAgeLessThan
findByAgeLessThanEqual…where x.age < ?1
…where x.age <= ?1GreaterThan findByAgeGreaterThan
findByAgeGreaterThanEqual…where x.age > ?1
…where x.age >= ?1After(DateTime) findByStartDateAfter …where x.startDate > ?1 Before(DateTime) findByStartDateBefore …where x.startDate < ?1 IsNull findByAgeIsNull
findByAge(Is)NotNull…where x.age is null
…where x.age not nullOrderBy findByAgeOrderByLastnameDesc …where x.age = ?1 order by x.lastname desc Not findByLastnameNot …where x.lastname <>?1 In findByAgeIn(Collection ages)
findByAgeNotIn(Collection ages)…where x.age in ?1
…where x.age not in ?1True findByActiveTrue
finByActiveFlase…where x.active = true
…where x.active = falseIgnoreCase findByFirstnameIgnoreCase …where UPPER(x.firstname) = UPPER(?1) Like findByFirstnameLike
findByFirstnameNotLike…where x.firstname like ?1
…where x.firstname not like ?1突然覺得這種JPA查詢好像很高大上的樣子,完全無SQL查詢。真是厲害了我的JPQL!
五、@Query查詢
@Query
註解允許在方法使用JPQL
-
JPQL
:面向對象和對象屬性@Query(value = "select u from User u where u.name = ?1 and u.department.id = ?2") public User findUser(String name, Integer departmentId);
-
SQL
:面向數據表名和字段名 (nativeQuery = true)@Query(value = "select * from User where name = ?1 and department_id = ?2", nativeQuery = true) public User findUser(String name, Integer departmentId);
-
無論是
JPQL
還是SQL
,均支持命名參數
。例如:@Query(value = "select * from User where name = :name and department_id = :departmentId", nativeQuery = true) public User findUser(String name, Integer departmentId);
命名參數在上一章節JDBC Template 中也提到了,這樣有利於SQL語句參數的和方法參數的映射,常規的索引方式固然方便,但是方法參數簽名順序變更後,SQL語句中的
?佔位符
順序也要變更。各有優劣。 -
Object[]數組
:非Entity
查詢返回結果的替代方案例如:分組統計每個部門的用戶數目
@Query(value = "select department_id, count(*) from User group by department_id", nativeQuery = true) public List<Object[]> queryUserCount();
-
@Modifying:JPQL更新、刪除操作時搭配使用
@Modifying @Query(value = "update User u set u.name = ?1 where u.id = ?2") public int updateName(String name, Integer id);
六、Example 查詢
上邊提到了方法名構造JPQL
,@Query構造JPQL
進行查詢,Example對象
的查詢方案是指:根據實體(Entity)創建一個Example對象,Spring Data
通過 Example對象
來構造JPQL
。
public List<User> getByExample(String name) {
// 創建對象
User user = new User();
Department dept = new Department();
// 屬性賦值
user.setName(name);
dept.setId(1);
// 組合對象user賦值
user.setDepartment(dept);
// Exanple.of靜態方法構造example對象
Example<User> example = Example.of(user);
// 調用findAll進行查詢
List<User> list = userDao.findAll(example);
return list;
}
總結下:通過構造實體查詢條件user,結合Example.of(user)的靜態方法構造Example對象,最後將Example對象作爲參數傳入JpaRepository(UserRepository)實現方法中進行執行查詢。
非完全匹配查詢
:ExampleMatcher
// ExampleMatcher提供了條件指定。下述代碼條件爲:名稱Tom開頭的所有用戶
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("Tom", GenericPropertyMatchers.startsWith().ignoreCase());
Example<User> example = Example.of(user, matcher);
七、EntityManager:JPA提供的底層數據庫訪問接口
書中這裏提到了一個例子:動態分頁查詢中使用EntityManager彌補Repository的不足。
這裏就直接上代碼說明了:
public Page<User> queryUser(Integer departmentId, Pageable page) {
//構造基礎JPQL,這個JPQL將作爲求和以及分頁查詢的基礎JPQL
StringBuilder baseJpql = new StringBuilder("from User u where 1 = 1 ");
// 初始化查詢參數Map
Map<String, Object> paras = new HashMap<String, Object>();
if (departmentId != null) {
// 添加附加添加參數,拼裝JPQL
baseJpql.append("and u.department.id = :deptId");
// 配置附加查詢參數 deptId
paras.put("deptId", departmentId);
}
//功能1:查詢滿足條件的總數
long count = getQueryCount(baseJpql, paras);
if (count == 0) {
return new PageImpl(Collections.emptyList(), page, 0);
}
//功能2:查詢滿足分頁條件結果集
List list = getQueryResult(baseJpql, paras, page);
//返回結果
Page ret = new PageImpl(list, page, count);
return ret;
}
通用:查詢參數配置方法:setQueryParameter()
private void setQueryParameter(Query query, Map<String, Object> paras) {
for (Entry<String, Object> entry : paras.entrySet()) {
query.setParameter(entry.getKey(), entry.getValue());
}
}
功能1:求和查詢 getQueryCount()
private Long getQueryCount(StringBuilder baseJpql,
Map<String, Object> paras) {
// 創建Query 查詢對象 結合傳遞的JPQL
Query query = em.createQuery("select count(1) " + baseJpql.toString());
// 調用通用查詢參數配置方法(見上)
setQueryParameter(query, paras);
// 調用query對象的查詢方法:getSingleResult()得到滿足條件的記錄總數
Number number = (Number) query.getSingleResult();
return number.longValue();
}
功能2:分頁查詢 getQueryResult()
private List getQueryResult(StringBuilder baseJpql, Map<String, Object> paras, Pageable page) {
// 創建Query 查詢對象 結合傳遞的JPQL
Query query = em.createQuery("select u " + baseJpql.toString());
// 調用通用查詢參數配置方法(見上上)
setQueryParameter(query, paras);
// 配置分頁相關信息
query.setFirstResult((int) page.getOffset());
query.setMaxResults(page.getPageNumber());
// 調用查詢方法得到list
List list = query.getResultList();
return list;
}
queryUser()
的邏輯梳理爲:首先通過拼裝好的JPQL進行求和查詢
,根據返回count是否爲 0,爲 0 則返回一個空頁(Collections.emptyList( )),非 0 便根據總數進行記錄分頁,分頁參數對象作爲參數傳入分頁查詢
方法,最後返回分頁結果數據集。