终于有人把Spring Data JPA 讲明白了 (必看指南 !)

目录

一、 ORM概述

二、 Hibernate与JPA概述

2.1 Hibernate概述

2.2 JPA概述

2.3 JPA与hibernate的关系

三、JPA的入门案例

3.1 导入jar包

3.2 搭建开发环境

3.2.1 传统工程导入jar包

3.2.2 创建客户的数据库表和客户的实体类

3.2.3 编写实体类和数据库表的映射配置[重点]

3.2.4 配置JPA的核心配置文件

 3.3 实现保存操作

四、JPA中的主键生成策略

五、JPA的API介绍

5.1 Persistence对象

5.2 EntityManagerFactory

5.3 EntityManager

5.4 EntityTransaction

六、抽取JPAUtil工具类

七、使用JPA完成增删改查操作

7.1 保存客户信息

7.2 修改信息

7.3 删除

7.4 查询

7.5 JPA中的复杂查询

7.5.1 查询全部

7.5.2 分页查询

7.5.3 条件查询

7.5.4 排序查询

7.5.5 统计查询

八、Spring Data JPA概述

8.1 Spring Data JPA的特性

8.2 Spring Data JPA 与 JPA和hibernate之间的关系

九、 Spring Data JPA的快速入门

9.1 需求说明

9.2 实现

9.2.1 Spring Data JPA的起步依赖

9.2.2 添加数据库驱动依赖

9.2.3 在application.yml中配置数据库和jpa的相关属性

9.2.4 使用JPA注解配置映射关系

9.3 使用Spring Data JPA完成需求

9.3.1 编写符合Spring Data JPA规范的Dao层接口

9.3.2 完成基本CRUD操作

十、 Spring Data JPA的内部原理剖析

10.1 Spring Data JPA的常用接口分析

10.2 Spring Data JPA的实现过程

10.3 Spring Data JPA完整的调用过程分析

十一、 Spring Data JPA的查询方式

11.1 使用Spring Data JPA中接口定义的方法进行查询

11.2 使用 JPQL 的方式查询

11.3 使用SQL语句查询

11.4 方法命名规则查询


一、 ORM概述

当面向对象的软件开发中,ORM(Object-Relational Mapping)起到了关键作用。ORM允许开发者使用面向对象的方式来处理数据库,而不必直接依赖于SQL语句。通过ORM框架,开发者可以定义实体类(对象),而框架会负责将这些对象映射到数据库中的表。这样,对实体类的操作就会被翻译成对数据库的操作。

主要的ORM框架,如Hibernate、MyBatis等,提供了各种映射和配置方式,以及对数据库操作的高级抽象。这样,开发者可以更专注于业务逻辑,而不必过于关注数据库底层的细节。ORM框架通常提供以下功能:

映射:将实体类映射到数据库表,每个类对应数据库中的一张表,类的属性对应表中的字段。

CRUD操作:提供对数据库的基本操作,包括创建(Create)、读取(Read)、更新(Update)、删除(Delete)。

事务管理:提供事务支持,确保对数据库的操作是原子的,要么全部执行,要么全部回滚。

查询语言:提供高级的查询语言,允许使用面向对象的查询语法而不是直接的SQL语句。

性能优化:提供一些性能优化的功能,例如缓存机制、延迟加载等。

ORM的使用使得数据库操作更加便捷,同时也提高了代码的可维护性。

二、 Hibernate与JPA概述

2.1 Hibernate概述

Hibernate是一个开源的对象关系映射框架,通过轻量级的封装提供了对JDBC的访问。它建立了Java对象(POJO)与数据库表之间的映射关系,实现了对象与数据库的无缝交互。Hibernate是一个全自动的ORM框架,能够自动生成SQL语句并执行,使得Java开发人员能够以更符合对象编程思维的方式来操作数据库。通过Hibernate,开发者可以更专注于业务逻辑的实现,而不必过多关注数据库操作的细节。

2.2 JPA概述

JPA(Java Persistence API)是Java持久化API的全称,是由SUN公司推出的一套基于对象关系映射(ORM)的规范。它由一系列接口和抽象类构成。

JPA的主要功能是通过注解描述对象与关系数据库表之间的映射关系,并负责将运行时的实体对象持久化到数据库中。通过这一规范,Java开发者可以更方便地进行对象的存储和检索,而不必直接编写大量的SQL语句。

