SpringData JPA学习笔记

一、JPA

ORM(Object Relational Mapping)表示对象关系映射。

为什么使用ORM?

当实现一个应用程序时(不使用ORM),可能会编写很多繁琐的数据访问层的代码,从数据库中增、删、改数据,而这些代码都是重复的。使用ORM则会大大减少重复性代码。ORM主要实现程序对象到关系数据库数据的映射。

常见的ORM框架

Mybatis、Hibernate、Jpa

1.1、概述

Java Persistence API:用于对象持久化的API,是sun公司推出的一套基于ORM的规范,内部是由一系列的接口和抽象类构成。

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

JPA与Hibernate的关系

  • JPA是hibernate的一个抽象(就像JDBC和JDBC驱动的关系):

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

    • Hibernate是实现:Hibernate除了作为ORM框架之外,它也是一种JPA实现

  • 从功能上来说,JPA是Hibernate功能的一个子集

在这里插入图片描述

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

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

JPA的优势

  • 标准化:JPA 是 JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。
  • 容器级特性的支持:JPA框架中支持大数据集、事务、并发等容器级事务,这使得JPA超越了简单持久化框架的局限,在企业应用中发挥更大的作用。
  • 简单易用,集成方便:JPA的主要目标之一就是提供更加简单的编程模型,在JPA框架下创建实现和创建Java类一样简单,只需要使用javax.persistence.Entitty进行注释;JPA的框架和接口也都是非常简单的
  • 可媲美JDBC的查询能力:JPA的查询语言是面向对象的,JPA的定义了独立的JPQL,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING等通常只有SQL才能够提供的高级查询特性,甚至还能够支持子查询。
  • 支持面向对象的高级特性:JPA中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化

JPA包括3方面的技术

  • ORM映射元数据:JPA支持XML和JDK5.0的注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中。
  • JPA的API:用来操作实现对象,执行CRUD操作,框架在后台完成所有的事情,开发者从繁琐的JDBC和SQL代码中解脱出来。
  • 查询语言(JPQL):这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序和具体的SQL紧密耦合。

1.2、JPA持久化对象的步骤

  • 创建persistence.xml,在这个文件中配置持久化单元
    • 需要指定跟哪个数据库进行交互
    • 需要指定JPA使用哪个持久化的框架以及配置该框架的基本属性
  • 创建实体类,使用annotation来描述实体类与数据库表之间的映射关系
  • 使用JPA API完成数据增加、删除、修改和查询操作
    • 创建EntityManagerFactory(对应Hibernate中的SessionFactory)
    • 创建EntityManager(对应Hibernate中的Session)
1、加载配置文件创建工厂(实体管理类工厂)对象
	Persistence:静态方法(根据持久化单元名称创建实体管理器工厂)
		createEntityManagerFactory(持久化单元名称)
    作用:创建实体管理器工厂
2、通过实体管理类工厂获取实体管理器
	EntityManagerFactory:获取EntityManager对象
	方法:createEntityManager
	- 内部维护的很多的内容
		内部维护了数据库信息
		维护了缓存信息
		维护了所有的实体管理器对象
		在创建EntityManagerFactory的过程中会根据配置创建数据库表
	- EntityManagerFactory的创建过程比较浪费资源
	特点:线程安全的对象
		多个线程访问同一个EntityManagerFactory不会有线程安全问题
	- 如何解决EntityManagerFactory的创建过程浪费资源(耗时)的问题?
	思路:创建一个公共的EntityManagerFactory的对象
	- 静态代码块的形式创建EntityManagerFactory
3、获取事务管理对象,开启事务
	EntityManager对象:实体类管理器
		beginTransaction:创建事务对象
		persist:保存
		merge:更新
		remove:删除
		find/getReference:根据id查询
	Transaction对象:事务
		begin:开启事务
		commit:提交事务
		rollback:回滚事务
4、完成增删该查操作
5、提交事务(回滚事务)
6、释放资源

二、JPA的基本操作

2.1、第一个JPA程序

1、新建项目,并导入jar包。

<?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>com.lskj</groupId>
    <artifactId>jpa</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</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>
</project>

