Skip to content

Latest commit

 

History

History
2326 lines (1819 loc) · 76.6 KB

readme.md

File metadata and controls

2326 lines (1819 loc) · 76.6 KB

Spring Data Jpa 笔记

简介

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

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

springdatajpa
	day1:orm思想和hibernate以及jpa的概述和jpa的基本操作
	day2:springdatajpa的运行原理以及基本操作
	day3:多表操作,复杂查询

第一 orm思想
	主要目的:操作实体类就相当于操作数据库表
	建立两个映射关系:
		实体类和表的映射关系
		实体类中属性和表中字段的映射关系
	不再重点关注:sql语句
	
	实现了ORM思想的框架:mybatis,hibernate

第二 hibernate框架介绍
	Hibernate是一个开放源代码的对象关系映射框架,
		它对JDBC进行了非常轻量级的对象封装,
		它将POJO与数据库表建立映射关系,是一个全自动的orm框架

第三 JPA规范
	jpa规范,实现jpa规范,内部是由接口和抽象类组成

第四 jpa的基本操作
	案例:是客户的相关操作(增删改查)
		客户:就是一家公司
	客户表:
	
	jpa操作的操作步骤
		1.加载配置文件创建实体管理器工厂
			Persisitence:静态方法(根据持久化单元名称创建实体管理器工厂)
				createEntityMnagerFactory(持久化单元名称)
			作用:创建实体管理器工厂
			
		2.根据实体管理器工厂,创建实体管理器
			EntityManagerFactory :获取EntityManager对象
			方法:createEntityManager
			* 内部维护的很多的内容
				内部维护了数据库信息,
				维护了缓存信息
				维护了所有的实体管理器对象
				再创建EntityManagerFactory的过程中会根据配置创建数据库表
			* EntityManagerFactory的创建过程比较浪费资源
			特点:线程安全的对象
				多个线程访问同一个EntityManagerFactory不会有线程安全问题
			* 如何解决EntityManagerFactory的创建过程浪费资源(耗时)的问题?
			思路:创建一个公共的EntityManagerFactory的对象
			* 静态代码块的形式创建EntityManagerFactory
			
		3.创建事务对象,开启事务
			EntityManager对象:实体类管理器
				beginTransaction : 创建事务对象
				presist : 保存
				merge  : 更新
				remove : 删除
				find/getRefrence : 根据id查询
				
			Transaction 对象 : 事务
				begin:开启事务
				commit:提交事务
				rollback:回滚
		4.增删改查操作
		5.提交事务
		6.释放资源
	
	i.搭建环境的过程
		1.创建maven工程导入坐标
		2.需要配置jpa的核心配置文件
			*位置:配置到类路径下的一个叫做 META-INF 的文件夹下
			*命名:persistence.xml
		3.编写客户的实体类
		4.配置实体类和表,类中属性和表中字段的映射关系
		5.保存客户到数据库中
	ii.完成基本CRUD案例
		persist : 保存
		merge : 更新
		remove : 删除
		find/getRefrence : 根据id查询
		
	iii.jpql查询
		sql:查询的是表和表中的字段
		jpql:查询的是实体类和类中的属性
		* jpql和sql语句的语法相似
		
		1.查询全部
		2.分页查询
		3.统计查询
		4.条件查询
		5.排序
		
		

JPA入门案例

1、新建maven工程
2、导入约束
<properties>      
    <project.build.sourceEncoding>
        UTF8
    </project.build.sourceEncoding>
 <project.hibernate.version>
     5.0.7.Final
    </project.hibernate.version>
</properties>

<dependencies>
        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </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>5.1.6</version>
        </dependency>
    </dependencies>
3、创建客户表
    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;
4、创建实体类并配置映射关系
/**
 * 配置实体类与数据库的映射关系
 * @Entity 声明实体类
 * @Table 建立实体类和表的映射关系
 * @Id 声明主键
 * @GeneratedValue 主键的生成策略
 * @Column 指定和表中字段的映射关系
 *
 */
@Entity
@Table(name="cst_customer")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long custId;   // 客户Id 主键

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

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

    @Column(name = "cust_industry")
    private String custIndustry; // 客户级别

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

    @Column(name = "cust_address")
    private String custAddress; // 客户地址

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

    // get set方法
}
5、创建JPA的核心配置类

在resource目录下新建META-INF的文件夹,在META-INF目录下新建persistence.xml文件

注:xml文件第一行不能添加注释

<!--Spring Data Jpa 的核心配置类-->
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">

    <!--配置持久化单元 
		name:持久化单元名称 
		transaction-type:事务类型
		 	RESOURCE_LOCAL:本地事务管理 
		 	JTA:分布式事务管理 -->
    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
        <!--jpa的实现方式-->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <!--数据库相关配置-->
        <properties>
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="root"/>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa"/>
        
        <!--配置jpa实现方(hibernate)的配置信息
        显示sql hibernate.show_sql: false|true 自动创建数据库表 :
        hibernate.hbm2ddl.auto:
            create : 程序运行时创建数据库表(如果有表,先删除表再创建)
            update :程序运行时创建表(如果有表,不会创建表)
        	none :不会创建表 -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
        </properties>
    </persistence-unit>