使用JPA,开发者可以通过注解(如@Entity、@Table、@Column等)来配置实体类与数据库表的映射关系,还可以定义查询语句、事务管理等。这样,开发者在进行数据库操作时可以更专注于业务逻辑的实现,而不必过多关注数据库细节。JPA的出现为Java应用程序提供了更便捷、标准的持久化方案。

2.3 JPA与hibernate的关系

JPA的出现旨在提供一个标准的ORM解决方案,使得开发者可以更轻松地切换不同的ORM实现,而不用过多关心底层的细节。JPA规范本质上就是一种ORM规范。

因此,JPA并不是直接取代Hibernate,而是提供了一个抽象层,使得开发者可以选择不同的实现供应商(如Hibernate、EclipseLink等)。JPA和Hibernate是一种规范与实现之间的典型关系,JPA提供了一套接口和规则,而Hibernate是其中的一种具体实现。这种分离使得开发者可以更加灵活地选择和切换不同的ORM实现。

在具体的项目中,你可以选择使用JPA接口进行开发,而具体的ORM实现可以是Hibernate。

hibernate 文档

三、JPA的入门案例

3.1 导入jar包

我们选择Hibernate作为JPA的实现,所以需要导入Hibernate的相关jar包

下载网址

https://sourceforge.net/projects/hibernate/files/hibernate-orm/5.6.5.Final/

 

3.2 搭建开发环境

创建一个普通java项目,或者maven项目。

 导入jar包

对于JPA操作,只需要从下载的压缩包中找到我们需要的jar导入到工程中即可。

3.2.1 传统工程导入jar包

 maven工程导入坐标

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.hibernate.version>5.6.5.Final</project.hibernate.version>
    </properties>

    <dependencies>
        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        
        <dependency>
          <groupId>javax.xml.bind</groupId>
          <artifactId>jaxb-api</artifactId>
          <version>2.3.1</version>
        </dependency>
        <!-- hibernate对jpa的支持包 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${project.hibernate.version}</version>
        </dependency>
        <!-- c3p0 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-c3p0</artifactId>
            <version>${project.hibernate.version}</version>
        </dependency>
        <!-- log日志 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!-- Mysql and   MariaDB -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
    </dependencies> 

3.2.2 创建客户的数据库表和客户的实体类

1.创建客户的数据库表

/*创建客户表*/ 
CREATE TABLE cust_customer (
    cust_id BIGINT ( 32 ) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
    cust_name VARCHAR ( 32 ) NOT NULL COMMENT '客户名称(公司名称)',
    cust_source VARCHAR ( 32 ) DEFAULT NULL COMMENT '客户信息来源',
    cust_industry VARCHAR ( 32 ) DEFAULT NULL COMMENT '客户所属行业',
    cust_level VARCHAR ( 32 ) DEFAULT NULL COMMENT '客户级别',
    cust_address VARCHAR ( 128 ) DEFAULT NULL COMMENT '客户联系地址',
    cust_phone VARCHAR ( 64 ) DEFAULT NULL COMMENT '客户联系电话',
PRIMARY KEY ( `cust_id` ) 
) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;

2.创建客户的实体类 

@Data
public class Customer implements Serializable {

    private Long custId;
    private String custName;
    private String custSource;
    private String custIndustry;
    private String custLevel;
    private String custAddress;
    private String custPhone;
}

3.2.3 编写实体类和数据库表的映射配置[重点]

  1.  在实体类上使用JPA注解的形式配置映射关系

    package com.kaifamiao.entity;
    
    import lombok.Data;
    
    import javax.persistence.*;
    import java.io.Serializable;
    
    /**
     *     所有的注解都是使用JPA的规范提供的注解,
     */
    @Data
    @Entity //声明实体类
    @Table(name="cust_customer") //建立实体类和表的映射关系
    public class Customer implements Serializable {
    
        @Id  //声明当前私有属性为主键
        @GeneratedValue(strategy= GenerationType.IDENTITY) //配置主键的生成策略
        @Column(name="cust_id") //指定和表中cust_id字段的映射关系
        private Long custId;
    
        @Column(name="cust_name") //指定和表中cust_name字段的映射关系
        private String custName;
    
        @Column(name="cust_source")//指定和表中cust_source字段的映射关系
        private String custSource;
    
        @Column(name="cust_industry")//指定和表中cust_industry字段的映射关系
        private String custIndustry;
    
        @Column(name="cust_level")//指定和表中cust_level字段的映射关系
        private String custLevel;
    
        @Column(name="cust_address")//指定和表中cust_address字段的映射关系
        private String custAddress;
    
        @Column(name="cust_phone")//指定和表中cust_phone字段的映射关系
        private String custPhone;
    }
    