2、配置jpa的核心配置文件。

  • 位置:配置到类路径下的META-INF文件夹下。
  • 命名:persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
    <!--需要配置persistence-unit节点
        持久化单元:
            name:持久化单元名称
            transaction-type:事务管理的方式
                JTA:分布式事务管理
                RESOURCE_LOCAL:本地事务管理
    -->
    <persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
        <!--jpa的实现方式-->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <properties>
            <!--数据库信息
                用户名:javax.persistence.jdbc.user
                密码:javax.persistence.jdbc.password
                驱动:javax.persistence.jdbc.driver
                数据库地址:javax.persistence.jdbc.url
            -->
            <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实现方的配置信息-->
            <!--配置jpa实现方(hibernate)的配置信息
                显示sql:hibernate.show_sql
                自动创建数据库表:hibernate.hbm2ddl.auto
                    create:程序运行时创建数据库表(如果表存在,先删除表再创建表)
                    update:程序运行时创建表(如果表存在,不会创建表)
                    none:不会创建表
            -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="create"/>
        </properties>
    </persistence-unit>
</persistence>

3、创建实体类,并配置实体类和表,类中属性和表中字段的映射关系。

package com.lskj.domain;

import javax.persistence.*;

/**
 * 客户实体类
 *  配置映射管理
 *      1、实体类与表的映射关系
 *          @Entity:声明实体类
 *          @Table:配置实体类和表的映射关系
 *          name:配置数据库表的名称
 *      2、实体类中属性与表中字段的映射关系
 */
@Entity
@Table(name = "cst_customer")
public class Customer {
    /**
     * @Id:声明主键
     * @GeneratedValue:配置主键的生成策略
     *    GenerationType.IDENTITY:自增
     *      底层数据库必须支持自动增长(MySQL)
     *    GenerationType.SEQUENCE:序列
     *      底层数据库必须支持序列(Oracle)
     *    GenerationType.TABLE:jpa提供的一种机制,通过一场数据库表的形式帮助完成主键自增
     *    GenerationType.AUTO:由程序自动的帮助选择主键生成策略
     * @Column:配置属性和字段的映射关系
     *  name:指定数据库表的列名称。
     *  unique:是否唯一  
     *  nullable:是否可以为空  
     *  inserttable:是否可以插入  
     *  updateable:是否可以更新  
     *  columnDefinition: 定义建表时创建此列的DDL  
     *  secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字搭建开发环境
     */
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    @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; //客户地址

    public Long getCustId() {
        return custId;
    }

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

    public String getCustName() {
        return custName;
    }

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

    public String getCustSource() {
        return custSource;
    }

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

    public String getCustLevel() {
        return custLevel;
    }

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

    public String getCustIndustry() {
        return custIndustry;
    }

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

    public String getCustPhone() {
        return custPhone;
    }

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

    public String getCustAddress() {
        return custAddress;
    }

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

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

4、保存信息到数据库中。

package com.lskj;

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

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

public class JpaTest {
    /**
     * 测试JPA的保存
     *  Jpa的操作步骤:
     *      1、加载配置文件创建工厂(实体管理类工厂)对象
     *      2、通过实体管理类工厂获取实体管理器
     *      3、获取事务管理对象,开启事务
     *      4、完成增删该查操作
     *      5、提交事务(回滚事务)
     *      6、释放资源
     */
    @Test
    public void testSave(){
        //1、加载配置文件创建工厂(实体管理类工厂)对象
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
        //2、通过实体管理类工厂获取实体管理器
        EntityManager em = factory.createEntityManager();
        //3、获取事务管理对象,开启事务
        EntityTransaction tx = em.getTransaction(); //获取事务对象
        tx.begin(); //开启事务
        //4、完成增删该查操作
        Customer customer = new Customer();
        customer.setCustName("test");
        customer.setCustIndustry("IT");
        //保存
        em.persist(customer);   //保存操作
        //5、提交事务(回滚事务)
        tx.commit();
        //6、释放资源
        em.close();
        factory.close();
    }
}

2.2、抽取jpaUtils工具类

package com.lskj.utils;

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

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

   static {
       //1、加载配置文件,创建entityManagerFactory
       factory = Persistence.createEntityManagerFactory("myJpa");
   }

   /**
    * 获取EntityManager对象
    */
   public static EntityManager getEntityManager(){
       return factory.createEntityManager();
   }
}

修改上述的testSave()方法:

@Test
public void testSave(){
    /*
    	//1、加载配置文件创建工厂(实体管理类工厂)对象
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
        //2、通过实体管理类工厂获取实体管理器
        EntityManager em = factory.createEntityManager();
    */
    EntityManager em = JpaUtils.getEntityManager();
    //3、获取事务管理对象,开启事务
    EntityTransaction tx = em.getTransaction(); //获取事务对象
    tx.begin(); //开启事务
    //4、完成增删该查操作
    Customer customer = new Customer();
    customer.setCustName("test");
    customer.setCustIndustry("IT");
    //保存
    em.persist(customer);   //保存操作
    //5、提交事务(回滚事务)
    tx.commit();
    //6、释放资源
    em.close();
    //factory.close();
}

2.3、使用JPA完成CRUD

@Test
public void testAdd(){
    //定义对象
    Customer customer = new Customer();
    customer.setCustName("张三");
    customer.setCustLevel("Lv1");
    customer.setCustIndustry("IT");
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        //获取实体管理对象
        em = JpaUtils.getEntityManager();
        //获取事务对象
        tx = em.getTransaction();
        //开启事务
        tx.begin();
        //执行操作
        em.persist(customer);
        //提交事务
        tx.commit();
    }catch (Exception e){
        //回滚事务
        tx.rollback();
        e.printStackTrace();
    } finally {
        //释放资源
        em.close();
    }
}

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

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

根据id查询

find

/**
* 立即加载
*/
@Test
public void testFind(){
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        //获取实体管理对象
        em = JpaUtils.getEntityManager();
        //获取事务对象
        tx = em.getTransaction();
        //开启事务
        tx.begin();
        //执行操作
        Customer customer = em.find(Customer.class, 6L);
        //提交事务
        tx.commit();
        System.out.println(customer);
    }catch (Exception e){
        //回滚事务
        tx.rollback();
        e.printStackTrace();
    } finally {
        //释放资源
        em.close();
    }
}

getReference

/**
 * getReference方法
 *  1、获取的对象是一个动态代理对象
 *  2、调用getReference方法不会立即放松sql语句查询数据库
 *      当调用查询结果对象时,才会发送查询的失去了语句:什么时候用,什么时候发送sql语句查询数据库
 * 延迟加载(懒加载)
 *  得到的是一个动态代理对象
 *  什么时候用,什么时候才会查询
 */
@Test
public void testReference(){
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        //获取实体管理对象
        em = JpaUtils.getEntityManager();
        //获取事务对象
        tx = em.getTransaction();
        //开启事务
        tx.begin();
        //执行操作
        Customer customer = em.getReference(Customer.class, 1L);
        //提交事务
        tx.commit();
        System.out.println(customer);
    }catch (Exception e){
        //回滚事务
        tx.rollback();
        e.printStackTrace();
    } finally {
        //释放资源
        em.close();
    }
}

2.4、JPA中的复杂查询

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

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

  • sql查询的是表和表中的字段
  • jpql查询的是实体类和类中的属性

进行jpql查询:

  • 创建query对象
  • 对参数进行赋值
  • 查询,并得到返回结果

查询全部

/**
 * 查询全部
 *  sql:select * from cst_customer
 *  jqpl: from Customer
 */
@Test
public void FindAll(){
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        //获取实体管理对象
        em  = JpaUtils.getEntityManager();
        //获取事务对象
        tx = em.getTransaction();
        tx.begin();
        //创建query对象,query对象才是执行jpql的对象
        String jpql = "from com.lskj.domain.Customer";
        Query query = em.createQuery(jpql);
        //发送查询,并封装结果集
        List list = query.getResultList();
        for (Object obj : list) {
            System.out.println(obj);
        }
        tx.commit();
    }catch (Exception e){
        //回滚事务
        tx.rollback();
        e.printStackTrace();
    }finally {
        //释放资源
        em.close();
    }
}

排序查询

/**
 * 倒序查询
 *  sql:select * from cst_customer order by cust_id desc
 *  jqpl: from Customer order by custId desc
 */
@Test
public void Orders(){
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        //获取实体管理对象
        em  = JpaUtils.getEntityManager();
        //获取事务对象
        tx = em.getTransaction();
        tx.begin();
        //创建query对象,query对象才是执行jpql的对象
        String jpql = "from Customer order by custId desc";
        Query query = em.createQuery(jpql);
        //发送查询,并封装结果集
        List list = query.getResultList();
        for (Object obj : list) {
            System.out.println(obj);
        }
        tx.commit();
    }catch (Exception e){
        //回滚事务
        tx.rollback();
        e.printStackTrace();
    }finally {
        //释放资源
        em.close();
    }
}

