基於Springboot講解SpringData JPA使用

SpringData框架

1、什麼是SpringData

SpringData是Spring的一個子項目,它存在的目的是用於簡化持久層(數據庫訪問層)開發,既支持關係型數據庫也支持NoSQL數據庫的操作。其主要的目標是數據庫的訪問變得更加便捷。

SpringData支持的非關係型數據庫:

  • MongoDB(文檔數據庫)

  • Neo4j(圖形數據庫)

  • Redis(鍵/值存儲)

  • HBase

  • ES(搜索庫)

SpringData支持的關係型數據庫:MySQL、DB2、Oracle等都支持:

  • JDBC:sun公司推出原生的Java操作數據庫的規範(JDBC接口編程)

  • JPA:JPA是sun公司給出持久層(ORM層)一種編程規範(不同的ORM框架對JPA都進行具體的實現:Hibernate)。

SpringData的官網: https://spring.io/projects/spring-data

SpringData JPA:致力於減少數據訪問層(DAO、Repository)的開發量,作爲開發者主要需要做的唯一的事情,根據SpringData JPA定義的接口來書寫相關的DAO層的子接口即可,其他的CRUD操作等全部都交給SpringData JPA完成。

2、SpringData JPA快速入門

2.1、搭建SpringData JPA的開發環境

說明環境:使用SpringBoot構建基礎環境,使用Maven作爲基礎的管理。然後搭建一套完成的web環境(SpringMVC、Spring、SpringData、SpringBoot)、使用開發工具是Idea。

2.1.1、使用maven創建項目

2.1.2、導入pom依賴和編寫配置

pom文件

 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 ​
     <groupId>com.qubo.springdata</groupId>
     <artifactId>springdata-demo</artifactId>
     <version>1.0-SNAPSHOT</version>
 ​
     <!-- 引入SpringBoot父工程-->
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
         <version>2.1.6.RELEASE</version>
     </parent>
     <!-- 配置一些jdk、編碼等信息 -->
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <java.version>1.8</java.version>
     </properties>
     <!--引入需要的依賴-->
     <dependencies>
         <!-- 引入springmvc,spring等依賴 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
         <!-- 導入springdata jpa -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-jpa</artifactId>
         </dependency>
         <!-- mysql驅動 -->
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
             <version>8.0.18</version>
         </dependency>
         <!-- lombok -->
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <version>1.18.8</version>
         </dependency>
     </dependencies>
 </project>

yml配置文件:

 # 服務器的端口號
 server:
   port: 80
 ​
 # springdatajap 和數據源的配置
 spring:
   jpa:  # springdata jpa相關的配置
     generate-ddl: false
     show-sql: true
     hibernate:
       ddl-auto: none
   datasource:
     url: jdbc:mysql://127.0.0.1:3306/springdata
     username: root
     password: root
     driver-class-name: com.mysql.jdbc.Driver
 ​
 # 日誌
 logging:
   level:
     root: info
     org.hibernate.*: debug
 ​

編寫核心啓動類:

 package com.qubo;
 ​
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 ​
 @SpringBootApplication
 public class SpringDataJPAStart {
     public static void main(String[] args) {
         SpringApplication.run(SpringDataJPAStart.class,args);
     }
 }

2.2、SpringData JPA的開發步驟

使用SpringDataJPA進行持久層開發的時候一般有4個步驟:

  1. 配置Spring整合JPA

  2. 在Spring配置文件中需要配置SpringData

  3. 定義持久層的接口,需要繼承SpringData中提供的Repository接口(一般繼承它的子接口JPARepository)

  4. 如果繼承的接口中的方法不夠用,可以在自己定義的接口中按照一定的規則去書寫方法操作數據庫

友情提示:

由於使用SpringBoot構建的項目環境,因此第一步和第二步基本省略了,其實改成註解方式。

2.3、Lombok插件

開發中需要大量書寫pojo實體類,雖然IDE能夠直接生成get、set、constructor、equals、hashCode、toString等等方法,但是還是比較麻煩,代碼比較多,看起來pojo類很臃腫。

使用lombok插件,能夠快速的生成get、set、constructor、equals、hashCode、toString等等方法。並且實體類中代碼很簡潔。

說明:這個插件是幫助我們去在編譯的時候,給生成的class文件中添加上述的方法。而不是說真正沒有這些方法。源碼中沒有,但是class文件(字節碼)中存在。

注意:需要導入lombok的依賴包的同時,還要給IDE(eclipse、idea)安裝插件。

  <!-- lombok -->
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <version>1.18.8</version>
         </dependency>

2.4、快速完成查詢操作

創建表:

 CREATE TABLE tb_user(
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50),
    PASSWORD VARCHAR(50),
    age INT ,
    salary DOUBLE
 )

