簡潔持久層開發(fā)—Spring Data JPA

1前言
Spring項目開發(fā)中,持久層的框架常用的有MyBatis、Mybatis Plus、Hibernate和Spring Data JPA等,國內(nèi)常用的是前兩種,本人最先接觸的也是MyBatis。下面我列舉了常用的三類框架的一些對比信息。今天我們要聊一聊一種解放雙手的持久成框架Spring Data JPA。
Mybatis:https://mybatis.org/mybatis-3/zh/java-api.html
Mybatis Plus:https://github.com/baomidou/mybatis Plus
Spring Data JPA:https://spring.io/projects/spring-data-jpa
Mybatis
Mybatis 是一個優(yōu)秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。Mybatis 避免了幾乎所有的 JDBC 代碼和手動設置參數(shù)以及獲取結(jié)果集。Mybatis 可以使用簡單的 XML 或注解來配置和映射原生類型、接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 對象)為數(shù)據(jù)庫中的記錄。
Mybatis Plus
MybatisPlus 是一款 Mybatis 的增強工具,在 Mybatis 的基礎(chǔ)上只做增強,不做改變,為簡化開發(fā)、提高效率而生。Mybatis Plus 為 Mybatis 提供了一些額外的功能,如:通用 Mapper、通用 Service、分頁插件、性能分析插件等。這些功能簡化了 CRUD 操作,減少了重復代碼的編寫,提高了開發(fā)效率。
Spring Data JPA
Spring Data JPA 是一個基于 Spring Data 和 JPA(Java Persistence API,Java 持久化 API)技術(shù)的持久層框架,它使得開發(fā)者可以更加輕松地實現(xiàn)對數(shù)據(jù)的訪問和操作。Spring Data JPA 提供了一套簡化 JPA 規(guī)范的抽象接口,通過擴展這些接口,可以實現(xiàn)對數(shù)據(jù)的 CRUD(增刪查改)操作。此外,Spring Data JPA 還支持自定義查詢、分頁查詢、排序等功能。
共同點
持久層框架:Mybatis、Mybatis Plus 和 Spring Data JPA 都是為了簡化和優(yōu)化數(shù)據(jù)庫訪問操作的持久層框架。
減少代碼量:三者都旨在減少開發(fā)者編寫 JDBC 代碼的工作量,簡化數(shù)據(jù)庫訪問操作。
易于整合:三個框架都可以輕松地與 Spring 框架進行整合,實現(xiàn)依賴注入和事務管理等功能。
支持 POJO:它們都支持將數(shù)據(jù)庫的表映射為 Java 對象,使得開發(fā)者可以更直觀地處理數(shù)據(jù)。
抽象接口:Mybatis、Mybatis Plus 和 Spring Data JPA 都提供了抽象接口,以簡化對數(shù)據(jù)的 CRUD 操作。
不同點
定位和發(fā)展歷史:
MyBatis是一個持久層框架,旨在提供對關(guān)系型數(shù)據(jù)庫的直接訪問,通過XML或注解配置SQL語句和映射。
MyBatis Plus是MyBatis的增強版本,提供了更多的便利功能和擴展,如代碼生成器、通用Mapper、分頁插件等。
Spring Data JPA是基于JPA(Java Persistence API)規(guī)范的持久層框架,與關(guān)系型數(shù)據(jù)庫和ORM(對象關(guān)系映射)框架無關(guān),它提供了一種更高級的抽象和簡化的方式來操作數(shù)據(jù)庫。
編程模型:
MyBatis和MyBatis Plus使用XML或注解配置SQL語句和映射關(guān)系,提供了靈活的SQL編寫方式,對SQL的精細控制力度較高。
Spring Data JPA使用繼承和命名約定來自動生成實體類與數(shù)據(jù)庫表之間的映射,并支持通過方法命名規(guī)則自動生成常用的增刪改查操作。
對象關(guān)系映射(ORM)支持:
MyBatis和MyBatis Plus提供了基本的ORM功能,但需要手動編寫SQL語句和映射關(guān)系。
Spring Data JPA是一個全功能的ORM框架,通過實體類和注解自動完成SQL查詢和結(jié)果映射,減少了開發(fā)人員編寫和維護SQL的工作量。
領(lǐng)域模型支持:
MyBatis和MyBatis Plus更加偏向于面向SQL和數(shù)據(jù)操作的編程模型,不涉及復雜的領(lǐng)域模型定義。
Spring Data JPA更加偏向于領(lǐng)域模型的設計,支持實體之間的關(guān)系映射、繼承關(guān)系等高級語義。
社區(qū)和生態(tài)系統(tǒng):
MyBatis和MyBatis Plus擁有龐大的用戶社區(qū)和豐富的生態(tài)系統(tǒng),提供了許多插件和擴展,具有豐富的第三方支持。
Spring Data JPA是Spring框架的一部分,與Spring集成緊密,與其他Spring組件無縫協(xié)作,享受Spring強大的功能和社區(qū)支持。
優(yōu)缺點
MyBatis: 優(yōu)點:
靈活性:MyBatis允許開發(fā)者編寫原生SQL語句,具有更高的靈活性,并可以更好地優(yōu)化SQL語句性能。
擴展性:MyBatis提供了許多插件和擴展機制,可以根據(jù)項目的需要進行定制和擴展。
易于學習和使用:MyBatis的學習曲線相對較低,配置簡單,上手快。
輕量級:相比于其他ORM框架,MyBatis的依賴較少,對系統(tǒng)資源的消耗較小。
缺點:
編寫大量的SQL語句:相比于ORM框架,MyBatis需要開發(fā)者手動編寫和維護大量的SQL語句,對于復雜查詢和關(guān)聯(lián)查詢需要更多的工作量。
開發(fā)效率較低:MyBatis需要編寫大量的XML配置文件,增加了開發(fā)的工作量。
MyBatis Plus: 優(yōu)點:
提供更多的便利功能:MyBatis Plus在MyBatis的基礎(chǔ)上進行增強,提供了更多便利的功能,如代碼生成器、通用Mapper、分頁插件等。
減少重復性的開發(fā):MyBatis Plus提供了常用的增刪改查操作的封裝,減少了重復性的開發(fā)工作。
缺點:
學習成本相對較高:相比于MyBatis,MyBatis Plus提供了更多的功能和特性,學習成本可能會略高。
過度封裝的問題:MyBatis Plus的部分功能可能過度封裝,不夠靈活,無法滿足一些復雜的需求。
Spring Data JPA: 優(yōu)點:
提高開發(fā)效率:Spring Data JPA提供了一種更高級的抽象和簡化的方式來操作數(shù)據(jù)庫,可以通過方法命名約定自動生成常用的增刪改查操作,減少了開發(fā)人員編寫和維護SQL的工作量。
領(lǐng)域模型的支持:Spring Data JPA支持實體之間的關(guān)系映射、繼承關(guān)系等高級語義,更適合于復雜業(yè)務需求和領(lǐng)域模型的開發(fā)。
缺點:
性能可能較低:相對于原生的SQL編寫和優(yōu)化,Spring Data JPA可能存在一些性能瓶頸,尤其是對于復雜查詢和關(guān)聯(lián)查詢。
不夠靈活:Spring Data JPA提供的抽象層可能不夠靈活,對于一些特殊需求,需要使用原生的SQL語句進行操作。
本人在項目應用中最先接觸的持久層框架是Mybatis,其支持xml文件并提供多種動態(tài)標簽來寫SQL語句,實現(xiàn)了靈活的數(shù)據(jù)處理方式。MyBatis采用#{}預編譯來取代${}的傳參方式,防止SQL注入危險(面試題)。Mybatis支持一級緩存、二級緩存。后來接觸Mybatis Plus與Spring Data JPA,兩者在使用上有些許類似。綜上述說,如果你的項目業(yè)務不是很復雜,持久層框架推薦采用Spring Data JPA,它會讓你的開發(fā)更加簡單,代碼更加簡潔。
2Spring Boot集成
用啥研究啥(σ???)σ..:*☆哎喲不錯哦
2.1依賴
Spring Data JPA的依賴可以通過使用Spring Data Release Train BOM(Bill of Materials)或聲明對Spring Data模塊的依賴這兩種方式來引入。
使用Spring Data Release Train BOM:
這種方式是通過引入Spring Data Release Train BOM來管理Spring Data相關(guān)依賴的版本。BOM是一個包含了各個模塊的版本信息的POM文件。
通過在項目的pom.xml文件中添加對Spring Data Release Train BOM的依賴聲明,可以自動管理Spring Data JPA以及其他Spring Data模塊的版本一致性。
通過BOM來統(tǒng)一管理版本,可以簡化依賴管理的工作,確保不會發(fā)生不兼容或沖突的版本問題。
聲明對Spring Data模塊的依賴:
這種方式是直接在項目的pom.xml文件中聲明對具體Spring Data模塊的依賴,比如Spring Data JPA。
在這種方式下,需要明確指定所使用的Spring Data模塊的版本號。
這種方式更加靈活,可以精確控制所使用的每個Spring Data模塊的版本以及依賴關(guān)系。
這兩種方式都能夠引入Spring Data JPA的依賴,主要區(qū)別在于版本管理的方式。如果你選擇使用Spring Data Release Train BOM,可以更方便地管理和升級Spring Data相關(guān)模塊的版本,減少版本沖突和不兼容性的問題。而通過聲明對具體Spring Data模塊的依賴,則可以更加精確地控制所使用的每個模塊的版本。
我們采用方式2來依賴Spring Data JPA。我們在通過IDEA創(chuàng)建Spring Boot項目時候,在Dependencies->SQL中勾選上Spring Data JPA,發(fā)現(xiàn)引入的依賴為
<dependency>
? ?<groupId>org.springframework.boot</groupId>
? ?<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
而官網(wǎng)提供的依賴為
<dependency>
? ?<groupId>org.springframework.data</groupId>
? ?<artifactId>spring-data-jpa</artifactId>
</dependency>
spring-boot-starter-data-jpa
和spring-data-jpa
兩者都是用于支持在Spring Boot項目中使用JPA(Java Persistence API)技術(shù)的依賴。它們的主要區(qū)別在于spring-boot-starter-data-jpa
是一個Spring Boot的“啟動器”,而spring-data-jpa
是一個基本的依賴。下面是兩者的區(qū)別:
spring-boot-starter-data-jpa:
這是一個Spring Boot的啟動器,它包含了一系列用于簡化Spring Boot項目中使用JPA技術(shù)的依賴。啟動器的作用是為了簡化開發(fā)者的配置工作,它會自動地引入所需的庫和配置。
當你在項目中添加
spring-boot-starter-data-jpa
依賴時,它會自動包含以下依賴:spring-data-jpa
:用于支持Spring Data JPA的基本功能。spring-orm
:用于支持Spring的對象關(guān)系映射(Object-Relational Mapping, ORM)。hibernate
:作為默認的JPA實現(xiàn)。spring-tx
:用于支持事務管理。其他可能需要的依賴,如數(shù)據(jù)庫連接池等。
spring-data-jpa:
這是一個基本的依賴,提供了Spring Data JPA的核心功能。它包含了對JPA技術(shù)的支持,如實體管理、查詢、事務處理等。如果你只想使用Spring Data JPA的基本功能,而不需要其他的自動配置,可以直接添加這個依賴。
使用spring-boot-starter-data-jpa
,因為它可以幫助你簡化配置和管理依賴,這也要求我們在啟動項目時要添加數(shù)據(jù)庫配置信息,我在此采用spring-boot-starter-data-jpa依賴。
2.2配置信息
如果沒有添加數(shù)據(jù)源配置信息,啟動會報一下錯誤,那么Spring Data JPA數(shù)據(jù)源的配置信息在哪里看呢[?_??]
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2023-06-30 17:13:39.327 ERROR 29980 --- [ ? ? ? ? ? main] o.s.b.d.LoggingFailureAnalysisReporter ? :
***************************
APPLICATION FAILED TO START
***************************
Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
Action:
Consider the following:
If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
Disconnected from the target VM, address: '127.0.0.1:59605', transport: 'socket'
如果你也有上述問題那么說明你對Spring Boot項目掌握還是不夠全面。
讓我們一起讀一下Spring Boot官網(wǎng)文檔吧(https://docs.spring.io/spring-boot/docs/current/reference/html/documentation.html#documentation.data)。
該官網(wǎng)頁面的目錄是不是和IDEA創(chuàng)建Spring Boot項目時,選擇Dependencies時的目錄很相似。Spring Data JPA相關(guān)的信息當然看“6.Data“目錄先查看。點擊“SQL: ”,再該頁面可以查看具體的配置內(nèi)容了,后面就請自行查閱吧。再次再補充一點,在application.properties文件中添加配置后,會發(fā)現(xiàn)所有的配置都來自于“package org.springframework.boot.autoconfigure”,該包的依賴為
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
該依賴是Spring Boot項目中常用的自動配置模塊。它提供了一組默認的配置類,用于根據(jù)項目中的依賴和配置文件的設置,自動配置Spring Boot應用程序的行為。通過這個依賴,您可以快速搭建起一個基本的Spring Boot應用,并根據(jù)需要進行個性化的配置。
2.2.1數(shù)據(jù)源配置
顧名思義不解釋。
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/db_example
spring.datasource.username=springuser
spring.datasource.password=ThePassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
記得,使用哪個數(shù)據(jù)庫,添加對應數(shù)據(jù)庫驅(qū)動依賴。
2.2.2創(chuàng)建和刪除JPA數(shù)據(jù)庫
直接看package org.springframework.boot.autoconfigure.orm.jpa;下的
@ConfigurationProperties(prefix = "spring.jpa")
public class JpaProperties {...}
該類的成員屬性都是配置項。
1. `spring.jpa.database`
? - 指定使用的數(shù)據(jù)庫類型。
2. `spring.jpa.database-platform`
? - 指定使用的數(shù)據(jù)庫方言(如何與特定數(shù)據(jù)庫交互)。
3. `spring.jpa.defer-datasource-initialization`
? - 延遲初始化數(shù)據(jù)源,即在啟動時不立即連接數(shù)據(jù)庫。
4. `spring.jpa.generate-ddl`
? - 是否根據(jù)實體類生成數(shù)據(jù)庫的DDL語句。
5. `spring.jpa.hibernate.ddl-auto`
? - 控制Hibernate在啟動時如何處理數(shù)據(jù)庫的DDL語句。在此配置中設置為create-drop,表示在應用程序啟動時創(chuàng)建表,并在應用程序關(guān)閉時刪除表。
6. `spring.jpa.hibernate.naming.implicit-strategy`
? - 設置Hibernate的隱式命名策略。在此配置中設置為org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl。
7. `spring.jpa.hibernate.naming.physical-strategy`
? - 設置Hibernate的物理命名策略。在此配置中設置為org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl。
8. `spring.jpa.hibernate.use-new-id-generator-mappings`
? - 是否啟用新的ID生成器映射。
9. `spring.jpa.mapping-resources`
? - 額外的JPA映射資源文件。
10. `spring.jpa.open-in-view`
? ?- 啟用OpenEntityManagerInView,即在視圖渲染過程中延遲加載關(guān)聯(lián)實體。
11. `spring.jpa.properties.asdf`
? ?- 自定義的JPA屬性。
12. `spring.jpa.show-sql`
? ?- 是否在控制臺上顯示SQL語句。
13. `spring.data.jpa.repositories.bootstrap-mode`
? ?- JPA倉庫的啟動模式。
14. `spring.data.jpa.repositories.enabled`
? ?- 是否啟用JPA倉庫。
至此,可以正常啟動Spring Boot項目了。
2.3CRUD說明
Spring Data存儲庫抽象中的中心接口是repository。此接口主要充當標記接口,用于捕獲要使用的類型。CrudRepository和ListCrudRepository接口為被管理的實體類提供了復雜的CRUD功能。
IDEA雙擊shift,搜索一下該接口,復制出源碼來,刪除多余的注釋:
package org.springframework.data.repository;
import java.util.Optional;
/**
* Interface for generic CRUD operations on a repository for a specific type.
*
* @author Oliver Gierke
* @author Eberhard Wolff
* @author Jens Schauder
*/
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
/**
?* Saves a given entity. Use the returned instance for further operations as the save operation might have changed the
?* entity instance completely.
?*
?* @param entity must not be {@literal null}.
?* @return the saved entity; will never be {@literal null}.
?* @throws IllegalArgumentException in case the given {@literal entity} is {@literal null}.
?*/
<S extends T> S save(S entity);
/**
?* Saves all given entities.
?*
?* @param entities must not be {@literal null} nor must it contain {@literal null}.
?* @return the saved entities; will never be {@literal null}. The returned {@literal Iterable} will have the same size
?* ? ? ? ? as the {@literal Iterable} passed as an argument.
?* @throws IllegalArgumentException in case the given {@link Iterable entities} or one of its entities is
?* ? ? ? ? ? {@literal null}.
?*/
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
/**
?* Retrieves an entity by its id.
?*
?* @param id must not be {@literal null}.
?* @return the entity with the given id or {@literal Optional#empty()} if none found.
?* @throws IllegalArgumentException if {@literal id} is {@literal null}.
?*/
Optional<T> findById(ID id);
/**
?* Returns whether an entity with the given id exists.
?*
?* @param id must not be {@literal null}.
?* @return {@literal true} if an entity with the given id exists, {@literal false} otherwise.
?* @throws IllegalArgumentException if {@literal id} is {@literal null}.
?*/
boolean existsById(ID id);
/**
?* Returns all instances of the type.
?*
?* @return all entities
?*/
Iterable<T> findAll();
/**
?* Returns all instances of the type {@code T} with the given IDs.
?* <p>
?* If some or all ids are not found, no entities are returned for these IDs.
?* <p>
?* Note that the order of elements in the result is not guaranteed.
?*
?* @param ids must not be {@literal null} nor contain any {@literal null} values.
?* @return guaranteed to be not {@literal null}. The size can be equal or less than the number of given
?* ? ? ? ? {@literal ids}.
?* @throws IllegalArgumentException in case the given {@link Iterable ids} or one of its items is {@literal null}.
?*/
Iterable<T> findAllById(Iterable<ID> ids);
/**
?* Returns the number of entities available.
?*
?* @return the number of entities.
?*/
long count();
/**
?* Deletes the entity with the given id.
?*
?* @param id must not be {@literal null}.
?* @throws IllegalArgumentException in case the given {@literal id} is {@literal null}
?*/
void deleteById(ID id);
/**
?* Deletes a given entity.
?*
?* @param entity must not be {@literal null}.
?* @throws IllegalArgumentException in case the given entity is {@literal null}.
?*/
void delete(T entity);
/**
?* Deletes all instances of the type {@code T} with the given IDs.
?*
?* @param ids must not be {@literal null}. Must not contain {@literal null} elements.
?* @throws IllegalArgumentException in case the given {@literal ids} or one of its elements is {@literal null}.
?* @since 2.5
?*/
void deleteAllById(Iterable<? extends ID> ids);
/**
?* Deletes the given entities.
?*
?* @param entities must not be {@literal null}. Must not contain {@literal null} elements.
?* @throws IllegalArgumentException in case the given {@literal entities} or one of its entities is {@literal null}.
?*/
void deleteAll(Iterable<? extends T> entities);
/**
?* Deletes all entities managed by the repository.
?*/
void deleteAll();
}
在這個接口中聲明的方法通常被稱為CRUD方法。ListCrudRepository提供了等價的方法,但是它們返回List,而CrudRepository方法返回Iterable。
除了CrudRepository之外,還有一個PagingAndSortingRepository抽象,它添加了額外的方法來簡化對實體的分頁訪問:
public interface PagingAndSortingRepository<T, ID> ?{
?Iterable<T> findAll(Sort sort);
?Page<T> findAll(Pageable pageable);
}
標準CRUD功能存儲庫通常具有對底層數(shù)據(jù)存儲的查詢。使用Spring Data,聲明使用這些查詢變成了一個四步的過程:
聲明一個接口繼承Repository或它的一個子接口,并輸入域類和它應該處理的ID類型,如下面的例子所示:
interface PersonRepository extends Repository<Person, Long> { … }在接口上聲明查詢方法。
interface PersonRepository extends Repository<Person, Long> {
?List<Person> findByLastname(String lastname);
}設置Spring以使用JavaConfig或XML配置為這些接口創(chuàng)建代理實例。
import org.springframework.data.….repository.config.EnableJpaRepositories;
@EnableJpaRepositories
class Config { … }注入存儲庫實例并使用它,如下例所示:
class SomeClient {
?private final PersonRepository repository;
?SomeClient(PersonRepository repository) {
? ?this.repository = repository;
?}
?void doSomething() {
? ?List<Person> persons = repository.findByLastname("Matthews");
?}
}
Y(^o^)Y好了,按照這四部就可以實現(xiàn)CRUD操作了,但是這里有個疑問:域類(domain class )與ID是什么意思?如果你曾經(jīng)使用過Hibernate,你大概就知道了domain概念的含義。
2.3JPA Domain
Hibernate:https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#domain-model
domain模型來自數(shù)據(jù)建模領(lǐng)域。它是最終描述您正在處理的問題域的模型。有時您還會聽到persistent classes這個術(shù)語。通過Hibernate提供的注解,實現(xiàn)domain class與數(shù)據(jù)庫表的映射,下面舉例了一個簡單表與domain model(class)的映射實現(xiàn)。
DDL:
create table Contact (
? ?id integer not null,
? ?first varchar(255),
? ?last varchar(255),
? ?middle varchar(255),
? ?notes varchar(255),
? ?starred boolean not null,
? ?website varchar(255),
? ?primary key (id)
)domain model:
@Entity(name = "Contact")
public static class Contact {
@Id
private Integer id;
private Name name;
private String notes;
private URL website;
private boolean starred;
//Getters and setters are omitted for brevity
}
@Embeddable
public class Name {
private String firstName;
private String middleName;
private String lastName;
// getters and setters omitted
}
Y(^o^)Y好了,簡單說明一下概念,想知道更多直接看官網(wǎng)吧。
2.4CRUD實操
創(chuàng)建Student domain class
package com.example.springdatajpademo.model.po;
/**
* @Author yrz
* @create 2023/7/1 9:37
* @Description TODO
*/
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Table(name = "students")
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class Student {
? ?@Id
? ?@GeneratedValue(strategy = GenerationType.IDENTITY)
? ?private Long id;
? ?@Column(name = "name")
? ?private String name;
? ?@Column(name = "age")
? ?private Integer age;
? ?@Column(name = "gender")
? ?private Integer gender;
? ?@Column(name = "class_id")
? ?private String classId;
? ?@Column(name = "discipline_id")
? ?private String disciplineId;
}編寫完domain class后啟動項目,自動創(chuàng)建數(shù)據(jù)庫表。
聲明接口以及查詢方法
package com.example.springdatajpademo.dao;
import com.example.springdatajpademo.model.po.Student;
import org.springframework.data.repository.CrudRepository;
/**
* @Author yrz
* @create 2023/7/3 9:50
* @Description
* 這里我繼承了CrudRepository,它為您提供了CRUD功能的方法。除此之外還可以繼承ListCrudRepository、ReactiveCrudRepository(響應式存儲)、
* RxJava3CrudRepository(響應式存儲)、CoroutineCrudRepository(Kotlin)、PagingAndSortingRepository、CoroutineSortingRepository,
* 如果不想擴展Spring Data接口,還可以用@RepositoryDefinition注釋存儲庫接口。
*/
public interface SpringDataJpaTestDao extends CrudRepository<Student, Long> {
? ?/**
? ? * 自定義方法
? ? * 根據(jù)名稱查詢學生
? ? * @param name
? ? * @return
? ? */
? ?Student findByName(String name);
? ?/**
? ? * 根據(jù)名稱和性別查詢
? ? * @param name
? ? * @param gender
? ? * @return
? ? */
? ?Student findByNameAndGender(String name, Integer gender);
? ?/**
? ? * 根據(jù)名稱和性別查詢
? ? * 無法解析該方法名稱,程序報錯
? ? * @return
? ? */
// ? ?Student findByMingChengAndXingBie(String mingCheng, Integer xingBie);
// ? ?Student findByMingChengAndXingBie(String name, Integer gender);
}package com.example.springdatajpademo;
import com.alibaba.fastjson2.JSON;
import com.example.springdatajpademo.dao.SpringDataJpaTestDao;
import com.example.springdatajpademo.model.po.Student;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Iterator;
import java.util.Optional;
@SpringBootTest
@Slf4j
class SpringDataJpaDemoApplicationTests {
? ?@Autowired
? ?private SpringDataJpaTestDao springDataJpaTestDao;
? ?@Test
? ?void contextLoads() {
? ?}
? ?@Test
? ?public void testFind(){
? ? ? ?// 查全部
? ? ? ?Iterable<Student> all = springDataJpaTestDao.findAll();
? ? ? ?Iterator<Student> iterator = all.iterator();
? ? ? ?while (iterator.hasNext()){
? ? ? ? ? ?Student next = iterator.next();
? ? ? ? ? ?log.info("findAll:{}", JSON.toJSONString(next));
? ? ? ?}
? ? ? ?// 根據(jù)ID查詢
? ? ? ?Optional<Student> byId = springDataJpaTestDao.findById(1L);
? ? ? ?Student student = byId.get();
? ? ? ?log.info("findById:{}", JSON.toJSONString(student));
? ? ? ?// 根據(jù)名稱查詢
? ? ? ?Student byName = springDataJpaTestDao.findByName("李四");
? ? ? ?log.info("findByName:{}",JSON.toJSONString(byName));
? ? ? ?// 新增
// ? ? ? ?Student student1 = new Student(5L,"張三",19,0,"1234","3");
// ? ? ? ?Student save = springDataJpaTestDao.save(student1);
// ? ? ? ?log.info("save:{}",JSON.toJSONString(save));
? ? ? ?// 根據(jù)名稱和性別查詢
? ? ? ?Student student2 = springDataJpaTestDao.findByNameAndGender("張三", 0);
? ? ? ?log.info("findByNameAndGender:{}",JSON.toJSONString(student2));
// ? ? ? ?Student student3 = springDataJpaTestDao.findByMingChengAndXingBie("張三", 1);
// ? ? ? ?log.info("findByMingChengAndXingBie:{}",JSON.toJSONString(student3));
? ?}
}Hibernate: select student0_.id as id1_0_, student0_.age as age2_0_, student0_.class_id as class_id3_0_, student0_.discipline_id as discipli4_0_, student0_.gender as gender5_0_, student0_.name as name6_0_ from students student0_
2023-07-03 11:00:25.198 ?INFO 31840 --- [ ? ? ? ? ? main] c.e.s.SpringDataJpaDemoApplicationTests ?: findAll:{"age":18,"classId":"321465","disciplineId":"1","gender":1,"id":1,"name":"張三"}
2023-07-03 11:00:25.198 ?INFO 31840 --- [ ? ? ? ? ? main] c.e.s.SpringDataJpaDemoApplicationTests ?: findAll:{"age":18,"classId":"321546648","disciplineId":"1","gender":1,"id":2,"name":"李四"}
2023-07-03 11:00:25.198 ?INFO 31840 --- [ ? ? ? ? ? main] c.e.s.SpringDataJpaDemoApplicationTests ?: findAll:{"age":18,"classId":"123","disciplineId":"1","gender":0,"id":3,"name":"王五"}
2023-07-03 11:00:25.198 ?INFO 31840 --- [ ? ? ? ? ? main] c.e.s.SpringDataJpaDemoApplicationTests ?: findAll:{"age":19,"classId":"1234","disciplineId":"3","gender":0,"id":4,"name":"張三"}
Hibernate: select student0_.id as id1_0_0_, student0_.age as age2_0_0_, student0_.class_id as class_id3_0_0_, student0_.discipline_id as discipli4_0_0_, student0_.gender as gender5_0_0_, student0_.name as name6_0_0_ from students student0_ where student0_.id=?
2023-07-03 11:00:25.214 ?INFO 31840 --- [ ? ? ? ? ? main] c.e.s.SpringDataJpaDemoApplicationTests ?: findById:{"age":18,"classId":"321465","disciplineId":"1","gender":1,"id":1,"name":"張三"}
Hibernate: select student0_.id as id1_0_, student0_.age as age2_0_, student0_.class_id as class_id3_0_, student0_.discipline_id as discipli4_0_, student0_.gender as gender5_0_, student0_.name as name6_0_ from students student0_ where student0_.name=?
2023-07-03 11:00:25.258 ?INFO 31840 --- [ ? ? ? ? ? main] c.e.s.SpringDataJpaDemoApplicationTests ?: findByName:{"age":18,"classId":"321546648","disciplineId":"1","gender":1,"id":2,"name":"李四"}
Hibernate: select student0_.id as id1_0_, student0_.age as age2_0_, student0_.class_id as class_id3_0_, student0_.discipline_id as discipli4_0_, student0_.gender as gender5_0_, student0_.name as name6_0_ from students student0_ where student0_.name=? and student0_.gender=?
2023-07-03 11:00:25.263 ?INFO 31840 --- [ ? ? ? ? ? main] c.e.s.SpringDataJpaDemoApplicationTests ?: findByNameAndGender:{"age":19,"classId":"1234","disciplineId":"3","gender":0,"id":4,"name":"張三"}
2023-07-03 11:00:25.287 ?INFO 31840 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2023-07-03 11:00:25.293 ?INFO 31840 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource ? ? ? : HikariPool-1 - Shutdown initiated...
Disconnected from the target VM, address: '127.0.0.1:62235', transport: 'socket'
2023-07-03 11:00:25.309 ?INFO 31840 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource ? ? ? : HikariPool-1 - Shutdown completed.Spring Data JPA的默認查找策略是
CreateIfNotFound
,參閱“3.創(chuàng)建配置類編寫代理實例”。通俗的講就是它可以通過在繼承Repository等接口的接口中自定義方法,然后方法名解析出該方法對應的SQL。下面是官網(wǎng)對于解析方法的一些說明,當我們在使用自定義持久層方法時,如果你想解放雙手不手寫SQL,也需要按照下面的說明命名方法。“
4.4.2. Query Creation
...
解析查詢方法名分為主語和謂語。第一部分(find…By, exists…By)定義查詢的主題,第二部分形成謂詞。引子句(主語)可以包含進一步的表達。find(或其他引入關(guān)鍵字)和By之間的任何文本都被認為是描述性的,除非使用結(jié)果限制關(guān)鍵字之一,如Distinct來在要創(chuàng)建的查詢上設置不同標志,或使用Top/First來限制查詢結(jié)果。
附錄包含查詢方法主題關(guān)鍵字和查詢方法謂詞關(guān)鍵字的完整列表,包括排序和大小寫修飾符。但是,第一個By充當分隔符,以指示實際條件謂詞的開始。在非?;镜募墑e上,您可以定義實體屬性上的條件,并將它們與And和Or連接起來。
解析方法的實際結(jié)果取決于為其創(chuàng)建查詢的持久性存儲。然而,有一些一般的事情需要注意:
表達式通常是屬性遍歷和可以連接的操作符的組合。可以將屬性表達式與“AND”和“OR”結(jié)合使用。對于屬性表達式,還支持Between、LessThan、GreaterThan和Like等操作符。受支持的操作符因數(shù)據(jù)存儲而異,因此請參閱參考文檔的相應部分。
方法解析器支持為單個屬性(例如,findByLastnameIgnoreCase(…))或支持忽略大小寫的類型的所有屬性(通常是字符串實例-例如,findByLastnameAndFirstnameAllIgnoreCase(…))設置IgnoreCase標志。是否支持忽略大小寫可能因存儲而異,因此請參閱參考文檔中的相關(guān)章節(jié),了解特定于存儲的查詢方法。
可以通過向引用屬性的查詢方法追加OrderBy子句和提供排序方向(Asc或Desc)來應用靜態(tài)排序。要創(chuàng)建支持動態(tài)排序的查詢方法,請參見“Paging, Iterating Large Results, Sorting”。
”
關(guān)于這部分更多內(nèi)容,請參閱官網(wǎng)文檔。
創(chuàng)建配置類編寫代理實例
當您使用Spring Data JPA時,您需要定義一個繼承自
JpaRepository
或其子接口的接口,用于定義對數(shù)據(jù)庫的操作。@EnableJpaRepositories
注解的作用是告訴Spring啟用JPA倉庫的自動配置,并掃描指定的包或類路徑,尋找這些定義的JPA倉庫接口。在使用
@EnableJpaRepositories
注解時,通常需要指定basePackages
屬性或value
屬性,以聲明要掃描的包或類路徑。這樣,Spring就會自動查找該包或類路徑下所有繼承自JpaRepository
或其子接口的接口,并自動生成對應的實現(xiàn)類。Spring Boot中使用Spring Data JPA時,不寫
@EnableJpaRepositories
注解和其他相關(guān)配置注解也是可以的。Spring Boot默認會自動配置JPA倉庫,包括使用
@EnableJpaRepositories
注解啟用JPA倉庫的自動配置。當您使用Spring Boot的默認約定和項目結(jié)構(gòu)時,它會自動掃描主應用程序類所在的包以及其子包中的所有
@Entity
注解的實體類和@Repository
注解的倉庫接口。然后,Spring Boot會自動生成和注冊這些JPA倉庫的實現(xiàn)類。只有當您的項目結(jié)構(gòu)不符合Spring Boot的默認約定,或者您想自定義JPA倉庫的配置時,才需要顯式地使用
@EnableJpaRepositories
注解和其他相關(guān)配置注解。在絕大多數(shù)情況下,您不需要顯式地添加
@EnableJpaRepositories
注解和其他相關(guān)配置注解,Spring Boot會自動為您配置和管理JPA倉庫。@EnableJpaRepositories
注解提供了一些可配置的屬性,用于自定義JPA倉庫的行為。以下是一些常用的屬性及其含義:Create
: 直接根據(jù)方法名查詢, 如果方法名不符合規(guī)則, 拋出異常, 是在項目啟動的時候就會拋出異常。UseDeclaredQuery
: 注解方式, 又叫聲明方式。 通過接口方法上面配置的注解查詢, 注解里面SQL寫錯了, 啟動的時候也會報錯。CreateIfNotFound
: 上面兩種方式的綜合體, 先通過注解找, 找不到就通過方法名找(默認)。basePackages
: 指定要掃描的包路徑,以查找繼承自JpaRepository
或其子接口的JPA倉庫接口??梢酝ㄟ^字符串數(shù)組的形式指定多個包路徑。value
: 與basePackages
屬性作用相同,可以指定要掃描的包路徑。basePackageClasses
: 指定含有JPA倉庫接口的類,Spring會根據(jù)這些類的包路徑進行掃描。可以通過類數(shù)組的形式指定多個類。entityManagerFactoryRef
: 指定要使用的EntityManagerFactory
bean的名稱。如果沒有指定,默認會使用一個默認的EntityManagerFactory
bean。transactionManagerRef
: 指定要使用的PlatformTransactionManager
bean的名稱。如果沒有指定,默認會使用一個默認的PlatformTransactionManager
bean。repositoryImplementationPostfix
: 定義自動生成的JPA倉庫實現(xiàn)類的后綴。默認值為Impl
,例如,對于UserRepository
接口,自動生成的實現(xiàn)類的名稱為UserRepositoryImpl
。namedQueriesLocation
: 指定在類路徑下的位置,用于加載命名查詢(Named Queries)??梢允且粋€文件路徑、類路徑或URL。repositoryFactoryBeanClass
: 指定用于創(chuàng)建JPA倉庫的工廠類。默認為org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean
。queryLookupStrategy
屬性用于指定JPA倉庫查詢方法的查找策略??梢酝ㄟ^設置不同的值來改變默認行為。以下是
queryLookupStrategy
屬性可用的選項:
2.5自定義SQL
盡管從方法名獲取查詢非常方便,但可能會遇到這樣的情況:方法名解析器不支持想要使用的關(guān)鍵字,或者方法名會變得不必要地難看。因此,您可以通過命名約定使用JPA命名查詢,也可以使用@Query注釋您的查詢方法(請參閱使用
了解詳細信息)。public interface UserRepository extends JpaRepository<User, Long> {
?@Query("select u from User u where u.emailAddress = ?1")
?User findByEmailAddress(String emailAddress);
}
2.5.1nativeQuery
在Spring Data JPA中,@Query
注解用于指定自定義的JPQL(Java Persistence Query Language)或SQL查詢。nativeQuery
參數(shù)用于指示是否使用原生的SQL查詢,可以設置為true
或false
。
當
nativeQuery
為true
時,表示使用原生的SQL查詢。這意味著可以直接編寫SQL語句,而不是JPQL語句。在這種情況下,查詢語句將會被傳遞給底層數(shù)據(jù)庫進行執(zhí)行,可以使用SQL特有的語法和函數(shù)。這對于復雜的查詢或需要使用數(shù)據(jù)庫特定功能的情況非常有用。
以下是使用nativeQuery
為true
的示例:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
? ?
? ?@Query(value = "SELECT * FROM users WHERE username = ?1", nativeQuery = true)
? ?User findUserByUsername(String username);
}
在上面的示例中,我們使用了nativeQuery = true
來指定查詢使用原生的SQL語句來查詢數(shù)據(jù)庫中的users
表。在執(zhí)行查詢時,Spring Data JPA會將該查詢語句傳遞給數(shù)據(jù)庫執(zhí)行,并將結(jié)果映射到User
對象。
當
nativeQuery
為false
時(默認值),表示使用JPQL查詢。JPQL是一種面向?qū)ο蟮牟樵冋Z言,與實體類和數(shù)據(jù)庫字段進行交互。在這種情況下,查詢語句將會由Spring Data JPA解釋和轉(zhuǎn)換為底層數(shù)據(jù)庫支持的SQL,然后執(zhí)行查詢。JPQL提供了一些與獨立于數(shù)據(jù)庫的對象模型交互的功能。
以下是使用nativeQuery
為false
的示例(默認值):
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
? ?
? ?@Query("SELECT u FROM User u WHERE u.username = ?1")
? ?User findUserByUsername(String username);
}
在上述示例中,我們使用了JPQL語句SELECT u FROM User u WHERE u.username = ?1
來查詢數(shù)據(jù)庫中的User
對象。
總結(jié)起來,使用nativeQuery
為true
表示使用原生的SQL查詢,而nativeQuery
為false
表示使用JPQL查詢。選擇使用哪種查詢?nèi)Q于具體的需求,包括查詢復雜度、數(shù)據(jù)庫特定功能等。
2.5.2傳參方式
@Query
注解定義的SQL查詢可以通過多種方式傳遞參數(shù)。下面是幾種常見的傳參方式:
位置參數(shù)(Positional Parameters):使用
@Query("SELECT u FROM User u WHERE u.id = ?1")?
占位符表示參數(shù),按照參數(shù)在方法中聲明的順序傳遞。示例:
User findUserById(Long id);在上面的例子中,
?1
表示第一個位置的參數(shù),即方法的id
參數(shù)。相應地,你可以使用?2
、?3
等表示后續(xù)參數(shù)。命名參數(shù)(Named Parameters):使用
@Query("SELECT u FROM User u WHERE u.username = :username"):
前綴給參數(shù)命名,然后在查詢語句中使用該命名參數(shù)。示例:
User findUserByUsername(@Param("username") String username);在上述示例中,
username
是一個命名參數(shù),使用@Param
注解指定其值是方法的username
參數(shù)。SpEL表達式參數(shù)(SpEL Expression Parameters):使用
@Query("SELECT u FROM User u WHERE u.age > :#{#minAge ?: 18}")#{}
將SpEL表達式嵌入查詢語句中。SpEL表達式可以引用方法參數(shù)、類屬性、靜態(tài)常量等。示例:
List<User> findByAgeGreaterThan(@Param("minAge") Integer minAge);上面的例子中,SpEL表達式
#{#minAge ?: 18}
表示取minAge
參數(shù)的值作為查詢中的最小年齡,在沒有提供minAge
參數(shù)時,默認使用18作為最小年齡。
2.5.3EntityManager
除了上述兩種(解析方法名、@Query)方法定義SQL外,還可以通過EntityManager來執(zhí)行自定義的SQL。
在Spring Data JPA中,EntityManager是JPA(Java Persistence API)的核心接口之一,用于管理實體對象和執(zhí)行與持久化相關(guān)的操作。
EntityManager的主要作用如下:
實體管理:EntityManager負責跟蹤、管理和操作實體對象的生命周期。它可以創(chuàng)建、讀取、更新和刪除實體對象??梢允褂肊ntityManager的persist、find、merge和remove等方法來執(zhí)行這些操作。
數(shù)據(jù)庫事務管理:EntityManager在事務管理方面起著重要的作用。通過EntityManager可以啟動、提交或回滾數(shù)據(jù)庫事務。在Spring Data JPA中,事務管理通常由Spring框架提供,但EntityManager是與事務管理密切相關(guān)的一部分。
緩存管理:EntityManager使用一級緩存來提高性能。一級緩存存儲已經(jīng)查詢或持久化的實體對象,以減少對數(shù)據(jù)庫的訪問。通過實體對象的標識符,可以快速檢索緩存中的實體對象,從而避免對數(shù)據(jù)庫的不必要查詢。
查詢執(zhí)行:EntityManager可以執(zhí)行查詢語句,包括JPQL查詢和原生SQL查詢。它提供了createQuery、createNamedQuery和createNativeQuery等方法,用于創(chuàng)建并執(zhí)行查詢。
實體關(guān)系管理:EntityManager支持實體之間的關(guān)系映射和管理。它可以處理實體之間的關(guān)聯(lián)關(guān)系,包括一對一、一對多、多對一和多對多等關(guān)系。通過EntityManager,可以加載和操作關(guān)聯(lián)的實體對象。
需要注意的是,在Spring Data JPA中,EntityManager是由JpaRepository接口和實現(xiàn)類(如SimpleJpaRepository)隱式地創(chuàng)建和使用的。你通常不需要直接使用EntityManager來執(zhí)行基本的CRUD操作,而是使用JpaRepository提供的方法。
我們可以調(diào)用createNativeQuery()方法來執(zhí)行SQL語句,這在項目中也有一定的應用場景,比如對同一組查詢結(jié)果進行avg、max等不同的聚合操作時候,我們可以通過字符串拼接SQL,這樣只需要替換SQL的聚合函數(shù)那部分的字符串即可??傊疄榱松傩㏒QL,EntityManager能提供更加靈活的方法。
3與Hibernate的關(guān)系
Spring Data JPA是對JPA(Java Persistence API)規(guī)范的實現(xiàn),而Hibernate是JPA規(guī)范的一個具體實現(xiàn)。
JPA是Java領(lǐng)域的一種持久化標準,定義了一系列用于對象關(guān)系映射(ORM)的接口和規(guī)則。它提供了一套API和標準注解,用于將Java實體類映射到關(guān)系型數(shù)據(jù)庫。
Hibernate是一個優(yōu)秀的ORM框架,它實現(xiàn)了JPA規(guī)范,并提供了更多的功能和特性。Hibernate提供了豐富的ORM功能,包括對象關(guān)系映射、事務管理、查詢語言和緩存等。
Spring Data JPA是Spring框架中的一個模塊,它提供了一種簡化的方式來訪問和操作數(shù)據(jù)庫,基于JPA規(guī)范實現(xiàn)了一套簡潔的CRUD(Create, Retrieve, Update, Delete)接口。Spring Data JPA封裝了Hibernate等ORM框架的底層細節(jié),提供了更加便捷的持久化操作。
因此,Spring Data JPA使用了Hibernate作為其默認的JPA實現(xiàn)提供商,通過使用Spring Data JPA,您可以更輕松地使用Hibernate進行數(shù)據(jù)庫訪問和操作。同時,Spring Data JPA還支持其他JPA實現(xiàn)提供商,如EclipseLink等,使得您可以更容易地切換和替換不同的JPA實現(xiàn)。