统计总数

/**
 * 统计总数
 *  sql:select count(cust_id) from cst_customer
 *  jqpl: select count(custId) from Customer
 */
@Test
public void Count(){
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        //获取实体管理对象
        em  = JpaUtils.getEntityManager();
        //获取事务对象
        tx = em.getTransaction();
        tx.begin();
        //创建query对象,query对象才是执行jpql的对象
        String jpql = "select count(custId) from Customer";
        Query query = em.createQuery(jpql);
        //getResultList:直接将查询结果封装为list集合
        //getSingleResult:得到唯一的结果集
        Object result = query.getSingleResult();
        System.out.println(result);
        tx.commit();
    }catch (Exception e){
        //回滚事务
        tx.rollback();
        e.printStackTrace();
    }finally {
        //释放资源
        em.close();
    }
}

分页查询

/**
 * 分页查询
 *  sql:select * from cst_customer limit ?,?
 *  jqpl: from Customer
 */
@Test
public void Paged(){
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        //获取实体管理对象
        em  = JpaUtils.getEntityManager();
        //获取事务对象
        tx = em.getTransaction();
        tx.begin();
        //创建query对象,query对象才是执行jpql的对象
        String jpql = "from Customer";
        Query query = em.createQuery(jpql);
        //对参数赋值
        /**
         * 分页参数:
         *  -起始索引
         *  -每页查询的条数
         */
        //起始索引
        query.setFirstResult(0);
        //每页查询的条数
        query.setMaxResults(2);
        //getResultList:直接将查询结果封装为list集合
        //getSingleResult:得到唯一的结果集
        List list = query.getResultList();
        for (Object obj : list) {
            System.out.println(obj);
        }
        tx.commit();
    }catch (Exception e){
        //回滚事务
        tx.rollback();
        e.printStackTrace();
    }finally {
        //释放资源
        em.close();
    }
}

条件查询

/**
 * 条件查询
 *  sql:select * from cst_customer where cust_name like ?
 *  jqpl: from Customer where custName like ?
 */
@Test
public void Condition(){
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
        //获取实体管理对象
        em  = JpaUtils.getEntityManager();
        //获取事务对象
        tx = em.getTransaction();
        tx.begin();
        //创建query对象,query对象才是执行jpql的对象
        String jpql = "from Customer where custName like ?";
        Query query = em.createQuery(jpql);
        //对参数赋值-占位符参数
        //第一个参数:占位符的索引位置(从1开始),第二个参数:取值
        query.setParameter(1,"t%");

        //getResultList:直接将查询结果封装为list集合
        //getSingleResult:得到唯一的结果集
        List list = query.getResultList();
        for (Object obj : list) {
            System.out.println(obj);
        }
        tx.commit();
    }catch (Exception e){
        //回滚事务
        tx.rollback();
        e.printStackTrace();
    }finally {
        //释放资源
        em.close();
    }
}

三、SpringData JPA概述

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

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

SpringData JPA与JPA、hibernate之间的关系

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

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

四、SpringData JPA的基本操作

4-1、第一个SpringData Jpa程序

1、创建项目,导入jar包。

<?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>com.lskj</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <spring.version>4.2.4.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>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>${c3p0.version}</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>1.9.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</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>

</project>

2、配置spring的配置文件(配置spring data jpa的整合)。

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
		http://www.springframework.org/schema/data/jpa
		http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <!--spring和spring data jpa的配置-->
    <!--1、创建entityManagerFactory对象交给spring容器管理-->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSoruce"/>
        <!--配置的扫描的包(实体类所在的包)-->
        <property name="packagesToScan" value="com.lskj.domain"/>
        <!--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>
    </bean>

    <!--创建数据库连接池-->
    <bean id="dataSoruce" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="root"/>
        <property name="password" value="root"/>
        <property name="jdbcUrl" value="jdbc:mysql:///jpa"/>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    </bean>

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

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

    <!--声明式事务-->
    
    <!--配置包扫描-->
    <context:component-scan base-package="com.lskj"></context:component-scan>
</beans>

3、编写实体类(Customer),使用jpa注解配置映射关系。

package com.lskj.domain;

import javax.persistence.*;

