原生JPA以及SpringDataJPA学习使用记录

第1章 ORM

1.ORM概述[了解]

ORM(Object-Relational Mapping) 表示对象关系映射。在面向对象的软件开发中,通过ORM,就可以把对象映射到关系型数据库中。只要有一套程序能够做到建立对象与数据库的关联,操作对象就可以直接操作数据库数据,就可以说这套程序实现了ORM对象关系映射

简单的说:ORM就是建立实体类和数据库表之间的关系,从而达到操作实体类就相当于操作数据库表的目的。

1.1 为什么使用ORM

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z1sfUAMW-1610011107470)(JPA.assets/01-回顾jdbc操作以及引入orm.png)]

当实现一个应用程序时(不使用O/R Mapping),我们可能会写特别多数据访问层的代码,从数据库保存数据、修改数据、删除数据,而这些代码都是重复的。而使用ORM则会大大减少重复性代码。对象关系映射(Object Relational Mapping,简称ORM,主要实现程序对象到关系数据库数据的映射。

1.2 常见ORM框架

常见的ORM框架:Mybatis(ibatis)HibernateJpa

第2章 hibernate与JPA的概述[了解]

2.1 hibernate概述

Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的ORM框架,Hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。

2.2 JPA概述

JPA的全称是Java Persistence API, 即Java 持久化API,是SUN公司推出的一套基于ORM的规范,内部是由一系列的接口和抽象类构成。

JPA通过JDK 5.0注解描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。

2.3 JPA的优势

1. 标准化

JPA 是 JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的访问API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。

2. 容器级特性的支持

JPA框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的局限,在企业应用发挥更大的作用。

3. 简单方便

JPA的主要目标之一就是提供更加简单的编程模型:在JPA框架下创建实体和创建Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity进行注释,JPA的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。JPA基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成

4. 查询能力

JPA的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是Hibernate HQL的等价物。JPA定义了独特的JPQL(Java Persistence Query Language),JPQL是EJB QL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。

5. 高级特性

JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。

2.4 JPA与hibernate的关系

JPA规范本质上就是一种ORM规范注意不是ORM框架——因为JPA并未提供ORM实现,它只是制订了一些规范,提供了一些编程的API接口,但具体实现则由服务厂商来提供实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yUVuN8fJ-1610011107472)(JPA.assets/02-jpa.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DgsYebwH-1610011107473)(JPA.assets/clip_image002.jpg)]

JPA和Hibernate的关系就像JDBC和JDBC驱动的关系,JPA是规范,Hibernate除了作为ORM框架之外,它也是一种JPA实现。JPA怎么取代Hibernate呢?JDBC规范可以驱动底层数据库吗?答案是否定的,也就是说,如果使用JPA规范进行数据库操作,底层需要hibernate作为其实现类完成数据持久化工作。

第3章 JPA的入门案例

3.1 需求介绍

本章节我们是实现的功能是保存一个客户到数据库的客户表中。

3.2 开发包介绍

由于JPA是sun公司制定的API规范,所以我们不需要导入额外的JPA相关的jar包,只需要导入JPA的提供商的jar包。我们选择Hibernate作为JPA的提供商,所以需要导入Hibernate的相关jar包。

下载网址:

http://sourceforge.net/projects/hibernate/files/hibernate-orm/5.0.7.Final/

页面显示如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DJGwb6X5-1610011107474)(JPA.assets/clip_image002-1604631612509.jpg)]

3.3 搭建开发环境[重点]

3.3.1 导入jar包

l 传统工程导入jar包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-783twjj6-1610011107475)(JPA.assets/clip_image001.png)]

l maven工程导入坐标

<?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>org.example</groupId>
    <artifactId>JPADemo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!--hibernate对JPA的支持包-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.4.23.Final</version>
        </dependency>
        <!--数据库连接池-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-c3p0</artifactId>
            <version>5.4.23.Final</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

</project>

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

l 创建客户的数据库表(一般情况下不需要手动创建数据库表,使用hibernate.hbm2ddl.auto配置自动生成数据库表)

/*创建客户表*/
CREATE TABLE cst_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;

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

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

package com.phz.domain;

import javax.persistence.*;

/**
 * 客户的实体类
 *      配置映射关系
 *   1.实体类和表的映射关系
 *      @Entity:声明实体类
 *      @Table : 配置实体类和表的映射关系
 *          name : 配置数据库表的名称
 *   2.实体类中属性和表中字段的映射关系
 */
@Entity
@Table(name = "cst_customer")
public class Customer {

    /**
     * @Id:声明主键的配置
     * @GeneratedValue:配置主键的生成策略
     *      strategy
     *          GenerationType.IDENTITY :自增,mysql
     *				   * 如果原纪录删除后再插入,不会忽略已经删除的记录id。比如1,2,3中删除了3,再插入的时候新记录的id值为4
     *                 * 底层数据库必须支持自动增长(底层数据库支持的自动增长方式,对id自增)
     *          GenerationType.SEQUENCE : 序列,oracle(因为oracle数据库不支持自增)
     *                 * 底层数据库必须支持序列
     *          GenerationType.TABLE : jpa提供的一种机制,通过一张数据库表的形式帮助我们完成主键自增
     *          GenerationType.AUTO : 由程序自动的帮助我们选择主键生成策略
     * @Column:配置属性和字段的映射关系
     *      name:数据库表中字段的名称
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long custId; //客户的主键

    @Column(name = "cust_name")
    private String custName;//客户名称

    @Column(name="cust_source")
    private String custSource;//客户来源

    @Column(name="cust_level")
    private String custLevel;//客户级别

    @Column(name="cust_industry")
    private String custIndustry;//客户所属行业

    @Column(name="cust_phone")
    private String custPhone;//客户的联系方式

    @Column(name="cust_address")
    private String custAddress;//客户地址
	
	//getter,setter和toString
}

l 常用注解的说明

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

3.3.4 配置JPA的核心配置文件

在java工程的src路径下创建一个名为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/ssh" />
			<!-- 数据库用户名 -->
			<property name="javax.persistence.jdbc.user" value="root" />
			<!-- 数据库密码 -->
			<property name="javax.persistence.jdbc.password" value="123456" />
			<!--jpa提供者的可选配置:我们的JPA规范的提供者为hibernate,所以jpa的核心配置中兼容hibernate -->
			<property name="hibernate.show_sql" value="true" />
			<property name="hibernate.format_sql" value="true" />
			<property name="hibernate.hbm2ddl.auto" value="update" />
		</properties>
	</persistence-unit>
</persistence>

3.4 实现保存操作

package test;

import com.phz.domain.Customer;
import org.junit.Test;

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

/**
 * @author PengHuAnZhi
 * @createTime 2020/11/6 14:21
 * @projectName JPA
 * @className JPATest.java
 * @description TODO
 */
public class JPATest {
    @Test
    public void test1() {
        /**
         * 测试jpa的保存
         *      案例:保存一个客户到数据库中
         *  Jpa的操作步骤
         *     1.加载配置文件创建工厂(实体管理器工厂)对象
         *     2.通过实体管理器工厂获取实体管理器
         *     3.获取事务对象,开启事务
         *     4.完成增删改查操作
         *     5.提交事务(回滚事务)
         *     6.释放资源
         */
        //记载配置文件创建工厂(实体管理器工厂)对象,“myJpa为核心配置文件中声明的persistence-unit的名字,可自定义”
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
        //创建实体管理类
        EntityManager entityManager = factory.createEntityManager();
        //获取事务对象
        EntityTransaction entityTransaction = entityManager.getTransaction();
        //开启事务
        entityTransaction.begin();
        Customer customer = new Customer();
        customer.setCustName("彭焕智");
        //保存操作
        entityManager.persist(customer);
        //提交事务
        entityTransaction.commit();
        //释放资源
        entityManager.close();
        factory.close();
    }
}

第4章 JPA中的主键生成策略

通过annotation(注解)来映射hibernate实体的,基于annotationhibernate主键标识为@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 "";  
    //表示主键初识值,默认为0
    int initialValue() default 0;  
    //表示每次主键值增加的大小,例如设置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 = "pk_gen",  
                table="tb_generator",  
                pkColumnName="gen_name",  
                valueColumnName="gen_value",  
                pkColumnValue="PAYABLEMOENY_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)  
    )

第5章 JPA的API介绍

5.1 Persistence对象

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

//1. 创建 EntitymanagerFactory
@Test
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的方法完成获取事务,以及持久化数据库的操作

方法说明:

API 解释
getTransaction 获取事务对象
persist 保存操作
merge 更新操作
remove 删除操作
find/getReference 根据id查询

5.4 EntityTransaction

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

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

第6章 抽取JPAUtil工具类

package com.phz.utils;

