JPA 入門實戰(4)--Spring Data JPA 使用

来源:https://www.cnblogs.com/wuyongyin/archive/2022/09/03/16542789.html
-Advertisement-
Play Games

在大多情況下,我們都是用new去實例化對象。但是,有時候有的對象的類別有很多種,又存在著共性,就好比如汽車,有賓士,紅旗,寶馬等品牌,如果是一個一個去創建類,那就需要創建很多,因此就需要用到工廠模式。 ...


本文主要介紹 Spring Boot 中如何使用 Sping Data JPA,相關的環境及軟體信息如下:Spring Boot 2.6.10。

1、Sping Data JPA 簡介

Spring Data JPA 是 Spring Data 家族的一部分,它對基於 JPA 的數據訪問提供了增強支持,讓 JPA 更加易用。 它使得使用 Spring 構建的應用程式訪問資料庫變得更加容易。

編寫應用程式的數據訪問層是一件很麻煩的事, 必須編寫太多樣板代碼來執行簡單的查詢以及分頁和審計。 Spring Data JPA 旨在通過減工作量來顯著改進數據訪問層的實現。 作為開發人員,編寫數據訪問層介面,包括自定義查詢方法,Spring 將自動提供實現。

1.1、Spring Data JPA 的特點

  • Sophisticated support to build repositories based on Spring and JPA

  • Support for Querydsl predicates and thus type-safe JPA queries

  • Transparent auditing of domain class

  • Pagination support, dynamic query execution, ability to integrate custom data access code

  • Validation of @Query annotated queries at bootstrap time

  • Support for XML based entity mapping

  • JavaConfig based repository configuration by introducing @EnableJpaRepositories.

1.2、Spring Data JPA 與 Hibernate 的關係

 Spring Data JPA 是 Spring 提供的一套對 JPA 操作更加高級的封裝,底層依賴 Hibernate 的 JPA 實現。

2、JpaRepository 和 JpaSpecificationExecutor 介面

Spring Data JPA 提供了 JpaRepository 和 JpaSpecificationExecutor 介面,通過它們可以快速定義 DAO 介面,Spring Data JPA 會自動實現該介面。

2.1、JpaRepository

JpaRepository 介面內置了很多方法:

如果不滿足業務需求,可以自定義方法。

2.1.1、使用 Query 註解

org.springframework.data.jpa.repository.Query 註解可以定義使用 JPQL 或 SQL 操作數據。 

/**
 * 通過 JPQL 查詢
 */
@Query("from Student where name=?1 and age=?2")
Student query(String name, Integer age);

/**
 * SQL 語句查詢數據
 */
@Query(value = "select * from a_student where name=? and age=?", nativeQuery = true)
Student queryBySql(String name, Integer age);

2.1.1、按照 Spring Data JPA 規範定義方法名稱

Spring Data JPA 提供一些關鍵詞,通過這些關鍵詞和實體屬性名稱來組裝方法名稱。 主題關鍵詞:
KeywordDescription

find…Byread…Byget…Byquery…Bysearch…Bystream…By

General query method returning typically the repository type, a Collection or Streamable subtype or a result wrapper such as PageGeoResults or any other store-specific result wrapper. Can be used as findBy…findMyDomainTypeBy… or in combination with additional keywords.

exists…By

Exists projection, returning typically a boolean result.

count…By

Count projection returning a numeric result.

delete…Byremove…By

Delete query method returning either no result (void) or the delete count.

…First<number>……Top<number>…

Limit the query results to the first <number> of results. This keyword can occur in any place of the subject between find (and the other keywords) and by.

…Distinct…

Use a distinct query to return only unique results. Consult the store-specific documentation whether that feature is supported. This keyword can occur in any place of the subject between find (and the other keywords) and by.

條件關鍵詞:
Logical keywordKeyword expressions

AND

And

OR

Or

AFTER

AfterIsAfter

BEFORE

BeforeIsBefore

CONTAINING

ContainingIsContainingContains

BETWEEN

BetweenIsBetween

ENDING_WITH

EndingWithIsEndingWithEndsWith

EXISTS

Exists

FALSE

FalseIsFalse

GREATER_THAN

GreaterThanIsGreaterThan

GREATER_THAN_EQUALS

GreaterThanEqualIsGreaterThanEqual

IN

InIsIn

IS

IsEquals, (or no keyword)

IS_EMPTY

IsEmptyEmpty

IS_NOT_EMPTY

IsNotEmptyNotEmpty

IS_NOT_NULL

NotNullIsNotNull

IS_NULL

NullIsNull

LESS_THAN

LessThanIsLessThan

