JPA & Spring Data JPA
一、JPA
1. JPA是什麼
JPA(Java Persistence API)Java持久化 API,是一套基於ORM思想的規範。
ORM(Object Relational Mapping)對象關係映射。實現了實體類對象和數據庫中表的一個映射關係。我們可以通過操作實體類對象(JavaBean)來操作數據庫表,從而實現數據庫的CRUD操作並且不需要去重點關注SQL語句。ORM主要涉及到兩個映射關係:1、實體類和表的映射關係;2、實體類中的屬性和表中的字段的映射關係。像Mybatis框架:一個不完全的ORM框架,需要開發人員寫一部分的SQL語句;Hibernate框架:是一個完全的ORM框架,需要編寫SQL語句
規範:顧名思義就是隻定義了而沒有實現,因此可以明白JPA的內部就是由一系列的接口和抽象類組成,自身並沒有去實現。
綜上所述:JPA就是衆多實現了ORM思想框架的一個規範,內部只有接口和抽象類,沒有實現類。(面向接口編程)
2. 爲什麼用
2.1 標準化
JPA是一個規範,這就說明任何實現了 JPA 規範的框架都遵循同樣的架構,提供相同的訪問API,這保證了基於JPA開發的企業應用能夠經過少量的修改就能夠在不同的JPA框架下運行。
2.2 容器級特性的支持
JPA框架中支持大數據集、事務、併發等容器級事務,這使得 JPA 超越了簡單持久化框架的侷限,在企業應用發揮更大的作用。
2.3 簡單方便
JPA的主要目標之一就是提供更加簡單的編程模型:在JPA框架下創建實體和創建Java 類一樣簡單,沒有任何的約束和限制,只需要使用 javax.persistence.Entity進行註釋,JPA的框架和接口也都非常簡單,沒有太多特別的規則和設計模式的要求,開發者可以很容易的掌握。JPA基於非侵入式原則設計,因此可以很容易的和其它框架或者容器集成
2.4 查詢能力
JPA的查詢語言是面向對象而非面向數據庫的,它以面向對象的自然語法構造查詢語句,可以看成是Hibernate HQL的等價物。JPA定義了獨特的JPQL(Java Persistence Query Language),JPQL是EJB QL的一種擴展,它是針對實體的一種查詢語言,操作對象是實體,而不是關係數據庫的表,而且能夠支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能夠提供的高級查詢特性,甚至還能夠支持子查詢。
2.5 高級特性
JPA 中能夠支持面向對象的高級特性,如類之間的繼承、多態和類之間的複雜關係,這樣的支持能夠讓開發者最大限度的使用面向對象的模型設計企業應用,而不需要自行處理這些特性在關係數據庫的持久化。
3. 怎麼用
3.1 JPA的實現
實現JPA規範的常見的框架有Hibernate、TopLink和OpenJPA. 以Hibernate爲例。
我們在使用JPA操作數據庫時,其底層還是由Hibernate去實現的
3.2 環境搭建
3.2.1 導入座標
<dependencies>
<!--hibernate對jpa的支持-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.10.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>5.4.10.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
3.2.2 創建配置文件persistence.xml
注:persistence.xml必須放在類路徑下的META-INF文件下
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<!--配置持久化單元
name:持久化單元名稱
transaction-type:事務類型
RESOURCE_LOCAL:本地事務管理
JTA:分佈式事務管理
-->
<persistence-unit name="myJPA" transaction-type="RESOURCE_LOCAL">
<!--配置JPA規範的服務提供商-->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!--配置數據源-->
<properties>
<!-- 數據庫信息
用戶名,javax.persistence.jdbc.user
密碼, javax.persistence.jdbc.password
驅動, javax.persistence.jdbc.driver
數據庫地址 javax.persistence.jdbc.url
-->
<!-- 標準配置方法,適用性高 -->
<!--<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/jpa"/>
<property name="javax.persistence.jdbc.username" value="root"/>
<property name="javax.persistence.jdbc.password" value="123456"/>-->
<!-- hibernate 的配置方法-->
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.url" value="jdbc:mysql:///jpa?characterEncoding=UTF-8"></property>
<property name="hibernate.connection.username" value="root"></property>
<property name="hibernate.connection.password" value="123456"></property>
<!--配置jpa實現方(hibernate)的配置信息
顯示sql : false|true
自動創建數據庫表 : hibernate.hbm2ddl.auto
create : 程序運行時創建數據庫表(如果有表,先刪除表再創建)
update :程序運行時創建表(如果有表,不會創建表)
none :不會創建表
-->
<!--配置方言-->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"></property>
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="update" />
</properties>
</persistence-unit>
</persistence>
3.2.3 編寫實體類
3.2.4 配置實體類和數據庫表、屬性和表中字段的映射關係
Entity //聲明這是一個實體類
@Table(name = "cst_customer") //實體類和數據庫表的映射
public class Customer implements Serializable {
private static final long serialVersionUID = 2845630796773320819L;
/**
* @Id:聲明主鍵的配置
* @GeneratedValue:配置主鍵的生成策略
* strategy
* GenerationType.IDENTITY :自增,mysql
* * 底層數據庫必須支持自動增長(底層數據庫支持的自動增長方式,對id自增)
* GenerationType.SEQUENCE : 序列,oracle
* * 底層數據庫必須支持序列
* GenerationType.TABLE : jpa提供的一種機制,通過一張數據庫表的形式幫助我們完成主鍵自增
* GenerationType.AUTO : 由程序自動的幫助我們選擇主鍵生成策略
* @Column:配置屬性和字段的映射關係
* name:數據庫表中字段的名稱
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cust_id")
private Integer custId;
@Column(name = "cust_name")
private String custName;
@Column(name = "cust_source")
private String custSource;
@Column(name = "cust_industry")
private String custIndustry;
@Column(name = "cust_level")
private String custLevel;
@Column(name = "cust_address")
private String custAddress;
@Column(name = "cust_phone")
private String custPhone;
//get | set | toString
.............
}
3.3 JPA操作數據庫的流程
3.3.1 加載總配置文件persistence.xml並且創建實體管理器工廠(EntityManagerFactory)
3.3.2 根據實體管理器工廠創建實體管理器(EntityManager)
問題:EntityManagerFactory內部維護了很多信息並且是線程安全的對象,所以創建的過程非常的浪費資源且耗時
解決辦法:利用靜態代碼塊在類加載的時候創建一個公共的EntityManagerFactory對象
注:多個線程訪問同一個EntityManagerFactory對象不會造成線程安全的問題
public class JpaUtils {
private static EntityManagerFactory factory;
static {
//創建一個公共的EntityManagerFactory
factory = Persistence.createEntityManagerFactory("myJPA");
}
/**
* 獲取EntityManager
*/
public static EntityManager getEntityManager(){
return factory.createEntityManager();
}
}
3.3.3 根據實體管理器獲取事務對象並開啓事務
獲取事務對象:getTransaction()
開啓事務:begin()
3.3.4 進行增刪改查操作
根據id查詢:find() | getRefrence()
/**
* find | getRefrence : 根據id查詢數據
* class:查詢數據的結果需要包裝的實體類類型的字節碼
* id:查詢的主鍵的取值
*
* 不同點:find 立即加載
* getRefrence 延遲加載
*/
Customer customer = entityManager.find(Customer.class, 1);
//Customer customer = entityManager.getReference(Customer.class, 1);
System.out.println(customer);
插入數據:persist()
更新數據:merge()
//先獲取數據,然後更新
Customer customer = entityManager.find(Customer.class, 1);
customer.setCustName("小王");
entityManager.merge(customer);
刪除數據:remove()
//先獲取數據,然後刪除
Customer customer = entityManager.find(Customer.class, 1);
entityManager.remove(customer);
3.3.5 提交事務、釋放資源
流程示例:
//加載配置文件並且創建EntityManagerFactory
//根據EntityManagerFactory創建EntityManager
EntityManager entityManager = JpaUtils.getEntityManager();
//獲取事務對象並開啓事務
EntityTransaction tx = entityManager.getTransaction();
tx.begin();
//進行增刪改查操作,以插入數據爲例
Customer customer = new Customer();
customer.setCustName("王五");
customer.setCustIndustry("計算機");
entityManager.persist(customer); //保存操作
//提交事務、釋放資源
tx.commit();
entityManager.close();
3.4 複雜查詢
JPQL(Java Persistence Query Language):Java持久化查詢語言
特點:面向對象、關鍵字和SQL語句一樣
規則: 1. 裏面不能出現表名,列名,只能出現java的類名,屬性名,區分大小寫
2. 關鍵字不區分大小寫
3. 不能寫select *
查詢的步驟:
1.EntityManager創建Query對象
2.如果包含參數,setParameter()
3.如果需要分頁,調用Query的setFirstResult()或者setMaxResult()
4.如果是select語句,使用getResultList()或者getSingleResult();
常見的方法:
//用於select語句
List getResultList()
Object getSingleResult()
//用於update,delete語句
int executeUpdate()
//分頁查詢時參數的設置
Query setFirstResult(int startPosition)
Query setMaxResult(int maxResult)
//設置參數
setParameter()
3.4.1 查詢全部
//創建實體管理器對象,開啓事務
.......
//查詢操作
//SQL語句:select * from cst_customer
String jpql = "from Customer";
//創建query對象(query對象纔是真正的執行jpql的對象)
Query query = entityManager.createQuery(jpql);
List list = query.getResultList();
for (Object customers : list) {
System.out.println(customers);
}
//提交事務、關閉對象
3.4.2 分頁查詢
/**
* 分頁查詢
* sql:select * from customer limit ?,?
* jpql: from Customer
*/
String jpql = "from Customer";
Query query = entityManager.createQuery(jpql);
//設置分頁參數
query.setFirstResult(0);
query.setMaxResults(2);
List list = query.getResultList();
for (Object customers : list) {
System.out.println(customers);
}
3.4.3 統計查詢
/**
* 統計查詢
* sql:select count(cust_id) from customer
* jpql: select count(custId) from Customer
*/
String jpql = "select count(custId) from Customer";
Query query = entityManager.createQuery(jpql);
Object result = query.getSingleResult();
System.out.println(result);
3.4.4 條件查詢
/**
* 條件查詢
* sql:select * from customer where cust_name like ?
* jpql: from Customer where custName like ?1
*/
String jpql = "from Customer where custName like ?1";
Query query = entityManager.createQuery(jpql);
//設置參數
query.setParameter(1,"li%");
List list = query.getResultList();
for (Object customers : list) {
System.out.println(customers);
}
3.4.5 排序查詢
/**
* 查詢所有並實現倒序的功能
* sql:select * from customer order by cust_id desc
* jpql: from Customer order by custId desc
*/
String jpql = "from Customer order by custId desc";
Query query = entityManager.createQuery(jpql);
List list = query.getResultList();
for (Object customers : list) {
System.out.println(customers);
}
二、Spring Data JPA
1. 是什麼
1.1 Spring Data的概述
Spring Data 是Spring的一個子項目,主要用來簡化數據庫的訪問,支持關係型數據庫和NoSQL,並且支持雲服務。
1.2 Spring Data JPA的概述
Spring Data JPA 是 Spring Data的一個子模塊,致力於減少數據訪問層(DAO)的開發量,它可以極大的簡化JPA的寫法,開發者只需要聲明持久層的接口就可以在幾乎不用寫實現的情況下,實現對數據的訪問和操作。除了CRUD外,還包括如分頁、排序等一些常用的功能。
2. 怎麼用
2.1 Spring Data JPA的實現過程
2.2 Spring Data JPA的核心接口
2.2.1 Repository
最頂層的接口,是一個空的接口,目的是爲了統一所有Repository的類型,且能讓組件掃描的時候自動識別
2.2.2 CrudRepository
是Repository的子接口,提供CRUD的功能
2.2.3 PageAndSortingRepository
是CrudRepository的子接口,添加分頁和排序的功能
2.2.4 JpaRepository
是PagingAndSortingRepository的子接口,實現一組 JPA 規範相關的方法。凡是自定義的持久層的接口,一般都要繼承這個接口
2.2.5 JpaSpecificationExecutor
用來做複雜查詢的接口
2.2.6 Specification
是Spring Data JPA提供的一個查詢規範,要做複雜的查詢,只需圍繞這個規範來設置查詢條件即可
2.3 開發步驟
2.3.1 導入Spring Data JPA 的相關依賴
<!--spring-data-jpa的相關依賴-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<!-- el 使用spring data jpa 必須引入 -->
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
2.3.2 整合spring 和 Spring Data JPA
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<!--spring的相關配置-->
<!--1.配置spring容器啓動是要掃描的包-->
<context:component-scan base-package="com.springdata"></context:component-scan>
<!--Spring Data JPA相關的配置-->
<!--1.創建entityManagerFactory對象交給spring容器管理-->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!--配置數據源-->
<property name="dataSource" ref="dataSource"></property>
<!--配置要掃描的包(實體類所在的包)-->
<property name="packagesToScan" value="com.springdata.bean"></property>
<!--jpa的實現廠商-->
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"></bean>
</property>
<!--jpa的供應商適配器-->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--配置是否自動創建數據庫表 -->
<property name="generateDdl" value="false" />
<!--指定數據庫類型 -->
<property name="database" value="MYSQL" />
<!--數據庫方言:支持的特有語法 -->
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
<!--是否顯示sql -->
<property name="showSql" value="true" />
</bean>
</property>
<!--jpa的方言 :高級的特性 -->
<property name="jpaDialect" >
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
</property>
</bean>
<!--2.數據源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa?characterEncoding=utf-8"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!--spring整合spring data jpa-->
<jpa:repositories base-package="com.springdata.dao" transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactory">
</jpa:repositories>
<!--配置事務管理器-->
<bean name="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"></property>
</bean>
</beans>
2.3.3 編寫實體類
2.3.4 聲明持久層的接口
需要繼承兩個接口:JpaRepository 、JpaSpecificationExecutor
/**
* 符合SpringDataJpa的dao層接口規範
* JpaRepository<操作的實體類類型,實體類中主鍵屬性的類型>
* * 封裝了基本CRUD操作
* JpaSpecificationExecutor<操作的實體類類型>
* * 封裝了複雜查詢(分頁)
*/
public interface CustomerDao extends JpaRepository<Customer,Integer>, JpaSpecificationExecutor<Customer> {
}
2.4 Spring Data JPA的查詢方式
2.4.1 使用Spring Data JPA中接口定義的方法進行查詢
注:不需要在持久層接口中寫任何方法
//持久層接口的代碼
public interface CustomerDao extends JpaRepository<Customer,Integer>, JpaSpecificationExecutor<Customer> {
}
//測試類中的方法
/**
* 根據id查詢 findById:立即加載 getOne:延遲加載
*/
@Test
public void testFindById(){
/*Optional<Customer> customer = customerDao.findById(1);
if (customer.isPresent()){
System.out.println(customer.get());
}else{
System.out.println("你要找的值不存在");
}*/
Customer customer1 = customerDao.findById(1).orElse(null);
System.out.println(customer1);
}
JpaRepository接口中的方法列表:
JpaSpecificationExecutor接口中的方法列表:
2.4.2 使用 JPQL的查詢方式
需要將JPQL語句配置到接口方法上
- 需要在dao接口上配置方法
- 在新添加的方法上,使用註解的形式配置jpql查詢語句
- 註解 : @Query
public interface CustomerDao extends JpaRepository<Customer,Integer>, JpaSpecificationExecutor<Customer> {
/**
* 使用jpql語句查詢
* 根據custName和custId查詢用戶的信息
* jpql語句:from Customer where custName = ?1 and custId = ?2
* ?1,?2代表參數的佔位符,其中1,2對應方法中的參數索引
*/
@Query("from Customer where custName = ?1 and custId = ?2")
public Customer findCustByNameId(String custName, Integer custId);
/**
* 使用jpql實現更新操作: @Query、@Modifying
* jpql: update Customer set custName = ?1 where custId = ?2
*
*/
@Query("update Customer set custName = ?1 where custId = ?2")
@Modifying
public void updateCustomer(String custName, Integer custId);
}
注:使用JPQL更新時,需要添加事務的支持
/**
* 使用jpql實現更新操作
* springDataJpa中使用jpql完成 更新/刪除操作
* 需要手動添加事務的支持
* 默認會執行結束之後,回滾事務
* Rollback : 設置是否自動回滾
* false | true
*/
@Test
@Transactional
@Rollback(value = false)
public void testUpdateCust(){
customerDao.updateCustomer("李四",2);
}
2.4.3 使用SQL語句查詢
需要將SQL語句配置到接口方法上
-
需要在dao接口上配置方法
-
在新添加的方法上,使用註解的形式配置SQL查詢語句
-
註解 : @Query
屬性:nativeQuery :true(SQL)| false(JPQL)
public interface CustomerDao extends JpaRepository<Customer,Integer>, JpaSpecificationExecutor<Customer> {
/**
* 使用sql語句查詢
* nativeQuery: false(jpql查詢)|true(使用本地查詢:sql查詢)
*/
@Query(value = "select * from cst_customer where cust_name like ?1",nativeQuery = true)
public List<Customer> findCustByName(String custName);
}
2.4.4 使用方法命名規則查詢
方法名要按照指定的命名規則來命名
public interface CustomerDao extends JpaRepository<Customer,Integer>, JpaSpecificationExecutor<Customer> {
/**
* 根據方法命名規則查詢
*
* 方法名的約定:
* findBy : 查詢
* 對象中的屬性名(首字母大寫) : 查詢的條件
*
* findByCustName -- 根據客戶名稱查詢
*
* 在springdataJpa的運行階段
* 會根據方法名稱進行解析 findBy from xxx(實體類)
* 屬性名稱 where custName =
*
* 1.findBy + 屬性名稱 (根據屬性名稱進行完成匹配的查詢=)
* 2.findBy + 屬性名稱 + “查詢方式(Like | isnull)”
* findByCustNameLike
* 3.多條件查詢
* findBy + 屬性名 + “查詢方式” + “多條件的連接符(and|or)” + 屬性名 + “查詢方式”
*/
/**
* 根據客戶名精準查詢
*/
public Customer findByCustName(String custName);
/**
*使用客戶名稱查詢
*/
public List<Customer> findByCustNameLike(String custName);
/**
* 使用客戶名稱模糊匹配和客戶所屬行業精準匹配的查詢
*/
public Customer findByCustNameLikeAndCustIndustry(String custName,String custIndustry);
}
常見的關鍵字對應的方法名稱命名規則
Keyword | Sample | 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) |
2.5 Spring Data JPA 的運行過程原理剖析
以findById()爲例
- 通過JdkDynamicAopProxy的invoke方法創建了一個動態代理對象SimJpaRepository
- SimpleJpaRepository當中封裝了JPA的操作(藉助JPA的api完成數據庫的CRUD)
- 通過hibernate完成數據庫操作(封裝了jdbc)
點開上圖中的find方法,如下圖所示
2.6 Specifications動態查詢
有時我們在查詢某個實體的時候,給定的條件是不固定的,這時就需要動態構建相應的查詢語句,在SpringData JPA中可以通過JpaSpecificationExecutor
接口查詢。相比JPQL
,其優勢是類型安全,更加的面向對象
public interface JpaSpecificationExecutor<T> {
//根據條件查詢單個對象
Optional<T> findOne(@Nullable Specification<T> var1);
//根據條件查詢全部
List<T> findAll(@Nullable Specification<T> var1);
//根據條件查詢全部,並未分頁,返回的是一個pageBean對象
//Pageable:分頁參數
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
//查詢並排序
//Sort:排序的參數
List<T> findAll(@Nullable Specification<T> var1, Sort var2);
//統計查詢
long count(@Nullable Specification<T> var1);
}
對於JpaSpecificationExecutor,這個接口基本是圍繞着Specification接口來定義的。我們可以簡單的理解爲,Specification構造的就是查詢條件。
public interface Specification<T> extends Serializable {
/**
* root:查詢的根對象(查詢的任何屬性都可以從根對象中獲取)
* CriteriaQuery:代表一個頂層查詢對象,用來自定義查詢
* CriteriaBuilder:用來構建查詢,此對象裏有很多條件方法
*/
//封裝查詢條件
Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
}
/**
* 自定義查詢條件
* 1.實現Specification接口(提供泛型:查詢的對象類型)
* 2.實現toPredicate方法(構造查詢條件)
* 3.需要藉助方法參數中的兩個參數(
* root:獲取需要查詢的對象屬性
* CriteriaBuilder:構造查詢條件的,內部封裝了很多的查詢條件(模糊匹配,精準匹配)
* )
*/
2.6.1 單條件查詢
/**
* 單條件查詢
* 案例:根據客戶名稱查詢,查詢客戶名爲王五的客戶
* 查詢條件
* 1.查詢方式
* cb對象
* 2.比較的屬性名稱
* root對象
*/
@Test
public void findOneTest(){
Specification<Customer> spec = new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
//1.獲取比較的屬性
Path custName = root.get("custName");
//2.構造查詢
/**
* 查詢方式
* 第一個參數:需要比較的屬性(path對象)
* 第二個參數:當前需要比較的取值
*/
Predicate predicate = criteriaBuilder.equal(custName, "王五");
return predicate;
}
};
Optional<Customer> customer = customerDao.findOne(spec);
System.out.println(customer);
}
2.6.2 多條件查詢
/**
* 多條件查詢
* 根據客戶名稱和行業查詢
*/
@Test
public void findAllSpecTest(){
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//獲取比較的屬性
Path<Object> custName = root.get("custName");
Path<Object> custIndustry = root.get("custIndustry");
//構造查詢
//1.構造客戶名的精準匹配查詢
Predicate predicate1 = criteriaBuilder.equal(custName, "lisi");
//2..構造所屬行業的精準匹配查詢
Predicate predicate2 = criteriaBuilder.equal(custIndustry, "education");
//3.將多個查詢條件組合到一起:組合(滿足條件一併且滿足條件二:與關係,滿足條件一或滿足條件二即可:或關係)
Predicate predicate = criteriaBuilder.and(predicate1, predicate2);
return predicate;
}
};
List<Customer> customers = customerDao.findAll(spec);
for (Customer customer : customers) {
System.out.println(customer);
}
}
2.6.3 模糊查詢
常見的方法名稱與SQL之間的對應關係
方法名稱 | Sql對應關係 |
---|---|
equle | filed = value |
gt(greaterThan ) | filed > value |
lt(lessThan ) | filed < value |
ge(greaterThanOrEqualTo ) | filed >= value |
le( lessThanOrEqualTo) | filed <= value |
notEqule | filed != value |
like | filed like value |
notLike | filed not like value |
/**
* 模糊查詢
* 根據客戶名稱完成模糊查詢
* gt,lt,ge,le,like : 得到path對象,根據path指定比較的參數類型,再去進行比較
* 指定參數類型:path.as(類型的字節碼對象)
*/
@Test
public void findBySpecTest(){
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//1. 獲取比價的屬性
Path<Object> custName = root.get("custName");
//構造查詢
Predicate predicate = criteriaBuilder.like(custName.as(String.class), "li%");
return predicate;
}
};
List<Customer> customers = customerDao.findAll(spec);
for (Customer customer : customers) {
System.out.println(customer);
}
}
2.6.4 分頁查詢
/**
* 分頁查詢
*/
@Test
public void findAllPageableTest(){
//根據條件查詢出相應的所有數據
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//1. 獲取比價的屬性
Path<Object> custName = root.get("custName");
//構造查詢
Predicate predicate = criteriaBuilder.like(custName.as(String.class), "li%");
return predicate;
}
};
//定義分頁參數
//PageRequest對象是Pageable接口的實現類
//第一個參數:指定開始查詢的頁碼 0表示第一頁
//第二個參數:指定每頁大大小
Pageable pageable = PageRequest.of(0,2);
Page<Customer> customerPage = customerDao.findAll(spec,pageable);
//獲取所有數據的集合列表
List<Customer> customers = customerPage.getContent();
for (Customer customer : customers) {
System.out.println(customer);
}
//獲取總共的元素數(所有數據的個數)
System.out.println(customerPage.getTotalElements());
//獲取總共的頁數
System.out.println(customerPage.getTotalPages());
}
2.6.5 排序查詢
/**
* 查詢並排序
*/
@Test
public void findAllSpecSortTest(){
//根據條件查詢出相應的所有數據
Specification<Customer> spec = new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//1. 獲取比價的屬性
Path<Object> custName = root.get("custName");
//構造查詢
Predicate predicate = criteriaBuilder.like(custName.as(String.class), "li%");
return predicate;
}
};
//定義排序的參數
//根據id倒敘
Sort sort = Sort.by(Sort.Direction.DESC,"custId");
List<Customer> customers = customerDao.findAll(spec, sort);
for (Customer customer : customers) {
System.out.println(customer);
}
}
2.7 多表操作
多表之間的關係有三種:
-
一對一
-
一對多:需要在從表上添加主表的主鍵信息作爲外鍵
一的一方:主表
多的一方:從表
-
多對多
中間表:中間表中最少應該由兩個字段組成,這兩個字段做爲外鍵指向兩張表的主鍵,又組成了聯合主鍵
2.7.1 一對多
主要註解:
@OneToMany:
作用:建立一對多的關係映射
屬性:
targetEntityClass:對方對象的字節碼對象
mappedBy:對方配置關係的屬性名稱。
cascade:指定要使用的級聯操作
fetch:指定是否採用延遲加載
orphanRemoval:是否使用孤兒刪除
@ManyToOne
作用:建立多對一的關係
屬性:
targetEntityClass:對方對象的字節碼對象
cascade:指定要使用的級聯操作
fetch:指定是否採用延遲加載或立即加載
optional:關聯是否可選。如果設置爲false,則必須始終存在非空關係。
@JoinColumn
作用:配置外鍵
屬性:
name:指定外鍵字段的名稱
referencedColumnName:指定引用主表的主鍵字段名稱
unique:是否唯一。默認值不唯一
nullable:是否允許爲空。默認值允許。
insertable:是否允許插入。默認值允許。
updatable:是否允許更新。默認值允許。
columnDefinition:列的定義信息。
//Customer.java 主表的實體類
/**
*客戶對聯繫人是一對多的關係
*/
// @OneToMany(targetEntity = LinkMan.class)
// @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
/**
* 放棄外鍵維護權
* mappedBy = "對方配置關係的屬性名稱"
*
* cascade : 配置級聯(可以配置到設置多表的映射關係的註解上)
* CascadeType.all: 所有
* MERGE: 更新
* PERSIST: 保存
* REMOVE: 刪除
*/
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
private Set<LinkMan> linkMen = new HashSet<>();
//LinkMan.java 從表的實體類
/**
* 聯繫人對客戶是多對一的關係
*/
@ManyToOne(targetEntity = Customer.class)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Customer customer;
2.7.2 多對多
@ManyToMany
作用:用於映射多對多關係
屬性:
cascade:配置級聯操作
fetch:配置是否採用延遲加載或立即加載
FetchType.EAGER:立即加載
FetchType.LAZY:延遲加載
targetEntity:配置目標的實體類
@JoinTable
作用:針對中間表的配置(外鍵)
屬性:
nam:配置中間表的名稱
joinColumns:配置當前實體類所對應表的主鍵字段
inverseJoinColumn:配置對方表的主鍵字段
//User.Java
@ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL)
@JoinTable(name = "sys_user_role",
//joinColumns,當前對象在中間表中的外鍵
joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")},
//inverseJoinColumns,對方對象在中間表的外鍵
inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")}
)
private Set<Role> roles = new HashSet<>();
//Role.Java
//被動的一方放棄外鍵的維護權
@ManyToMany(mappedBy = "roles") //配置多表關係
private Set<User> users = new HashSet<>();
2.7.3 對象導航查詢
對象導航查詢:查詢一個對象的同時,通過此對象查詢他的關聯對象
以一對多爲例:
從一方關聯查詢多方:默認採用延遲加載
從多方關聯查詢一方:默認採用立即加載# 歡迎使用Markdown編輯器