/**
 * @author PengHuAnZhi
 * @createTime 2020/11/6 15:21
 * @projectName JPA
 * @className JpaUtils.java
 * @description TODO
 */

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

/**
 * 解决实体管理器工厂的浪费资源和耗时问题
 *      通过静态代码块的形式,当程序第一次访问此工具类时,创建一个公共的实体管理器工厂对象
 *
 * 第一次访问getEntityManager方法:经过静态代码块创建一个factory对象,再调用方法创建一个EntityManager对象
 * 第二次方法getEntityManager方法:直接通过一个已经创建好的factory对象,创建EntityManager对象
 */
public class JpaUtils {
    private static EntityManagerFactory entityManagerFactory = null;
    static {
        if(entityManagerFactory == null){
            entityManagerFactory = Persistence.createEntityManagerFactory("myJpa");
        }
    }
    /**
     * 获取EntityManager对象
     */
    public static EntityManager getEntityManager(){
        return entityManagerFactory.createEntityManager();
    }
}

第7章 使用JPA完成增删改查操作

7.1 保存

/**
 * 测试jpa的保存
 *      案例:保存一个客户到数据库中
 *  Jpa的操作步骤
 *     1.加载配置文件创建工厂(实体管理器工厂)对象
 *     2.通过实体管理器工厂获取实体管理器
 *     3.获取事务对象,开启事务
 *     4.完成增删改查操作
 *     5.提交事务(回滚事务)
 *     6.释放资源
 */
@Test
public void testSave() {
    /**
     * 测试jpa的保存
     *      案例:保存一个客户到数据库中
     *  Jpa的操作步骤
     *     1.加载配置文件创建工厂(实体管理器工厂)对象
     *     2.通过实体管理器工厂获取实体管理器
     *     3.获取事务对象,开启事务
     *     4.完成增删改查操作
     *     5.提交事务(回滚事务)
     *     6.释放资源
     */
    //这里使用工具类创建实体管理类
    EntityManager entityManager = JpaUtils.getEntityManager();
    //获取事务对象
    EntityTransaction tx = null;
    try {
        tx = entityManager.getTransaction();
        tx.begin();
        Customer customer = new Customer();
        customer.setCustName("彭焕智");
        //保存操作
        entityManager.persist(customer);
        //提交事务
        tx.commit();
    } catch (Exception e) {
        tx.rollback();
        e.printStackTrace();
    } finally {
        //释放资源
        entityManager.close();
    }
}

7.2 修改

/**
 * 更新客户的操作
 * merge(Object)
 */
@Test
public void testUpdate() {
    //1.通过工具类获取entityManager
    EntityManager entityManager = JpaUtils.getEntityManager();
    //2.开启事务
    EntityTransaction tx = null;
    try {
        tx = entityManager.getTransaction();
        tx.begin();
        //3.增删改查 -- 更新操作

        //i 查询客户
        Customer customer = entityManager.find(Customer.class, 1L);
        //ii 更新客户
        customer.setCustIndustry("it教育");
        entityManager.merge(customer);

        //4.提交事务
        tx.commit();
    }catch (Exception e){
        tx.rollback();
        e.printStackTrace();
    }finally {
        //5.释放资源
        entityManager.close();
    }
}

7.3 删除

/**
 * 删除客户的案例
 */
@Test
public void testRemove() {
    //1.通过工具类获取entityManager
    EntityManager entityManager = JpaUtils.getEntityManager();
    //2.开启事务
    EntityTransaction tx = null;
    try {
        tx = entityManager.getTransaction();
        tx.begin();
        //3.增删改查 -- 删除客户

        //i 根据id查询客户
        Customer customer = entityManager.find(Customer.class, 1L);
        //ii 调用remove方法完成删除操作
        entityManager.remove(customer);

        //4.提交事务
        tx.commit();
    }catch (Exception e){
        tx.rollback();
    }finally {
        //5.释放资源
        entityManager.close();
    }
}

7.4 根据id查询

/**
 * 根据id查询客户
 * 使用find方法查询:
 * 1.查询的对象就是当前客户对象本身
 * 2.在调用find方法的时候,就会发送sql语句查询数据库
 * 立即加载
 */
@Test
public void testFind() {
    //1.通过工具类获取entityManager
    EntityManager entityManager = JpaUtils.getEntityManager();
    //2.开启事务
    EntityTransaction tx = null;
    try {
        tx = entityManager.getTransaction();
        tx.begin();
        //3.增删改查 -- 根据id查询客户
        /**
         * find : 根据id查询数据
         *      class:查询数据的结果需要包装的实体类类型的字节码
         *      id:查询的主键的取值
         */
        Customer customer = entityManager.find(Customer.class, 1L);
        // System.out.print(customer);
        //4.提交事务
        tx.commit();
    } catch (Exception e) {
        tx.rollback();
        e.printStackTrace();
    } finally {
        //5.释放资源
        entityManager.close();
    }
}
/**
 * 根据id查询客户
 * getReference方法
 * 1.获取的对象是一个动态代理对象
 * 2.调用getReference方法不会立即发送sql语句查询数据库
 * * 当调用查询结果对象的时候,才会发送查询的sql语句:什么时候用,什么时候发送sql语句查询数据库
 * 延迟加载(懒加载)
 * * 得到的是一个动态代理对象
 * * 什么时候用,什么使用才会查询
 */
@Test
public void testReference() {
    //1.通过工具类获取entityManager
    EntityManager entityManager = JpaUtils.getEntityManager();
    //2.开启事务
    EntityTransaction tx = null;
    try {
        tx = entityManager.getTransaction();
        tx.begin();
        //3.增删改查 -- 根据id查询客户
        /**
         * getReference : 根据id查询数据
         *      class:查询数据的结果需要包装的实体类类型的字节码
         *      id:查询的主键的取值
         */
        Customer customer = entityManager.getReference(Customer.class, 1L);
        System.out.print(customer);
        //4.提交事务
        tx.commit();
    } catch (Exception e) {
        tx.rollback();
        e.printStackTrace();
    } finally {
        //5.释放资源
        entityManager.close();
    }
}

7.5 JPA中的复杂查询

JPQL全称Java Persistence Query Language

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

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

7.5.1 查询全部

/**
 * 查询全部
 * jqpl:from cn.itcast.domain.Customer
 * sql:SELECT * FROM cst_customer
 */
@Test
public void testFindAll() {
    //1.获取entityManager对象
    EntityManager em = JpaUtils.getEntityManager();
    //2.开启事务
    EntityTransaction tx = null;
    try {
        tx = em.getTransaction();
        tx.begin();
        //3.查询全部
        String jpql = "from Customer ";
        Query query = em.createQuery(jpql);//创建Query查询对象,query对象才是执行jqpl的对象

        //发送查询,并封装结果集
        List list = query.getResultList();

        for (Object obj : list) {
            System.out.print(obj);
        }

        //4.提交事务
        tx.commit();
    } catch (Exception e) {
        tx.rollback();
        e.printStackTrace();
    } finally {
        //5.释放资源
        em.close();
    }
}

7.5.2 分页查询

/**
 * 分页查询
 * sql:select * from cst_customer limit 0,2
 * jqpl : from Customer
 */
@Test
public void testPaged() {
    //1.获取entityManager对象
    EntityManager em = JpaUtils.getEntityManager();
    //2.开启事务
    EntityTransaction tx = null;
    try {
        tx = em.getTransaction();
        tx.begin();
        //3.查询全部
        //i.根据jpql语句创建Query查询对象
        String jpql = "from Customer";
        Query query = em.createQuery(jpql);
        //ii.对参数赋值 -- 分页参数
        //起始索引
        query.setFirstResult(0);
        //每页查询的条数
        query.setMaxResults(2);
        //iii.发送查询,并封装结果

        /**
         * getResultList : 直接将查询结果封装为list集合
         * getSingleResult : 得到唯一的结果集
         */
        List list = query.getResultList();

        for (Object obj : list) {
            System.out.println(obj);
        }

        //4.提交事务
        tx.commit();
    }catch (Exception e){
        tx.rollback();
        e.printStackTrace();
    }finally {
        //5.释放资源
        em.close(); 
    }
}

7.5.3 条件查询

/**
 * 条件查询
 * 案例:查询客户名称以‘传智播客’开头的客户
 * sql:SELECT * FROM cst_customer WHERE cust_name LIKE  ?
 * jpql : from Customer where custName like ?
 */