</persistence>
6、创建测试类
public class CustomerTest {
  
    @Test
    public void saveCustomerTest() {
        // myJpa是persistence.xml中persistence-unit标签的name值
        // 1、加载配置文件创建工厂(实体管理类工厂)
        EntityManagerFactory entityManagerFactory =
                Persistence.createEntityManagerFactory("myJpa");
        // 2、通过实体管理类工厂获取实体管理器
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        // 3、获取事务对象,开启事务
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();

		4完成保存操作
        Customer customer = new Customer();
        customer.setCustName("Spring Data JPA");
        entityManager.persist(customer);
        
		// 5、提交事务
        transaction.commit();
		// 6、释放资源
        entityManager.close();
        entityManagerFactory.close();
    }
}

JPA的主键生成策略

  • GenerationType.IDENTITY 自增,mysql,底层数据库必须支持自动增长

  • GenerationType.SEQUENCE 序列,oracle,底层数据库必须支持序列

  • GenerationType.TABLE jpa提供的一种机制,通过一张数据库表的形式帮助完成主键自增

  • GenerationType.AUTO 程序自动帮我们选择主键生成策略

JPA操作小结

  • 1、加载配置文件创建工厂(实体管理类工厂)

    • Persistence: 静态方法 根据持久化单元名称创建实体管理器工厂

      createEntityManagerFactory ( 持久化单元名称 )

    • 作用:创建实体管理器工厂

  • 2、通过实体管理类工厂获取实体管理器

    • EntityManagerFactory: 获取EntityManager对象

      方法:createEntityManager

      • 内部维护了很多内容

      • 内部维护了数据库信息

      • 维护了缓存信息

      • 维护了所有的实体管理器

      • 在创建EntityManagerFactory的过程中会根据配置创建数据表

    • EntityManagerFactory的常见过程比较浪费资源

      特点:线程安全的类

      • 如何解决EntityManagerFactory创建浪费资源耗时问题

        创建一个公共的EntityManagerFactory的对象

        静态代码块的形式创建EntityManagerFactory

  • 3、获取事务对象,开启事务

  • EntityManager对象,实体管理器。真正操作数据库的对象

    beginTransaction: 创建事务对象

    presist: 保存

    merge: 更新

    remove: 删除

    find/getRefrence: 根据id查询

  • Trancastion 对象: 事务

    begin: 开启

    commit:提交

    rollback: 回滚

  • 4、完成保存操作

  • 5、提交事务

  • 6、释放资源

JPA简单的CURD操作

Spring Data Jpa 入门案例

1、新建maven工程
2、导入约束

springDataJpa的2.0版本以上必须和Spring5.0以上版本整合,否则会出现错误

<properties>    <spring.version>4.3.25.RELEASE</spring.version>    <hibernate.version>5.0.7.Final</hibernate.version>
        <slf4j.version>1.6.6</slf4j.version>
        <log4j.version>1.2.12</log4j.version>
        <c3p0.version>0.9.1.2</c3p0.version>
        <mysql.version>5.1.6</mysql.version>
    </properties>
<dependencies>
        <!-- junit单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
            <scope>test</scope>
        </dependency>

        <!-- spring beg -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.8</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- spring end -->

        <!-- hibernate beg -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.2.1.Final</version>
        </dependency>
        <!-- hibernate end -->

        <!-- c3p0 beg -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.9</version>
        </dependency>
        <!-- c3p0 end -->

        <!-- log end -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <!-- log end -->


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>2.0.10.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>

        <!-- el beg 使用spring data jpa 必须引入 -->
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>2.2.4</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.web</groupId>
            <artifactId>javax.el</artifactId>
            <version>2.2.4</version>
        </dependency>
        <!-- el end -->
    </dependencies>
3、spring整合springdatajpa配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
		http://www.springframework.org/schema/data/jpa
		http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <!--1、数据库连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/jpa"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <!--2、配置entityManagerFactory-->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--配置扫描的包(实体类)-->
        <property name="packagesToScan" value="com.kxj.entity"/>
        <!--jpa的实现厂家-->
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider"></bean>
        </property>
        <!--jpa的供应商适配器-->
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!--配置是否自动创建数据库 -->
                <property name="generateDdl" value="false"/>
                <!--指定数据库类型-->
                <property name="database" value="MYSQL"/>
                <!--数据库方言-->
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
                <!--是否显示SQL-->
                <property name="showSql" value="true"/>
            </bean>
        </property>

        <!-- jpa的方言-->
        <property name="jpaDialect">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
        </property>
    </bean>

    <!--3、事务管理器 JPA事务管理器-->
    <bean name="tranasctionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <!--4、整合spring data jpa-->
    <jpa:repositories base-package="com.kxj.dao" transaction-manager-ref="tranasctionManager"
        entity-manager-factory-ref="entityManagerFactory"
    >
    </jpa:repositories>

    <!--5、声明式事务-->
    <tx:advice id="txAdvice" transaction-manager="tranasctionManager">
        <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>

    <!--6、配置包扫描-->
    <context:component-scan base-package="com.kxj"/>