PS:常用注解的说明

注解作用
@Entity指定当前类是实体类
@Table指定实体类和表之间的对应关系
属性name :指定数据库表的名称
@Id指定当前字段是主键
@GeneratedValue指定主键的生成方式
属性strategy :指定主键生成策略。
@Column指定实体类属性和数据库表之间的对应关系
属性name:指定数据库表的列名称
unique:是否唯一
nullable:是否可以为空
inserttable:是否可以插入
updateable:是否可以更新
columnDefinition: 定义建表时创建此列的DDL
secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字搭建开发环境[重点]

3.2.4 配置JPA的核心配置文件

在java工程的src路径(maven项目在resouces目录)下创建一个名为META-INF的文件夹,在此文件夹下创建一个名为persistence.xml的配置文件。

<?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>
        <!-- 数据库驱动 -->
        <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
        <!-- 数据库地址 -->
        <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa"/>
        <!-- 数据库用户名 -->
        <property name="javax.persistence.jdbc.user" value="root"/>
        <!-- 数据库密码 -->
        <property name="javax.persistence.jdbc.password" value=""/>
        <!--jpa提供者的可选配置:我们的JPA规范的提供者为hibernate,所以jpa的核心配置中兼容hibernate的配   -->
        <!--  显示sql  -->
        <property name="hibernate.show_sql" value="true"/>
        <!--  格式化sql  -->
        <property name="hibernate.format_sql" value="true"/>
        <!--  create:表示启动的时候先drop,再create
create-drop: 也表示创来建,只不过再系统关自闭前执行一下drop
update: 这个操作启动的时候会去检查schema是否一致,如果不一致会做百scheme更新
validate: 启动时验证现有schema与你配置的hibernate是否一致,如果不一致就抛出异常度,并不做更新 -->
        <property name="hibernate.hbm2ddl.auto" value="create"/>
    </properties>
</persistence-unit>
</persistence>

 3.3 实现保存操作

@Test
public void test() {
    /**
         *  创建实体管理类工厂,借助Persistence的静态方法获取
         *  其中传递的参数为持久化单元名称,需要jpa配置文件中指定
         */
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
    //创建实体管理类
    EntityManager em = factory.createEntityManager();
    //获取事务对象
    EntityTransaction tx = em.getTransaction();
    //开启事务
    tx.begin();
    //创建对象
    Customer c = new   Customer();
    //设置值
    c.setCustName("云创动力");
    //保存操作
    em.persist(c);
    //提交事务
    tx.commit();
    //释放资源
    em.close();
    factory.close();
}

 

四、JPA中的主键生成策略

通过注解(annotation)来映射Hibernate实体时,使用@Id标识主键,其生成规则由@GeneratedValue设定。这里的@Id和@GeneratedValue都是JPA的标准用法。

JPA提供了四种标准的生成策略,分别是:TABLE、SEQUENCE、IDENTITY、AUTO。

具体说明如下:

  •  IDENTITY: 主键由数据库自动生成(主要是自增长型)

用法:

@Id  
@GeneratedValue(strategy = GenerationType.IDENTITY) 
private Long custId;
  • SEQUENCE :根据底层数据库的序列来生成主键,条件是数据库支持序列。

用法:

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator="payablemoney_seq")
@SequenceGenerator(name="payablemoney_seq", sequenceName="seq_payment")
private Long custId;
//@SequenceGenerator源码中的定义
@Target({TYPE, METHOD, FIELD})   
@Retention(RUNTIME)  
public @interface SequenceGenerator {  
    //表示该表主键生成策略的名称,它被引用在@GeneratedValue中设置的“generator”值中
    String name();  
    //属性表示生成策略用到的数据库序列名称。
    String sequenceName() default "";  
    //表示主键初识值,默认为1
    int initialValue() default 1;  
    //表示每次主键值增加的大小,例如设置1,则表示每次插入新记录后自动加1,默认为50
    int allocationSize() default 50;  
}

AUTO :主键由程序控制

用法:

@Id  
@GeneratedValue(strategy = GenerationType.AUTO)  
private Long custId;

TABLE :使用一个特定的数据库表格来保存主键

用法:

@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator="payablemoney_gen")
@TableGenerator(name = "payablemoney_gen",
    table="tb_generator",
    pkColumnName="gen_name",
    valueColumnName="gen_value",
    pkColumnValue="CUSTOMER_PK",
    allocationSize=1
)
private Long custId;
//@TableGenerator的定义:
@Target({TYPE, METHOD, FIELD})   
@Retention(RUNTIME)  
public @interface TableGenerator {  
    //表示该表主键生成策略的名称,它被引用在@GeneratedValue中设置的“generator”值中
    String name();  
    //表示表生成策略所持久化的表名,例如,这里表使用的是数据库中的“tb_generator”。
    String table() default "";  
    //catalog和schema具体指定表所在的目录名或是数据库名
    String catalog() default "";  
    String schema() default "";  
    //属性的值表示在持久化表中,该主键生成策略所对应键值的名称。例如在“tb_generator”中将“gen_name”作为主键的键值
    String pkColumnName() default "";  
    //属性的值表示在持久化表中,该主键当前所生成的值,它的值将会随着每次创建累加。例如,在“tb_generator”中将“gen_value”作为主键的值 
    String valueColumnName() default "";  
    //属性的值表示在持久化表中,该生成策略所对应的主键。例如在“tb_generator”表中,将“gen_name”的值为“CUSTOMER_PK”。 
    String pkColumnValue() default "";  
    //表示主键初识值,默认为0。 
    int initialValue() default 0;  
    //表示每次主键值增加的大小,例如设置成1,则表示每次创建新记录后自动加1,默认为50。
    int allocationSize() default 50;  
    UniqueConstraint[] uniqueConstraints() default {};  
} 

//这里应用表tb_generator,定义为 :
CREATE TABLE  tb_generator (
    id NUMBER NOT NULL,  
    gen_name VARCHAR2(255) NOT NULL,  
    gen_value NUMBER NOT NULL,  
    PRIMARY KEY(id)  
)

五、JPA的API介绍

JPA的API

5.1 Persistence对象

Persistence对象主要作用是用于获取EntityManagerFactory对象的 。通过调用该类的createEntityManagerFactory静态方法,根据配置文件中持久化单元名称创建EntityManagerFactory。

String unitName = "myJpa";
EntityManagerFactory factory = Persistence.createEntityManagerFactory(unitName);

5.2 EntityManagerFactory

EntityManagerFactory 接口主要用来创建 EntityManager 实例。

//创建实体管理类
EntityManager em = factory.createEntityManager();

由于EntityManagerFactory 是一个线程安全的对象(即多个线程访问同一个EntityManagerFactory 对象不会有线程安全问题),并且EntityManagerFactory 的创建极其浪费资源,所以在使用JPA编程时,我们可以对EntityManagerFactory 的创建进行优化,只需要做到一个工程只存在一个EntityManagerFactory 即可。‍

5.3 EntityManager

在JPA规范中,EntityManager是完成持久化操作的核心对象。实体类作为普通Java对象,只有在调用EntityManager将其持久化后才会变成持久化对象。EntityManager对象在一组实体类与底层数据源之间进行O/R映射的管理。它可以用来管理和更新Entity Bean,根据主键查找Entity Bean,还可以通过JPQL语句查询实体。

我们可以通过调用EntityManager的方法完成获取事务以及持久化数据库的操作。

方法说明:

getTransaction() 获取事务对象
persist() // 保存操作
merge() // 更新操作
remove() //删除操作
find/getReference() // 查询

5.4 EntityTransaction

在 JPA 规范中, EntityTransaction是完成事务操作的核心对象,对于EntityTransaction在我们的java代码中的功能比较简单。

begin:开启事务
commit:提交事务
rollback:回滚事务

六、抽取JPAUtil工具类

package com.kaifamiao.dao;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

/**
 * JPAUtil
 * @Version v1.0
 */
public final class JPAUtil {

    // JPA的实体管理器工厂:相当于Hibernate的SessionFactory
    private static EntityManagerFactory em;

    // 使用静态代码块赋值
    static {
        // 注意:该方法参数必须和persistence.xml中persistence-unit标签name属性取值一致
        em = Persistence.createEntityManagerFactory("myJpa");
    }

    /**
     * 使用管理器工厂生产一个管理器对象
     *
     * @return
     */
    public static EntityManager getEntityManager() {
        return em.createEntityManager();
    }

}

七、使用JPA完成增删改查操作

JPA操作时,所有的修改(新增、修改、删除)操作应该在事务中完成。

7.1 保存客户信息