@Test
public void testCondition() {
    //1.获取entityManager对象
    EntityManager em = JpaUtils.getEntityManager();
    //2.开启事务
    EntityTransaction tx = em.getTransaction();
    tx.begin();
    //3.查询全部
    //i.根据jpql语句创建Query查询对象,新版的Hibernate已经对占位符做了更新
    String jpql = "from Customer where custName like ?2";
    Query query = em.createQuery(jpql);
    //ii.对参数赋值 -- 占位符参数
    //第一个参数:?后面接的数字,第二个参数:取值
    query.setParameter(2, "彭焕智%");

    //iii.发送查询,并封装结果

    /**
     * getResultList : 直接将查询结果封装为list集合
     * getSingleResult : 得到唯一的结果集
     */
    List list = query.getResultList();

    for (Object obj : list) {
        System.out.println(obj);
    }

    //4.提交事务
    tx.commit();
    //5.释放资源
    em.close();
}

7.5.4 排序查询(倒序)

/**
 * 排序查询: 倒序查询全部客户(根据id倒序)
 * sql:SELECT * FROM cst_customer ORDER BY cust_id DESC
 * jpql:from Customer order by custId desc
 * <p>
 * 进行jpql查询
 * 1.创建query查询对象
 * 2.对参数进行赋值
 * 3.查询,并得到返回结果
 */
@Test
public void testOrders() {
    //1.获取entityManager对象
    EntityManager em = JpaUtils.getEntityManager();
    //2.开启事务
    EntityTransaction tx = null;
    try {
        tx = em.getTransaction();
        tx.begin();
        //3.查询全部
        String jpql = "from Customer order by custId desc";
        Query query = em.createQuery(jpql);//创建Query查询对象,query对象才是执行jqpl的对象

        //发送查询,并封装结果集
        List list = query.getResultList();

        for (Object obj : list) {
            System.out.println(obj);
        }

        //4.提交事务
        tx.commit();
    } catch (Exception e) {
        tx.rollback();
        e.printStackTrace();
    } finally {
        //5.释放资源
        em.close();
    }
}

7.5.5 统计查询

/**
 * 使用jpql查询,统计客户的总数
 * sql:SELECT COUNT(cust_id) FROM cst_customer
 * jpql:select count(custId) from Customer
 */
@Test
public void testCount() {
    //1.获取entityManager对象
    EntityManager em = JpaUtils.getEntityManager();
    //2.开启事务
    EntityTransaction tx = null;
    try {
        tx = em.getTransaction();
        tx.begin();
        //3.查询全部
        //i.根据jpql语句创建Query查询对象
        String jpql = "select count(custId) from Customer";
        Query query = em.createQuery(jpql);
        //ii.对参数赋值
        //iii.发送查询,并封装结果

        /**
         * getResultList : 直接将查询结果封装为list集合
         * getSingleResult : 得到唯一的结果集
         */
        Object result = query.getSingleResult();

        System.out.println(result);

        //4.提交事务
        tx.commit();
    } catch (Exception e) {
        tx.rollback();
        e.printStackTrace();
    } finally {
        //5.释放资源
        em.close();
    }
}

第8章 Spring Data JPA的概述

8.1 Spring Data JPA概述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NGTozuDB-1610011107477)(JPA.assets/clip_image002-1604631684302.jpg)]

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

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

8.2 Spring Data JPA的特性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CAugZke4-1610011107478)(JPA.assets/clip_image002-1604631690826.jpg)]

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IH9yHz5Y-1610011107479)(JPA.assets/image-20201106164839261.png)]

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

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

第9章 Spring Data JPA的快速入门

9.1 需求说明

Spring Data JPA完成客户的基本CRUD操作

9.2 搭建Spring Data JPA的开发环境

9.2.1 引入Spring Data JPA的坐标

使用Spring Data JPA,需要整合SpringSpring Data JPA,并且需要提供JPA的服务提供者hibernate,所以需要导入spring相关坐标,hibernate坐标,数据库驱动坐标等

<?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>org.example</groupId>
    <artifactId>SpringDataJpaDemo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!-- junit单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>compile</scope>
        </dependency>

        <!-- spring aop -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.8</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.0</version>
        </dependency>

        <!--spring ioc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.0</version>
        </dependency>

        <!--spring对orm框架的支持包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.3.0</version>
        </dependency>

        <!--hibernate-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.4.23.Final</version>
        </dependency>

        <!--hibernate对jpa实现的支持包-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.4.23.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.1.6.Final</version>
        </dependency>

        <!-- c3p0连接池 -->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>

        <!-- 日志-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.30</version>
            <scope>test</scope>
        </dependency>

        <!--mysql的驱动包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!--Spring Data Jpa坐标-->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>1.9.0.RELEASE</version>
        </dependency>

        <!--Spring提供的进行单元测试的一个jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.0</version>
            <scope>test</scope>
        </dependency>

        <!--使用Spring data jpa必须导入的两个el包,不然会报错(亲测版本不对都报错)-->
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>3.0.1-b01</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.el</artifactId>
            <version>3.0.1-b08</version>
        </dependency>
    </dependencies>
</project>

9.2.2 整合Spring Data JPA与Spring

<?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="entityManagerFactoty" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--配置的扫描的包(实体类所在的包)-->
        <property name="packagesToScan" value="com.phz.entity"/>
        <!-- jpa的实现厂家 -->
        <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"/>
                <!--数据库方言:支持的特有语法,sql语句出现type=MyISAM的时候报错,就是因为方言和我的数据库版本不匹配的问题 -->
                <property name="databasePlatform" value="org.hibernate.dialect.MySQL5Dialect"/>
                <!--是否显示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="user" value="root"/>
        <property name="password" value="123456"/>
        <property name="jdbcUrl" value="jdbc:mysql:///jpa_test?characterEncoding=utf-8"/>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    </bean>
    <!--3.整合spring data Jpa-->
    <jpa:repositories base-package="com.phz.dao" transaction-manager-ref="transactionManager"
                      entity-manager-factory-ref="entityManagerFactoty"/>
    <!--4.配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactoty"/>
    </bean>
    <!-- 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(* com.phz.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
    </aop:config>
    <!--5.声明式事务 -->
    <!-- 6. 配置包扫描-->
    <context:component-scan base-package="com.phz"/>
</beans>

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

package com.phz.entity;

/**
 * @author PengHuAnZhi
 * @createTime 2020/11/6 20:32
 * @projectName JPA
 * @className Customer.java
 * @description TODO
 */

import javax.persistence.*;

/**
 * 1.实体类和表的映射关系
 *
 * @Eitity
 * @Table 2.类中属性和表中字段的映射关系
 * @Id
 * @GeneratedValue
 * @Column
 */
@Entity
@Table(name = "cst_customer")
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")
    private String custName;
    @Column(name = "cust_phone")
    private String custPhone;
    @Column(name = "cust_source")
    private String custSource;
	
    //getter,setter and toString
}

9.3 使用Spring Data JPA完成需求

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

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

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

1.创建一个Dao层接口,并实现JpaRepositoryJpaSpecificationExecutor

2.提供相应的泛型

package com.phz.dao;

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

/**
 * @author PengHuAnZhi
 * @createTime 2020/11/6 20:53
 * @projectName JPA
 * @className CustomerDao.java
 * @description TODO
 */
/**
 * 符合SpringDataJpa的dao层接口规范
 *      JpaRepository<操作的实体类类型,实体类中主键属性的类型>
 *          * 封装了基本CRUD操作
 *      JpaSpecificationExecutor<操作的实体类类型>
 *          * 封装了复杂查询(分页)
 */
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {}

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

9.3.2 完成基本CRUD操作

插一句嘴,当执行单元测试的时候,出现执行一个test方法但是所有test方法都执行,应该是IDEA设置的问题,如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m4ZKI6jQ-1610011107479)(JPA.assets/image-20201107143345073.png)]

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

package test;

import com.phz.dao.CustomerDao;
import com.phz.entity.Customer;
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 org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * @author PengHuAnZhi
 * @createTime 2020/11/6 21:03
 * @projectName JPA
 * @className CustomerDaoTest.java
 * @description TODO
 */

