摘要:編寫測試用例代碼打開框架自動生成的測試代碼文件編寫測試用例,測試增刪改查效果,測試代碼如下注釋,它可以對類成員變量方法及構造函數進行標注,完成自動裝配的工作。
文章系列
【從零入門系列-0】Sprint Boot 之 Hello World
【從零入門系列-1】Sprint Boot 之 程序結構設計說明
【從零入門系列-2】Sprint Boot 之 數據庫實體類
前言前一章簡述了如何設計實現數據庫實體類,本篇文章在此基礎上進行開發,完成對該數據庫表的常用操作,主要包括使用Spring Data JPA進行簡單的增刪改查和復雜查詢操作。
Spring Data JPA是Spring提供的一套簡化JPA開發的框架,按照約定好的【方法命名規則】寫dao層接口,就可以在不寫接口實現的情況下,實現對數據庫的訪問和操作,同時提供了很多除了CRUD之外的功能,如分頁、排序、復雜查詢等等,Spring Data JPA 可以理解為 JPA 規范的再次封裝抽象,底層還是使用了 Hibernate 的 JPA 技術實現。通過引入Spring Data JPA后,我們可以基本不用寫代碼就能實現對數據庫的增刪改查操作。
此外,由于Spring Data JPA自帶實現了很多內置的后臺操作方法,因此在調用方法時必須根據其規范使用,深刻理解規范和約定。
表的基本操作實現(CRUD)在這里,先介紹一下JpaRepository,這是類型為interface的一組接口規范,是基于JPA的Repository接口,能夠極大地減少訪問數據庫的代碼編寫,是實現Spring Data JPA技術訪問數據庫的關鍵接口。
編寫數據操作接口
在使用時,我們只需要定義一個繼承該接口類型的接口即可實現對表的基本操作方法,在此我們需要對實體類Book進行操作,因此在Dao目錄上右鍵New->Java Class,然后設置名稱為BookJpaRepository,kind類型選Interface即可,然后添加注解及繼承自JpaRepository,文件BookJpaRepository.java內容如下所示:
package com.arbboter.demolibrary.Dao; import com.arbboter.demolibrary.Domain.Book; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface BookJpaRepository extends JpaRepository{ }
@Repository持久層組件,用于標注數據訪問組件,即DAO組件,此時配合上上一篇文章中的JPA配置,我們就可以進行增刪改查啦,不用添加任何其他代碼,因為JpaRepository已經幫我們實現好了。
編寫測試用例代碼
打開框架自動生成的測試代碼文件DemoLibraryApplicationTests.java編寫測試用例,測試增刪改查效果,測試代碼如下:
@RunWith(SpringRunner.class) @SpringBootTest public class DemoLibraryApplicationTests { /** * @Autowired 注釋,它可以對類成員變量、方法及構造函數進行標注,完成自動裝配的工作。 * 通過 @Autowired的使用來消除 set ,get方法,簡化程序代碼 * 此處自動裝配我們實現的BookJpaRepository接口,然后可以直接使用bookJpaRepository操作數據庫 * 如果不加@Autowired,直接使用bookJpaRepository,程序運行會拋出異常 */ @Autowired private BookJpaRepository bookJpaRepository; @Test public void contextLoads() { Book book = new Book(); // 增 book.setName("Spring Boot 入門學習實踐"); book.setAuthor("arbboter"); book.setImage("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2656353677,2997395625&fm=26&gp=0.jpg"); bookJpaRepository.save(book); System.out.println("保存數據成功:" + book); // 查 book = bookJpaRepository.findById(book.getId()).get(); System.out.println("新增后根據ID查詢結果:" + book); // 修改 book.setName("Spring Boot 入門學習實踐(修改版)"); bookJpaRepository.save(book); System.out.println("修改后根據ID查詢結果:" + book); // 刪除 bookJpaRepository.deleteById(book.getId()); } }
注意在測試代碼用需要對屬性bookJpaRepository使用@Autowired 自動注入實現初始化,@Autowired 注解,它可以對類成員變量、方法及構造函數進行標注,完成自動裝配的工作。
測試結果
通過測試結果我們可以看到,程序已經能夠對表數據進行增刪改查,且我們通過刪除SQL可以觀察到,獲取生成記錄ID的SQL語句為:
Hibernate: select next_val as id_val from hibernate_sequence with (updlock, holdlock, rowlock) Hibernate: update hibernate_sequence set next_val= ? where next_val=?
因此可推斷出,JpaRepository對默認的自增ID均使用表hibernate_sequence作為ID生成器,所有默認的ID表公用此ID生成器。
通過上述例子,我們可以發現,雖然我們沒有寫任何一條SQL語句,但是程序已經可以正常操作數據庫了,這對苦逼的C++程序員手寫SQL來說真是不要說太幸福哈。不過上述示例也存在一些問題,數據查詢均是通過ID操作的,但是實際使用中,數據查詢還需要根據其他條件,比如書名或作者,是不是需要手寫SQL實現?答案是否定的,JpaRepository支持接口規范方法名查詢,意思是如果在接口中定義的查詢方法符合它的命名規則,就可以不用寫實現,框架自動提供實現的方法,只需要聲明無需自己實現即可使用。
JpaRepository的規范方法名查詢在我們實現的接口中,可以只定義查詢方法,如果是符合規范的,可以不用寫實現,就可以直接使用。
JpaRepository會對方法名進行校驗,不符合規范會報錯,除非添加@Query注解。
在本示例中,我們希望通過書名和作者的常用場景提供查詢方案,可按下述實現:
@Repository public interface BookJpaRepository extends JpaRepository{ /** * 根據書名精準查詢書籍列表 * @param name 查詢的書名 * @return 名字為name的書籍列表 */ List findByName(String name); /** * * 根據書名模糊查詢書籍列表 * @param name 查詢的書名 * @return 查詢結果 */ List findByNameLike(String name); /** * 根據書名和作者查詢,注意參數列表順序和名字順序保持一致(約定!) * @param name 查詢的書名 * @param author 查詢的作者名 * @return 查詢結果 */ List findByNameAndAuthor(String name, String author); /** * 根據書名或作者查詢,注意參數列表順序和名字順序保持一致(約定!) * @param name 查詢的書名 * @param author 查詢的作者名 * @return 查詢結果 */ List findByNameOrAuthor(String name, String author); /** * 根據作者集合查詢 * @param authors 書列表名 * @return */ List findByAuthorIn(Collection authors); }
上述代碼通,我們實現了模糊、精準、And和Or以及In的查詢定義,都是根據JPA的命名規范定義方法,此時我們不用自己去實現方法,直接可以調用。
測試代碼如下:
// 模擬數據 for (int i=0; i<20; i++){ Book b = new Book(); b.setName("書名_" + i); b.setAuthor("作者_" + i%5); b.setImage("img" + i); bookJpaRepository.save(b); } ListbookList; // 根據書名精準查詢 bookList = bookJpaRepository.findByName("書名_2"); System.out.println("根據書名精準查詢:" + bookList); // 根據書名模糊查詢 bookList = bookJpaRepository.findByNameLike("書名_2"); System.out.println("根據書名模糊查詢:" + bookList); // 根據書名和作者名查詢 bookList = bookJpaRepository.findByNameAndAuthor("書名_2", "作者_2"); System.out.println("根據書名和作者名查詢:" + bookList); // 根據書名或作者名查詢 bookList = bookJpaRepository.findByNameOrAuthor("書名_2", "作者_2"); System.out.println("根據書名或作者名查詢:" + bookList); // 根據作者名集合查詢 Collection c = new ArrayList(); c.add("作者_1"); c.add("作者_3"); bookList = bookJpaRepository.findByAuthorIn(c); System.out.println("根據作者名集合查詢:" + bookList);
運行結果為:
Hibernate: select book0_.id as id1_0_, book0_.author as author2_0_, book0_.image as image3_0_, book0_.name as name4_0_ from library_book book0_ where book0_.name=? 根據書名精準查詢:[Book{id=9, name="書名_2", author="作者_2", image="img2"}] Hibernate: select book0_.id as id1_0_, book0_.author as author2_0_, book0_.image as image3_0_, book0_.name as name4_0_ from library_book book0_ where book0_.name like ? escape ? 根據書名模糊查詢:[Book{id=9, name="書名_2", author="作者_2", image="img2"}] Hibernate: select book0_.id as id1_0_, book0_.author as author2_0_, book0_.image as image3_0_, book0_.name as name4_0_ from library_book book0_ where book0_.name=? and book0_.author=? 根據書名和作者名查詢:[Book{id=9, name="書名_2", author="作者_2", image="img2"}] Hibernate: select book0_.id as id1_0_, book0_.author as author2_0_, book0_.image as image3_0_, book0_.name as name4_0_ from library_book book0_ where book0_.name=? or book0_.author=? 根據書名或作者名查詢:[Book{id=9, name="書名_2", author="作者_2", image="img2"}, Book{id=14, name="書名_7", author="作者_2", image="img7"}, Book{id=19, name="書名_12", author="作者_2", image="img12"}, Book{id=24, name="書名_17", author="作者_2", image="img17"}] Hibernate: select book0_.id as id1_0_, book0_.author as author2_0_, book0_.image as image3_0_, book0_.name as name4_0_ from library_book book0_ where book0_.author in (? , ?) 根據作者名集合查詢:[Book{id=8, name="書名_1", author="作者_1", image="img1"}, Book{id=10, name="書名_3", author="作者_3", image="img3"}, Book{id=13, name="書名_6", author="作者_1", image="img6"}, Book{id=15, name="書名_8", author="作者_3", image="img8"}, Book{id=18, name="書名_11", author="作者_1", image="img11"}, Book{id=20, name="書名_13", author="作者_3", image="img13"}, Book{id=23, name="書名_16", author="作者_1", image="img16"}, Book{id=25, name="書名_18", author="作者_3", image="img18"}]
JpaRepository規范方法名查詢規約說明:JpaRepository框架在進行方法名解析時,會先把方法名多余的前綴截取掉,比如 find、findBy、read、readBy、get、getBy,然后對剩下部分進行解析。
方法關鍵字必須遵循完全的駝峰形式,因為JPA的方法名稱解析引擎算法是通過駝峰來解析的
下劃線可以被用來中斷解析算法的語義,但是它是一個保留字,不建議使用
In和NotIn也可以將Collection的任何子類作為參數以及數組或可變參數。
JpaRepository的復雜查詢在我們的圖書管理系統中要提供查詢功能,可以根據書籍ID、書名或者作者中的三個任意組合查詢,且支持查詢結果自定義分頁和排序,這樣的話,使用JpaRepository規范方法名查詢可能就變得很復雜了,由于組合后方案很多,不可能每種方案區分對待,此時應該提供一種通用可自適應的方法來實現,具備動態構建相應的查詢語句的能力。
Sppring Boot JPA通過JpaSpecificationExecutor提供復雜查詢的能力,繼承該接口后,重寫接口Predicate toPredicate(Root
1.繼承JpaSpecificationExecutor
@Repository public interface BookJpaRepository extends JpaRepository, JpaSpecificationExecutor { /*內容省略*/ }
在原有的BookJpaRepository補充繼承JpaSpecificationExecutor即可。
2.創建Service接口
由于需要自實現toPredicate方法,所以這里把搜索查詢功能實現放到Service層,在Service目錄上右鍵New->Java Class創建BookService.java文件:
@Service public class BookService { @Autowired BookJpaRepository bookJpaRepository; /** * 搜索查詢接口 * @param para: 鍵值對包含name,id,author,pageSize,pageNumber,ordName,ordDir * @return */ Pagesearch(Map para){ return null; } }
上述代碼中@Service注解該類為服務類。
3.重寫toPredicate方法
// 構造查詢條件 Specificationspecification = new Specification () { @Override public Predicate toPredicate(Root root, CriteriaQuery> query, CriteriaBuilder cb) { List predicate = new ArrayList<>(); // 根據支持參數列表獲取查詢參數 String matchMode = para.getOrDefault("matchMode", "AND"); List bookFields = Arrays.asList("id", "name", "author", "image"); for (String p : bookFields){ String buf = para.get(p); if(buf != null){ if(matchMode == "LIKE") { predicate.add(cb.like(root.get(p).as(String.class), "%" + buf + "%")); } else { predicate.add(cb.equal(root.get(p).as(String.class), buf)); } } } Predicate[] pre = new Predicate[predicate.size()]; return query.where(predicate.toArray(pre)).getRestriction(); } };
該方法返回的Predicate即為查詢條件。
4.分頁排序及查詢
// 分頁排序 Integer pageNumber = para.get("pageNumber") == null ? 0:Integer.valueOf(para.get("pageNumber")); Integer pageSize = para.get("pageSize") == null ? 10:Integer.valueOf(para.get("pageSize")); Sort.Direction sortDir = para.getOrDefault("sortDir", "DESC") == "DESC" ? Sort.Direction.DESC : Sort.Direction.ASC; String ordName = para.getOrDefault("ordName", "id"); Pageable pageable = PageRequest.of(pageNumber, pageSize, sortDir, ordName); return bookJpaRepository.findAll(specification, pageable);
最后bookJpaRepository.findAll(specification, pageable)返回的結果即為查詢結果
5.完整的搜索方法
@Service public class BookService { @Autowired private BookJpaRepository bookJpaRepository; /** * 搜索查詢接口 * 默認值:pageSize-10 pageNumber-0 ordName-id sortDir-ASC matchMode-EQUAL * @param para: 鍵值對包含name,id,author,pageSize,pageNumber,ordName,sortDir,matchMode * @return */ public Pagesearch(Map para){ // 構造查詢條件 Specification specification = new Specification () { @Override public Predicate toPredicate(Root root, CriteriaQuery> query, CriteriaBuilder cb) { List predicate = new ArrayList<>(); // 根據支持參數列表獲取查詢參數 String matchMode = para.getOrDefault("matchMode", "AND"); List bookFields = Arrays.asList("id", "name", "author", "image"); for (String p : bookFields){ String buf = para.get(p); if(buf != null){ if(matchMode == "LIKE") { predicate.add(cb.like(root.get(p).as(String.class), "%" + buf + "%")); } else { predicate.add(cb.equal(root.get(p).as(String.class), buf)); } } } Predicate[] pre = new Predicate[predicate.size()]; return query.where(predicate.toArray(pre)).getRestriction(); } }; // 分頁排序 Integer pageNumber = para.get("pageNumber") == null ? 0:Integer.valueOf(para.get("pageNumber")); Integer pageSize = para.get("pageSize") == null ? 10:Integer.valueOf(para.get("pageSize")); Sort.Direction sortDir = para.getOrDefault("sortDir", "DESC") == "DESC" ? Sort.Direction.DESC : Sort.Direction.ASC; String ordName = para.getOrDefault("ordName", "id"); Pageable pageable = PageRequest.of(pageNumber, pageSize, sortDir, ordName); return bookJpaRepository.findAll(specification, pageable); } }
6.測試代碼
@Autowired private BookService bookService; @Test public void search(){ Mappara = new HashMap<>(); Page books = bookService.search(para); System.out.println("分頁3-1降序查詢:" + books.getTotalElements() + ",頁元素數目:" + books.getNumberOfElements()); para.put("sortDir", "DESC"); para.put("pageSize", "3"); para.put("pageNumber", "1"); books = bookService.search(para); System.out.println("分頁3-1降序查詢:" + books.getTotalElements() + ",頁元素數目:" + books.getNumberOfElements()); para.put("author", "作者_2"); para.put("pageNumber", "0"); books = bookService.search(para); System.out.println("作者名為[作者_2]查詢結果:" + books.getTotalElements() + ",頁元素數目:" + books.getNumberOfElements()); para.put("id", "9"); books = bookService.search(para); System.out.println("作者為[作者_2] id為9的查詢結果:" + books.getTotalElements() + ",頁元素數目:" + books.getNumberOfElements()); para.put("matchMode", "LIKE"); books = bookService.search(para); System.out.println("作者為[作者_2] id為9的模糊查詢結果:" + books.getTotalElements() + ",頁元素數目:" + books.getNumberOfElements()); }
此處BookService對象采用@Autowired注解自動裝配初始化,然后再測試代碼中針對分頁、查詢模式都分別測試。
7.測試執行結果
2019-05-14 18:44:43.422 INFO 131236 --- [ main] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory Hibernate: select TOP(?) book0_.id as id1_0_, book0_.author as author2_0_, book0_.image as image3_0_, book0_.name as name4_0_ from library_book book0_ where 1=1 order by book0_.id desc Hibernate: select count(book0_.id) as col_0_0_ from library_book book0_ where 1=1 分頁3-1降序查詢:23,頁元素數目:10 Hibernate: WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __hibernate_row_nr__ FROM ( select TOP(?) book0_.id as id1_0_, book0_.author as author2_0_, book0_.image as image3_0_, book0_.name as name4_0_ from library_book book0_ where 1=1 order by book0_.id desc ) inner_query ) SELECT id1_0_, author2_0_, image3_0_, name4_0_ FROM query WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ? Hibernate: select count(book0_.id) as col_0_0_ from library_book book0_ where 1=1 分頁3-1降序查詢:23,頁元素數目:3 Hibernate: select TOP(?) book0_.id as id1_0_, book0_.author as author2_0_, book0_.image as image3_0_, book0_.name as name4_0_ from library_book book0_ where book0_.author=? order by book0_.id desc Hibernate: select count(book0_.id) as col_0_0_ from library_book book0_ where book0_.author=? 作者名為[作者_2]查詢結果:4,頁元素數目:3 Hibernate: select TOP(?) book0_.id as id1_0_, book0_.author as author2_0_, book0_.image as image3_0_, book0_.name as name4_0_ from library_book book0_ where cast(book0_.id as varchar(255))=? and book0_.author=? order by book0_.id desc 作者為[作者_2] id為9的查詢結果:1,頁元素數目:1 Hibernate: select TOP(?) book0_.id as id1_0_, book0_.author as author2_0_, book0_.image as image3_0_, book0_.name as name4_0_ from library_book book0_ where (cast(book0_.id as varchar(255)) like ?) and (book0_.author like ?) order by book0_.id desc 作者為[作者_2] id為9的模糊查詢結果:2,頁元素數目:2結束語
本章節篇幅較長,簡單介紹了下JPA的基本增刪改查功能,并進一步介紹定義了JPA的規范方法名查詢,最后引入JpaSpecificationExecutor通過搜索查詢接口,闡述了復雜場景下的查詢搜索。
下一篇內容將整合當前方法服務,編寫控制層的接口,提供WEB服務接口,請繼續關注。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77689.html
摘要:結束語非常智能化,為開發者提供大量的默認配置細節,因此在的幫助下可以快速完成項目的運行,極簡入門繼續看從零入門系列程序結構設計說明 環境準備 java 開發環境 JDK1.8 安裝 Maven 安裝,jar自動依賴及包管理工具 IDE編輯器:IntelliJ IDEA 2019 說明 本項目為從零入門示例,目標為構建一個書籍增刪改查管理頁,力爭記錄一個無java基礎的程序員學習筆...
摘要:務必在之前引入最新的核心文件為了偷懶,我們這里引入的第三方庫文件都是采用的方式,也可以選擇把庫下載到本地然后再引用。 文章系列 【從零入門系列-0】Spring Boot 之 Hello World 【從零入門系列-1】Spring Boot 之 程序結構設計說明 【從零入門系列-2】Spring Boot 之 數據庫實體類 【從零入門系列-3】Spring Boot 之 數據庫操作...
摘要:務必在之前引入最新的核心文件為了偷懶,我們這里引入的第三方庫文件都是采用的方式,也可以選擇把庫下載到本地然后再引用。 文章系列 【從零入門系列-0】Spring Boot 之 Hello World 【從零入門系列-1】Spring Boot 之 程序結構設計說明 【從零入門系列-2】Spring Boot 之 數據庫實體類 【從零入門系列-3】Spring Boot 之 數據庫操作...
摘要:結束語本章預先提供了項目實際效果圖以及項目的整體結構設計,后續文章會根據本篇章設計依次實現各個模塊,請持續關注。 文章系列 【從零入門系列】Sprint Boot 之 Hello World 設計效果圖 頁面展示showImg(https://raw.githubusercontent.com/arbboter/resource/master/segmentfault/image/...
閱讀 3011·2021-10-27 14:15
閱讀 2999·2021-09-07 10:18
閱讀 1320·2019-08-30 15:53
閱讀 1570·2019-08-26 18:18
閱讀 3373·2019-08-26 12:15
閱讀 3460·2019-08-26 10:43
閱讀 654·2019-08-23 16:43
閱讀 2207·2019-08-23 15:27