@Test
public void testAdd(){
    // 定义对象
    Customer c = new Customer();
    c.setCustName("云创动力");
    c.setCustLevel("VIP客户");
    c.setCustSource("网络");
    c.setCustIndustry("开发喵");
    c.setCustAddress("西安市未央区");
    c.setCustPhone("150****7009");
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        // 获取实体管理对象
        em = JPAUtil.getEntityManager();
        // 获取事务对象
        tx = em.getTransaction();
        // 开启事务
        tx.begin();
        // 执行操作
        em.persist(c);
        // 提交事务
        tx.commit();
    } catch (Exception e) {
        // 回滚事务
        tx.rollback();
        e.printStackTrace();
    } finally {
        // 释放资源
        em.close();
    }

}

7.2 修改信息

@Test
public void testMerge(){
    //定义对象
    EntityManager em=null;
    EntityTransaction tx=null;
    try{
        //获取实体管理对象
        em=JPAUtil.getEntityManager();
        //获取事务对象
        tx=em.getTransaction();
        //开启事务
        tx.begin();
        //执行操作
        Customer c1 = em.find(Customer.class, 1L);
        c1.setCustName("西安云创动力");
        em.clear();//把c1对象从缓存中清除出去
        em.merge(c1);
        //提交事务
        tx.commit();
    }catch(Exception e){
        //回滚事务
        tx.rollback();
        e.printStackTrace();
    }finally{
        //释放资源
        em.close();
    }
}

7.3 删除

/**
     * 删除
     */
@Test
public void testRemove() {
    // 定义对象
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        // 获取实体管理对象
        em = JPAUtil.getEntityManager();
        // 获取事务对象
        tx = em.getTransaction();
        // 开启事务
        tx.begin();
        // 执行操作
        Customer c1 = em.find(Customer.class, 2L);
        em.remove(c1);
        // 提交事务
        tx.commit();
    } catch (Exception e) {
        // 回滚事务
        tx.rollback();
        e.printStackTrace();
    } finally {
        // 释放资源
        em.close();
    }
}

7.4 查询

/**
     * 查询一个: 使用立即加载的策略
     */
@Test
public void testGetOne() {
    // 定义对象
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        // 获取实体管理对象
        em = JPAUtil.getEntityManager();
        // 获取事务对象
        tx = em.getTransaction();
        // 开启事务
        tx.begin();
        // 执行操作
        Customer c1 = em.find(Customer.class, 1L);
        // 提交事务
        tx.commit();
        System.out.println(c1); // 输出查询对象
    } catch (Exception e) {
        // 回滚事务
        tx.rollback();
        e.printStackTrace();
    } finally {
        // 释放资源
        em.close();
    }
}

// 查询实体的缓存问题
@Test
public void testGetOne1() {
    // 定义对象
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        // 获取实体管理对象
        em = JPAUtil.getEntityManager();
        // 获取事务对象
        tx = em.getTransaction();
        // 开启事务
        tx.begin();
        // 执行操作
        Customer c1 = em.find(Customer.class, 1L);
        Customer c2 = em.find(Customer.class, 1L);
        System.out.println(c1 == c2);// 输出结果是true,EntityManager也有缓存
        // 提交事务
        tx.commit();
        System.out.println(c1);
    } catch (Exception e) {
        // 回滚事务
        tx.rollback();
        e.printStackTrace();
    } finally {
        // 释放资源
        em.close();
    }
}

// 延迟加载策略的方法:
/**
     * 查询一个: 使用延迟加载策略
     */
@Test
public void testLoadOne() {
    // 定义对象
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        // 获取实体管理对象
        em = JPAUtil.getEntityManager();
        // 获取事务对象
        tx = em.getTransaction();
        // 开启事务
        tx.begin();
        // 执行操作
        Customer c1 = em.getReference(Customer.class, 1L);
        // 提交事务
        tx.commit();
        System.out.println(c1);
    } catch (Exception e) {
        // 回滚事务
        tx.rollback();
        e.printStackTrace();
    } finally {
        // 释放资源
        em.close();
    }
} 

 

7.5 JPA中的复杂查询

JPQL全称Java Persistence Query Language。

Java持久化查询语言(JPQL)基于首次在EJB 2.0中引入的EJB查询语言(EJB QL)。JPQL是一种可移植的查询语言,旨在以面向对象表达式语言的形式将SQL语法和简单查询语义绑定在一起。使用这种语言编写的查询是可移植的,可以被编译成所有主流数据库服务器上的SQL。

JPQL的特征与原生SQL语句类似,并且完全面向对象。通过类名和属性访问,而不是表名和表的属性。

7.5.1 查询全部