改在覈心啓動類:

 @SpringBootApplication
 // 配置持久層接口所在的包
 @EnableJpaRepositories(basePackages = "com.qubo.repository")
 // 配置pojo所在的包
 @EntityScan("com.qubo.pojo")
 public class SpringDataJPAStart {
     public static void main(String[] args) {
         SpringApplication.run(SpringDataJPAStart.class,args);
     }
 }

編寫Controller類:

 package com.qubo.controller;
 ​
 import com.qubo.pojo.User;
 import com.qubo.service.UserService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 ​
 import java.util.List;
 ​
 @RestController
 public class UserController {
 ​
     @Autowired
     private UserService userService;
     @RequestMapping("users")
     public List<User> findUsers(){
         return this.userService.findUsers();
     }
 }
 ​

編寫servcie類:

 package com.qubo.service;
 ​
 import com.qubo.pojo.User;
 import com.qubo.repository.UserRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 ​
 import java.util.List;
 ​
 @Service
 public class UserService {
     @Autowired
     private UserRepository userRepository;
     public List<User> findUsers() {
         return this.userRepository.findAll();
     }
 }
 ​

編寫repository接口:

 package com.qubo.repository;
 ​
 import com.qubo.pojo.User;
 import org.springframework.data.jpa.repository.JpaRepository;
 ​
 /**
  *  在使用SpringDataJPA自定義的接口需要繼承Repository接口或者其子接口
  *      Repository : 它只是一個接口中沒有提供方法
  *      JpaRepository : 它提供CRUD的方法,以及分頁,排序等方法
  *      接口中泛型:
  *          第一個:實體類類型
  *          第二個:主鍵的類型
  */
 public interface UserRepository extends JpaRepository<User,Integer> {
 }
 ​

測試結果:

 @Data
 @NoArgsConstructor  // 無參構造
 @AllArgsConstructor // 有參數構造
 @ToString
 @EqualsAndHashCode
 @Getter
 @Setter
 public class User {
     private String username;
     private String password;
     private Integer age;
     private Double salary;
 }
 ​

3、關於SpringData JPA接口介紹

3.1、Repository接口

 @Indexed
 public interface Repository<T, ID> {
 ​
 }

Repository接口是SpringData 的一個核心接口,但是它中不提供任何的方法,類似於Java中標記型接口概念。如果我們編程的時候直接繼承這個接口,那麼就需要自己在自定義的接口中添加CRUD等方法。

SpringData 提供了一些定義方法的規範,這樣可以省略書寫SQL過程:

Keyword Sample JPQL snippet
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, Null findByAge(Is)Null … 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 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection ages) … 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)
 package com.qubo.repository;
 ​
 import com.qubo.pojo.User;
 import org.springframework.data.repository.Repository;
 ​
 import java.util.List;
 ​
 public interface UserRepository2 extends Repository<User, Integer> {
     // select * from tb_user where username = ? and password = ?
     public List<User> findByUsernameAndPassword(String username , String password);
 }
 ​

3.2、CrudRepository接口

CrudRepository:它是Repository接口的子接口,其中主要提供簡單的增刪改查操作的方法。

 public interface CrudRepository<T, ID> extends Repository<T, ID>

 

CrudRepository細節:

在進行根據主鍵操作的時候,我們需要給pojo指定主鍵和主鍵的策略

3.3、PagingAndSortingRepository接口

PagingAndSortingRepository:它主要在CrudRepository基礎上提供分頁和排序功能。

@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {


	Iterable<T> findAll(Sort sort);   //  排序


	Page<T> findAll(Pageable pageable);  // 分頁
}

準備分頁對象:

@Data
public class PageResult<T> {
    // 設置總頁數
    private Integer total;
    private Integer pages;
    private List<T> datas;
}

service中的邏輯代碼:

     public PageResult<User> getPage(Integer page, Integer size) {
         //  設置分頁參數
         Pageable pageable = PageRequest.of(page , size);
         // 執行查詢
         Page<User> users = this.userRepository.findAll(pageable);
         // 需要將數據構建到自定義的分頁結果對象中
         PageResult<User> pageResult = new PageResult<>();
         // 封裝分頁頁面所需的數據
         pageResult.setDatas(users.getContent());
         // 封裝總頁數
         pageResult.setPages(users.getTotalPages());
         // 封裝總記錄數
         pageResult.setTotal(users.getTotalElements());
         return pageResult;
     }

controller的代碼:

 @RequestMapping("page")
 public PageResult<User> getPage(
     @RequestParam(name="page" ,required = true , defaultValue = "0")Integer page,
     @RequestParam(name="size" ,required = true , defaultValue = "2")Integer size   ){
         return this.userService.getPage(page , size);
     }