</beans>

JPA的API介绍

  • Persistence对象

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

//1. 创建 EntitymanagerFactory
@Test
String unitName = "myJpa";
EntityManagerFactory factory= Persistence.createEntityManagerFactory(unitName);
  • EntityManagerFactory

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

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

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

  • EntityManager

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

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

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

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

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

JPA中的复杂查询

JPQL全称Java Persistence Query Language

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

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

查询所有

//查询所有客户
	@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();
		}
	}

分页查询( 基于JPA1.9 )

//分页查询客户
	@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();
		}
	}

条件查询

//条件查询
	@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();
		}
	}

倒叙查询

//根据客户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();
		}
	}

统计查询

//统计查询
	@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();
		}
	}

第二天

SpringDataJpa第二天
	orm思想,hibernate,JPA的相关操作
	
* SpringDataJpa

第一 springDataJpa的概述

第二 springDataJpa的入门操作
	案例:客户的基本CRUD
	i.搭建环境
		创建工程导入坐标
		配置spring的配置文件(配置spring Data jpa的整合)
		编写实体类(Customer),使用jpa注解配置映射关系
	ii.编写一个符合springDataJpa的dao层接口
		* 只需要编写dao层接口,不需要编写dao层接口的实现类
		* dao层接口规范
			1.需要继承两个接口(JpaRepository,JpaSpecificationExecutor)
			2.需要提供响应的泛型
	
	*   findOne(id) :根据id查询
		save(customer):保存或者更新(依据:传递的实体类对象中,是否包含id属性)
		delete(id) :根据id删除
		findAll() : 查询全部

第三 springDataJpa的运行过程和原理剖析
	1.通过JdkDynamicAopProxy的invoke方法创建了一个动态代理对象
	2.SimpleJpaRepository当中封装了JPA的操作(借助JPA的api完成数据库的CRUD)
	3.通过hibernate完成数据库操作(封装了jdbc)


第四 复杂查询
	i.借助接口中的定义好的方法完成查询
		findOne(id):根据id查询
	ii.jpql的查询方式
		jpql : jpa query language  (jpq查询语言)
		特点:语法或关键字和sql语句类似
			查询的是类和类中的属性
			
		* 需要将JPQL语句配置到接口方法上
			1.特有的查询:需要在dao接口上配置方法
			2.在新添加的方法上,使用注解的形式配置jpql查询语句
			3.注解 : @Query

	iii.sql语句的查询
			1.特有的查询:需要在dao接口上配置方法
			2.在新添加的方法上,使用注解的形式配置sql查询语句
			3.注解 : @Query
				value :jpql语句 | sql语句
				nativeQuery :false(使用jpql查询) | true(使用本地查询:sql查询)
					是否使用本地查询
					
	iiii.方法名称规则查询

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

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

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

使用JPA注解配置映射关系

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * 
 *      * 所有的注解都是使用JPA的规范提供的注解,
 *      * 所以在导入注解包的时候,一定要导入javax.persistence下的
 */
@Entity //声明实体类
@Table(name="cst_customer") //建立实体类和表的映射关系
public class Customer {
    
    @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;
    
	// get set方法
}

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

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

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

*1.创建一个Dao层接口,并实现JpaRepository和JpaSpecificationExecutor*

*2.提供相应的泛型**

import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

import cn.itcast.entity.Customer;

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

完成基本CRUD操作

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class CustomerDaoTest {

    @Autowired
    private CustomerDao customerDao;
    
    /**
     * 保存客户:调用save(obj)方法
     */
    @Test
    public void testSave() {
        Customer c = new Customer();
        c.setCustName("播客");
        customerDao.save(c);
    }
    
    /**
     * 修改客户:调用save(obj)方法
     *      对于save方法的解释:如果执行此方法是对象中存在id属性,即为更新操作会先根据id查询,再更新    
     *                      如果执行此方法中对象中不存在id属性,即为保存操作
     *          
     */
    @Test
    public void testUpdate() {
        //根据id查询id为1的客户
        Customer customer = customerDao.findOne(1l);
        //修改客户名称
        customer.setCustName("播客顺义校区");
        //更新
        customerDao.save(customer);
    }
    
    /**
     * 根据id删除:调用delete(id)方法
     */
    @Test
    public void testDelete() {
        customerDao.delete(1l);
    }
    
    /**
     * 根据id查询:调用findOne(id)方法
     */
    @Test
    public void testFindById() {
        Customer customer = customerDao.findOne(2l);
        System.out.println(customer);
    }
    
    /**
     * getOne的用法
     *   需要在实体类上加@Proxy(lazy = false)注解
     *   或者 在测试类上加上@Transactional
     *
     *   因为getOne实际em.getReference() 延迟加载
     */
    @Test
    @Transactional
    public void findTest() {
        Customer one = customerDao.getOne(1L);
        System.out.println(one);
    }
}

