1. 程式人生 > >Spring Boot簡明教程之資料訪問(二):JPA(超詳細)

Spring Boot簡明教程之資料訪問(二):JPA(超詳細)

Spring Boot簡明教程之資料訪問(二):JPA(超詳細)

文章目錄

建立專案

建立的過程和我們的第一篇文章:SpringBoot簡明教程之快速建立第一個SpringBoot應用大致相同,差別只是我們在挑選所需要的元件時,除了Web元件外,我們需要新增如下三個元件:JPA、MySQL、JDBC

在這裡插入圖片描述

或者,我們按照第一次建立完成後,手動在pom.xml檔案中加入以下配置:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jdbc</artifactId>
</
dependency
>
<dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>

我們發現,與我們直接使用JDBC template不同的是,這次我們引入了一個新的包:spring-boot-starter-data-jpa,我們進一步檢視,就會發現,其主要是引入了一個spring-data-jpa的包。

在這裡插入圖片描述

Spring Data簡介

Spring Data是為了簡化構建基於 Spring 框架應用的資料訪問技術,包括關係、非關係資料庫、
雲資料服務等等。SpringData為我們提供使用統一的API來對資料訪問層進行操作,讓我們在使用關係型或者非關係型資料訪問技術時都基於Spring提供的統一標準,標準包含了CRUD(建立、獲取、更新、刪除)、查詢、
排序和分頁的相關操作,同時為我們提供了統一的資料訪問類的模板,例如:MongoTemplate、RedisTemplate等。

JPA簡介

JPA(Java Persistence API)定義了一系列物件持久化的標準,它的出現主要是為了簡化現有的持久化開發工作和整合 ORM 技術,目前實現了這一規範的有 Hibernate、TopLink、JDO 等 ORM 框架。

Spring Data 與JPA

我們可以將Spring-data-jpa理解為Spring Boot對於JPA的再次封裝,使得我們通過Spring-data-jpa即實現常用的資料庫操作:

  • JpaRepository實現基本功能: 編寫介面繼承JpaRepository既有crud及分頁等基本功能
  • 定義符合規範的方法命名: 在介面中只需要宣告符合規範的方法,即擁有對應的功能
  • 支援自定義查詢: @Query自定義查詢,定製查詢SQL
  • Specifications查詢(Spring Data JPA支援JPA2.0的Criteria查詢)

使用Spring Data JPA的基本流程

建立實體類(entity)

package cn.newtol.springboot07.entity;
import javax.persistence.*;

/**
 * @Author: 公眾號:Newtol
 * @Description:  JPA使用示例:使用JPA註解配置對映關係
 * @Date: Created in 18:45 2018/9/24
 */

@Entity   //表示一個實體類,和資料表進行對映
@Table(name = "t_user")   //所對映的表的名字,可省略,預設為實體類名
public class User {


    @Id //設定為主鍵
    @GeneratedValue(strategy = GenerationType.IDENTITY) //定義為自增主鍵
    private Integer id;

    @Column(name = "t_name",nullable = false)  //設定該欄位在資料表中的列名,並設定該欄位設定為不可為空
    private String name;

    @Column     //預設則欄位名就為屬性名
    private Integer phone;

    @Transient   //增加該註解,則在資料表中不會進行對映
    private String address;
    
    // //省略getter settet方法、構造方法,建立時自行補上
}

建立Dao層(Repository)

package cn.newtol.springboot07.repository;

import cn.newtol.springboot07.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * @Author: 公眾號:Newtol
 * @Description:
 * @Date: Created in 19:35 2018/9/24
 */
//定義為介面
public interface UserRepository extends JpaRepository<User,Integer>{
	//直接繼承即可,不用編寫任何方法
}

編寫配置檔案

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot?useSSL=false
    username: root
    password:

  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true

建立Controller

package cn.newtol.springboot07.controller;

import cn.newtol.springboot07.entity.User;
import cn.newtol.springboot07.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;

/**
 * @Author: 公眾號:Newtol
 * @Description:
 * @Date: Created in 19:37 2018/9/24
 */


@RestController
public class UserController {

    @Autowired
    UserRepository userRepository;

    //根據Id查詢使用者
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable("id") Integer id){
        User user = userRepository.getOne(id);
        return user;
    }

    //插入使用者
    @GetMapping("/user")
    public User addUser(User user){
        userRepository.save(user);
        return user;   //返回插入的物件及其自增ID
    }
}