@RunWith(SpringJUnit4ClassRunner.class) //声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class CustomerDaoTest {
    @Autowired
    private CustomerDao customerDao;

    /**
     * 根据id查询
     */
    @Test
    public void testFindOne() {
        Customer customer = customerDao.findOne(4l);
        System.out.println(customer);
    }

    /**
     * save : 保存或者更新
     *      根据传递的对象是否存在主键id,
     *      如果没有id主键属性:保存
     *      存在id主键属性,根据id查询数据,更新数据
     */
    @Test
    public void testSave() {
        Customer customer  = new Customer();
        customer.setCustName("彭焕智");
        customer.setCustLevel("vip");
        customer.setCustIndustry("it");
        customerDao.save(customer);
    }

    @Test
    public void testUpdate() {
        Customer customer  = new Customer();
        customer.setCustId(4l);
        customer.setCustName("老彭老牛逼了");
        customerDao.save(customer);
    }

    @Test
    public void testDelete () {
        customerDao.delete(3l);
    }


    /**
     * 查询所有
     */
    @Test
    public void testFindAll() {
        List<Customer> list = customerDao.findAll();
        for(Customer customer : list) {
            System.out.println(customer);
        }
    }

    /**
     * 测试统计查询:查询客户的总数量
     *      count:统计总条数
     */
    @Test
    public void testCount() {
        long count = customerDao.count();//查询全部的客户数量
        System.out.println(count);
    }

    /**
     * 测试:判断id为4的客户是否存在
     *      1. 可以查询以下id为4的客户
     *          如果值为空,代表不存在,如果不为空,代表存在
     *      2. 判断数据库中id为4的客户的数量
     *          如果数量为0,代表不存在,如果大于0,代表存在
     */
    @Test
    public void  testExists() {
        boolean exists = customerDao.exists(4l);
        System.out.println("id为4的客户 是否存在:"+exists);
    }


    /**
     * 根据id从数据库查询
     *      @Transactional : 保证getOne正常运行而不报错,毕竟是懒加载嘛
     *
     *  findOne:
     *      em.find()           :立即加载
     *  getOne:
     *      em.getReference     :延迟加载
     *      * 返回的是一个客户的动态代理对象
     *      * 什么时候用,什么时候查询
     */
    @Test
    @Transactional
    public void  testGetOne() {
        Customer customer = customerDao.getOne(4l);
        System.out.println(customer);
    }
}

第10章 Spring Data JPA的内部原理剖析

10.1 Spring Data JPA的常用接口分析

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sLn3NK6Z-1610011107480)(JPA.assets/image-20201107153820200.png)]

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

在程序的执行过程中,自动的帮我们生成了接口类对象的实现类

10.2 Spring Data JPA的实现过程

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

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

l 代理子类的实现过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p80Xzyod-1610011107481)(JPA.assets/image-20201107145426427.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-26qImHPf-1610011107481)(JPA.assets/image-20201107145730509.png)]

进一步查看customerDao对象发现是由jdkDynamicAopProxy帮我自动创建的,而spring AOP内部就是通过动态代理的方式创建对象

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R9o2OXGn-1610011107482)(JPA.assets/image-20201107150422319.png)]

继续断点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CFBJJgYP-1610011107483)(JPA.assets/image-20201107150846806.png)]

现在进入SimpleJpaRepository源码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0FxeCF6o-1610011107483)(JPA.assets/image-20201107151045745.png)]

发现这个动态代理对象也实现了JpaRepository,和JpaSpecificationExecutor,也就相当于是CustomerDao的一个实现类。

继续打断点,通过SimpleJpaRepository的源码分析,定位到了findOne方法,在此方法中,返回em.find()的返回结果,那么em又是什么呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dBwawghQ-1610011107484)(JPA.assets/image-20201107151405682.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YOhqUXEG-1610011107484)(JPA.assets/image-20201107151525829.png)]

总结:

1、通过JDKDynamicAopProxy创建动态代理对象

2、动态代理对象:SimpleJpaRepository

3、findOne():通过EntityManager完成查询操作

为什么Spring data Jpa知道应该给我们动态生成一个CustomerDao对象呢??

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X0E71Vsb-1610011107485)(JPA.assets/image-20201107152314432.png)]

表示要对配置中的dao包下的接口进行动态代理增强!

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6i36suQ6-1610011107485)(JPA.assets/clip_image001-1604631789107.png)]

  • spring Data Jpa的运行过程和原理剖析
    1.通过JdkDynamicAopProxyinvoke方法创建了一个动态代理对象
    2.SimpleJpaRepository当中封装了JPA的操作(借助JPAapi完成数据库的CRUD
    3.通过hibernate完成数据库操作(封装了jdbc

第11章 Spring Data JPA的查询方式

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

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

l 继承JpaRepository后的方法列表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-75cemGIU-1610011107486)(JPA.assets/image-20201107153155550.png)]

l 继承JpaSpecificationExecutor的方法列表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SPrPVKzf-1610011107487)(JPA.assets/image-20201107153214914.png)]

11.2 使用JPQL的方式查询

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

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

package com.phz.dao;

import com.phz.entity.Customer;
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 java.util.List;

/**
 * @author PengHuAnZhi
 * @createTime 2020/11/6 20:53
 * @projectName JPA
 * @className CustomerDao.java
 * @description TODO
 */
public interface CustomerDao extends JpaRepository<Customer,Long> ,JpaSpecificationExecutor<Customer> {

    /**
     * 案例:根据客户名称查询客户
     *      使用jpql的形式查询
     *  jpql:from Customer where custName = ?
     *
     *  配置jpql语句,使用的@Query注解
     */
    @Query(value="from Customer where custName = ?1")
    Customer findJpql(String custName);


    /**
     * 案例:根据客户名称和客户id查询客户
     *      jpql: from Customer where custName = ? and custId = ?
     *
     *  对于多个占位符参数
     *      赋值的时候,默认的情况下,占位符的位置需要和方法参数中的位置保持一致
     *
     *  可以指定占位符参数的位置
     *      ? 索引的方式,指定此占位的取值来源
     */
    @Query(value = "from Customer where custName = ?2 and custId = ?1")//表示从2号位获取name参数,1号位获取id参数
    Customer findCustNameAndId(Long id,String name);

    /**
     * 使用jpql完成更新操作
     *      案例 : 根据id更新,客户的名称
     *          更新4号客户的名称,将名称改为“黑马程序员”
     *
     *  sql  :update cst_customer set cust_name = ? where cust_id = ?
     *  jpql : update Customer set custName = ? where custId = ?
     *
     *  @Query : 代表的是进行查询
     *
     *      * 声明此方法是用来进行更新操作
     *      * 当前执行的是一个更新操作
     *  @Modifying
     */
    @Query(value = " update Customer set custName = ?2 where custId = ?1 ")
    @Modifying
    void updateCustomer(long custId,String custName);

    /**
     * 使用sql的形式查询:
     *     查询全部的客户
     *  sql : select * from cst_customer;
     *  Query : 配置sql查询
     *      value : sql语句
     *      nativeQuery : 查询方式
     *          true : sql查询
     *          false:jpql查询
     *
     */
    //@Query(value = " select * from cst_customer" ,nativeQuery = true)
    //nativeQuery = true 表示原生sql语句
    @Query(value="select * from cst_customer where cust_name like ?1",nativeQuery = true)
    //使用sql语句查询的时候,返回值统统都是List集合
    List<Object [] > findSql(String name);

    /**
     * 方法名的约定:
     *      findBy : 查询
     *            对象中的属性名(首字母大写) : 查询的条件
     *            CustName
     *            * 默认情况 : 使用 等于的方式查询
     *
     *  findByCustName   --   根据客户名称查询
     *
     *  再springdataJpa的运行阶段
     *          会根据方法名称进行解析  findBy    from  xxx(实体类)
     *                                      属性名称      where  custName =
     *
     *      1.findBy  + 属性名称 (根据属性名称进行完成匹配的查询=)
     *      2.findBy  + 属性名称 + “查询方式(Like | isnull)”
     *          findByCustNameLike
     *      3.多条件查询
     *          findBy + 属性名 + “查询方式”   + “多条件的连接符(and|or)”  + 属性名 + “查询方式”
     */
    Customer findByCustName(String custName);

    List<Customer> findByCustNameLike(String custName);

    //使用客户名称模糊匹配和客户所属行业精准匹配的查询
    Customer findByCustNameLikeAndCustIndustry(String custName,String custIndustry);
}

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

@Query(value="update Customer set custName = ?1 where custId = ?2")
@Modifying
public void updateCustomer(String custName,Long custId);

11.3 使用SQL语句查询

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

@Query(value="select * from cst_customer",nativeQuery=true)
public void findSql();

11.4 方法命名规则查询

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

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

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

具体的关键字,使用方法和生产成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)

11.5 整体测试

package test;

import com.phz.dao.CustomerDao;
import com.phz.entity.Customer;
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 org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;

/**
 * @author PengHuAnZhi
 * @createTime 2020/11/7 15:44
 * @projectName JPA
 * @className JpqlTest.java
 * @description TODO
 */