Spring Data Jpa的查询方式

  1. 继承JpaRepository后的方法列表

    • 继承JpaSpecificationExecutor的方法列表
    • 继承JpaRepository后的方法列表
  2. 使用JPQL查询

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

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

    public interface CustomerDao extends JpaRepository<Customer, Long>,JpaSpecificationExecutor<Customer> {    
       //@Query 使用jpql的方式查询。
       @Query(value="from Customer") Customer是表对用的类名
       public List<Customer> findAllCustomer();
       
       //@Query 使用jpql的方式查询。?1代表参数的占位符,其中1对应方法中的参数索引
       @Query(value="from Customer where custName = ?1")
       public Customer findCustomer(String custName);
    }

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

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

3、使用SQL查询

需要加上nativeQuery

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

4、方法命名规则查询

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

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

//方法命名方式查询(根据客户名称查询客户)
public Customer findByCustName(String custName);
*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)

第三天

第一 Specifications动态查询

JpaSpecificationExecutor 方法列表

	T findOne(Specification<T> spec);  //查询单个对象
	List<T> findAll(Specification<T> spec);  //查询列表

	//查询全部,分页
	//pageable:分页参数
	//返回值:分页pageBean(page:是springdatajpa提供的)
	Page<T> findAll(Specification<T> spec, Pageable pageable);

	//查询列表
	//Sort:排序参数
	List<T> findAll(Specification<T> spec, Sort sort);

	long count(Specification<T> spec);//统计查询
	
* Specification :查询条件
	自定义我们自己的Specification实现类
		实现
        //root:查询的根对象(查询的任何属性都可以从根对象中获取)
        //CriteriaQuery:顶层查询对象,自定义查询方式(了解:一般不用)
        //CriteriaBuilder:查询的构造器,封装了很多的查询条件
        Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb); //封装查询条件

第二 多表之间的关系和操作多表的操作步骤

表关系
	一对一
	一对多:
		一的一方:主表
		多的一方:从表
		外键:需要再从表上新建一列作为外键,他的取值来源于主表的主键
	多对多:
		中间表:中间表中最少应该由两个字段组成,这两个字段做为外键指向两张表的主键,又组成了联合主键

讲师对学员:一对多关系
		
实体类中的关系
	包含关系:可以通过实体类中的包含关系描述表关系
	继承关系

分析步骤
	1.明确表关系
	2.确定表关系(描述 外键|中间表)
	3.编写实体类,再实体类中描述表关系(包含关系)
	4.配置映射关系

第三 完成多表操作

i.一对多操作
	案例:客户和联系人的案例(一对多关系)
		客户:一家公司
		联系人:这家公司的员工
	
		一个客户可以具有多个联系人
		一个联系人从属于一家公司
		
	分析步骤
		1.明确表关系
			一对多关系
		2.确定表关系(描述 外键|中间表)
			主表:客户表
			从表:联系人表
				* 再从表上添加外键
		3.编写实体类,再实体类中描述表关系(包含关系)
			客户:再客户的实体类中包含一个联系人的集合
			联系人:在联系人的实体类中包含一个客户的对象
		4.配置映射关系
			* 使用jpa注解配置一对多映射关系

	级联:
		操作一个对象的同时操作他的关联对象
		
		级联操作:
			1.需要区分操作主体
			2.需要在操作主体的实体类上,添加级联属性(需要添加到多表映射关系的注解上)
			3.cascade(配置级联)
		
		级联添加,
			案例:当我保存一个客户的同时保存联系人
		级联删除
			案例:当我删除一个客户的同时删除此客户的所有联系人
			
ii.多对多操作
	案例:用户和角色(多对多关系)
		用户:
		角色:

	分析步骤
		1.明确表关系
			多对多关系
		2.确定表关系(描述 外键|中间表)
			中间间表
		3.编写实体类,再实体类中描述表关系(包含关系)
			用户:包含角色的集合
			角色:包含用户的集合
		4.配置映射关系
		
iii.多表的查询
	1.对象导航查询
		查询一个对象的同时,通过此对象查询他的关联对象
		案例:客户和联系人
		从一方查询多方
			* 默认:使用延迟加载(****)
			
		从多方查询一方
			* 默认:使用立即加载

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);
   	//根据条件分页查询
 	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);

使用Specifications完成条件查询

//依赖注入customerDao
@Autowired
private CustomerDao customerDao;	
@Test
public void testSpecifications() {
    //使用匿名内部类的方式,创建一个Specification的实现类,并实现toPredicate方法
    Specification <Customer> spec = new Specification<Customer>() {
        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            //cb:构建查询,添加查询方式   like:模糊匹配
            //root:从实体Customer对象中按照custName属性进行查询
            return cb.like(root.get("custName").as(String.class), "播客%");
        }
    };
    Customer customer = customerDao.findOne(spec);
    System.out.println(customer);
}

Specifications的多条件查询

package com.kxj.test;

import com.kxj.dao.CustomerDao;
import com.kxj.entity.Customer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.persistence.criteria.*;
import java.util.List;
import java.util.Optional;

