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)

猜你喜欢

转载自blog.csdn.net/jiduochou963/article/details/105549781