@RunWith(SpringJUnit4ClassRunner.class) //声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class JpqlTest {
    @Autowired
    private CustomerDao customerDao;

    @Test
    public void  testFindJPQL() {
        Customer customer = customerDao.findJpql("彭焕智");
        System.out.println(customer);
    }


    @Test
    public void testFindCustNameAndId() {
        // Customer customer =  customerDao.findCustNameAndId("彭焕智",1l);
        Customer customer =  customerDao.findCustNameAndId(2L,"彭焕智");
        System.out.println(customer);
    }

    /**
     * 测试jpql的更新操作
     *  * springDataJpa中使用jpql完成 更新/删除操作
     *         * 需要手动添加事务的支持
     *         * 默认会执行结束之后,回滚事务
     *   @Rollback : 设置是否自动回滚
     *          false | true
     */
    @Test
    @Transactional //添加事务的支持,不然在执行更新操作会报错
    @Rollback(value = false)
    public void testUpdateCustomer() {
        customerDao.updateCustomer(4l,"两米以下皆凡人");
    }

    //测试sql查询
    @Test
    public void testFindSql() {
        List<Object[]> list = customerDao.findSql("彭焕智%");
        for(Object [] obj : list) {
            System.out.println(Arrays.toString(obj));
        }
    }

    //测试方法命名规则的查询
    @Test
    public void testNaming() {
        Customer customer = customerDao.findByCustName("彭焕智");
        System.out.println(customer);
    }

    //测试方法命名规则的查询
    @Test
    public void testFindByCustNameLike() {
        List<Customer> list = customerDao.findByCustNameLike("彭焕智%");
        for (Customer customer : list) {
            System.out.println(customer);
        }
    }

    //测试方法命名规则的查询
    @Test
    public void testFindByCustNameLikeAndCustIndustry() {
        Customer customer = customerDao.findByCustNameLikeAndCustIndustry("彭焕智%", "it");
        System.out.println(customer);
    }
}

第12章 Specifications动态查询

有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;

/**
 *	JpaSpecificationExecutor中定义的方法
 **/
public interface JpaSpecificationExecutor<T> {
    //根据条件查询一个对象
    T findOne(Specification<T> spec);	
    //根据条件查询集合
    List<T> findAll(Specification<T> spec);
    //查询全部,分页
    //pageable:分页参数
    //返回值:分页pageBean(page:是springdatajpa提供的)
    Page<T> findAll(Specification<T> spec, Pageable pageable);
    Page<T> findAll(Specification<T> spec, Pageable pageable);
    //排序查询查询
    List<T> findAll(Specification<T> spec, Sort sort);
    //统计查询
    long count(Specification<T> spec);
}

对于JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的。我们可以简单的理解为,Specification构造的就是查询条件。

Specification接口中只定义了如下一个方法:

//构造查询条件
/**
*	root	:Root接口,代表查询的根对象,可以通过root获取实体中的属性
*	query	:代表一个顶层查询对象,用来自定义查询
*	cb		:用来构建查询,此对象里有很多条件方法
**/
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);

12.1 使用Specifications完成条件查询

/**
 * 根据条件,查询单个对象
 *
 */
@Test
public void testSpec() {
    //匿名内部类
    /**
     * 自定义查询条件
     *      1.实现Specification接口(提供泛型:查询的对象类型)
     *      2.实现toPredicate方法(构造查询条件)
     *      3.需要借助方法参数中的两个参数(
     *          root:获取需要查询的对象属性
     *          CriteriaBuilder:构造查询条件的,内部封装了很多的查询条件(模糊匹配,精准匹配)
     *       )
     *  案例:根据客户名称查询,查询客户名为彭焕智的客户
     *          查询条件
     *              1.查询方式
     *                  cb对象
     *              2.比较的属性名称
     *                  root对象
     */
    Specification<Customer> spec = new Specification<Customer>() {
        @Override
        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            //1.获取比较的属性
            Path<Object> custName = root.get("custId");
            //2.构造查询条件  :    select * from cst_customer where cust_name = '彭焕智'
            /**
             * 第一个参数:需要比较的属性(path对象)
             * 第二个参数:当前需要比较的取值
             */
            Predicate predicate = cb.equal(custName, "彭焕智");//进行精准的匹配  (比较的属性,比较的属性的取值)
            return predicate;
        }
    };
    Customer customer = customerDao.findOne(spec);
    System.out.println(customer);
}

/**
 * 多条件查询
 *      案例:根据客户名(彭焕智)和客户所属行业查询(it)
 *
 */
@Test
public void testSpec1() {
    /**
     *  root:获取属性
     *      客户名
     *      所属行业
     *  cb:构造查询
     *      1.构造客户名的精准匹配查询
     *      2.构造所属行业的精准匹配查询
     *      3.将以上两个查询联系起来
     */
    Specification<Customer> spec = new Specification<Customer>() {
        @Override
        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            Path<Object> custName = root.get("custName");//客户名
            Path<Object> custIndustry = root.get("custIndustry");//所属行业

            //构造查询
            //1.构造客户名的精准匹配查询
            Predicate p1 = cb.equal(custName, "彭焕智");//第一个参数,path(属性),第二个参数,属性的取值
            //2..构造所属行业的精准匹配查询
            Predicate p2 = cb.equal(custIndustry, "it");
            //3.将多个查询条件组合到一起:组合(满足条件一并且满足条件二:与关系,满足条件一或满足条件二即可:或关系)
            Predicate and = cb.and(p1, p2);//以与的形式拼接多个查询条件
            // cb.or();//以或的形式拼接多个查询条件
            return and;
        }
    };
    Customer customer = customerDao.findOne(spec);
    System.out.println(customer);
}

/**
 * 案例:完成根据客户名称的模糊匹配,返回客户列表
 *      客户名称以 ’彭焕智‘ 开头
 *
 * equal :直接的到path对象(属性),然后进行比较即可
 * gt,lt,ge,le,like : 得到path对象,根据path指定比较的参数类型,再去进行比较
 *      指定参数类型:path.as(类型的字节码对象)
 */
@Test
public void testSpec3() {
    //构造查询条件
    Specification<Customer> spec = new Specification<Customer>() {
        @Override
        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            //查询属性:客户名
            Path<Object> custName = root.get("custName");
            //查询方式:模糊匹配
            Predicate like = cb.like(custName.as(String.class), "彭焕智%");
            return like;
        }
    };
    //        List<Customer> list = customerDao.findAll(spec);
    //        for (Customer customer : list) {
    //            System.out.println(customer);
    //        }
    //添加排序
    //创建排序对象,需要调用构造方法实例化sort对象
    //第一个参数:排序的顺序(倒序,正序)
    //   Sort.Direction.DESC:倒序
    //   Sort.Direction.ASC : 升序
    //第二个参数:排序的属性名称
    Sort sort = new Sort(Sort.Direction.DESC,"custId");
    List<Customer> list = customerDao.findAll(spec, sort);
    for (Customer customer : list) {
        System.out.println(customer);
    }
}

12.2 基于Specifications的分页查询

/**
 * 分页查询
 *      Specification: 查询条件
 *      Pageable:分页参数
 *          分页参数:查询的页码,每页查询的条数
 *          findAll(Specification,Pageable):带有条件的分页
 *          findAll(Pageable):没有条件的分页
 *  返回:Page(springDataJpa为我们封装好的pageBean对象,数据列表,共条数)
 */
@Test
public void testSpec4() {

    Specification spec = null;
    //PageRequest对象是Pageable接口的实现类
    /**
     * 创建PageRequest的过程中,需要调用他的构造方法传入两个参数
     *      第一个参数:当前查询的记录数(从0开始)
     *      第二个参数:每页查询的数量
     */
    Pageable pageable = new PageRequest(0,2);
    //分页查询
    Page<Customer> page = customerDao.findAll(null, pageable);
    System.out.println(page.getContent()); //得到数据集合列表
    System.out.println(page.getTotalElements());//得到总条数
    System.out.println(page.getTotalPages());//得到总页数
}

对于Spring Data JPA中的分页查询,是其内部自动实现的封装过程,返回的是一个Spring Data JPA提供的pageBean对象。其中的方法说明如下:

//获取总页数
int getTotalPages();
//获取总记录数	
long getTotalElements();
//获取列表数据
List<T> getContent();

12.3 方法对应关系

方法名称 Sql对应关系
equle filed = value
gt(greaterThan ) filed > value
lt(lessThan ) filed < value
ge(greaterThanOrEqualTo ) filed >= value
le( lessThanOrEqualTo) filed <= value
notEqule filed != value
like filed like value
notLike filed not like value

第13章 多表设计

13.1 表之间关系的划分

数据库中多表之间存在着三种关系,如图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ROdz58jB-1610011107487)(JPA.assets/clip_image002-1604737960921.jpg)]

从图可以看出,系统设计的三种实体关系分别为:多对多一对多一对一关系。注意:一对多关系可以看为两种: 即一对多,多对一。所以说四种更精确。

明确: 我们今天只涉及实际开发中常用的关联关系,一对多和多对多。而一对一的情况,在实际开发中几乎不用

13.2 在JPA框架中表关系的分析步骤

在实际开发中,我们数据库的表难免会有相互的关联关系,在操作表的时候就有可能会涉及到多张表的操作。而在这种实现了ORM思想的框架中(如JPA),可以让我们通过操作实体类就实现对数据库表的操作。所以今天我们的学习重点是:掌握配置实体之间的关联关系。

第一步:首先确定两张表之间的关系。

​ 如果关系确定错了,后面做的所有操作就都不可能正确。

