【從零入門系列-3】Spring Boot 之 數據庫操做

文章系列


前言

前一章簡述瞭如何設計實現數據庫實體類,本篇文章在此基礎上進行開發,完成對該數據庫表的經常使用操做,主要包括使用Spring Data JPA進行簡單的增刪改查和複雜查詢操做。java

Spring Data JPASpring提供的一套簡化JPA開發的框架,按照約定好的【方法命名規則】寫dao層接口,就能夠在不寫接口實現的狀況下,實現對數據庫的訪問和操做,同時提供了不少除了CRUD以外的功能,如分頁、排序、複雜查詢等等,Spring Data JPA 能夠理解爲 JPA 規範的再次封裝抽象,底層仍是使用了 Hibernate 的 JPA 技術實現。經過引入Spring Data JPA後,咱們能夠基本不用寫代碼就能實現對數據庫的增刪改查操做。程序員

此外,因爲Spring Data JPA自帶實現了不少內置的後臺操做方法,所以在調用方法時必須根據其規範使用,深入理解規範約定算法


表的基本操做實現(CRUD)

在這裏,先介紹一下JpaRepository,這是類型爲interface的一組接口規範,是基於JPA的Repository接口,可以極大地減小訪問數據庫的代碼編寫,是實現Spring Data JPA技術訪問數據庫的關鍵接口。spring

  • 編寫數據操做接口

在使用時,咱們只須要定義一個繼承該接口類型的接口便可實現對錶的基本操做方法,在此咱們須要對實體類Book進行操做,所以在Dao目錄上右鍵New->Java Class,而後設置名稱爲BookJpaRepository,kind類型選Interface便可,而後添加註解及繼承自JpaRepository,文件BookJpaRepository.java內容以下所示:sql

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<Book, Integer> {
}

@Repository持久層組件,用於標註數據訪問組件,即DAO組件,此時配合上上一篇文章中的JPA配置,咱們就能夠進行增刪改查啦,不用添加任何其餘代碼,由於JpaRepository已經幫咱們實現好了。數據庫

  • 編寫測試用例代碼

打開框架自動生成的測試代碼文件DemoLibraryApplicationTests.java編寫測試用例,測試增刪改查效果,測試代碼以下:segmentfault

@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 註解,它能夠對類成員變量、方法及構造函數進行標註,完成自動裝配的工做。數組

  • 測試結果

1557817269112
經過測試結果咱們能夠看到,程序已經可以對錶數據進行增刪改查,且咱們經過刪除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生成器。ide

經過上述例子,咱們能夠發現,雖然咱們沒有寫任何一條SQL語句,可是程序已經能夠正常操做數據庫了,這對苦逼的C++程序員手寫SQL來講真是不要說太幸福哈。不過上述示例也存在一些問題,數據查詢均是經過ID操做的,可是實際使用中,數據查詢還須要根據其餘條件,好比書名做者,是否是須要手寫SQL實現?答案是否認的,JpaRepository支持接口規範方法名查詢,意思是若是在接口中定義的查詢方法符合它的命名規則,就能夠不用寫實現,框架自動提供實現的方法,只須要聲明無需本身實現便可使用。


JpaRepository的規範方法名查詢

在咱們實現的接口中,能夠只定義查詢方法,若是是符合規範的,能夠不用寫實現,就能夠直接使用。

JpaRepository會對方法名進行校驗,不符合規範會報錯,除非添加@Query註解。

在本示例中,咱們但願經過書名和做者的經常使用場景提供查詢方案,可按下述實現:

@Repository
public interface BookJpaRepository extends JpaRepository<Book, Integer> {
    /**
     * 根據書名精準查詢書籍列表
     * @param name 查詢的書名
     * @return 名字爲name的書籍列表
     */
    List<Book> findByName(String name);

    /**
     *
     * 根據書名模糊查詢書籍列表
     * @param name 查詢的書名
     * @return 查詢結果
     */
    List<Book> findByNameLike(String name);

    /**
     * 根據書名和做者查詢,注意參數列表順序和名字順序保持一致(約定!)
     * @param name 查詢的書名
     * @param author 查詢的做者名
     * @return 查詢結果
     */
    List<Book> findByNameAndAuthor(String name, String author);

    /**
     * 根據書名或做者查詢,注意參數列表順序和名字順序保持一致(約定!)
     * @param name 查詢的書名
     * @param author 查詢的做者名
     * @return 查詢結果
     */
    List<Book> findByNameOrAuthor(String name, String author);

    /**
     * 根據做者集合查詢
     * @param authors 書列表名
     * @return
     */
    List<Book> 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);
}

List<Book> bookList;
// 根據書名精準查詢
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<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);來實現自定義的接口查詢條件。

1.繼承JpaSpecificationExecutor

@Repository
public interface BookJpaRepository extends JpaRepository<Book, Integer>, 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
        */
       Page<Book> search(Map<String, String> para){
           return null;
       }
   }

上述代碼中@Service註解該類爲服務類。

3.重寫toPredicate方法

// 構造查詢條件
Specification<Book> specification = new Specification<Book>() {
    @Override
    public Predicate toPredicate(Root<Book> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        List<Predicate> predicate = new ArrayList<>();

        // 根據支持參數列表獲取查詢參數
        String matchMode = para.getOrDefault("matchMode", "AND");
        List<String> 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 Page<Book> search(Map<String, String> para){
        // 構造查詢條件
        Specification<Book> specification = new Specification<Book>() {
            @Override
            public Predicate toPredicate(Root<Book> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> predicate = new ArrayList<>();

                // 根據支持參數列表獲取查詢參數
                String matchMode = para.getOrDefault("matchMode", "AND");
                List<String> 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(){
    Map<String, String> para = new HashMap<>();
    Page<Book> 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服務接口,請繼續關注。

相關文章
相關標籤/搜索