/**
 * @author kxj
 * @date 2019/11/11 22:33
 * @Desc 动态查询
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class CustomerTest {

    @Autowired
    private CustomerDao customerDao;

    /**
     * 根据条件 查询对象
     */
    @Test
    public void testSpec() {
        /**
         * 匿名内部类
         *      1、实现Specification接口(提供泛型,查询的对象类型)
         *      2、实现toPredicate方法
         *      3、需要借助方法参数中的两个参数
         *          root:获取需要查询的对象属性
         *          CriteraBuilder:构造查询条件的,内部封账了很多的查询条件(模糊查询,精准匹配)
         *
         *
         * 案例:根据客户名称查询
         *      1、查询方式
         *          criteriaBuilder对象
         *      2、比较的属性名称
         *          root对象
         */
        Specification<Customer> specification = new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //1、获取比较的属性
                Path<Object> custName = root.get("custName");
                //2、构造查询条件
                /**
                 * 第一个参数: 需要比较的比象(path对象)
                 * 第二个参数:当前需要比较的取值
                 */
                Predicate predicate = criteriaBuilder.equal(custName, "奥巴马");
                return predicate;
            }
        };
        Optional<Customer> customerOptional = customerDao.findOne(specification);
        if (customerOptional.isPresent()) {
            Customer customer = customerOptional.get();
            System.out.println(customer);
        }
    }

    /**
     * 多条件 查询对象
     */
    @Test
    public void testSpec2() {
        Specification<Customer> specification = new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //1、获取比较的属性
                Path<Object> custName = root.get("custName");
                Path<Object> custIndustry = root.get("custIndustry");
                //2、构造查询条件
                /**
                 * 第一个参数: 需要比较的比象(path对象)
                 * 第二个参数:当前需要比较的取值
                 */
                Predicate p1 = criteriaBuilder.equal(custName, "奥巴马");
                Predicate p2 = criteriaBuilder.equal(custIndustry, "服务业");

                // 将多个查询条件组合在一起(and,or)
                Predicate predicate = criteriaBuilder.and(p1, p2);
                return predicate;
            }
        };
        Optional<Customer> customerOptional = customerDao.findOne(specification);
        if (customerOptional.isPresent()) {
            Customer customer = customerOptional.get();
            System.out.println(customer);
        }
    }

    /**
     * 模糊匹配 查询对象
     *  equal:直接得到path对象(属性),然后进行比较即可
     *  gt,lt,ge,,le,like:得到path对象,根据path指定的参数类型,在去比较即可
     *      指定参数类型:path.as(类型的字节码对象)
     */
    @Test
    public void testSpec3() {
        Specification<Customer> specification = new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //1、获取比较的属性
                Path<Object> custName = root.get("custName");
                //2、构造查询条件
                /**
                 * 第一个参数: 需要比较的比象(path对象)
                 * 第二个参数:当前需要比较的取值
                 */
                Predicate predicate = criteriaBuilder.like(custName.as(String.class),"%奥巴马%");

                // 将多个查询条件组合在一起(and,or)
                return predicate;
            }
        };
        List<Customer> customers = customerDao.findAll(specification);
        for (Customer customer : customers) {
            System.out.println(customer);
        }
    }

    @Test
    public void testSpec4() {
        Specification<Customer> specification = new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                Path<Object> custName = root.get("custName");
                Predicate predicate = criteriaBuilder.like(custName.as(String.class),"%奥巴马%");
                return predicate;
            }
        };

        Sort sort = new Sort(Sort.Direction.DESC, "custLevel");
        List<Customer> customers = customerDao.findAll(specification, sort);
        for (Customer customer : customers) {
            System.out.println(customer);
        }
    }
    

/**

  • 多条件查询

/ Specification specification = (root, query, criteriaBuilder) -> { List predicatesList = new ArrayList<>(); if (startDate != null) { predicatesList.add(criteriaBuilder.greaterThan(root.get("createTime"), startDate)); } if (endDate != null) { predicatesList.add(criteriaBuilder.lessThan(root.get("createTime"), endDate)); } Predicate[] predicate = new Predicate[predicatesList.size()]; return criteriaBuilder.and(predicatesList.toArray(predicate)); };


    /**
     * 分页查询
     *
     *      Specification: 查询条件
     *      Pageable:分页参数
     *          分页参数: 查询的页码, ,每页查询的条件
     *          findAll(Specification, Pageable) 带有条件的分页
     *          findAll(Pageable):没有条件的分页
     *
     *
     */
    @Test
    public void testSpec5() {

        Specification specification = null;
        /**
         * PageReuqest对象是Pageable接口的实现类
         *
         * 创建PageRequest的过程中,需要调用它的构造方法传入两个参数
         *  第一个参数:当前查询的页数(从0开始)
         *  第二个参数:每页查询的数量
         *
         *  直接new PageRequest已经过时,PageRequest.of取代
         */
        Pageable pageRequest = PageRequest.of(0, 2);

        // 分页查询
        Page<Customer> customers = customerDao.findAll((Specification<Customer>) null, pageRequest);
        for (Customer customer : customers) {
            System.out.println(customer);
        }

        System.out.println("总条数 "+customers.getTotalElements());
        System.out.println("集合列表 "+customers.getContent());
        System.out.println("总页数 "+customers.getTotalPages());

    }

}