LESS_THAN_EQUAL

LessThanEqualIsLessThanEqual

LIKE

LikeIsLike

NEAR

NearIsNear

NOT

NotIsNot

NOT_IN

NotInIsNotIn

NOT_LIKE

NotLikeIsNotLike

REGEX

RegexMatchesRegexMatches

STARTING_WITH

StartingWithIsStartingWithStartsWith

TRUE

TrueIsTrue

WITHIN

WithinIsWithin

  詳細說明可參考官網文檔:https://docs.spring.io/spring-data/jpa/docs/2.6.6/reference/html/#repository-query-keywords。 命名樣例:
方法名 JPQL
findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
findByStartDateBetween … where x.startDate between ?1 and ?2
findByAgeLessThan … where x.age < ?1
findByAgeLessThanEqual … where x.age ⇐ ?1

2.2、JpaSpecificationExecutor

JpaSpecificationExecutor 介面主要用來在 JpaRepository 介面無法滿足要求時,可以使用 JPA 的標準 API 來操作數據。

3、Sping Data JPA 使用

這裡演示下 Spring Data JPA 的基本使用,工程目錄結構如下:

3.1、引入依賴

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.6.10</version>
  <relativePath />
</parent>
<dependencies>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>

  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
  </dependency>

  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.29</version>
  </dependency>

  <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
  </dependency>

  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.24</version>
      <scope>provided</scope>
  </dependency>
</dependencies>

3.2、創建實體類

package com.abc.demojpa.entity;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import javax.persistence.*;
import java.time.LocalDateTime;

@NoArgsConstructor
@Data
@Entity
@Table(name = "a_student")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @CreationTimestamp
    @Column(name = "create_time")
    private LocalDateTime createTime;

    @UpdateTimestamp
    @Column(name = "modify_time")
    private LocalDateTime modifyTime;

    private String name;

    private Integer age;

    @Column(name = "home_address")
    private String homeAddress;
}

3.3、編寫配置文件(application.yml)

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://10.49.196.10:3306/test?useUnicode=true&characterEncoding=UTF-8
    username: root
    password: 123456
  jpa:
    hibernate:
      ddl-auto: update

3.4、定義 DAO 介面

package com.abc.demojpa.dao;

import com.abc.demojpa.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface IStudentRepository extends JpaRepository<Student, Long>, JpaSpecificationExecutor<Student> {
    /**
     * 根據姓名和年齡查詢
     */
    List<Student> findByNameAndAge(String name, Integer age);

    /**
     * 根據姓名和年齡查詢前5條
     */
    List<Student> findTop5ByNameAndAge(String name, Integer age);

    /**
     * 根據姓名和年齡查詢並按id排序
     */
    List<Student> findByNameAndAgeOrderByIdAsc(String name, Integer age);

    /**
     * 根據姓名和年齡查詢第一條
     */
    Student findFirstByNameAndAge(String name, Integer age);

    /**
     * 通過 JPQL 查詢
     */
    @Query("from Student where name=?1 and age=?2")
    Student query(String name, Integer age);

    /**
     * 通過 JPQL 查詢
     */
    @Query("from Student where name=:name and age=:age")
    Student query2(@Param("name") String name, @Param("age") Integer age);

    /**
     * 通過 JPQL 更新數據
     */
    @Query("update Student set homeAddress=?1 where id=?2")
    @Modifying
    void updateHomeAddress(String homeAddress, Long id);

    /**
     * SQL 語句查詢數據
     */
    @Query(value = "select * from a_student where name=? and age=?", nativeQuery = true)
    Student queryBySql(String name, Integer age);
}

3.5、編寫啟動類

package com.abc.demojpa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class DemoJpaApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoJpaApplication.class, args);
    }
}

3.6、編寫測試用例

3.6.1、增加數據

/**
 * 增加數據
 * Spring Boot 事務測試時預設會回滾操作避免產生測試數據,如果不需要回滾可使用 @Commit 註解
 */
@Test
@Transactional
@Commit
public void add() {
    Student student = new Student();
    student.setName("小明");
    student.setAge(15);
    student.setHomeAddress("江蘇");

    Student student2 = new Student();
    student2.setName("小紅");
    student2.setAge(18);
    student2.setHomeAddress("廣東");

    studentRepository.save(student);
    studentRepository.save(student2);
}

3.6.2、修改數據

@Transactional
@Commit
@Test
public void modify() {
    Student student = new Student();
    student.setId(3L);
    student.setName("小明2");
    student.setAge(15);
    student.setHomeAddress("江蘇");
    //設置了 id 表示更新數據
    studentRepository.save(student);

    //調用自定義的更新方法
    studentRepository.updateHomeAddress("上海", 12L);
}