//查询所有客户
@Test
public void findAll() {
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        //获取实体管理对象
        em = JPAUtil.getEntityManager();
        //获取事务对象
        tx = em.getTransaction();
        tx.begin();
        // 创建query对象
        String jpql = "from Customer";
        Query query = em.createQuery(jpql);
        // 查询并得到返回结果
        List list = query.getResultList(); // 得到集合返回类型
        for (Object object : list) {
            System.out.println(object);
        }
        tx.commit();
    } catch (Exception e) {
        // 回滚事务
        tx.rollback();
        e.printStackTrace();
    } finally {
        // 释放资源
        em.close();
    }
}

7.5.2 分页查询

//分页查询客户
@Test
public void findPaged () {
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        //获取实体管理对象
        em = JPAUtil.getEntityManager();
        //获取事务对象
        tx = em.getTransaction();
        tx.begin();

        //创建query对象
        String jpql = "from Customer";
        Query query = em.createQuery(jpql);
        //起始索引
        query.setFirstResult(0);
        //每页显示条数
        query.setMaxResults(2);
        //查询并得到返回结果
        List list = query.getResultList(); //得到集合返回类型
        for (Object object : list) {
            System.out.println(object);
        }
        tx.commit();
    } catch (Exception e) {
        // 回滚事务
        tx.rollback();
        e.printStackTrace();
    } finally {
        // 释放资源
        em.close();
    }
}

7.5.3 条件查询

//条件查询
@Test
public void findCondition () {
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        //获取实体管理对象
        em = JPAUtil.getEntityManager();
        //获取事务对象
        tx = em.getTransaction();
        tx.begin();
        //创建query对象
        String jpql = "from Customer where custName like ? ";
        Query query = em.createQuery(jpql);
        //对占位符赋值,从1开始
        query.setParameter(1, "%云创%");
        //查询并得到返回结果
        Object object = query.getSingleResult(); //得到唯一的结果集对象
        System.out.println(object);
        tx.commit();
    } catch (Exception e) {
        // 回滚事务
        tx.rollback();
        e.printStackTrace();
    } finally {
        // 释放资源
        em.close();
    }
}

7.5.4 排序查询

//根据客户id倒序查询所有客户
//查询所有客户
@Test
public void testOrder() {
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        //获取实体管理对象
        em = JPAUtil.getEntityManager();
        //获取事务对象
        tx = em.getTransaction();
        tx.begin();
        // 创建query对象
        String jpql = "from Customer order by custId desc";
        Query query = em.createQuery(jpql);
        // 查询并得到返回结果
        List list = query.getResultList(); // 得到集合返回类型
        for (Object object : list) {
            System.out.println(object);
        }
        tx.commit();
    } catch (Exception e) {
        // 回滚事务
        tx.rollback();
        e.printStackTrace();
    } finally {
        // 释放资源
        em.close();
    }
}

7.5.5 统计查询

//统计查询
@Test
public void findCount() {
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        //获取实体管理对象
        em = JPAUtil.getEntityManager();
        //获取事务对象
        tx = em.getTransaction();
        tx.begin();
        // 查询全部客户
        // 1.创建query对象
        String jpql = "select count(custId) from Customer";
        Query query = em.createQuery(jpql);
        // 2.查询并得到返回结果
        Object count = query.getSingleResult(); // 得到集合返回类型
        System.out.println(count);
        tx.commit();
    } catch (Exception e) {
        // 回滚事务
        tx.rollback();
        e.printStackTrace();
    } finally {
        // 释放资源
        em.close();
    }
}

 

八、Spring Data JPA概述

