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的基本流程,最後詳細的介紹了我們主要使用的幾個註解、注意事項等;以及除了使用默認的查詢方式外,如何去使用自定義查詢和複雜查詢。

源碼地址

源碼地址

最重要的事

有關轉載、錯誤指正、問題諮詢等事宜請掃碼關注個人公衆號進行聯繫,更有大量視頻學習資源分享
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章