3.6.3、查詢數據

@Test
public void query() {
    //根據 id 查詢
    Optional<Student> optional = studentRepository.findById(12L);
    logger.info("student={}", optional.get());

    //查詢所有
    List<Student> list = studentRepository.findAll();
    logger.info("list={}", list);

    //分頁及排序查詢
    List<Sort.Order> orders = new ArrayList<>();
    orders.add(new Sort.Order(Sort.Direction.DESC, "id"));
    orders.add(new Sort.Order(Sort.Direction.ASC, "name"));
    Page<Student> page = studentRepository.findAll(PageRequest.of(0, 10, Sort.by(orders)));
    logger.info("page.getTotalElements={},page.getTotalPages()={},page.toList()={}", page.getTotalElements(), page.getTotalPages(), page.toList());

    //通過 JPA 標準 API 查詢,可以用來動態拼接查詢條件
    Specification<Student> specification = new Specification<Student>() {
        @Override
        public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
            List<Predicate> list = new ArrayList<>();
            list.add(criteriaBuilder.equal(root.get("name"), "小紅"));
            list.add(criteriaBuilder.ge(root.get("age"), 10));
            return criteriaBuilder.and(list.toArray(new Predicate[]{}));
        }
    };
    list = studentRepository.findAll(specification);
    logger.info("list={}", list);

    list = studentRepository.findByNameAndAge("小紅", 18);
    logger.info("list={}", list);

    list = studentRepository.findTop5ByNameAndAge("小紅", 18);
    logger.info("list={}", list);

    list = studentRepository.findByNameAndAgeOrderByIdAsc("小紅", 18);
    logger.info("list={}", list);

    Student student = studentRepository.findFirstByNameAndAge("小紅", 18);
    logger.info("student={}", student);

    student = studentRepository.query("小紅", 18);
    logger.info("student={}", student);

    student = studentRepository.query2("小紅", 18);
    logger.info("student={}", student);

    student = studentRepository.queryBySql("小紅", 18);
    logger.info("student={}", student);
}

3.6.4、刪除數據

@Test
public void remove() {
    //根據實體刪除
    Student student = new Student();
    student.setId(3L);
    studentRepository.delete(student);

    List<Student> students = new ArrayList<>();
    Student student2 = new Student();
    student.setId(4L);
    students.add(student);
    students.add(student2);
    studentRepository.deleteAll(students);

    //根據 id 刪除
    studentRepository.deleteById(5L);
    List<Long> ids = new ArrayList<Long>(){{
        add(6L);
        add(7L);
    }};
    studentRepository.deleteAllByIdInBatch(ids);
}

3.6.5、完整代碼

package com.abc.demojpa.service;