官网(https://spring.io/projects/spring-data-jpa,建议使用翻译功能后查看,体验更佳)

Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!

Spring Data JPA 让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现,在实际的工作工程中,推荐使用Spring Data JPA + ORM(如:hibernate)完成操作,这样在切换不同的ORM框架时提供了极大的方便,同时也使数据库层操作更加简单,方便解耦。

8.1 Spring Data JPA的特性

SpringData Jpa 极大简化了数据库访问层代码。如何简化的呢?使用了SpringDataJpa,我们的dao层中只需要写接口,就自动具有了增删改查、分页查询等方法。

8.2 Spring Data JPA 与 JPA和hibernate之间的关系

JPA是一套规范,内部是有接口和抽象类组成的。hibernate是一套成熟的ORM框架,而且Hibernate实现了JPA规范,所以也可以称hibernate为JPA的一种实现方式,我们使用JPA的API编程,意味着站在更高的角度上看待问题(面向接口编程)。

Spring Data JPA是Spring提供的一套对JPA操作更加高级的封装,是在JPA规范下的专门用来进行数据持久化的解决方案。

九、 Spring Data JPA的快速入门

9.1 需求说明

使用SpringBoot整合Spring Data JPA 完成客户的基本CRUD操作。

9.2 实现

9.2.1 Spring Data JPA的起步依赖

<!-- springBoot JPA的起步依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

9.2.2 添加数据库驱动依赖

<!-- MySQL连接驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.17</version>
</dependency>

9.2.3 在application.yml中配置数据库和jpa的相关属性

spring:
  # DB Configuration:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jpa
    username: root
    password:
    type: com.alibaba.druid.pool.DruidDataSource
    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    filters: wall,stat,log4j

  # jpa
  jpa:
    database: mysql
    show-sql: true
    generate-ddl: true
    hibernate:
      ddl-auto: update

9.2.4 使用JPA注解配置映射关系

我们使用之前案例中的Customer实体类对象,已经配置好了映射关系。

package com.kaifamiao.jpa.entity;

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;

/**
 * @Classname Customer
 *     所有的注解都是使用JPA的规范提供的注解,
 *     所以在导入注解包的时候,一定要导入javax.persistence下的
 * @Version v1.0
 */
@Data
@Entity //声明实体类
@Table(name="cust_customer") //建立实体类和表的映射关系
public class Customer implements Serializable {

//    @Id  //声明当前私有属性为主键
//    @GeneratedValue(strategy= GenerationType.IDENTITY) //配置主键的生成策略
//    @Column(name="cust_id") //指定和表中cust_id字段的映射关系
//    private Long custId;

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator="payablemoney_gen")
    @TableGenerator(name = "payablemoney_gen",
            table="tb_generator",
            pkColumnName="gen_name",
            valueColumnName="gen_value",
            pkColumnValue="CUSTOMER_PK",
            allocationSize=1
    )
    private Long custId;



    @Column(name="cust_name") //指定和表中cust_name字段的映射关系
    private String custName;

    @Column(name="cust_source")//指定和表中cust_source字段的映射关系
    private String custSource;

    @Column(name="cust_industry")//指定和表中cust_industry字段的映射关系
    private String custIndustry;

    @Column(name="cust_level")//指定和表中cust_level字段的映射关系
    private String custLevel;

    @Column(name="cust_address")//指定和表中cust_address字段的映射关系
    private String custAddress;

    @Column(name="cust_phone")//指定和表中cust_phone字段的映射关系
    private String custPhone;
}

9.3 使用Spring Data JPA完成需求

9.3.1 编写符合Spring Data JPA规范的Dao层接口

Spring Data JPA 是spring提供的一款对于数据访问层(Dao层)的框架,使用Spring Data JPA,只需要按照框架的规范提供dao接口,不需要实现类就可以完成数据库的增删改查、分页查询等方法的定义,极大的简化了我们的开发过程。

在Spring Data JPA中,对于定义符合规范的Dao层接口,我们只需要遵循以下几点就可以了:

  1. 1. 创建一个Dao层接口,并实现org.springframework.data.jpa.repository.JpaRepository和 org.springframework.data.jpa.repository.JpaSpecificationExecutor接口

  2. 2. 提供相应的泛型

package com.kaifamiao.jpa.dao;

import com.kaifamiao.jpa.entity.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

/**
 * @Classname CustomerDao
 * @Description JpaRepository<实体类类型,主键类型>:用来完成基本CRUD操作
 *               JpaSpecificationExecutor<实体类类型>:用于复杂查询(分页等查询操作)
 */
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
}

这样我们就定义好了一个符合Spring Data JPA规范的Dao层接口。

9.3.2 完成基本CRUD操作

完成了Spring Data JPA的环境搭建,并且编写了符合Spring Data JPA 规范的Dao层接口之后,就可以使用定义好的Dao层接口进行客户的基本CRUD操作。

package com.kaifamaio.jpa.dao;

import com.kaifamaio.jpa.entity.Customer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Optional;

@SpringBootTest
class CustomerDaoTest {

    @Autowired
    private CustomerDao customerDao;

    @Test
    public void findOne(){
        Optional<Customer> customer = customerDao.findById(3L);
        System.out.println(customer.get());
    }

    @Test
    public void findAll(){
        System.out.println(customerDao.findAll());
    }


    @Test
    public void save(){
        Customer customer = new Customer();
        customer.setCustName("spring-data");
        customer.setCustPhone("123123123");

        System.out.println(customerDao.save(customer));
    }