直接啟動專案,看到Spring Boot自動幫我們在資料庫中自動建立了t_user表:

在這裡插入圖片描述

並且因為我們之前對address屬相使用了@Transient註解,不進行對映,所以我們看到表中沒有address欄位。現在我們將@Transient註釋後,再次啟動專案:

在這裡插入圖片描述

address就也被成功的建立。

接著,我們嘗試著往資料庫中插入一條資料,我們在瀏覽器中輸入:localhost:8080/user?name=zhangsan&phone=123&address=beijing

在這裡插入圖片描述

再到資料庫中進行檢視:

在這裡插入圖片描述
插入成功。

現在我們來查詢剛剛插入的資料,瀏覽器輸入:http://localhost:8080/user/1

在這裡插入圖片描述

成功查詢到剛才插入的資料,並且我們可以在控制檯看到Spring Boot為我們列印的剛才執行的查詢語句:

在這裡插入圖片描述

JPA常用註解說明

雖然我們在上面的示例中已經使用了常用了的註解,但是為了方便和理解,我們在這個地方歸納一下關於JPA的一些常用的註解及其對應的引數。

  • @Entity:表示該類是一個的實體類。@Entity標註是必需的 ,name屬性為可選;需要注意的是:@Entity標註的實體類至少需要有一個無參的構造方法。

  • @Table:表示該實體類所對映的資料表資訊,需要標註在類名前,不能標註在方法或屬性前。引數如下:

    引數 說明
    name 實體所對應表的名稱,預設表名為實體名
    catalog 實體指定的目錄名
    schema 表示實體指定的資料庫名
    uniqueConstraints 該實體所關聯的唯一約束條件,一個實體可以有多個唯一的約束,預設沒有約束條件。建立方式:uniqueConstraints = {@uniqueConstraint(columnNames = {“name”,“phone”})}
    indexes 該實體所關聯的索引。建立方式:indexes = { @Index(name = “index_name”, columnList = “t_name”)}
  • @Column:表示該屬性所對映的欄位,此標記可以標註在Getter方法或屬性前。它有如下引數:

    引數 說明
    unique 欄位是否為唯一標識,預設為false
    nullable 欄位是否可以為null值,預設為true
    insertable 使用“INSERT” SQL語指令碼插入資料時,是否需要插入該欄位的值。
    updatable 使用“UPDATE”指令碼插入資料時,是否需要更新該欄位的值
    table 當對映多個表時,指定表中的欄位。預設值為主表的表名。
    length 當欄位的型別為varchar時的欄位的長度,預設為255個字元。
    precision 用於表示精度,數值的總長度
    scale 用於表示精度,小數點後的位數。
    columnDefinition 用於建立表時新增該欄位所需要另外執行的SQL語句
  • @Id:表示該屬性為主鍵,每一個實體類至少要有一個主鍵(Primary key)。

    引數 說明
    strategy 表示生成主鍵的策略 ,有4種類型:GenerationType.TABLE 、 GenerationType.SEQUENCE 、 GenerationType.IDENTITY 、 GenerationType.AUTO 預設為:AUTO,表示自動生成。
    generator 生成規則名,不同的策略有不同的配置
  • @Basic: 實體屬性設定載入方式為惰性載入

    引數 說明
    fetch 表示獲取值的方式,它的值定義的列舉型,可選值為LAZY(惰性載入)、EAGER(即時載入),預設為即時載入。
    optional 屬性是否可以為null,不能用於java基本資料型( byte、int、short、long、boolean、char、float、double )

自定義查詢

我們剛剛在上面使用的是JPA已經封裝好的一些預設的查詢方式,但是我們在實際的專案中,可能需要自定義一些符合實際需求的查詢,那麼我們就需要用到自定義查詢

關鍵字查詢

JPA已經幫我們做好了關鍵字,我們只需要將這些關鍵字組成方法名,就可以實現相對應的查詢。

