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個步驟:
-
配置Spring整合JPA
-
在Spring配置文件中需要配置SpringData
-
定義持久層的接口,需要繼承SpringData中提供的Repository接口(一般繼承它的子接口JPARepository)
-
如果繼承的接口中的方法不夠用,可以在自己定義的接口中按照一定的規則去書寫方法操作數據庫
友情提示:
由於使用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);
說明:
-
在接口中自定義的方法上使用@Query註解來實現自定義sql語句,在sql語句中參數使用?數字,數字從1往後排。要求數字和方法上的參數一一對應
-
SQL中表名的位置不能書寫表名,需要書寫的是表對應的POJO的名字。並且需要給起別名
-
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;
}