/**
 * 1、实体类和表的映射关系
 * @Entity
 * @Table
 * 2、类中属性和表中字段的映射关系
 * @Id
 * @GenerateValue
 * @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;

    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;
    }

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

4、编写一个符合spring data jpa的dao层接口。

  • 只需要编写dao层接口,不需要编写dao层接口的实现类
  • dao层接口规范
    • 需要继承两个接口(JpaRepositotyJpaSpecificationExecutor
    • 需要提供相应的泛型
package com.lskj.dao;

import com.lskj.domain.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

/**
 * 符合SpringData Jpa的dao层接口规范
 *  JpaRepository<操作的实体类类型,实体类中主键属性的类型>
 *      封装了基本的CRUD操作
 *  JpaSpecificationExecutor<操作的实体类类型>
 *      封装了复杂查询(分页)
 */
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
}

5、测试

package com.lskj;

import com.lskj.dao.CustomerDao;
import com.lskj.domain.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 java.util.List;

@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(2l);
        System.out.println(customer);
    }

    /**
     * save:保存或更新
     *  根据传递的对象是否存在主键id,若不存在id主键属性:保存,反之,根据id查询数据,更新数据
     */
    @Test
    public void testSave(){
        Customer customer = new Customer();
        customer.setCustId(3l);
        customer.setCustName("李四");
        customer.setCustIndustry("IT");
        //customer.setCustLevel("Lv2");
        customer.setCustLevel("Lv3");
        customerDao.save(customer);
    }

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

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

4-2、SpringData Jpa的运行过程和原理

  • 通过JdkDynamicAopProxy的invoke方法创建一个动态代理对象
  • SimpleJpaRepository当中封装了JPA的操作(借助JPA的api完成数据库的CRUD)
  • 通过hibernate完成数据库操作(封装了JDBC)

4-3、复杂查询

  • 借助接口中定义好的方法完成查询

  • jpql的查询方式

    需要将JPQL语句配置到接口方法上

    • 特有的查询:需要在dao接口上配置方法
    • 在新添加的方法上,使用注解的形式配置jpql查询语句
    • 注解:@Query
  • sql语句的查询

    • 特有的查询:需要在dao接口上配置方法

    • 在新添加的方法上,使用注解的形式配置sql查询语句

    • 注解:@Query

      value:jpql语句 | sql语句

      nativeQuery:false | true(使用本地查询:sql查询)

  • 方法名称规则查询

    • 是对jpql查询,更加深入的一层封装
    • 只需按照SpringData Jpa提供的方法名称定义方法,不需要再配置jpql语句,完成查询

    findBy开头:代笔查询,对象中属性的名称(首字符大写),含义:根据属性名称进行查询

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

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

dao层接口:

package com.lskj.dao;

import com.lskj.domain.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;

/**
 * 符合SpringData Jpa的dao层接口规范
 *  JpaRepository<操作的实体类类型,实体类中主键属性的类型>
 *      封装了基本的CRUD操作
 *  JpaSpecificationExecutor<操作的实体类类型>
 *      封装了复杂查询(分页)
 */
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
    /**
     * 根据客户名称查询客户
     * jpql:form Customer where custName = ?
     * 配置jpql语句,使用@Query注解
     */
    @Query(value = "from Customer where custName = ?")
    public Customer findJpql(String custName);

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

    @Query(value = "from Customer where custName = ?2 and custId = ?1")
    public Customer findCustomerByNameAndId(Long id,String name);

    /**
     * 使用jpql完成更新操作
     * jpql:update Customer set custName = ? where custId = ?
     *
     * @Query:代表的是进行查询
     * @Modifying:当前执行的是一个更新操作
     */
    @Query(value = "update Customer set custName = ?2 where custId = ?1")
    @Modifying
    public void updateCustomer(Long custId,String custName);

    /**
     * 使用sql的形式查询
     * 查询全部的客户
     * sql:select * from cst_customer
     */
    @Query(value = "select * from cst_customer",nativeQuery = true)
    public List<Object []> findSql();

    @Query(value = "select * from cst_customer where cust_name like ?1",nativeQuery = true)
    public List<Object []> findSql(String name);

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

    public List<Customer> findByCustNameLike(String custName);

    //根据用户名称模糊匹配以及客户所属行业进行查询
    public Customer findByCustNameLikeAndCustIndustry(String custName,String custIndustry);
}

测试:

package com.lskj;

import com.lskj.dao.CustomerDao;
import com.lskj.domain.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;