關鍵字 示例 同功能JPQL
And findByLastnameAndFirstname where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals where x.firstname = 1?
Between findByStartDateBetween where x.startDate between 1? and ?2
LessThan findByAgeLessThan where x.age < ?1
LessThanEqual findByAgeLessThanEqual where x.age <= ?1
GreaterThan findByAgeGreaterThan where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual where x.age >= ?1
After findByStartDateAfter where x.startDate > ?1
Before findByStartDateBefore where x.startDate < ?1
IsNull findByAgeIsNull where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull where x.age not null
Like findByFirstnameLike where x.firstname like ?1
NotLike findByFirstnameNotLike where x.firstname not like ?1
StartingWith findByFirstnameStartingWith where x.firstname like ?1 (引數前面加 %)
EndingWith findByFirstnameEndingWith where x.firstname like ?1 (引數後面加 %)
Containing findByFirstnameContaining where x.firstname like ?1 (引數兩邊加 %)
OrderBy findByAgeOrderByLastnameDesc where x.age = ?1 order by x.lastname desc
Not findByLastnameNot where x.lastname <> ?1
In findByAgeIn(Collection<Age> ages) where x.age in ?1
NotIn findByAgeNotIn(Collection<Age> age) where x.age not in ?1
True findByActiveTrue() where x.active = true
False findByActiveFalse() where x.active = false
IgnoreCase findByFirstnameIgnoreCase where UPPER(x.firstame) = UPPER(?1)

我們除了使用find關鍵字以外,還可以使用countdeleteget等。

例如,我們在Controller中加入如下方法得:

/**
 * @Author: 公眾號:Newtol
 * @Description: 自定義關鍵字查詢
 * @Date: Created in 19:37 2018/9/24
 */
@GetMapping("/test/{address}/{phone}")
public List<User> getUserByAddressAndPhone (@PathVariable("address") String address, @PathVariable("phone") Integer phone){
	return userRepository.findByAddressEqualsAndPhoneNot(address,phone);
}
    

在userRepository中雞加入如下方法:


/**
 * @Author: 公眾號:Newtol
 * @Description:
 * @Date: Created in 19:35 2018/9/24
 */
public interface UserRepository extends JpaRepository<User,Integer>{
    public List<User> findByAddressEqualsAndPhoneNot (String address, Integer phone);
}

在資料庫中插入下面兩條資料:

INSERT INTO `t_user` VALUES ('2', 'beijing', 'zhangsi', '456');
INSERT INTO `t_user` VALUES ('3', 'beijing', 'wangwu', '123');

在瀏覽器輸入:http://localhost:8080/test/beijing/456

我們就可以看到返回瞭如下資料,查詢成功。

在這裡插入圖片描述

自定義SQL語句

通常如果使用JPA提供給我們的關鍵字組成的查詢方法仍然無法滿足需求,那麼我們就可以使用@Query來實現自定的SQL語句。

原生SQL

JPA中支援使用原生的SQL語句,例如:

在userRepository中加入下面的方法:

//自定義SQL查詢
    @Query(value = "select * from t_user WHERE phone = ? " ,nativeQuery = true)
    List<User> getAllUser (Integer phone);

在controller中加入以下方法:

 //自定義SQL查詢
    @GetMapping("/test/{phone}")
    public List<User> getAllUser(@PathVariable("phone") Integer phone){
        return userRepository.getAllUser(phone);
    }

在瀏覽器中輸入:http://localhost:8080/test/123

返回資料,查詢成功。在這裡插入圖片描述

但是,如果我們需要執行UPDATEDELETE操作時,除了使用@Query外,還需要使用@Modifying
@Transactional兩個註解。

例如:

在userRepository中加入下面的方法:

 //自定義SQL刪除
    @Query(value = "delete from t_user WHERE phone = ? ",nativeQuery = true)
    @Modifying
    @Transactional
    void deleteUser(Integer phone);

在controller中加入以下方法:

//自定義SQL刪除
    @GetMapping("/del/{phone}")
    public void deleteUser(@PathVariable("phone") Integer phone){
       userRepository.deleteUser(phone);
    }

在瀏覽器中輸入:http://localhost:8080/del/456,我們就會發現資料庫中phone為456的資料就已經被刪除了。

JPQL查詢

在userRepository中加入下面的方法:

//JPQL查詢
    @Query("select u from User u where u.name = ?1")
    User getUserByName(String name);

在controller中加入以下方法:

//JPQL查詢
    @GetMapping("/get/{name}")
    public User getUserByName(@PathVariable("name") String name){
        return userRepository.getUserByName(name);
    }