基于Specifications的分页查询**

    @Test
	public void testPage() {
		//构造查询条件
		Specification<Customer> spec = new Specification<Customer>() {
			public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				return cb.like(root.get("custName").as(String.class), "%");
			}
		};
		
		/**
		 * 构造分页参数
		 * 		Pageable : 接口
		 * 			PageRequest实现了Pageable接口,调用构造方法的形式构造
		 * 				第一个参数:页码(从0开始)
		 * 				第二个参数:每页查询条数
		 */
		Pageable pageable = new PageRequest(0, 5);
		
		/**
		 * 分页查询,封装为Spring Data Jpa 内部的page bean
		 * 		此重载的findAll方法为分页方法需要两个参数
		 * 			第一个参数:查询条件Specification
		 * 			第二个参数:分页参数
		 */
		Page<Customer> page = customerDao.findAll(spec,pageable);
		
	}

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

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

方法对应关系

方法名称 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
 Page<PhenomenonKeys> keysPage = phenomenonKeysDao.findAll((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicatesList = new ArrayList<>();
            if (startDate != null) {
                predicatesList.add(criteriaBuilder.greaterThan(root.get("createTime"), startDate));
            }
            if (endDate != null) {
                predicatesList.add(criteriaBuilder.lessThan(root.get("createTime"), DateUtil.addOneDay(endDate)));
            }
            Predicate[] predicate = new Predicate[predicatesList.size()];
            return criteriaBuilder.and(predicatesList.toArray(predicate));
        }, pageable);

表之间关系的划分

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

img

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

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

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

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

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

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

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

JPA中多一对多

示例分析:
我们采用的示例为客户和联系人。
 客户:指的是一家公司,我们记为A。
 联系人:指的是A公司中的员工。
 在不考虑兼职的情况下,公司和员工的关系即为一对多。

表关系建立

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

什么是外键?

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

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

img

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

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

/**
 * 客户的实体类
 * 明确使用的注解都是JPA规范的
 * 所以导包都要导入javax.persistence包下的
 */
@Entity//表示当前类是一个实体类
@Table(name="cst_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;
	
    //配置客户和联系人的一对多关系
  	@OneToMany(targetEntity=LinkMan.class)
	@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
	private Set<LinkMan> linkmans = new HashSet<LinkMan>(0);
}

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

/**
 * 联系人的实体类(数据模型)
 */
@Entity
@Table(name="cst_linkman")
public class LinkMan implements Serializable {
	@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;

	//多对一关系映射:多个联系人对应客户
	@ManyToOne(targetEntity=Customer.class)
	@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
	private Customer customer;//用它的主键,对应联系人表中的外键
}

映射的注解说明

@OneToMany:
   	作用建立一对多的关系映射
    属性targetEntityClass指定多的多方的类的字节码
    	mappedBy指定从表实体类中引用主表对象的名称cascade指定要使用的级联操作
    	fetch指定是否采用延迟加载
    	orphanRemoval是否使用孤儿删除

@ManyToOne
    作用建立多对一的关系
    属性targetEntityClass指定一的一方实体类字节码
    	cascade指定要使用的级联操作
    	fetch指定是否采用延迟加载
    	optional关联是否可选如果设置为false则必须始终存在非空关系@JoinColumn
     作用用于定义主键字段和外键字段的对应关系属性name指定外键字段的名称
    	referencedColumnName指定引用主表的主键字段名称
    	unique是否唯一默认值不唯一
    	nullable是否允许为空默认值允许insertable是否允许插入默认值允许updatable是否允许更新默认值允许columnDefinition列的定义信息

一对多的操作

  • 添加

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations="classpath:applicationContext.xml")
    public class OneToManyTest {
    
    	@Autowired
    	private CustomerDao customerDao;
    	
    	@Autowired
    	private LinkManDao linkManDao;
    
    	/**
    	 * 保存操作
    	 * 需求:
    	 * 	保存一个客户和一个联系人
    	 * 要求:
    	 * 	创建一个客户对象和一个联系人对象
    	 *  建立客户和联系人之间关联关系(双向一对多的关联关系)
    	 *  先保存客户,再保存联系人
    	 * 问题:
    	 *		当我们建立了双向的关联关系之后,先保存主表,再保存从表时:
    	 *		会产生2条insert和1条update.
    	 * 		而实际开发中我们只需要2条insert。
    	 *  
    	 */
    	@Test
    	@Transactional  //开启事务
    	@Rollback(false)//设置为不回滚
    	public void testAdd() {
    		Customer c = new Customer();
    		c.setCustName("TBD云集中心");
    		c.setCustLevel("VIP客户");
    		c.setCustSource("网络");
    		c.setCustIndustry("商业办公");
    		c.setCustAddress("昌平区北七家镇");
    		c.setCustPhone("010-84389340");
    		
    		LinkMan l = new LinkMan();
    		l.setLkmName("TBD联系人");
    		l.setLkmGender("male");
    		l.setLkmMobile("13811111111");
    		l.setLkmPhone("010-34785348");
    		l.setLkmEmail("[email protected]");
    		l.setLkmPosition("老师");
    		l.setLkmMemo("还行吧");
    
    		c.getLinkMans().add(l);
    		l.setCustomer(c);
    		customerDao.save(c);
    		linkManDao.save(l);
    	}
    }
    

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

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

    删除

    @Autowired
    private CustomerDao customerDao;
    
    @Test
    @Transactional
    @Rollback(false)//设置为不回滚
    public void testDelete() {
        customerDao.delete(1l);
    }

    删除操作的说明如下:

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

    删除主表数据:

    有从表数据

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

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

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

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

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

    级联操作

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

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

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