第二步:在数据库中实现两张表的关系

第三步:在实体类中描述出两个实体的关系

第四步:配置出实体类和数据库表的关系映射(重点)

第14章 JPA中的一对多

14.1 示例分析

我们采用的示例为客户和联系人。

客户:指的是一家公司,我们记为A。

联系人:指的是A公司中的员工。

在不考虑兼职的情况下,公司和员工的关系即为一对多。

14.2 表关系建立

在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对多的关系,需要使用数据库的外键约束。

什么是外键?

指的是从表中有一列,取值参照主表的主键,这一列就是外键。

一对多数据库关系的建立,如下图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fm08uacT-1610011107488)(JPA.assets/clip_image002-1604631912290.jpg)]

14.3 实体类关系建立以及映射配置

<?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"
       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="entityManagerFactoty" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--配置的扫描的包(实体类所在的包) -->
        <property name="packagesToScan" value="com.phz.entity"/>
        <!-- jpa的实现厂家 -->
        <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.MySQL5Dialect"/>
                <!--是否显示sql -->
                <property name="showSql" value="true"/>
            </bean>
        </property>
        <!--jpa的方言 :高级的特性 -->
        <property name="jpaDialect">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
        </property>
        <!--注入jpa的配置信息
            加载jpa的基本配置信息和jpa实现方式(hibernate)的配置信息
            hibernate.hbm2ddl.auto : 自动创建数据库表
            create : 每次都会重新创建数据库表
            update:有表不会重新创建,没有表会重新创建表
        -->
        <property name="jpaProperties" >
            <props>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>
    <!--2.创建数据库连接池 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="root"/>
        <property name="password" value="123456"/>
        <property name="jdbcUrl" value="jdbc:mysql:///jpa_test?characterEncoding=utf-8"/>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    </bean>
    <!--3.整合spring dataJpa-->
    <jpa:repositories base-package="com.phz.dao" transaction-manager-ref="transactionManager"
                      entity-manager-factory-ref="entityManagerFactoty"/>
    <!--4.配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactoty"/>
    </bean>
    <!-- 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(* com.phz.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
    </aop:config>
    <!--5.声明式事务 -->
    <!-- 6. 配置包扫描-->
    <context:component-scan base-package="com.phz"/>
</beans>

在实体类中,由于客户是少的一方,它应该包含多个联系人,所以实体类要体现出客户中有多个联系人的信息,代码如下:

package com.phz.entity;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

/**
 * 1.实体类和表的映射关系
 *      @Eitity
 *      @Table
 * 2.类中属性和表中字段的映射关系
 *      @Id
 *      @GeneratedValue
 *      @Column
 */
@Entity
@Table(name="cst_customer")
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")
    private String custName;
    @Column(name="cust_phone")
    private String custPhone;
    @Column(name="cust_source")
    private String custSource;

    //配置客户和联系人之间的关系(一对多关系)
    /**
     * 使用注解的形式配置多表关系
     *      1.声明关系
     *          @OneToMany : 配置一对多关系
     *              targetEntity :对方对象的字节码对象
     *      2.配置外键(中间表)
     *              @JoinColumn : 配置外键
     *                  name:外键字段名称
     *                  referencedColumnName:参照的主表的主键字段名称
     *
     *  * 在客户实体类上(一的一方)添加了外键了配置,所以对于客户而言,也具备了维护外键的作用
     *
     */

//    @OneToMany(targetEntity = LinkMan.class)
//    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    
    /**
     * 放弃外键维护权
     *      mappedBy:对方配置关系的属性名称,表示自己不维护,参照对方
     * cascade : 配置级联(可以配置到设置多表的映射关系的注解上)
     *      CascadeType.all         :所有
     *                  MERGE       :更新
     *                  PERSIST     :保存
     *                  REMOVE      :删除
     *
     * fetch : 配置关联对象的加载方式
     *          EAGER   :立即加载
     *          LAZY    :延迟加载

      */
    @OneToMany(mappedBy = "customer")
    private Set<LinkMan> linkMans = new HashSet<LinkMan>();

    public Long getCustId() {
        return custId;
    }

    public void setCustId(Long custId) {
        this.custId = custId;
    }

    public String getCustAddress() {
        return custAddress;
    }

    public void setCustAddress(String custAddress) {
        this.custAddress = custAddress;
    }

    public String getCustIndustry() {
        return custIndustry;
    }

    public void setCustIndustry(String custIndustry) {
        this.custIndustry = custIndustry;
    }

    public String getCustLevel() {
        return custLevel;
    }

    public void setCustLevel(String custLevel) {
        this.custLevel = custLevel;
    }

    public String getCustName() {
        return custName;
    }

    public void setCustName(String custName) {
        this.custName = custName;
    }

    public String getCustPhone() {
        return custPhone;
    }

    public void setCustPhone(String custPhone) {
        this.custPhone = custPhone;
    }

    public String getCustSource() {
        return custSource;
    }

    public void setCustSource(String custSource) {
        this.custSource = custSource;
    }

    public Set<LinkMan> getLinkMans() {
        return linkMans;
    }

    public void setLinkMans(Set<LinkMan> linkMans) {
        this.linkMans = linkMans;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "custId=" + custId +
                ", custAddress='" + custAddress + '\'' +
                ", custIndustry='" + custIndustry + '\'' +
                ", custLevel='" + custLevel + '\'' +
                ", custName='" + custName + '\'' +
                ", custPhone='" + custPhone + '\'' +
                ", custSource='" + custSource + '\'' +
                '}';
    }
}

由于联系人是多的一方,在实体类中要体现出,每个联系人只能对应一个客户,代码如下:

package com.phz.entity;

import javax.persistence.*;

@Entity
@Table(name = "cst_linkman")
public class LinkMan {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "lkm_id")
    private Long lkmId; //联系人编号(主键)
    @Column(name = "lkm_name")
    private String lkmName;//联系人姓名
    @Column(name = "lkm_gender")
    private String lkmGender;//联系人性别
    @Column(name = "lkm_phone")
    private String lkmPhone;//联系人办公电话
    @Column(name = "lkm_mobile")
    private String lkmMobile;//联系人手机
    @Column(name = "lkm_email")
    private String lkmEmail;//联系人邮箱
    @Column(name = "lkm_position")
    private String lkmPosition;//联系人职位
    @Column(name = "lkm_memo")
    private String lkmMemo;//联系人备注

    /**
     * 配置联系人到客户的多对一关系
     *     使用注解的形式配置多对一关系
     *      1.配置表关系
     *          @ManyToOne : 配置多对一关系
     *              targetEntity:对方的实体类字节码
     *      2.配置外键(中间表)
     *          @JoinColumn : 配置外键
     *              name:外键字段名称
     *              referencedColumnName:参照的主表的主键字段名称
     *
     * * 配置外键的过程,配置到了多的一方,就会在多的一方维护外键
     *
     */
    @ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    private Customer customer;

    public Long getLkmId() {
        return lkmId;
    }

    public void setLkmId(Long lkmId) {
        this.lkmId = lkmId;
    }

    public String getLkmName() {
        return lkmName;
    }

    public void setLkmName(String lkmName) {
        this.lkmName = lkmName;
    }

    public String getLkmGender() {
        return lkmGender;
    }

    public void setLkmGender(String lkmGender) {
        this.lkmGender = lkmGender;
    }

    public String getLkmPhone() {
        return lkmPhone;
    }

    public void setLkmPhone(String lkmPhone) {
        this.lkmPhone = lkmPhone;
    }

    public String getLkmMobile() {
        return lkmMobile;
    }

    public void setLkmMobile(String lkmMobile) {
        this.lkmMobile = lkmMobile;
    }

    public String getLkmEmail() {
        return lkmEmail;
    }

    public void setLkmEmail(String lkmEmail) {
        this.lkmEmail = lkmEmail;
    }

    public String getLkmPosition() {
        return lkmPosition;
    }

    public void setLkmPosition(String lkmPosition) {
        this.lkmPosition = lkmPosition;
    }

    public String getLkmMemo() {
        return lkmMemo;
    }

    public void setLkmMemo(String lkmMemo) {
        this.lkmMemo = lkmMemo;
    }

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    @Override
    public String toString() {
        return "LinkMan{" +
                "lkmId=" + lkmId +
                ", lkmName='" + lkmName + '\'' +
                ", lkmGender='" + lkmGender + '\'' +
                ", lkmPhone='" + lkmPhone + '\'' +
                ", lkmMobile='" + lkmMobile + '\'' +
                ", lkmEmail='" + lkmEmail + '\'' +
                ", lkmPosition='" + lkmPosition + '\'' +
                ", lkmMemo='" + lkmMemo + '\'' +
                '}';
    }
}

14.4 映射的注解说明

@OneToMany:

作用:建立一对多的关系映射

属性:

​ targetEntityClass:指定多的多方的类的字节码

​ mappedBy:指定从表实体类中引用主表对象的名称。

​ cascade:指定要使用的级联操作

​ fetch:指定是否采用延迟加载

​ orphanRemoval:是否使用孤儿删除

@ManyToOne

作用:建立多对一的关系

属性:

​ targetEntityClass:指定一的一方实体类字节码

​ cascade:指定要使用的级联操作

​ fetch:指定是否采用延迟加载

​ optional:关联是否可选。如果设置为false,则必须始终存在非空关系。

@JoinColumn

作用:用于定义主键字段和外键字段的对应关系。

属性:

​ name:指定外键字段的名称

​ referencedColumnName:指定引用主表的主键字段名称

​ unique:是否唯一。默认值不唯一

​ nullable:是否允许为空。默认值允许。

​ insertable:是否允许插入。默认值允许。

​ updatable:是否允许更新。默认值允许。

​ columnDefinition:列的定义信息。

14.5 一对多的操作

14.5.1 添加

package test;

import com.phz.dao.CustomerDao;
import com.phz.dao.LinkManDao;
import com.phz.entity.Customer;
import com.phz.entity.LinkMan;
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 org.springframework.transaction.annotation.Transactional;

/**
 * @author PengHuAnZhi
 * @createTime 2020/11/7 19:28
 * @projectName JPA
 * @className OneToManyTest.java
 * @description TODO
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class OneToManyTest {
    @Autowired
    private CustomerDao customerDao;
    @Autowired
    private LinkManDao linkManDao;

    /**
     * 保存一个客户,保存一个联系人
     *  效果:客户和联系人作为独立的数据保存到数据库中
     *      联系人的外键为空
     *  原因?
     *      实体类中没有配置关系
     */
    @Test
    @Transactional
    @Rollback(value = false)
    public void testAdd(){
        Customer customer = new Customer();
        customer.setCustName("彭焕智");
        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("彭焕智的手下");
        /**
         * 配置了客户到联系人的关系
         *      从客户的角度上:发送两条insert语句,发送一条更新语句更新数据库(更新外键)
         * 由于我们配置了客户到联系人的关系:客户可以对外键进行维护
         */
//        customer.getLinkMans().add(linkMan);
        linkMan.setCustomer(customer);
        customerDao.save(customer);
        linkManDao.save(linkMan);
    }
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testAdd1() {
        //创建一个客户,创建一个联系人
        Customer customer = new Customer();
        customer.setCustName("百度");

        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("小李");

        /**
         * 配置联系人到客户的关系(多对一)
         *    只发送了两条insert语句
         * 由于配置了联系人到客户的映射关系(多对一)
         */
        linkMan.setCustomer(customer);

        customerDao.save(customer);
        linkManDao.save(linkMan);
    }

    /**
     * 会有一条多余的update语句
     *      * 由于一的一方可以维护外键:会发送update语句
     *      * 解决此问题:只需要在一的一方放弃维护权即可
     *
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testAdd2() {
        //创建一个客户,创建一个联系人
        Customer customer = new Customer();
        customer.setCustName("百度");

        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("小李");


        linkMan.setCustomer(customer);//由于配置了多的一方到一的一方的关联关系(当保存的时候,就已经对外键赋值)
        customer.getLinkMans().add(linkMan);//由于配置了一的一方到多的一方的关联关系(发送一条update语句)

        customerDao.save(customer);
        linkManDao.save(linkMan);
    }
}

通过保存的案例,我们可以发现在设置了双向关系之后,会发送两条insert语句,一条多余的update语句,那我们的解决是思路很简单,就是一的一方放弃维护权

/**
 *放弃外键维护权的配置将如下配置改为
 */
//@OneToMany(targetEntity=LinkMan.class)
//@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")	
//设置为
@OneToMany(mappedBy="customer")

14.5.2 删除

package test;

import com.phz.dao.CustomerDao;
import com.phz.dao.LinkManDao;
import com.phz.entity.Customer;
import com.phz.entity.LinkMan;
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 org.springframework.transaction.annotation.Transactional;

/**
 * @author PengHuAnZhi
 * @createTime 2020/11/7 19:28
 * @projectName JPA
 * @className OneToManyTest.java
 * @description TODO
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class OneToManyTest {
    @Autowired
    private CustomerDao customerDao;

    /**
     * 级联删除:
     *      删除1号客户的同时,删除1号客户的所有联系人
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testCascadeRemove() {
        //1.查询1号客户
        Customer customer = customerDao.findOne(1l);
        //2.删除1号客户
        customerDao.delete(customer);
    }
}

删除操作的说明如下:

删除从表数据:可以随时任意删除。

删除主表数据:

u 有从表数据

1、在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表结构上,外键字段有非空约束,默认情况就会报错了。

2、如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null,没有关系)因为在删除时,它根本不会去更新从表的外键字段了。

3、如果还想删除,使用级联删除引用

u 没有从表数据引用:随便删

在实际开发中,级联删除请慎用!(在一对多的情况下)

14.5.3 级联操作

级联操作:指操作一个对象同时操作它的关联对象

使用方法:只需要在操作主体的注解上配置cascade

/**
* cascade:配置级联操作
* 		CascadeType.MERGE	级联更新
* 		CascadeType.PERSIST	级联保存:
* 		CascadeType.REFRESH 级联刷新:
* 		CascadeType.REMOVE	级联删除:
* 		CascadeType.ALL		包含所有
*/
@OneToMany(mappedBy="customer",cascade=CascadeType.ALL)
package test;

import com.phz.dao.CustomerDao;
import com.phz.dao.LinkManDao;
import com.phz.entity.Customer;
import com.phz.entity.LinkMan;
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 org.springframework.transaction.annotation.Transactional;

/**
 * @author PengHuAnZhi
 * @createTime 2020/11/7 19:28
 * @projectName JPA
 * @className OneToManyTest.java
 * @description TODO
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class OneToManyTest {
    @Autowired
    private CustomerDao customerDao;
    @Autowired
    private LinkManDao linkManDao;
   
	/**
     * 级联添加:保存一个客户的同时,保存客户的所有联系人
     *      需要在操作主体的实体类上,配置casacde属性
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testCascadeAdd() {
        Customer customer = new Customer();
        customer.setCustName("百度1");

        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("小李1");

        linkMan.setCustomer(customer);
        customer.getLinkMans().add(linkMan);

        //如果没有设置级联操作,那这里就只会保存一个customer进入数据库表,如果设置了级联操作,那么两者都会插入数据库表
        customerDao.save(customer);
    }


    /**
     * 级联删除:
     *      删除1号客户的同时,删除1号客户的所有联系人
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testCascadeRemove() {
        //1.查询1号客户
        Customer customer = customerDao.findOne(1l);
        //2.删除1号客户
        customerDao.delete(customer);
    }
}

第15章 JPA中的多对多

15.1 示例分析

我们采用的示例为用户和角色。

用户:指的是咱们班的每一个同学。

角色:指的是咱们班同学的身份信息。

比如A同学,它是我的学生,其中有个身份就是学生,还是家里的孩子,那么他还有个身份是子女。

同时B同学,它也具有学生和子女的身份。

那么任何一个同学都可能具有多个身份。同时学生这个身份可以被多个同学所具有。

所以我们说,用户和角色之间的关系是多对多。

15.2 表关系建立

多对多的表关系建立靠的是中间表,其中用户表和中间表的关系是一对多,角色表和中间表的关系也是一对多,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GB5M57hf-1610011107489)(JPA.assets/clip_image002.png)]

15.3 实体类关系建立以及映射配置

一个用户可以具有多个角色,所以在用户实体类中应该包含多个角色的信息,代码如下:

package com.phz.entity;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

/**
 * @author PengHuAnZhi
 * @createTime 2020/11/7 20:01
 * @projectName JPA
 * @className User.java
 * @description TODO
 */
@Entity
@Table(name = "sys_user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="user_id")
    private Long userId;
    @Column(name="user_name")
    private String userName;
    @Column(name="age")
    private Integer age;

    /**
     * 配置用户到角色的多对多关系
     *      配置多对多的映射关系
     *          1.声明表关系的配置
     *              @ManyToMany(targetEntity = Role.class)  //多对多
     *                  targetEntity:代表对方的实体类字节码
     *          2.配置中间表(包含两个外键)
     *                @JoinTable
     *                  name : 中间表的名称
     *                  joinColumns:配置当前对象在中间表的外键
     *                      @JoinColumn的数组
     *                          name:外键名
     *                          referencedColumnName:参照的主表的主键名
     *                  inverseJoinColumns:配置对方对象在中间表的外键
     */
    @ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL)
    @JoinTable(name = "sys_user_role",
            //joinColumns,当前对象在中间表中的外键
            joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")},
            //inverseJoinColumns,对方对象在中间表的外键
            inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")}
    )
    private Set<Role> roles = new HashSet<Role>();

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
}