    @Test
    public void update(){
        Customer customer = customerDao.findById(3L).get();

        customer.setCustName(customer.getCustName() + "-update");

        customerDao.save(customer);
    }

    @Test
    public void delete(){
        customerDao.deleteById(3L);
    }

}

十、 Spring Data JPA的内部原理剖析

10.1 Spring Data JPA的常用接口分析

在客户的案例中,我们发现在自定义的CustomerDao中,并没有提供任何方法就可以使用其中的很多方法,那么这些方法究竟是怎么来的呢?答案很简单,对于我们自定义的Dao接口,由于继承了org.springframework.data.jpa.repository.JpaRepositoryorg.springframework.data.jpa.repository.JpaSpecificationExecutor,所以我们可以使用这两个接口的所有方法。

在使用Spring Data JPA时,一般继承 JpaRepository 和 JpaSpecificationExecutor 接口,这样就可以使用这些接口中定义的方法,但是这些方法都只是一些声明,没有具体的实现方式,那么在 Spring Data JPA中它又是怎么实现的呢?

10.2 Spring Data JPA的实现过程

通过对客户案例,以debug断点调试的方式,通过分析Spring Data JPA的原来来分析程序的执行过程。

我们以findOne方法为例进行分析:

代理子类的实现过程

断点执行到方法上时,我们可以发现注入的customerDao对象,本质上是通过JdkDynamicAopProxy生成的一个代理对象。

l代理对象中方法调用的分析

当程序执行的时候,会通过JdkDynamicAopProxy的invoke方法,对customerDao对象生成动态代理对象。根据对Spring Data JPA介绍而知,要想进行findOne查询方法,最终还是会出现 JPA 规范的API完成操作,那么这些底层代码存在于何处呢?答案很简单,都隐藏在通过JdkDynamicAopProxy生成的动态代理对象当中,而这个动态代理对象就是org.springframework.data.jpa.repository.support.SimpleJpaRepository

通过org.springframework.data.jpa.repository.support.SimpleJpaRepository的源码分析,定位到了findOne方法,在此方法中,返回em.find()的返回结果,那么em又是什么呢?

带着问题继续查找em对象,我们发现em就是EntityManager对象,而他是JPA原生的实现方式,所以我们得到结论Spring Data JPA只是对标准JPA操作进行了进一步封装,简化了Dao层代码的开发。

10.3 Spring Data JPA完整的调用过程分析

十一、 Spring Data JPA的查询方式

11.1 使用Spring Data JPA中接口定义的方法进行查询

在继承 JpaRepository 和 JpaSpecificationExecutor 接口后,我们就可以使用接口中定义的方法进行查询。

接口中的方法列表:

11.2 使用 JPQL 的方式查询

使用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询。

@Query 注解的使用非常简单,只需在方法上面标注该注解,同时提供一个JPQL查询语句即可。

public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {

    /**
     *  @Query 使用jpql的方式查询。
     */
    @Query(value="from Customer")
    public List<Customer> findAllCustomer();

    /**
     * @Query 使用jpql的方式查询。?1代表参数的占位符,其中1对应方法中的参数索引
     */
    @Query(value="from Customer where custName = ?1")
    public Customer findByCustName(String custName);

}

此外,也可以通过使用 @Query 来执行一个更新操作,为此,我们需要在使用 @Query 的同时,用 @Modifying 来将该操作标识为修改查询,这样框架最终会生成一个更新的操作,而非查询。

@Query(value="update Customer set custName = ?1 where custId = ?2")
@Modifying
@Transactional //让spirng来管理事务
public void updateCustomer(String custName,Long custId);

11.3 使用SQL语句查询

Spring Data JPA同样也支持sql语句的查询,如下:

/**
 * nativeQuery : 使用本地sql的方式查询
 */
@Query(value="select * from cust_customer",nativeQuery=true)
public List<Customer> findSql();

11.4 方法命名规则查询

顾名思义,方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照Spring Data JPA提供的方法命名规则定义方法的名称,就可以完成查询工作。Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询。

按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。

//方法命名方式查询(根据客户名称查询客户)
public Customer findByCustName(String custName);

具体的关键字,使用方法和生产成SQL如下表所示:

关键字举例JPQL
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstnameIs, findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age ⇐ ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1 (parameter bound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection age)… where x.age not in ?1
TRUEfindByActiveTrue()… where x.active = true
FALSEfindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = UPPER(?1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值