JPA的多对多

示例分析
 我们采用的示例为用户和角色。
 用户指的是咱们班的每一个同学。
 角色指的是咱们班同学的身份信息。

 比如A同学它是我的学生其中有个身份就是学生还是家里的孩子那么他还有个身份是子女。
 同时B同学它也具有学生和子女的身份。
 那么任何一个同学都可能具有多个身份同时学生这个身份可以被多个同学所具有。
 所以我们说用户和角色之间的关系是多对多

表关系简历

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

img

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

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

/**
 * 用户的数据模型
 */
@Entity
@Table(name="sys_user")
public class SysUser implements Serializable {
	
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="user_id")
	private Long userId;
	@Column(name="user_code")
	private String userCode;
	@Column(name="user_name")
	private String userName;
	@Column(name="user_password")
	private String userPassword;
	@Column(name="user_state")
	private String userState;
	
	//多对多关系映射
	@ManyToMany(mappedBy="users")
	private Set<SysRole> roles = new HashSet<SysRole>(0);
	
}

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

/**
 * 角色的数据模型
 */
@Entity
@Table(name="sys_role")
public class SysRole implements Serializable {
	
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="role_id")
	private Long roleId;
	@Column(name="role_name")
	private String roleName;
	@Column(name="role_memo")
	private String roleMemo;
	
	//多对多关系映射
	@ManyToMany
	@JoinTable(name="user_role_rel",//中间表的名称
			  //中间表user_role_rel字段关联sys_role表的主键字段role_id
			  joinColumns={@JoinColumn(name="role_id",referencedColumnName="role_id")},
			  //中间表user_role_rel的字段关联sys_user表的主键user_id
			  inverseJoinColumns={@JoinColumn(name="user_id",referencedColumnName="user_id")}
	)
	private Set<SysUser> users = new HashSet<SysUser>(0);
	
}

映射的注解说明

@ManyToMany
	作用用于映射多对多关系
	属性cascade配置级联操作fetch配置是否采用延迟加载targetEntity配置目标的实体类映射多对多的时候不用写@JoinTable
    作用针对中间表的配置
    属性nam配置中间表的名称
    	joinColumns中间表的外键字段关联当前实体类所对应表的主键字段			  			
    	inverseJoinColumn中间表的外键字段关联对方表的主键字段
    	
@JoinColumn
    作用用于定义主键字段和外键字段的对应关系属性name指定外键字段的名称
    	referencedColumnName指定引用主表的主键字段名称
    	unique是否唯一默认值不唯一
    	nullable是否允许为空默认值允许insertable是否允许插入默认值允许updatable是否允许更新默认值允许columnDefinition列的定义信息

多对多操作

  • 保存

        @Autowired
    	private UserDao userDao;
    	
    	@Autowired
    	private RoleDao roleDao;
    	/**
    	 * 需求:
    	 * 	保存用户和角色
    	 * 要求:
    	 * 	创建2个用户和3个角色
    	 * 	让1号用户具有1号和2号角色(双向的)
    	 * 	让2号用户具有2号和3号角色(双向的)
    	 *  保存用户和角色
    	 * 问题:
    	 *  在保存时,会出现主键重复的错误,因为都是要往中间表中保存数据造成的。
    	 * 解决办法:
    	 * 	让任意一方放弃维护关联关系的权利
    	 */
    	@Test
    	@Transactional  //开启事务
    	@Rollback(false)//设置为不回滚
    	public void test1(){
    		//创建对象
    		SysUser u1 = new SysUser();
    		u1.setUserName("用户1");
    		SysRole r1 = new SysRole();
    		r1.setRoleName("角色1");
    		//建立关联关系
    		u1.getRoles().add(r1);
    		r1.getUsers().add(u1);
    		//保存
    		roleDao.save(r1);
    		userDao.save(u1);
    	}

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

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

    @Autowired
    private UserDao userDao;
    /**
    	 * 删除操作
    	 * 	在多对多的删除时,双向级联删除根本不能配置
    	 * 禁用
    	 *	如果配了的话,如果数据之间有相互引用关系,可能会清空所有数据
    	 */
    @Test
    @Transactional
    @Rollback(false)//设置为不回滚
    public void testDelete() {
        userDao.delete(1l);
    }

Spring Data Jpa 的多表查询

对象导航查询

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

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

@Autowired
private CustomerDao customerDao;

