一.Spring Data JPA簡(jiǎn)介
Spring Data JPA 是 Spring 基于 ORM 框架傍菇、JPA 規(guī)范的基礎(chǔ)上封裝的一套 JPA 應(yīng)用框架,底層使用了 Hibernate 的 JPA 技術(shù)實(shí)現(xiàn)省艳,可使開(kāi)發(fā)者用極簡(jiǎn)的代碼即可實(shí)現(xiàn)對(duì)數(shù)據(jù)的訪(fǎng)問(wèn)和操作岭粤。它提供了包括增刪改查等在內(nèi)的常用功能接口晨抡,且易于擴(kuò)展熏瞄!學(xué)習(xí)并使用 Spring Data JPA 可以極大提高開(kāi)發(fā)效率脚祟!
由于微服務(wù)系統(tǒng)的廣泛應(yīng)用,服務(wù)粒度逐漸細(xì)化强饮,多表關(guān)聯(lián)查詢(xún)的場(chǎng)景一定程度減少。單表查詢(xún)和單表的數(shù)據(jù)操作正是JPA的優(yōu)勢(shì)为黎。我們本節(jié)就為大家介紹如何在Spring Boot中使用JPA邮丰。
二.將Spring Data JPA集成到Spring Boot
2.1 引入maven依賴(lài)包行您,包括Spring Data JPA和Mysql的驅(qū)動(dòng)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2.2 配置數(shù)據(jù)庫(kù)連接和jpa的相關(guān)信息
spring:
datasource:
url: jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
jpa:
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
hibernate:
ddl-auto: validate
database: mysql
show-sql: true
- spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 。Hibernate 創(chuàng)建數(shù)據(jù)庫(kù)表的時(shí)候剪廉,默認(rèn)使用的數(shù)據(jù)庫(kù)存儲(chǔ)引擎是 MyISAM 娃循,這個(gè)參數(shù)作用是在建表的時(shí)候,將存儲(chǔ)引擎切換為 InnoDB 斗蒋。
- spring.jpa.show-sql=true 在日志中打印出執(zhí)行的 SQL 語(yǔ)句信息捌斧。
- spring.jpa.properties.hibernate.hbm2ddl.auto是hibernate的配置屬性,其主要作用是:自動(dòng)根據(jù)實(shí)體類(lèi)的定義創(chuàng)建泉沾、更新捞蚂、驗(yàn)證數(shù)據(jù)庫(kù)表結(jié)構(gòu)。所以這個(gè)參數(shù)是一個(gè)比較危險(xiǎn)的參數(shù)跷究,使用的時(shí)候一定要注意姓迅。該參數(shù)的幾種配置如下:
- create:每次加載hibernate時(shí)都會(huì)刪除上一次的生成的表,然后根據(jù)你的model類(lèi)再重新來(lái)生成新表俊马,哪怕兩次沒(méi)有任何改變也要這樣執(zhí)行丁存,這就是導(dǎo)致數(shù)據(jù)庫(kù)表數(shù)據(jù)丟失的一個(gè)重要原因。
- create-drop:每次加載hibernate時(shí)根據(jù)model類(lèi)生成表柴我,但是sessionFactory一關(guān)閉,表就自動(dòng)刪除解寝。
- update:最常用的屬性,第一次加載hibernate時(shí)根據(jù)model類(lèi)會(huì)自動(dòng)建立起表的結(jié)構(gòu)(前提是先建立好數(shù)據(jù)庫(kù))艘儒,以后加載hibernate時(shí)根據(jù)model類(lèi)自動(dòng)更新表結(jié)構(gòu)聋伦,即使表結(jié)構(gòu)改變了但表中的行仍然存在不會(huì)刪除以前的行。要注意的是當(dāng)部署到服務(wù)器后彤悔,表結(jié)構(gòu)是不會(huì)被馬上建立起來(lái)的嘉抓,是要等應(yīng)用第一次運(yùn)行起來(lái)后才會(huì)。
- validate:每次加載hibernate時(shí)晕窑,驗(yàn)證創(chuàng)建數(shù)據(jù)庫(kù)表結(jié)構(gòu)抑片,只會(huì)和數(shù)據(jù)庫(kù)中的表進(jìn)行比較,不會(huì)創(chuàng)建新表杨赤,但是會(huì)插入新值敞斋。
三.Spring Data JPA核心用法
3.1 開(kāi)發(fā)實(shí)體Model類(lèi)
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
@Table(name="article")
public class Article {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(nullable = false,length = 32)
private String author;
@Column(nullable = false, unique = true,length = 32)
private String title;
@Column(length = 512)
private String content;
private Date createTime;
}
- @Entity 必選注解,表示這個(gè)類(lèi)是一個(gè)實(shí)體類(lèi)疾牲,接受JPA控制管理植捎,對(duì)應(yīng)數(shù)據(jù)庫(kù)中的一個(gè)表
- @Table 可選注解,指定這個(gè)類(lèi)對(duì)應(yīng)數(shù)據(jù)庫(kù)中的表名阳柔。如果這個(gè)類(lèi)名和數(shù)據(jù)庫(kù)表名符合駝峰及下劃線(xiàn)規(guī)則焰枢,可以省略這個(gè)注解。如FlowType類(lèi)名對(duì)應(yīng)表名flow_type。
- @Id 指定這個(gè)字段為表的主鍵
- @GeneratedValue(strategy=GenerationType.IDENTITY) 指定主鍵的生成方式济锄,一般主鍵為自增的話(huà)暑椰,就采用GenerationType.IDENTITY的生成方式
- @Column 注解針對(duì)一個(gè)字段,對(duì)應(yīng)表中的一列荐绝。nullable = false表示數(shù)據(jù)庫(kù)字段不能為空, unique = true表示數(shù)據(jù)庫(kù)字段不能有重復(fù)值,length = 32表示數(shù)據(jù)庫(kù)字段最大程度為32.
3.2 開(kāi)發(fā)數(shù)據(jù)操作接口
我們只需創(chuàng)建類(lèi)似XxxRepository接口繼承JpaRepository<T,ID>即可一汽,JpaRepository為我們提供了各種針對(duì)單表的數(shù)據(jù)操作方法:增刪改查、分頁(yè)低滩、排序等召夹。
public interface ArticleRepository extends JpaRepository<Article,Long> {
}
3.3 關(guān)鍵字查詢(xún)接口
除了JpaRepository為我們提供的增刪改查的方法。我們還可以自定義方法恕沫,使用起來(lái)非常簡(jiǎn)單监憎,甚至可以說(shuō)是強(qiáng)大。把下面的方法名放到ArticleRepository 里面昏兆,它就自動(dòng)為我們實(shí)現(xiàn)了通過(guò)author字段查找article表的所有數(shù)據(jù)枫虏。也就是說(shuō),我們使用了find(查找)關(guān)鍵字爬虱,JPA就自動(dòng)將方法名為我們解析成數(shù)據(jù)庫(kù)SQL操作隶债,太智能了。
public interface ArticleRepository extends JpaRepository<Article,Long> {
//注意這個(gè)方法的名稱(chēng)跑筝,jPA會(huì)根據(jù)方法名自動(dòng)生成SQL執(zhí)行
Article findByAuthor(String author);
}
Article findByAuthor(String author);
等同于SELECT * FROM article WHERE author = ?
死讹,其他具體的關(guān)鍵字,使用方法和生產(chǎn)成 SQL 如下表所示:
關(guān)鍵字 | 接口函數(shù)例子 | JPQL 片段 |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | 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 (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 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) |
可以看到我們這里沒(méi)有任何類(lèi)SQL語(yǔ)句就完成了兩個(gè)條件查詢(xún)方法曲梗。這就是Spring-data-jpa的一大特性:通過(guò)解析方法名創(chuàng)建查詢(xún)赞警。
四.SpringDataJPA實(shí)現(xiàn)分頁(yè)排序
如果一次性加載成千上萬(wàn)的列表數(shù)據(jù),在網(wǎng)頁(yè)上顯示將十分的耗時(shí)虏两,用戶(hù)體驗(yàn)不好愧旦。所以處理較大數(shù)據(jù)查詢(xún)結(jié)果展現(xiàn)的時(shí)候,分頁(yè)查詢(xún)是必不可少的定罢。分頁(yè)查詢(xún)必然伴隨著一定的排序規(guī)則笤虫,否則分頁(yè)數(shù)據(jù)的狀態(tài)很難控制,導(dǎo)致用戶(hù)可能在不同的頁(yè)看到同一條數(shù)據(jù)祖凫。本文的主要內(nèi)容就是給大家介紹一下琼蚯,如何使用Spring Data JPA進(jìn)行分頁(yè)與排序。
4.1 分頁(yè)參數(shù)Page
Pageable
是Spring定義的接口惠况,用于分頁(yè)參數(shù)的傳遞遭庶,我們可以使用如下代碼進(jìn)行分頁(yè)操作:查詢(xún)第一頁(yè)(從0開(kāi)始)的數(shù)據(jù),每頁(yè)10條數(shù)據(jù)稠屠。
Pageable pageable = PageRequest.of(0, 10); //第一頁(yè)
//Pageable pageable = PageRequest.of(1, 10); //第二頁(yè)
//Pageable pageable = PageRequest.of(2, 10); // 第三頁(yè)
//數(shù)據(jù)庫(kù)操作獲取查詢(xún)結(jié)果
Page<Article> articlePage = articleRepository.findAll(pageable);
//將查詢(xún)結(jié)果轉(zhuǎn)換為L(zhǎng)ist
List<Article> articleList = articlePage.getContent();
findAll方法以Page類(lèi)的對(duì)象作為響應(yīng)峦睡,如果我們想獲取查詢(xún)結(jié)果List翎苫,可以使用getContent()方法。但是不建議這樣進(jìn)行轉(zhuǎn)換赐俗,因?yàn)榍岸苏故疽粋€(gè)分頁(yè)列表拉队,不僅需要數(shù)據(jù)弊知,而且還需要一些分頁(yè)信息阻逮。如:當(dāng)前第幾頁(yè),每頁(yè)多少條秩彤,總共多少頁(yè)叔扼,總共多少條。這些信息在Page(articlePage)對(duì)象里面均可以獲取到漫雷。
4.2 排序參數(shù)Sort
Spring Data JPA提供了一個(gè)Sort
對(duì)象瓜富,用以提供一種排序機(jī)制。
articleRepository.findAll(Sort.by("createTime"));
articleRepository.findAll(Sort.by("author").ascending().and(Sort.by("createTime").descending()));
- 第一個(gè)findAll方法是按照createTime的升序進(jìn)行排序
- 第一個(gè)findAll方法是按照author的升序排序降盹,再按照createTime的降序進(jìn)行排序
分頁(yè)和排序在一起:
Pageable pageable = PageRequest.of(0, 10,Sort.by("createTime"));
4.3 分頁(yè)結(jié)果Slice與Page
Slice 和Page 都是Spring Data JPA的數(shù)據(jù)響應(yīng)接口与柑,其中 Page 是 Slice的子接口,它們都用于保存和返回?cái)?shù)據(jù)蓄坏。
Slice的一些重要方法:
public interface Slice<T> extends Streamable<T> {
List <T> getContent(); //獲取切片的內(nèi)容
Pageable getPageable(); //當(dāng)前切片的分頁(yè)信息
boolean hasContent(); //是否有查詢(xún)結(jié)果价捧?
boolean isFirst(); //是否是第一個(gè)切片
boolean isLast(); //是否是最后一個(gè)切片
Pageable nextPageable(); // 下一個(gè)切片的分頁(yè)信息
Pageable previousPageable(); // 上一個(gè)切片的分頁(yè)信息
}
Page的一些重要方法:
public interface Page<T> extends Slice<T> {
//總頁(yè)數(shù)
int getTotalPages();
//總數(shù)據(jù)條數(shù)
long getTotalElements();
}
通過(guò)這兩個(gè)接口的函數(shù)定義可以看出,Slice只關(guān)心是不是存在下一個(gè)分片(分頁(yè))涡戳,不會(huì)去數(shù)據(jù)庫(kù)count計(jì)算總條數(shù)结蟋、總頁(yè)數(shù)。所以比較適合大數(shù)據(jù)量列表的的鼠標(biāo)或手指滑屏操作渔彰,不關(guān)心總共有多少頁(yè)嵌屎,只關(guān)心有沒(méi)有下一頁(yè)。Page比較適合傳統(tǒng)應(yīng)用中的table開(kāi)發(fā)恍涂,需要知道總頁(yè)數(shù)和總條數(shù)宝惰。
五.測(cè)試JPA關(guān)鍵字查詢(xún)和分頁(yè)查詢(xún)
5.1 完善3.3節(jié)ArticleRepository接口,增加分頁(yè)查詢(xún):
public interface ArticleRepository extends JpaRepository<Article,Long> {
//注意這個(gè)方法的名稱(chēng)再沧,jPA會(huì)根據(jù)方法名自動(dòng)生成SQL執(zhí)行
Article findByAuthor(String author);
//根據(jù)author字段查詢(xún)article表數(shù)據(jù)尼夺,傳入Pageable分頁(yè)參數(shù),不需要自己寫(xiě)SQL
Page<Article> findByAuthor(String author, Pageable pageable);
}
由于JpaRepository
接口繼承自PagingAndSortingRepository
接口产园,所以默認(rèn)已實(shí)現(xiàn)分頁(yè)和排序查詢(xún)汞斧。
5.2編寫(xiě)并運(yùn)行測(cè)試用例
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class JPATest {
@Resource
private ArticleRepository articleRepository;
@Test
public void testFindByAuthor() {
Article article = articleRepository.findByAuthor("Jerry");
log.info(article.toString());
}
@Test
public void testPageable() {
// 查詢(xún)第一頁(yè),每頁(yè)10條數(shù)據(jù)什燕,按照createTime字段降序排列
Pageable pageable = PageRequest.of(0, 10,Sort.by("createTime").descending());
Page<Article> page = articleRepository.findByAuthor("William", pageable);
for (Article article : page.getContent()) {
log.info(article.toString());
}
}
}