import com.abc.demojpa.dao.IStudentRepository;
import com.abc.demojpa.entity.Student;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.annotation.Commit;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestService {
    private static final Logger logger = LoggerFactory.getLogger(TestService.class);

    @Autowired
    private IStudentRepository studentRepository;

    /**
     * 增加數據
     * Spring Boot 事務測試時預設會回滾操作避免產生測試數據,如果不需要回滾可使用 @Commit 註解
     */
    @Test
    @Transactional
    @Commit
    public void add() {
        Student student = new Student();
        student.setName("小明");
        student.setAge(15);
        student.setHomeAddress("江蘇");

        Student student2 = new Student();
        student2.setName("小紅");
        student2.setAge(18);
        student2.setHomeAddress("廣東");

        studentRepository.save(student);
        studentRepository.save(student2);
    }

    /**
     * 修改數據
     */
    @Transactional
    @Commit
    @Test
    public void modify() {
        Student student = new Student();
        student.setId(3L);
        student.setName("小明2");
        student.setAge(15);
        student.setHomeAddress("江蘇");
        //設置了 id 表示更新數據
        studentRepository.save(student);

        //調用自定義的更新方法
        studentRepository.updateHomeAddress("上海", 12L);
    }


    /**
     * 查詢數據
     */
    @Test
    public void query() {
        //根據 id 查詢
        Optional<Student> optional = studentRepository.findById(12L);
        logger.info("student={}", optional.get());

        //查詢所有
        List<Student> list = studentRepository.findAll();
        logger.info("list={}", list);

        //分頁及排序查詢
        List<Sort.Order> orders = new ArrayList<>();
        orders.add(new Sort.Order(Sort.Direction.DESC, "id"));
        orders.add(new Sort.Order(Sort.Direction.ASC, "name"));
        Page<Student> page = studentRepository.findAll(PageRequest.of(0, 10, Sort.by(orders)));
        logger.info("page.getTotalElements={},page.getTotalPages()={},page.toList()={}", page.getTotalElements(), page.getTotalPages(), page.toList());

        //通過 JPA 標準 API 查詢,可以用來動態拼接查詢條件
        Specification<Student> specification = new Specification<Student>() {
            @Override
            public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> list = new ArrayList<>();
                list.add(criteriaBuilder.equal(root.get("name"), "小紅"));
                list.add(criteriaBuilder.ge(root.get("age"), 10));
                return criteriaBuilder.and(list.toArray(new Predicate[]{}));
            }
        };
        list = studentRepository.findAll(specification);
        logger.info("list={}", list);

        list = studentRepository.findByNameAndAge("小紅", 18);
        logger.info("list={}", list);

        list = studentRepository.findTop5ByNameAndAge("小紅", 18);
        logger.info("list={}", list);

        list = studentRepository.findByNameAndAgeOrderByIdAsc("小紅", 18);
        logger.info("list={}", list);

        Student student = studentRepository.findFirstByNameAndAge("小紅", 18);
        logger.info("student={}", student);

        student = studentRepository.query("小紅", 18);
        logger.info("student={}", student);

        student = studentRepository.query2("小紅", 18);
        logger.info("student={}", student);

        student = studentRepository.queryBySql("小紅", 18);
        logger.info("student={}", student);
    }

    /**
     * 刪除數據
     */
    @Test
    public void remove() {
        //根據實體刪除
        Student student = new Student();
        student.setId(3L);
        studentRepository.delete(student);

        List<Student> students = new ArrayList<>();
        Student student2 = new Student();
        student.setId(4L);
        students.add(student);
        students.add(student2);
        studentRepository.deleteAll(students);

        //根據 id 刪除
        studentRepository.deleteById(5L);
        List<Long> ids = new ArrayList<Long>(){{
            add(6L);
            add(7L);
        }};
        studentRepository.deleteAllByIdInBatch(ids);
    }
}
TestService.java

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我的另一篇博文中提到JavaScript 有哪些是假值,哪些是真值。對於 null、undefined、"",等一些假值,JavaScript 直接視為 false。 我有一個需求,判斷從瀏覽器中獲取的 Cookie 是否存在,如果存在我就返回 true,否則返回 false。useCookies( ...
  • 每日 3 題 13 以下代碼執行後,控制臺中的輸出內容為? Object.prototype.a = 1; Function.prototype.b = 2; function F() {} var f = new F(); console.log(F.a); console.log(F.b); c ...
  • Array.apply(null, { length: 1000 }) 點擊打開視頻講解更加詳細 在閱讀VueJS教程時有這麼段demo code: render: function (createElement) { return createElement('div', Array.apply( ...
  • JavaScript 對象 對象 在JavaScript中,對象是一組無序的相關屬性和方法的集合,所有的事物都是對象,例如字元串、數值、數組、函數等。 對象是由屬性和方法組成的。 屬性:事物的特征,在對象中用屬性來表示(常用名詞) 方法:事物的行為,在對象中用方法來表示(常用動詞) 保存一個值時,可 ...
  • 網頁偽靜態 1.什麼是偽靜態網頁? 偽靜態是相對於靜態文件來說的,例如https://www.cnblogs.com/hesujian/p/11165818.html 將一個動態網頁偽裝成靜態網頁 將url地址模擬成html結尾的樣子,看上去像是一個靜態文件,只是改變了URL的表現形式,實際上還是動 ...
  • 使用pyhive的時候出現了這個問題,我使用的是anaconda3。查了很多帖子都不能解決。 參考: https://blog.csdn.net/weixin_43142260/article/details/115198097 https://blog.csdn.net/wenjun_xiao/a ...
  • 線程基礎01 1.程式 進程 線程 程式(program):是為完成的特定任務,用某種語言編寫的一組指令的集合。簡單來說,就是我們寫的代碼。 進程: 進程是指運行中的程式,比如我們使用QQ,就啟動了一個進程,操作系統就會為該進程分配空間。當我們使用迅雷,又啟動了一個進程,操作系統將為迅雷分配新的記憶體 ...
  • 1、請求處理參數 1.1 請求參數 @RequestParam 1.1.1 不使用 @RequestParam 註解 請求參數處理,不使用參數註解: 1.如果請求參數名和請求處理的形參名一致,springMvc 框架會自動將你的請求參數名對應的參數值,綁定到請求方法的形參中,方法內就可以直接使用,不 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...