@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 testFindByNameAndId(){
        //Customer customer = customerDao.findCustomerByNameAndId("张三", 2l);
        Customer customer = customerDao.findCustomerByNameAndId( 2l,"张三");
        System.out.println(customer);
    }

    /**
     * spring data jpa中使用jpql完成更新/删除操作
     *  需要手动添加事务的支持
     *  默认会执行结束之后,回滚事务
     *
     * @Rollback:设置是否自动回滚
     */
    @Test
    @Transactional //添加事务支持
    @Rollback(value = false)
    public void testUpdateCustomer(){
        customerDao.updateCustomer(3l,"李四1");
    }

    @Test
    public void testFindSql(){
        List<Object[]> list = customerDao.findSql();
        for (Object[] obj : list) {
            System.out.println(Arrays.toString(obj));
        }
    }

    @Test
    public void testFindSqlByName(){
        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);
    }
}

4-4、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);

方法对应关系:

方法名称Sql对应关系
equlefiled = value
gt(greaterThan )filed > value
lt(lessThan )filed < value
ge(greaterThanOrEqualTo )filed >= value
le( lessThanOrEqualTo)filed <= value
notEqulefiled != value
likefiled like value
notLikefiled not like value

测试:

package com.lskj;

import com.lskj.dao.CustomerDao;
import com.lskj.domain.Customer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
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;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpecTest {
    @Autowired
    private CustomerDao customerDao;

    /**
     * 根据条件查询单个对象
     */
    @Test
    public void testSpec(){
        //匿名内部类
        /**
         * 自定义查询条件:
         * 1、实现Specification接口(提供泛型:查询的对象类型)
         * 2、实现toPredicate方法(构造查询方法)
         * 3、需要借助方法参数中的两个参数(root:获取需要查询的对象属性,CriteriaBuilder:构造查询条件的,内部封装了很多的查询条件(模糊匹配,精准查询))
         *
         * 根据客户名称查询,查询客户名为张三的客户
         *  查询条件
         *      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、构造查询条件
                Predicate predicate = criteriaBuilder.equal(custName, "张三");//进行精准的匹配(比较的属性,比较的属性取值)
                return predicate;
            }
        };
        Customer customer = customerDao.findOne(specification);
        System.out.println(customer);
    }

    /**
     * 根据客户名称和客户所属行业进行查询
     */
    @Test
    public void testSpec02(){
        Specification<Customer> specification = new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                Path<Object> custName = root.get("custName");
                Path<Object> custIndustry = root.get("custIndustry");
                //构造查询
                Predicate p1 = criteriaBuilder.equal(custName, "张三");
                Predicate p2 = criteriaBuilder.equal(custIndustry, "IT");
                //将多个查询条件组合到一起:组合(满足条件一并且满足条件二:与关系;满足条件二或满足条件二:或关系)
                Predicate predicate = criteriaBuilder.and(p1, p2); //以与的形式拼接多个查询条件
                return predicate;
            }
        };
        Customer customer = customerDao.findOne(specification);
        System.out.println(customer);
    }

    /**
     * 根据客户名称进行模糊查询,返回客户列表
     */
    @Test
    public void testSpec03(){
        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;
            }
        };
        /*List<Customer> list = customerDao.findAll(specification);
        for (Customer customer : list) {
            System.out.println(customer);
        }*/
        //排序
        //第一个参数:排序的顺序(倒序Sort.Direction.DESC、正序Sort.Direction.ASC);第二个参数:排序的属性名称
        Sort sort = new Sort(Sort.Direction.DESC, "custId");
        List<Customer> list = customerDao.findAll(specification, sort);
        for (Customer customer : list) {
            System.out.println(customer);
        }
    }
}

分页查询

/**
 * 分页查询
 * findAll(Specification,Pageable):带有条件的分页
 * findAll(Specification):没有条件的分页
 *  Specification:查询条件
 *  Pageable:分页参数
 * 返回:Page,SpringData JPA封装了pageBean对象,数据列表,总条数
 */
@Test
public void testSpec04(){
    Specification specification = null;
    //PageRequest对象是Pageable接口的实现类
    /**
     * 创建PageRequest的过程中,需要调用它的构造方法传入两个参数
     *  第一个参数:当前查询的页数(从0开始)
     *  第二个参数:每页查询的数量
     */
    Pageable pageable = new PageRequest(0,2);
    //分页查询
    Page<Customer> page = customerDao.findAll(null, pageable);
    //获取总条数
    System.out.println(page.getTotalElements());
    //获取数据集合列表
    System.out.println(page.getContent());
    //获取总页数
    System.out.println(page.getTotalPages());
}

