SpringDataJPA(二):SpringDataJPA的運行原理以及基本操作

一、Spring Data JPA的概述

官網:https://spring.io/projects/spring-data-jpa

1.1 Spring Data JPA概述

Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA based repositories. This module deals with enhanced support for JPA based data access layers. It makes it easier to build Spring-powered applications that use data access technologies.

Implementing a data access layer of an application has been cumbersome for quite a while. Too much boilerplate code has to be written to execute simple queries as well as perform pagination, and auditing. Spring Data JPA aims to significantly improve the implementation of data access layers by reducing the effort to the amount that’s actually needed. As a developer you write your repository interfaces, including custom finder methods, and Spring will provide the implementation automatically.

Spring Data JPA 是 Spring 基於 ORM 框架、JPA 規範的基礎上封裝的一套JPA應用框架,可使開發者用極簡的代碼即可實現對數據庫的訪問和操作。它提供了包括增刪改查等在內的常用功能,且易於擴展!學習並使用 Spring Data JPA 可以極大提高開發效率!

Spring Data JPA 讓我們解脫了DAO層的操作,基本上所有CRUD都可以依賴於它來實現,在實際的工作工程中,推薦使用Spring Data JPA + ORM(如:hibernate)完成操作,這樣在切換不同的ORM框架時提供了極大的方便,同時也使數據庫層操作更加簡單,方便解耦。

1.2 Spring Data JPA的特性

Features

  • Sophisticated support to build repositories based on Spring and JPA
  • Support for Querydsl predicates and thus type-safe JPA queries
  • Transparent auditing of domain class
  • Pagination support, dynamic query execution, ability to integrate custom data access code
  • Validation of @Query annotated queries at bootstrap time
  • Support for XML based entity mapping
  • JavaConfig based repository configuration by introducing @EnableJpaRepositories.

SpringData Jpa 極大簡化了數據庫訪問層代碼。 如何簡化的呢? 使用了SpringDataJpa,我們的dao層中只需要寫接口,就自動具有了增刪改查、分頁查詢等方法。

1.3 Spring Data JPA 與 JPA和hibernate之間的關係

在這裏插入圖片描述

JPA是一套規範,內部是有接口和抽象類組成的。hibernate是一套成熟的ORM框架,而且Hibernate實現了JPA規範,所以也可以稱hibernate爲JPA的一種實現方式,我們使用JPA的API編程,意味着站在更高的角度上看待問題(面向接口編程)

Spring Data JPA是Spring提供的一套對JPA操作更加高級的封裝,是在JPA規範下的專門用來進行數據持久化的解決方案。

二、Spring Data JPA的快速入門

案例:客戶的基本CRUD:

i.搭建環境
	創建工程導入座標
	配置spring的配置文件(配置spring Data jpa的整合)
	編寫實體類(Customer),使用jpa註解配置映射關係
ii.編寫一個符合springDataJpa的dao層接口
	* 只需要編寫dao層接口,不需要編寫dao層接口的實現類
	* dao層接口規範
		1.需要繼承兩個接口(JpaRepository,JpaSpecificationExecutor)
		2.需要提供響應的泛型

* 
	findOne(id) 	:根據id查詢
	findAll() 		: 查詢全部
	save(customer)	:保存或者更新(依據:傳遞的實體類對象中,是否包含id屬性)
	delete(id) 	:根據id刪除

pom.xml:

<?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>top.onefine</groupId>
    <artifactId>jpa_day2</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.hibernate.version>5.4.14.Final</project.hibernate.version>
        <project.spring.version>5.2.5.RELEASE</project.spring.version>
        <project.slf4j.version>1.7.30</project.slf4j.version>
        <project.log4j.version>1.2.17</project.log4j.version>
        <project.c3p0.version>0.9.5.5</project.c3p0.version>
        <project.mysql.version>8.0.19</project.mysql.version>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>

        <!-- 以下兩個是spring aop相關的座標 -->
        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${project.spring.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${project.spring.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${project.spring.version}</version>
        </dependency>

        <!-- spring對orm框架的支持包 -->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${project.spring.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${project.spring.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${project.spring.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${project.hibernate.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${project.hibernate.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <!--            <version>6.1.4.Final</version>-->
            <version>5.4.3.Final</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/log4j/log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${project.log4j.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${project.slf4j.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${project.slf4j.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>${project.c3p0.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <!-- Mysql and MariaDB -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${project.mysql.version}</version>
        </dependency>

        <!-- spring data jpa 的座標 -->
        <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <!--            <version>2.2.6.RELEASE</version>-->
            <!--            <version>1.9.0.RELEASE</version>-->
            <version>1.11.23.RELEASE</version>
        </dependency>

        <!-- spring 提供的單元測試的座標 -->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${project.spring.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- 以下兩個 spring data jpa必須導入的座標 -->
        <!-- https://mvnrepository.com/artifact/javax.el/javax.el-api -->
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>2.2.4</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.glassfish.web/javax.el -->
        <dependency>
            <groupId>org.glassfish.web</groupId>
            <artifactId>javax.el</artifactId>
            <version>2.2.4</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

</project>

src\main\resources\applicationContext.xml:

<?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 和 spring data jpa 的配置 -->
    <!-- 1. 創建entityManagerFactory對象交給spring容器管理 -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <!-- 配置數據源 -->
        <property name="dataSource" ref="dataSource"/>

        <!-- 配置掃描的包——實體類所在的包 -->
        <property name="packagesToScan" value="top.onefine.domain"/>

        <!-- 配置jpa的實現廠家 -->
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
        </property>

        <!-- JPA的供應商適配器 -->
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!-- 配置是否自動創建數據庫表,這裏no -->
                <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.cj.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/study_eesy?serverTimezone=UTC" />
        <property name="user" value="root" />
        <property name="password" value="963123" />

        <!-- 連接池參數 -->
        <property name="initialPoolSize" value="5" />
        <property name="maxPoolSize" value="8" />
        <property name="checkoutTimeout" value="3000" />
    </bean>

    <!-- 3. 整合spring dataJpa
            dao接口所在包
    -->
    <jpa:repositories base-package="top.onefine.dao"
                      transaction-manager-ref="transactionManager"
                      entity-manager-factory-ref="entityManagerFactory" />

    <!-- 4. 配置事務管理器 -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <!-- 5. 配置聲明式事務 -->


    <!-- 6. 配置包掃描 -->
    <context:component-scan base-package="top.onefine" />
</beans>

src\main\java\top\onefine\domain\ Customer .java:

package top.onefine.domain;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import javax.persistence.*;

/**
 * 1. 實體類和表的映射關係
 *      -@Eitity
 *      -@Table
 * 2. 類中屬性和數據庫表中字段的映射關係
 *      -@Id 主鍵
 *      -@GeneratedValue 主鍵生成策略
 *      -@Column
 */
@Entity
@Table(name = "cst_customer")
@Data
@RequiredArgsConstructor
@NoArgsConstructor
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long custId;
    @Column(name = "cust_address")
    private String custAddress;
    @Column(name = "cust_industry")
    private String custIndustry;
    @Column(name = "cust_level")
    private String custLevel;
    @Column(name = "cust_name")
    @NonNull private String custName;
    @Column(name = "cust_phone")
    private String custPhone;
    @Column(name = "cust_source")
    @NonNull private String custSource;
}

src\main\java\top\onefine\dao\ CustomerDao.java:

Spring Data JPA是spring提供的一款對於數據訪問層(Dao層)的框架,使用Spring Data JPA,只需要按照框架的規範提供dao接口,不需要實現類就可以完成數據庫的增刪改查、分頁查詢等方法的定義,極大的簡化了我們的開發過程。

在Spring Data JPA中,對於定義符合規範的Dao層接口,我們只需要遵循以下幾點就可以了:

  1. 創建一個Dao層接口,並實現JpaRepository和JpaSpecificationExecutor

  2. 提供相應的泛型

package top.onefine.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import top.onefine.domain.Customer;

/**
 * 符合SpringDataJpa的dao層接口規範
 *      JpaRepository<操作的實體類類型, 實體類中主鍵屬性的類型>
 *          * 封裝了基本CURD操作
 *      JpaSpecificationExecutor<操作的實體類類型>
 *          * 封裝了複雜查詢如分頁操作
 */
public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {

}

src\test\java\top\onefine\dao\ CustomerDaoTest .java:

package top.onefine.dao;


import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import top.onefine.domain.Customer;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)  // 聲明spring提供的單元測試環境
@ContextConfiguration(locations = "classpath:applicationContext.xml")  // 指定spring容器的配置信息
public class CustomerDaoTest {

    @Autowired  // 從容器中獲取CustomerDao對象
    private CustomerDao customerDao;

    /**
     * 根據id查詢一個
     */
    @Test
    public void testFindOne() {
        Customer customer = customerDao.findOne(1L);  // 查詢:select
        System.out.println(customer);
    }

    /**
     * 查詢所有
     */
    @Test
    public void testFindAll() {
        List<Customer> customers = customerDao.findAll();  // 查詢:select
        System.out.println(customers);
    }

    /**
     * 保存或更新
     *      save根據傳遞的對象是否存在主鍵id,
     *      - 如果沒有id主鍵屬性則保存
     *      - 如果存在id主鍵屬性,根據id查詢數據,更新數據
     */
    @Test
    public void testSave() {
        Customer customer = new Customer("one fine", "unit test");
        customerDao.save(customer);  // 保存:insert
    }

    @Test
    public void testUpdate() {
        Customer customer = new Customer("one fine", "unit test");
        customer.setCustId(2L);
        customer.setCustPhone("12399880880");
        customerDao.save(customer);  // 更新:select + update
        // 注意:若customer對象中屬性值爲空,會將數據庫原來存在的字段值置爲空
    }

    /**
     * 根據id刪除
     */
    @Test
    public void testDelete() {
        customerDao.delete(4L);  // 刪除:select + delete
    }
}

參考配置:

<?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">
	
	<!-- 1.dataSource 配置數據庫連接池-->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="driverClass" value="com.mysql.jdbc.Driver" />
		<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa" />
		<property name="user" value="root" />
		<property name="password" value="111111" />
	</bean>
	
	<!-- 2.配置entityManagerFactory -->
	<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="packagesToScan" value="cn.itcast.entity" />
		<property name="persistenceProvider">
			<bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
		</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" />
				<property name="showSql" value="true" />
			</bean>
		</property>
		<property name="jpaDialect">
			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
		</property>
	</bean>
    
	
	<!-- 3.事務管理器-->
	<!-- JPA事務管理器  -->
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>
	
	<!-- 整合spring data jpa-->
	<jpa:repositories base-package="cn.itcast.dao"
		transaction-manager-ref="transactionManager"
		entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
		
	<!-- 4.txAdvice-->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="save*" propagation="REQUIRED"/>
			<tx:method name="insert*" propagation="REQUIRED"/>
			<tx:method name="update*" propagation="REQUIRED"/>
			<tx:method name="delete*" propagation="REQUIRED"/>
			<tx:method name="get*" read-only="true"/>
			<tx:method name="find*" read-only="true"/>
			<tx:method name="*" propagation="REQUIRED"/>
		</tx:attributes>
	</tx:advice>
	
	<!-- 5.aop-->
	<aop:config>
		<aop:pointcut id="pointcut" expression="execution(* cn.itcast.service.*.*(..))" />
		<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
	</aop:config>
	
	<context:component-scan base-package="cn.itcast"></context:component-scan>
		
	<!--組裝其它 配置文件-->
	
</beans>

三、Spring Data JPA的內部原理剖析

  1. 通過JdkDynamicAopProxy的invoke方法創建了一個動態代理對象
  2. SimpleJpaRepository當中封裝了JPA的操作(藉助JPA的api完成數據庫的CRUD)
  3. 通過hibernate完成數據庫操作(封裝了jdbc)

3.1 Spring Data JPA的常用接口分析

在客戶的案例中,我們發現在自定義的CustomerDao中,並沒有提供任何方法就可以使用其中的很多方法,那麼這些方法究竟是怎麼來的呢?答案很簡單,對於我們自定義的Dao接口,由於繼承了JpaRepository和JpaSpecificationExecutor,所以我們可以使用這兩個接口的所有方法。

在這裏插入圖片描述

在使用Spring Data JPA時,一般實現JpaRepository和JpaSpecificationExecutor接口,這樣就可以使用這些接口中定義的方法,但是這些方法都只是一些聲明,沒有具體的實現方式,那麼在 Spring Data JPA中它又是怎麼實現的呢?

3.2 Spring Data JPA的實現過程

通過對客戶案例,以debug斷點調試的方式,通過分析Spring Data JPA的原來來分析程序的執行過程

我們以findOne方法爲例進行分析

代理子類的實現過程

在這裏插入圖片描述

斷點執行到方法上時,我們可以發現注入的customerDao對象,本質上是通過JdkDynamicAopProxy生成的一個代理對象

代理對象中方法調用的分析

當程序執行的時候,會通過JdkDynamicAopProxy的invoke方法,對customerDao對象生成動態代理對象。根據對Spring Data JPA介紹而知,要想進行findOne查詢方法,最終還是會出現JPA規範的API完成操作,那麼這些底層代碼存在於何處呢?答案很簡單,都隱藏在通過JdkDynamicAopProxy生成的動態代理對象當中,而這個動態代理對象就是SimpleJpaRepository

在這裏插入圖片描述

通過SimpleJpaRepository的源碼分析,定位到了findOne方法,在此方法中,返回em.find()的返回結果,那麼em又是什麼呢?

在這裏插入圖片描述

帶着問題繼續查找em對象,我們發現em就是EntityManager對象,而他是JPA原生的實現方式,所以我們得到結論Spring Data JPA只是對標準JPA操作進行了進一步封裝,簡化了Dao層代碼的開發

3.3 Spring Data JPA完整的調用過程分析

在這裏插入圖片描述

動態分析:
在這裏插入圖片描述

spring data jpa的運行過程:
在這裏插入圖片描述

四、Spring Data JPA的查詢方式

4.1 使用Spring Data JPA中接口定義的方法進行查詢

在繼承JpaRepository,和JpaRepository接口後,我們就可以使用接口中定義的方法進行查詢。

  • 繼承JpaRepository後的方法列表
    在這裏插入圖片描述

  • 繼承JpaSpecificationExecutor的方法列表

在這裏插入圖片描述

栗子:

src\test\java\top\onefine\dao\CustomerDaoTest.java:

package top.onefine.dao;


import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import top.onefine.domain.Customer;

import javax.transaction.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)  // 聲明spring提供的單元測試環境
@ContextConfiguration(locations = "classpath:applicationContext.xml")  // 指定spring容器的配置信息
public class CustomerDaoTest {

    @Autowired  // 從容器中獲取CustomerDao對象
    private CustomerDao customerDao;

    /**
     * 測試統計查詢,查詢客戶的總數量
     *      count 統計記錄總條數
     */
    @Test
    public void testCount() {
        long count = customerDao.count();  //  查詢全部的客戶數量:select count(*)
        System.out.println(count);
    }

    /**
     * 判斷指定id的客戶是否存在,傳統方式:
     *      1. 可以查詢一下id爲指定值的客戶
     *          若值爲空,代表不存在;否則存在
     *      2. 判斷數據庫中id爲指定值的客戶數量
     *          若數量爲0,代表不存在;否則存在
     */
    @Test
    public void testExists() {
        boolean b = customerDao.exists(5L);  // select count(*)
        System.out.println(b ? "存在" : "不存在");
    }

    /**
     * 根據id從數據庫中查詢
     *      @Transactional: 事務,保證getOne方法正常運行
     *
     *  findOne:
     *      em.find()       : 立即加載
     *  getOne:
     *      em.getReference : 延遲加載
     *      - 返回的是一個客戶的動態代理對象
     *      - 什麼時候用,什麼時候查詢
     */
    @Test
    @Transactional
    public void testGetOne() {
        Customer customer = customerDao.getOne(4L);  // 不會發送查詢
        try {
            System.out.println(customer);  // 發送查詢
        } catch (Exception e) {
//            e.printStackTrace();
            System.out.println("查詢結果不存在");
        }
    }
}

4.2 使用JPQL的方式查詢

使用Spring Data JPA提供的查詢方法已經可以解決大部分的應用場景,但是對於某些業務來說,我們還需要靈活的構造查詢條件,這時就可以使用@Query註解,結合JPQL(jpa query language,jpq查詢語言)的語句方式完成查詢,@Query 註解的使用非常簡單,只需在方法上面標註該註解,同時提供一個JPQL查詢語句即可。此外,也可以通過使用 @Query 來執行一個更新操作,爲此,我們需要在使用 @Query 的同時,用 @Modifying來將該操作標識爲修改查詢,這樣框架最終會生成一個更新的操作,而非查詢。

栗子:

src\main\java\top\onefine\dao\CustomerDao.java:

package top.onefine.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import top.onefine.domain.Customer;

/**
 * 符合SpringDataJpa的dao層接口規範
 *      JpaRepository<操作的實體類類型, 實體類中主鍵屬性的類型>
 *          * 封裝了基本操作如CURD操作
 *      JpaSpecificationExecutor<操作的實體類類型>
 *          * 封裝了複雜查詢如分頁操作
 *
 *   jpql特點:語法或關鍵字和sql語句類似
 * 	            查詢的是類和類中的屬性
 *              需要將JPQL語句配置到接口方法上
 *
 * 	    1.特有的查詢:需要在dao接口上配置方法
 * 	    2.在新添加的方法上,使用註解的形式配置jpql查詢語句
 * 	    3.註解 : @Query
 */
public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
    /**
     * 使用jpql的形式查詢——根據客戶名稱查詢客戶
     *  jpql:from Customer where custName = ?
     *
     *  配置jpql語句,使用@Query註解
     */
    @Query(value = "from Customer where custName = ?1")
    public Customer findByJPQL(String custName);

    /**
     * 根據客戶名稱和客戶id查詢客戶
     *  指定佔位符參數的位置: ?+索引
     *  jpql:from Customer where custName = ?1 and custId = ?2
     */
//    @Query(value = "from Customer where custName = ?1 and custId = ?2")
//    public Customer findByJPQL_CustNameAndId(String name, Long id);
    @Query(value = "from Customer where custName = ?2 and custId = ?1")
    public Customer findByJPQL_CustNameAndId(Long id, String name);

    /**
     * 使用jqpl完成更新操作
     *      根據id更新客戶的name
     *  sql:update cst_customer set cust_name = ? where cust_id = ?
     *  jpql:update Customer set custName = ?1 where custId = ?2
     *
     * @Query表示進行查詢
     * 但此方法是用來進行更新操作的,用@Modifying表示當前執行的是一個更新操作
     */
    @Query(value = "update Customer set custName = ?2 where custId = ?1")
    @Modifying
    public void updateCustomer(long custId, String custName);
}

src\test\java\top\onefine\dao\CustomerDaoTest.java:

package top.onefine.dao;


import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import top.onefine.domain.Customer;

import javax.transaction.Transactional;


@RunWith(SpringJUnit4ClassRunner.class)  // 聲明spring提供的單元測試環境
@ContextConfiguration(locations = "classpath:applicationContext.xml")  // 指定spring容器的配置信息
public class CustomerDaoTest {

    @Autowired  // 從容器中獲取CustomerDao對象
    private CustomerDao customerDao;

    @Test
    public void testFindByJPQL() {
        Customer customer = customerDao.findByJPQL("one");  // select
        System.out.println(customer);
    }

    @Test
    public void testFindByJPQL_CustNameAndId() {
        Customer customer = customerDao.findByJPQL_CustNameAndId(7L, "one");  // select
        System.out.println(customer);
    }

    /**
     * 測試jpql的更新操作
     *   springdatajpa中使用jpql完成更新或者刪除操作時
     *      - 需要手動添加事務的支持
     *      - 默認情況是執行操作完成後,回滾事務(並不是提交事務)
     * @Rollback: 設置是否自動回滾事務
     */
    @Test
    @Transactional  // 添加事務的支持:執行更新或刪除要有事務
    @Rollback(value = false)  // 不回滾事務——提交事務
    public void testUpdateCustomer() {
        customerDao.updateCustomer(5L, "fine");  // update
    }
}

4.3 使用SQL語句查詢

1.特有的查詢:需要在dao接口上配置方法
2.在新添加的方法上,使用註解的形式配置sql查詢語句
3.註解 : @Query

  • value :jpql語句 | sql語句
  • nativeQuery :是否使用本地查詢(sql查詢),false(使用jpql查詢) | true(使用本地查詢:sql查詢)

栗子:

src\main\java\top\onefine\dao\CustomerDao.java:

package top.onefine.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import top.onefine.domain.Customer;

import java.util.List;

/**
 * 符合SpringDataJpa的dao層接口規範
 *      JpaRepository<操作的實體類類型, 實體類中主鍵屬性的類型>
 *          * 封裝了基本操作如CURD操作
 *      JpaSpecificationExecutor<操作的實體類類型>
 *          * 封裝了複雜查詢如分頁操作
 *
 */
public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
    /**
     * 使用sql的形式查詢全部客戶
     * sql:select * from cst_customer
     * @Query: 配置sql查詢
     *      value: sql語句
     *      nativeQuery: 指定查詢方式
     *          true: sql查詢
     *          false: jpql查詢
     *
     * @return List<Object []>
     */
    @Query(value = "select * from cst_customer", nativeQuery = true)
    public List<Object []> findAllBySQL();

    /**
     * 條件查詢全部:根據用戶名模糊匹配查詢
     */
    @Query(value = "select * from cst_customer where cust_name like ?1", nativeQuery = true)
    public List<Object []> findAllBySQL_Criteria(String name);
}

src\test\java\top\onefine\dao\CustomerDaoTest.java:

package top.onefine.dao;


import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import top.onefine.domain.Customer;

import javax.transaction.Transactional;
import java.util.Arrays;
import java.util.List;


@RunWith(SpringJUnit4ClassRunner.class)  // 聲明spring提供的單元測試環境
@ContextConfiguration(locations = "classpath:applicationContext.xml")  // 指定spring容器的配置信息
public class CustomerDaoTest {

    @Autowired  // 從容器中獲取CustomerDao對象
    private CustomerDao customerDao;

    @Test
    public void testFindAllBySQL() {
        List<Object[]> list = customerDao.findAllBySQL();
        for (Object [] customer : list)  // object類型的數組
            System.out.println(Arrays.toString(customer));
    }

    @Test
    public void testFindAllBySQL_CriteriaL() {
        List<Object[]> list = customerDao.findAllBySQL_Criteria("one%");  // 注意%
        for (Object [] customer : list)  // object類型的數組
            System.out.println(Arrays.toString(customer));
    }
}

4.4 方法命名規則查詢

顧名思義,方法命名規則查詢就是根據方法的名字,就能創建查詢。只需要按照Spring Data JPA提供的方法命名規則定義方法的名稱,就可以完成查詢工作。Spring Data JPA在程序執行的時候會根據方法名稱進行解析,並自動生成查詢語句進行查詢

按照Spring Data JPA 定義的規則,查詢方法以findBy開頭,涉及條件查詢時,條件的屬性用條件關鍵字連接,要注意的是:條件屬性首字母需大寫。框架在進行方法名解析時,會先把方法名多餘的前綴截取掉,然後對剩下部分進行解析。

//方法命名方式查詢(根據客戶名稱查詢客戶)
public Customer findByCustName(String custName);

栗子:
src\main\java\top\onefine\dao\CustomerDao.java:

package top.onefine.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import top.onefine.domain.Customer;

import java.util.List;

/**
 * 符合SpringDataJpa的dao層接口規範
 *      JpaRepository<操作的實體類類型, 實體類中主鍵屬性的類型>
 *          * 封裝了基本操作如CURD操作
 *      JpaSpecificationExecutor<操作的實體類類型>
 *          * 封裝了複雜查詢如分頁操作
 *
 * 方法名稱規則查詢
 *  - 是對jpql查詢更加深入的一層封裝
 *  - 只需要按照SpringDataJPA提供的方法名稱規則定義方法,不需要再去配置jpql語句,即可完成查詢
 *
 *  方法名的約定:
 *      findBy: 查詢
 *      findBy+對象中的屬性名(首字母大寫):查詢的條件——根據屬性名稱進行查詢
 *          如:屬性名 custName——findByCustName,根據客戶名稱查詢
 *
 *              在SpringDataJpa的運行階段,會根據方法名稱進行解析
 *                  findBy解析爲 from xxx(實體類)
 *                  CustName解析爲 where custName = // 默認使用 '=' 的方式查詢
 *                  即:findByCustName -> from Customer where custName = ?1
 *      1. findBy + 屬性名稱   :根據屬性名稱完成匹配的查詢,省略查詢方式
 *      2. findBy + 屬性名稱 + 查詢方式(Like | isnull),不省略查詢方式
 *          findByCustNameLike -> from Customer where custName like ?1
 *      3. 多條件查詢
 *          findBy + 屬性名稱 + 查詢方式(Like | isnull) + "多條件的連接符(and | or)" + 屬性名 + 查詢方式(Like | isnull),可省略查詢方式
 */
public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {

    public Customer findByCustName(String customerName);  // 參數名隨意

    public List<Customer> findByCustNameLike(String customerName);

    // 使用客戶名稱模糊匹配和客戶所屬行業精準匹配的查詢
    public List<Customer> findByCustNameLikeAndCustIndustry(String custName, String custIndustry);  // 參數順序不能調整,對應CustName和CustIndustry
}

src\test\java\top\onefine\dao\CustomerDaoTest.java:

package top.onefine.dao;


import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import top.onefine.domain.Customer;

import javax.transaction.Transactional;
import java.util.Arrays;
import java.util.List;


@RunWith(SpringJUnit4ClassRunner.class)  // 聲明spring提供的單元測試環境
@ContextConfiguration(locations = "classpath:applicationContext.xml")  // 指定spring容器的配置信息
public class CustomerDaoTest {

    @Autowired  // 從容器中獲取CustomerDao對象
    private CustomerDao customerDao;

    @Test
    public void testFindByCustName() {
        Customer customer = customerDao.findByCustName("one");
        System.out.println(customer);
    }

    @Test
    public void testFindByCustNameLike() {
        List<Customer> customers = customerDao.findByCustNameLike("one%");
        System.out.println(customers);
    }

    @Test
    public void testFindByCustNameLikeAndCustIndustry() {
        List<Customer> customers = customerDao.findByCustNameLikeAndCustIndustry("one%", "軟件開發");
        System.out.println(customers);
    }
}

具體的關鍵字,使用方法和生產成SQL如下表所示:

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