@Test
//由于是在java代码中测试,为了解决no session问题,将操作配置到同一个事务中
@Transactional 
public void testFind() {
    Customer customer = customerDao.findOne(5l);
    Set<LinkMan> linkMans = customer.getLinkMans();//对象导航查询
    for(LinkMan linkMan : linkMans) {
        System.out.println(linkMan);
    }
}

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

@Autowired
private LinkManDao linkManDao;

@Test
public void testFind() {
    LinkMan linkMan = linkManDao.findOne(4l);
    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;

使用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);
		}
	}

个别总结

spring data jpa 1.0 和2.0部分方法存在差异,因此在使用的时候需要注意,否则,1.0能够跑起来的程序,2.0版本可能是会报错的。这里的1.0版本和2.0版本分别值得是我测试的版本,分别为1.9。0,2.0版本为2.1.12.其他版本差异可以参考官方文档。

例如:

  • JPQL和SQL语句存在差异

    不可混用,否则报错

    sql  : update cst_customer set cust_name = ? where cust_id = ?
    jpql : update Customer set custName = ? where custId = ?
    
  • 方法名的差异:

​ springdatajpa:1.0版本 boolean exists(ID primaryKey)

​ springdatajpa:2.0版本 boolean existsById(ID primaryKey)

  • JPQL创建查询时:

​ springdatajpa:1.0版本 可以指定展位符的位置,也可以不指定

@Query(value="from Customer where custName = ?")
// 或@Query(value="from Customer where custName = ?")
public Customer findJpql(String custName);

​ 2.0版本中必须指定位置,否则程序运行会报错

@Query(value="from Customer where custName = ?")
public Customer findJpql(String custName);

​ 同时2.0版本中 Using named parameters 也不再支持

public interface UserRepository extends JpaRepository<User, Long> {

  @Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
  User findByLastnameOrFirstname(@Param("lastname") String lastname,
                                 @Param("firstname") String firstname);
}
提升
  1. Spring Data Jpa @Query注解解析对象(使用SPEL)

    @Query(value = "from ProduceRecord where VIN like concat('%',?#{#produceRecord.VIN}, '%') " +
                "and (?#{#produceRecord.equipmentType} IS NULL or equipmentType = ?#{#produceRecord.equipmentType})"+
                "and (:startDate IS NULL or writeTime >= :startDate)"+
                "and (:endDate IS NULL or writeTime <= :endDate)"+
                "and (?#{#produceRecord.workshop} IS NULL or workshop = ?#{#produceRecord.workshop})"+
                "and (?#{#produceRecord.equipmentIP} IS NULL or equipmentIP = ?#{#produceRecord.equipmentIP})"
        )
        public List<ProduceRecord> findProduceRecordList(@Param("produceRecord") ProduceRecord produceRecord,@Param("startDate") Date startDate,@Param("endDate") Date endDate,@Param("pagebale") Pageable pagebale);
    • 传入Pageable对象会自动解析
    • **and(字段值 is null or 字段属性=字段值)**根据字段是否存在添加and 条件
    • ?#{#Object.property} 获得对象参数
@Column(name = "partName_en", nullable = false, columnDefinition = "varchar(30)")
nullable 未生效

@Column(name = "ecu_type", nullable = false, columnDefinition = "varchar(20)")
@NotNull
生效 成功映射到数据库约束

Specification

 public Page<TransferRecord> findTransferRecords(Date pushStartTime, Date pushEndTime, String partNameNo, String equipmentIP, Integer isSuccess, Integer page, Integer pageSize) {
        Specification<TransferRecord> specification = new Specification<TransferRecord>() {
            @Override
            public Predicate toPredicate(Root<TransferRecord> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
                List<Predicate> predicates = new ArrayList<>();

                if (pushStartTime != null) {
                    predicates.add(cb.greaterThanOrEqualTo(root.<Date>get("pushTime"), pushStartTime));
                }
                if (pushEndTime != null) {
                    predicates.add(cb.lessThanOrEqualTo(root.<Date>get("pushTime"), pushEndTime));
                }
                if (partNameNo != null) {
                    predicates.add(cb.like(root.get("partSoftNo"), "%" + partNameNo + "%"));
                }
                if (equipmentIP != null) {
                    predicates.add(cb.like(root.get("equipmentIP"), "%" + equipmentIP + "%"));
                }
                if (isSuccess != null) {
                    predicates.add(cb.equal(root.get("isSuccess"), isSuccess));
                }
                return cb.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        };
        Sort sort = new Sort(Sort.Direction.DESC, "id");
        Pageable pageable = PageRequest.of(page, pageSize, sort);
        return transferRecordDao.findAll(specification, pageable);

    }

关于思考题: 1:在数据库主键设置为自增的情况下,插入数据是自定义主键,是否有效果?会发生什么? 经过测试,在设置数据库的主键为自增时,在插入数据的时候的即使为该字段自定义了值,数据入库后也没有效果。数据是可以落库的,但是主键值还是按照自增的策略增加, 并没有使用自定义的主键值,暂时也没有找到相关的方法.