五、多表操作

5-1、分析步骤

  • 明确表关系
  • 确定表关系(描述 外键|中间表)
  • 编写实体类,在实体类中描述表关系(包含关系)
  • 配置映射关系

5-2、映射注解说明

@OneToMany:

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

属性:

  • targetEntityClass:指定多的多方的类的字节码
  • mappedBy:指定从表实体类中引用主表对象的名称。
  • cascade:指定要使用的级联操作
  • fetch:指定是否采用延迟加载
  • orphanRemoval:是否使用孤儿删除

@ManyToOne

作用:建立多对一的关系

属性:

  • targetEntityClass:指定一的一方实体类字节码
  • cascade:指定要使用的级联操作
  • fetch:指定是否采用延迟加载
  • optional:关联是否可选。如果设置为false,则必须始终存在非空关系。

@JoinColumn

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

属性:

  • name:指定外键字段的名称
  • referencedColumnName:指定引用主表的主键字段名称
  • unique:是否唯一。默认值不唯一
  • nullable:是否允许为空。默认值允许。
  • insertable:是否允许插入。默认值允许。
  • updatable:是否允许更新。默认值允许。
  • columnDefinition:列的定义信息。

5-3、一对多

添加

Customer.java

package com.lskj.domain;

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

/**
 * 1、实体类和表的映射关系
 * @Entity
 * @Table
 * 2、类中属性和表中字段的映射关系
 * @Id
 * @GenerateValue
 * @Column
 */
@Entity
@Table(name = "cst_customer")
public class Customer {
    //...

    //配置客户和联系人的关系(一对多关系)

    /**
     * 使用注解的形式配置多表关系
     *  1、声明关系
     * @OneToMany:配置一对多关系,targetEntity:对方对象的字节码对象
     *  2、配置外键(中间表)
     * @JoinColumn:配置外键,name:外键字段名称,referencedColumnName:参照的主表的主键字段名称
     *
     * 在客户实体类上(一)添加了外键的配置,所以对于客户而言,也具备了维护外键的作用
     */
    //@OneToMany(targetEntity = Contacts.class)
    //@JoinColumn(name = "cont_cust_id",referencedColumnName = "cust_id")
    //放弃外键维护,mappedBy:对方配置关系的属性名称
    @OneToMany(mappedBy = "customer")
    private Set<Contacts> contacts = new HashSet<>();

    //...

    public Set<Contacts> getContacts() {
        return contacts;
    }

    public void setContacts(Set<Contacts> contacts) {
        this.contacts = contacts;
    }

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

Contacts.java

package com.lskj.domain;

import javax.persistence.*;

@Entity
@Table(name = "cst_contacts")
public class Contacts {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cont_id")
    private Long contId;

    @Column(name = "cont_name")
    private String contName;

    @Column(name = "cont_gender")
    private String contGender;

    @Column(name = "cont_Phone")
    private String contPhone;

    @Column(name = "cont_email")
    private String contEmail;

    @Column(name = "cont_position")
    private String contPosition;

    @Column(name = "cont_memo")
    private String contMemo;

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

    public Long getContId() {
        return contId;
    }

    public void setContId(Long contId) {
        this.contId = contId;
    }

    public String getContName() {
        return contName;
    }

    public void setContName(String contName) {
        this.contName = contName;
    }

    public String getContGender() {
        return contGender;
    }

    public void setContGender(String contGender) {
        this.contGender = contGender;
    }

    public String getContPhone() {
        return contPhone;
    }

    public void setContPhone(String contPhone) {
        this.contPhone = contPhone;
    }

    public String getContEmail() {
        return contEmail;
    }

    public void setContEmail(String contEmail) {
        this.contEmail = contEmail;
    }

    public String getContPosition() {
        return contPosition;
    }

    public void setContPosition(String contPosition) {
        this.contPosition = contPosition;
    }

    public String getContMemo() {
        return contMemo;
    }

    public void setContMemo(String contMemo) {
        this.contMemo = contMemo;
    }

    public Customer getCustomer() {
        return customer;
    }

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