一个角色可以赋予多个用户,所以在角色实体类中应该包含多个用户的信息,代码如下:

package com.phz.entity;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

/**
 * @author PengHuAnZhi
 * @createTime 2020/11/7 19:58
 * @projectName JPA
 * @className Role.java
 * @description TODO
 */
@Entity
@Table(name = "sys_role")
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Long roleId;
    @Column(name = "role_name")
    private String roleName;

    //配置多对多
    @ManyToMany(mappedBy = "roles")  //配置多表关系
    private Set<User> users = new HashSet<User>();

    public Long getRoleId() {
        return roleId;
    }

    public void setRoleId(Long roleId) {
        this.roleId = roleId;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public Set<User> getUsers() {
        return users;
    }

    public void setUsers(Set<User> users) {
        this.users = users;
    }
}

15.4 映射的注解说明

@ManyToMany

​ 作用:用于映射多对多关系

​ 属性:

​ cascade:配置级联操作。

​ fetch:配置是否采用延迟加载。

​ targetEntity:配置目标的实体类。映射多对多的时候不用写。

@JoinTable

作用:针对中间表的配置

属性:

​ nam:配置中间表的名称

​ joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段

​ inverseJoinColumn:中间表的外键字段关联对方表的主键字段

@JoinColumn

作用:用于定义主键字段和外键字段的对应关系。

属性:

​ name:指定外键字段的名称

​ referencedColumnName:指定引用主表的主键字段名称

​ unique:是否唯一。默认值不唯一

​ nullable:是否允许为空。默认值允许。

​ insertable:是否允许插入。默认值允许。

​ updatable:是否允许更新。默认值允许。

​ columnDefinition:列的定义信息。

15.5 多对多的操作

15.5.1 保存

package test;

import com.phz.dao.RoleDao;
import com.phz.dao.UserDao;
import com.phz.entity.Role;
import com.phz.entity.User;
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 org.springframework.transaction.annotation.Transactional;

/**
 * @author PengHuAnZhi
 * @createTime 2020/11/7 19:59
 * @projectName JPA
 * @className ManyToManyTest.java
 * @description TODO
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ManyToManyTest {
    @Autowired
    private UserDao userDao;
    @Autowired
    private RoleDao roleDao;

    @Test
    @Transactional
    @Rollback(value = false)
    public void testAdd() {
        User user = new User();
        user.setUserName("小李");

        Role role = new Role();
        role.setRoleName("java程序员");

        //配置用户到角色关系,可以对中间表中的数据进行维护     1-1
        user.getRoles().add(role);

        //配置角色到用户的关系,可以对中间表的数据进行维护     1-1
        role.getUsers().add(user);

        userDao.save(user);
        roleDao.save(role);
    }
    //测试级联添加(保存一个用户的同时保存用户的关联角色)
    @Test
    @Transactional
    @Rollback(false)
    public void  testCasCadeAdd() {
        User user = new User();
        user.setUserName("小李");

        Role role = new Role();
        role.setRoleName("java程序员");

        //配置用户到角色关系,可以对中间表中的数据进行维护     1-1
        user.getRoles().add(role);

        //配置角色到用户的关系,可以对中间表的数据进行维护     1-1
        role.getUsers().add(user);

        userDao.save(user);
    }
}

在多对多(保存)中,如果双向都设置关系,意味着双方都维护中间表,都会往中间表插入数据,中间表的2个字段又作为联合主键,所以报错,主键重复,解决保存失败的问题:只需要在任意一方放弃对中间表的维护权即可,推荐在被动的一方放弃,配置如下:

//放弃对中间表的维护权,解决保存中主键冲突的问题
@ManyToMany(mappedBy="roles")
private Set<SysUser> users = new HashSet<SysUser>(0);

15.5.2 删除

package test;

import com.phz.dao.RoleDao;
import com.phz.dao.UserDao;
import com.phz.entity.Role;
import com.phz.entity.User;
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 org.springframework.transaction.annotation.Transactional;

/**
 * @author PengHuAnZhi
 * @createTime 2020/11/7 19:59
 * @projectName JPA
 * @className ManyToManyTest.java
 * @description TODO
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ManyToManyTest {
    @Autowired
    private UserDao userDao;
    @Autowired
    private RoleDao roleDao;    
	/**
     * 案例:删除id为1的用户,同时删除他的关联对象
     */
    @Test
    @Transactional
    @Rollback(false)
    public void  testCasCadeRemove() {
        //查询1号用户
        User user = userDao.findOne(1l);
        //删除1号用户
        userDao.delete(user);
    }
}

第16章 Spring Data JPA中的多表查询

16.1 对象导航查询

对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。例如:我们通过ID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人。对象导航查询的使用要求是:两个对象之间必须存在关联关系。

查询一个客户,获取该客户下的所有联系人

@Test
@Transactional // 解决在java代码中的no session问题
public void  testQuery1() {
    //查询id为1的客户
    Customer customer = customerDao.getOne(1l);
    //对象导航查询,此客户下的所有联系人
    Set<LinkMan> linkMans = customer.getLinkMans();

    for (LinkMan linkMan : linkMans) {
        System.out.println(linkMan);
    }
}

查询一个联系人,获取该联系人的所有客户

/**
 * 从联系人对象导航查询他的所属客户
 *      * 默认 : 立即加载
 *  延迟加载:
 *      fetch = FetchType.LAZY
 */
@Test
@Transactional // 解决在java代码中的no session问题
public void  testQuery3() {
    LinkMan linkMan = linkManDao.findOne(2l);
    //对象导航查询所属的客户
    Customer customer = linkMan.getCustomer();
    System.out.println(customer);
}

对象导航查询的问题分析

问题1:我们查询客户时,要不要把联系人查询出来?

分析:如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的,不使用时又会白白的浪费了服务器内存。

解决:采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询。

配置方式:

/**
  * 在客户对象的@OneToMany注解中添加fetch属性
  * 		FetchType.EAGER	:立即加载
  * 		FetchType.LAZY	:延迟加载
  */
@OneToMany(mappedBy="customer",fetch=FetchType.EAGER)
private Set<LinkMan> linkMans = new HashSet<>(0);

问题2:我们查询联系人时,要不要把客户查询出来?

分析:例如:查询联系人详情时,肯定会看看该联系人的所属客户。如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的话,一个对象不会消耗太多的内存。而且多数情况下我们都是要使用的。

解决: 采用立即加载的思想。通过配置的方式来设定,只要查询从表实体,就把主表实体对象同时查出来

配置方式

/**
  * 在联系人对象的@ManyToOne注解中添加fetch属性
  * 		FetchType.EAGER	:立即加载
  * 		FetchType.LAZY	:延迟加载
  */
@ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER)
@JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id")
private Customer customer;

16.2 使用Specification查询

/**
 * Specification的多表查询
 */
@Test
public void testFind() {
   Specification<LinkMan> spec = new Specification<LinkMan>() {
       public Predicate toPredicate(Root<LinkMan> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
           //Join代表链接查询,通过root对象获取
           //创建的过程中,第一个参数为关联对象的属性名称,第二个参数为连接查询的方式(left,inner,right)
           //JoinType.LEFT : 左外连接,JoinType.INNER:内连接,JoinType.RIGHT:右外连接
           Join<LinkMan, Customer> join = root.join("customer",JoinType.INNER);
           return cb.like(join.get("custName").as(String.class),"传智播客1");
       }
   };
   List<LinkMan> list = linkManDao.findAll(spec);
   for (LinkMan linkMan : list) {
       System.out.println(linkMan);
   }
}

16.3 Specification恒真恒假断言

恒真

// 这里 cb 是一个 CriteriaBuilder 实例
Predicate constPredicate1 = cb.equal(cb.literal(1), 1); // 1=1
Predicate constPredicate2 = cb.gt(cb.literal(1),0);      // 1>0       
Predicate constPredicate3 = cb.isFalse(cb.literal(Boolean.FALSE)); // false = false
Predicate constPredicate4 = cb.isTrue(cb.literal(Boolean.TRUE));  // true = true

恒假

// 这里 cb 是一个 CriteriaBuilder 实例
Predicate constPredicate1 = cb.equal(cb.literal(1), 0);  // 1 = 0
Predicate constPredicate2 = cb.gt(cb.literal(1),2);       // 1 > 2      
Predicate constPredicate3 = cb.isFalse(cb.literal(Boolean.TRUE)); // true = false
Predicate constPredicate4 = cb.isTrue(cb.literal(Boolean.FALSE));  // false = true
上一篇:架构师学习-springDataJpa-特殊查询(六十一)


下一篇:vim使用心得