瀏覽器輸入:http://localhost:8080/get/zhangsan,資料返回,查詢成功:

在這裡插入圖片描述

複雜查詢

分頁查詢

我們經常會遇到進行分頁查詢的情況,而JPA就很好的替我們解決了這一個問題。只需要幾行程式碼就可以解決:

例如:在controller中加入以下方法:

//分頁查詢
@GetMapping("/userList/{page}/{size}")
public Page<User> getUserList(@PathVariable("page") Integer page, @PathVariable("size") Integer size) {
	Sort sort = new Sort(Sort.Direction.DESC,"id");
    PageRequest pageRequest = PageRequest.of(page,size,sort);
    return  userRepository.findAll(pageRequest);
 }

瀏覽器輸入:http://localhost:8080/userList/0/3

返回的資料格式為:

{
    "content": [
        {
            "id": 3, 
            "name": "wangwu", 
            "phone": 123, 
            "address": "beijing"
        }, 
        {
            "id": 2, 
            "name": "lisi", 
            "phone": 789, 
            "address": "shanghai"
        }, 
        {
            "id": 1, 
            "name": "zhangsan", 
            "phone": 123, 
            "address": "beijing"
        }
    ], 
    "pageable": {
        "sort": {
            "sorted": true, 
            "unsorted": false
        }, 
        "offset": 0, 
        "pageSize": 3, 
        "pageNumber": 0, 
        "paged": true, 
        "unpaged": false
    }, 
    "totalPages": 1, 
    "last": true, 
    "totalElements": 3, 
    "number": 0, 
    "size": 3, 
    "sort": {
        "sorted": true, 
        "unsorted": false
    }, 
    "numberOfElements": 3, 
    "first": true
}

限制查詢

除了限制查詢以外,對於排行榜類的資料,或許我們只需要取出前面幾名即可,Jpa也對這種情況做了很好的支援:

例如:在userRepository中加入下面的方法:

//限制查詢 
    List<User> findFirst2ByAddressOrderByIdDesc (String address);

在controller中加入以下方法:

@GetMapping("/top/{address}")
public List<User> getTop2User(@PathVariable("address") String address){
	return userRepository.findFirst2ByAddressOrderByIdDesc(address);
 }

在瀏覽器輸入:http://localhost:8080/top/beijing

返回的資料為:

[{"id":3,"name":"wangwu","phone":123,"address":"beijing"},{"id":1,"name":"zhangsan","phone":123,"address":"beijing"}]

常見問題及其解決方案:

  1. 如果提示:No identifier specified for entity: xxx

    解決方案:這是因為在建立實體類時,匯入的jar包錯誤導致的。我們將import org.springframework.data.annotation.*;更改為:import javax.persistence.*;即可

  2. 如果提示: No default constructor for entity: :

    解決方案:這是因為在實體類中建立了含引數的構造方法造成的,這是因為在使用類反射機制 Class.newInstance()方法建立例項時,需要有一個預設的無引數構造方法,否則會執出例項化異常(InstantiationException),所以將構造方法更改為無引數即可。

  3. 如果提示:Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query

    解決方案:這是因為在JPA中執行DELETE/UPDATE操作時,需要使用@Modifying
    @Transactional兩個註解,補上即可。

  4. 如果提示:Can not issue data manipulation statements with executeQuery()。

    解決方案:如果提示這個錯誤,就需要查詢檢查自己的SQL語句是否書寫正確,如果正確,檢查是否加上@Modifying@Transactional兩個註解

  5. 如果提示:Ambiguous handler methods mapped for HTTP path

    解決方案:這是由於URL對映重複引起的,將你所請求的URL重新進行定義即可。

  6. 如果在使用PageRequest方法顯示過期

    解決方案:將構造方法更改為:PageRequest.of即可。在2.0版本以後,就使用of(…) 方法代替 PageRequest(…)構造器。

總結

我們首先介紹了spring Data,然後演示了一遍使用JPA的基本流程,最後詳細的介紹了我們主要使用的幾個註解、注意事項等;以及除了使用預設的查詢方式外,如何去使用自定義查詢和複雜查詢。

原始碼地址

原始碼地址

聯絡作者

有關轉載、錯誤指正、問題諮詢等事宜請掃碼關注個人公眾號進行聯絡,更有大量視訊學習資源分享