3.4、JpaRepository接口

JpaRepository : 它是我們書寫代碼的時候真正需要繼承的持久層的接口。JpaRepository接口將SpringData JPA提供的所有的常用的功能都繼承到了

 

3.5、JpaSpecificationExecutor接口

JpaSpecificationExecutor : 它主要通過編碼的方式完成條件查詢。

public interface JpaSpecificationExecutor<T> {
	Optional<T> findOne(@Nullable Specification<T> spec);

	List<T> findAll(@Nullable Specification<T> spec);

	Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);

	List<T> findAll(@Nullable Specification<T> spec, Sort sort);

	long count(@Nullable Specification<T> spec);
}

Specification:使用Specification來封裝查詢的條件。由於Specification又是一個接口,因此經常配合內部類或者使用Lambda表達式完成條件的封裝

Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
    public List<User> findNames(String name) {
 ​
         //  構建查詢的條件 , 使用匿名內部類
         Specification spec = new Specification(){
             /*
                 在使用Specification接口構建查詢條件的時候,需要複寫toPredicate方法
                 但是toPredicate方法上有三個參數:
                     Root root:與數據庫表對應的實體類對象,就是pojo
                     CriteriaQuery query:獲取Root中的屬性名,添加額外的一些查詢條件
                     CriteriaBuilder criteriaBuilder:用來構建出查詢條件對象
              */
             public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) {
                 // SELECT * FROM tb_user WHERE username LIKE '%唐%'
                 Path username = root.get("username"); // 獲取到屬性名,對應將來就是列名
                 return criteriaBuilder.like(username , "%"+name+"%");
             }
         };
         // List<User> users = this.userRepository.findAll(spec);
         // 上的寫法完成可以使用Lambda表達式代替
         List<User> users = this.userRepository.findAll( (root , query  ,criteriaBuilder )-> criteriaBuilder.like(root.get("username"),"%"+name+"%") );
         return users;
     }

3.5、總結

使用SpringData JPA的時候,我們需要在持久層書寫接口,然後去繼承JPARepository接口。這時我們的接口就已經具備了對數據庫表的基本的操作了。

比如:增刪改查,分頁,排序等等。

但是不具備編寫一些複雜的條件查詢,於是一般建議自定義的接口同時還要去繼承JpaSpecificationExecutor 。

 public interface UserRepository extends JpaRepository<User,Integer> , JpaSpecificationExecutor<User> 

3.6、@Query註解

有時使用SpringData JPA的時候,已經提供的哪些方法無法滿足我們的需求,只能自己手寫sql來完成查詢操作。這時需要就需要在自定義的方法上使用@Query註解來書寫sql語句。

3.6.1、數字佔位符

     //  自定義的方法,不要和接口中已經存在的方法重名,也不要和已經提供的方法命名規範重合
     //@Query("select * from tb_user where id = ?")
     @Query("select u from User u where id = ?1")
     public User findUserById(Integer id);
 ​

說明:

  1. 在接口中自定義的方法上使用@Query註解來實現自定義sql語句,在sql語句中參數使用?數字,數字從1往後排。要求數字和方法上的參數一一對應

  2. SQL中表名的位置不能書寫表名,需要書寫的是表對應的POJO的名字。並且需要給起別名

  3. select後面千萬不要使用 * 號,使用別名代替

3.6.2、使用參數佔位符

上面使用的數字佔位符,參數多了非常容易搞錯。可以使用參數佔位符代替。但是需要在方法上使用@Param註解聲明參數佔位符名稱。

    @Query("select u from User u where u.username = :username and u.password = :password")
    public User findUserByNameAndPwd(String username , String password);

注意:

參數佔位符需要還是用:號,後面跟着參數的名稱。

有時我們希望真正書寫的是SQL語句,而不是上面這種寫法。其實嚴格意義上來講不算合法的SQL。

    @Query(value="select * from tb_user where id = ?1 and age = ?2" , nativeQuery = true)
    public User findUserByIdAndAge(Integer id , Integer age);

3.7、@Modifying註解

使用SpringData JPA的時候對數據庫進行查詢之外,還有增刪改的操作。一般對數據庫的增刪改的操作,都會配合事務一起操作。

如果需要自定義SQL,還是增刪改的操作,需要兩個註解一起使用。

    @Modifying   
    @Query("update User set username=:name where id =:id")
    public Integer updateUserName(String name , Integer id);

說明:@Modifying 註解來說明是對數據庫進行非查詢操作

    public Integer updateUserName(String username , Integer id){
        return this.userRepository.updateUserName(username , id);
    }

執行程序發生下面的異常:

上面的異常是因爲SpringData JPA規定,在對數據庫進行增刪改的時候,必須放在事務中。

    @Transactional
    public Integer updateUserName(String username , Integer id){
        return this.userRepository.updateUserName(username , id);
    }

4、多表的操作

多表的操作:一對一、一對多、多對多

4.1、一對一查詢操作

假設一對一的場景:用戶和住址,一般一個用戶只能擁有一個地址(戶籍歸屬地)。

CREATE TABLE tb_addr(
   id INT PRIMARY KEY AUTO_INCREMENT,
   address VARCHAR(100),
   uid INT 
)

SpringData JPA的一對一查詢:提供@oneToOne註解,標註一對一的關係。同時還需要使用@JoinColumn註解聲明關聯關係。

 package com.qubo.pojo;
 ​
 import lombok.Data;
 ​
 import javax.persistence.*;
 ​
 @Data
 @Entity
 @Table(name="tb_addr")
 public class Address {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Integer id;
     private String address;
     //private Integer uid;
     //  在一對一中,需要在某個pojo中添加另外一個實體的引用
     @OneToOne
     @JoinColumn(name="uid" , referencedColumnName = "id")
     private User user;
 }

說明:

  • @OneToOne : 來指定當前pojo中的屬性(實體引用)和另外類之間是一個一對一關係

  • @JoinColumn(name="uid" , referencedColumnName = "id") : 一對一中的主外鍵關係。name屬性書寫的是外鍵列名,referencedColumnName屬性的是外鍵引用的主鍵的列名。

還需要編寫關於tb_addr 表查詢的Repository接口:

 public interface AddressRepository extends JpaRepository<Address , Integer> {
 ​
 }

關於service類:

 @Service
 public class AddressService {
 ​
     @Autowired
     private AddressRepository addressRepository;
 ​
     public Address findAddrById(Integer id){
         Address address = this.addressRepository.findById(id).get();
         return address;
     }
 }

4.2、一對多的查詢操作

假設一對多業務場景:一個用戶可以編著多本書(作者和讀書)。通過User對應的多個Book。

 CREATE TABLE tb_book(
   id INT PRIMARY KEY AUTO_INCREMENT,
   NAME VARCHAR(50),
   price DOUBLE,
   pages INT,
   uid INT
 )

最終的查詢目標:通過用戶的id查詢用戶的數據,同時還要找到對應的著作信息

一對多:需要在一的一方pojo中添加集合,關聯多的一方的數據。

 // 多的一方的實體類
 @Data
 @Entity
 @Table(name = "tb_book")
 public class Book {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Integer id;
     private String name;
     private Double price;
     private Integer uid;
 }
 @Data
 @Entity
 @Table(name="tb_user")
 public class User {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Integer id;
     private String username;
     private String password;
     private Integer age;
     private Double salary;
     // 添加對應的多的一方的引用
     @OneToMany
     @JoinColumn(name="uid",referencedColumnName = "id")
     private List<Book> books;
 }

編寫Repository接口

 public interface BookRepository extends JpaRepository<Book , Integer> {
     // 需要提供一個方法,可以通過外鍵查詢book數據
     public Book findByUid(Integer uid);
 }

4.3、多對多查詢

假設業務場景:訂單和商品關係,一個訂單中可以擁有多個商品,某類商品可以被添加到不同的訂單中。

 CREATE TABLE tb_order(
   id INT PRIMARY KEY AUTO_INCREMENT,
   uid INT,
   order_number VARCHAR(100)
 );
 ​
 CREATE TABLE tb_item(
   id INT PRIMARY KEY AUTO_INCREMENT,
   item_name VARCHAR(100),
   item_price DOUBLE,
   item_detail VARCHAR(200)
 )
 ​
 CREATE TABLE tb_order_detail(
   id INT PRIMARY KEY AUTO_INCREMENT,
   order_id INT,
   item_id INT,
   total_price DOUBLE
 )
 @Entity
 @Data
 @Table(name="tb_order")
 public class Order {
     private Integer id;
     private Integer uid;
     private String orderNumber;
 ​
     @ManyToMany // 聲明是多對多關係
     @JoinTable(
             name="tb_order_detail" ,  // 多對多的中間表的名稱
             joinColumns = { @JoinColumn( name="order_id" ,referencedColumnName = "id")},
             inverseJoinColumns = { @JoinColumn( name="item_id" , referencedColumnName = "id") } )
     private List<Item> items;
 }
 ​
 @Entity
 @Table(name = "tb_item")
 public class Item {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     @Getter@Setter
     private Integer id;
     @Getter@Setter
     private String itemName;
     @Getter@Setter
     private Double itemPrice;
     @Getter@Setter
     private String itemDetail;
     @ManyToMany(targetEntity = Order.class , mappedBy = "items")
     private List<Order> orders;
 }

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章