    @Override
    public String toString() {
        return "Contacts{" +
                "contId=" + contId +
                ", contName='" + contName + '\'' +
                ", contGender='" + contGender + '\'' +
                ", contPhone='" + contPhone + '\'' +
                ", contEmail='" + contEmail + '\'' +
                ", contPosition='" + contPosition + '\'' +
                ", contMemo='" + contMemo + '\'' +
                '}';
    }
}

CustomerDao.java

package com.lskj.dao;

import com.lskj.domain.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;

/**
 * 符合SpringData Jpa的dao层接口规范
 *  JpaRepository<操作的实体类类型,实体类中主键属性的类型>
 *      封装了基本的CRUD操作
 *  JpaSpecificationExecutor<操作的实体类类型>
 *      封装了复杂查询(分页)
 */
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
}

ContactsDao.java

package com.lskj.dao;

import com.lskj.domain.Contacts;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface ContactsDao extends JpaRepository<Contacts,Long>,JpaSpecificationExecutor<Contacts> {
}

测试:

package com.lskj;

import com.lskj.dao.ContactsDao;
import com.lskj.dao.CustomerDao;
import com.lskj.domain.Contacts;
import com.lskj.domain.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;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class OneToManyTest {
    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private ContactsDao contactsDao;

    /**
     * 保存一个客户,保存一个联系人
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testAdd(){
        Customer customer = new Customer();
        customer.setCustName("王二");

        Contacts contacts = new Contacts();
        contacts.setContName("李五");

        /**
         * 配置客户到联系人的关系
         *  从客户的角度:发送两天insert语句,发送一条更新语句更新外键
         */
        customer.getContacts().add(contacts);

        customerDao.save(customer);
        contactsDao.save(contacts);
    }

    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testAdd02(){
        Customer customer = new Customer();
        customer.setCustName("王二");

        Contacts contacts = new Contacts();
        contacts.setContName("李五");

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

        customerDao.save(customer);
        contactsDao.save(contacts);
    }

    /**会有一条多余的update语句
     * 由于一的一方可以维护外键:会发送update语句
     * 只需要在一的一方放弃维护即可
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testAdd03(){
        Customer customer = new Customer();
        customer.setCustName("王二");

        Contacts contacts = new Contacts();
        contacts.setContName("李五");

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

        customerDao.save(customer);
        contactsDao.save(contacts);
    }
}

删除

  • 删除从表数据,可以随时任意删除

  • 删除主表数据:

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

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

      3、如果还需删除,则使用级联删除。

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

级联操作

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

级联操作:

  • 需要区分操作主体
  • 需要在操作主体的实体类上,添加级联属性(需要添加到多表映射关系的注解上)
  • cascade(配置级联)
/**
 * cascade:配置级联操作
 * CascadeType.MERGE	级联更新
 * CascadeType.PERSIST	级联保存:
 * CascadeType.REFRESH 级联刷新:
 * CascadeType.REMOVE	级联删除:
 * CascadeType.ALL		包含所有
 */
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)

5-4、多对多

映射注解

@ManyToMany

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

属性:

  • cascade:配置级联操作。
  • fetch:配置是否采用延迟加载。
  • targetEntity:配置目标的实体类。映射多对多的时候不用写。

@JoinTable

作用:针对中间表的配置

属性:

  • nam:配置中间表的名称
  • joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
  • inverseJoinColumn:中间表的外键字段关联对方表的主键字段

@JoinColumn

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

属性:

  • name:指定外键字段的名称
  • referencedColumnName:指定引用主表的主键字段名称
  • unique:是否唯一。默认值不唯一
  • nullable:是否允许为空。默认值允许。
  • insertable:是否允许插入。默认值允许。
  • updatable:是否允许更新。默认值允许。
  • columnDefinition:列的定义信息。

5-5、多表查询

对象导航查询

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

@Autowired
private CustomerDao customerDao;

//查询一个客户,获取该客户下的所有联系人
@Test
//由于是在java代码中测试,为了解决no session问题,将操作配置到同一个事务中
@Transactional 
public void testFind() {
    Customer customer = customerDao.findOne(5l);
    Set<Contacts> contactsSets = customer.getContacts();//对象导航查询
    for(Contacts contacts : contactsSets) {
        System.out.println(contacts);
    }
}
@Autowired
private ContactsDao contactsDao;

//查询一个联系人,获取该联系人的所有客户
@Test
public void testFind() {
    Contacts contacts = contactsDao.findOne(4l);
    Customer customer = contacts.getCustomer(); //对象导航查询
    System.out.println(customer);
}

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

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

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

配置方式:

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

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

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

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

配置方式:

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

使用Specification查询

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值