Spring Data JPA开发入门教程

1、数据库操作演变历程

开篇立即进入正题,以下是最古老的数据库操作方式(应用程序直接连接数据库):

Java Application
MySQL数据库
Oracle数据库
SQLServer数据库
DB2数据库
...数据库

应用程序去访问不同的数据库需要不同的数据库API去访问,这样就会导致MySQL提供一套、Oracle提供一套、SQLServer提供一套…这样太过麻烦…😥

后来SUN公司意识到这个问题,为解决不同数据库连接使用API的混乱场面,因此出了一套规范JDBC,JDBC实际只定义了接口并没有提供接口实现类,Java程序需要访问数据库的时候,只需要访问JDBC的接口,接口实现交给各数据库厂商提供实现类,即数据库驱动包-jar包。

Java Application
JDBC 规范
MySQL驱动
Oracle驱动
SQLServer驱动
DB2驱动
...驱动
MySQL数据库
Oracle数据库
SQLServer数据库
DB2数据库
...数据库

2、ORM概述

JDBC操作比较繁琐,它需要完成如下步骤:

准备SQL语句
加载JDBC驱动
建立链接
占位符赋值
发送SQL
返回结果集
关闭链接

为了解决JDBC操作繁琐的问题,提出了操作实体类就相当于操作数据库表的概念。
需要达到此目的需要建立两个映射关系:

  • 实体类和表的映射关系
  • 实体类中属性和字段的映射关系
    若完成两种映射,则可以不再重点关注SQL语句。
    目前市面上实现了ORM思想的框架有:MybatisHibernate等等。

3、Hibernate与JPA的概述

3.1、Hibernate概述

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

3.2、JPA概述

JPA(Java Persistence API,Java持久化API),定义了对象-关系映射(ORM思想:操作实体类就是操作数据库表)以及实体对象持久化的标准接口
因市面上有众多类似Hibernate、Mybatis、TopLink等这些ORM框架,为了能统一使用不同的ORM理念的框架(降低各类框架的学习成本),在ORM框架的基础之上SUN公司统一约定了一种叫JPA的规范,它只做接口定义而不做真正的实现,实现交给Hibernate、Toplink等厂商去完成。

Java Application
JPA规范
Hibernate
TopLink
其他ORM框架
JDBC 规范
MySQL驱动
Oracle驱动
SQLServer驱动
DB2驱动
...驱动
MySQL数据库
Oracle数据库
SQLServer数据库
DB2数据库
...数据库

JPA是Hibernate的一个抽象,反过来Hibernate也可以理解为是JPA的一种实现。
JPA的目标之一就是制定一个可以由众多供应商实现的API,目前Hibernate 3.2+、TopLink 10.1+和OpenJPA都提供了JPA的实现。

3.3、JPA的优势

1、标准化
JPA是JCP组织发布的Java EE标准之一,符合JPA标准的ORM框架都遵循同样的规约,并提供相同的访问API,这就使得开发人员能够使用少量的修改就能在不同的JPA规约下运行不同的ORM框架。

2、容器级特性的支持
JPA支持大数据集、事务、并发等容器级事务,在企业中得到广泛运用。

3、使用简单
在JPA下创建实体和创建Java类一样,没有任何约束和限制,使用javax.persistence.Entity进行注释,JPA的框架和接口简单,开发者容易上手,并且JPA是非侵入式的,与其他框架或容器集成非常容易。

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

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

以上优势总结,参考百度百科

3.4、JPA与Hibernate的关系

JPA与Hibernate的关系类似JDBC与JDBC驱动的关系,JPA是一种规范,Hibernate是JPA的一种实现。应用程序对JPA的操作,底层是由Hibernate进行响应并完成相应操作数据库的目的。

实现JPA规范
ORM思想
Hibernate
JPA
数据库
Application Code

3.5、入门案例

我们以学生信息操作为例来简单介绍JPA在Java程序中的运用。

3.5.1、创建数据库表

我们在MySQL数据库中创建一个名为jpa的数据库,数据库中创建表,表名为:jpa_student,建表语句如下:

CREATE TABLE `jpa_student` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '学生编号(主键)',
  `stu_name` varchar(20) DEFAULT NULL COMMENT '学生姓名',
  `stu_age` int(11) DEFAULT NULL COMMENT '学生年龄',
  `stu_sex` tinyint(1) DEFAULT NULL COMMENT '学生性别,1为男性,2为女性',
  `stu_grade` varchar(11) DEFAULT NULL COMMENT '学生年级',
  `stu_score` double DEFAULT NULL COMMENT '学生得分',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

3.5.2、创建Maven工程【IDEA】

  1. 打开IDEA,点击 +Create New Project 开始创建一个新项目
    在这里插入图片描述
  2. 右侧选择Maven,选择JDK版本,下一步
    在这里插入图片描述
  3. 设置项目名称和存放地址,点击 完成
    在这里插入图片描述以上完成Maven工程的创建工作。
  4. 导入Maven坐标,引入支持的Jra包,pom.xml按如下内容写入:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</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.13</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驱动包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>
    </dependencies>
</project>

3.5.3、编写JPA核心配置文件

在resources目录下创建META-INF,并在META-INF下创建persistence.xml文件:
在这里插入图片描述
persistence.xml完整配置如下,配置值的详细说明,请查看具体注释内容:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
    <!--配置持久化单元
                    name: 持久化单元名称(自定义)
        transaction-type: 事务管理方式,可以选择JTA和RESOURCE_LOCAL
                     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="xxxxxx"/>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://xxx.xxx.xxx.xxx:3306/jpa?serverTimezone=GMT%2B8&amp;useUnicode=true&amp;characterEncoding=utf8"/><!--characterEncoding=utf8解决中文乱码问题-->
            <!--配置jpa实现方(hibernate)的配置信息-配置可选
                          显示sql: false|true
                   自动创建数据库表: 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.5.4、编写实体类

新建包:com.jpa.domain
包下新建类:Student
如下图示:
在这里插入图片描述建成的Student实体类,类需要与数据库表进行映射,类属性需要与表字段进行映射

以下为jpa数据库jpa_student表的结构截图:
在这里插入图片描述
实体类的具体映射关系见以下代码:

package com.jpa.domain;

import javax.persistence.*;

/**
 * 学生实体类
 */
@Entity                        //1、声明此类是实体类,包名为:javax.persistence.Entity
@Table(name = "jpa_student")   //2、实体类和表的映射关系,name取数据库表的表名称,包名为:javax.persistence.Table
public class Student {
                               //3、对类属性和表字段的映射关系
    /**
     * @Id:声明主键的配置
     * @GeneratedValue:配置主键的生成策略 GenerationType.IDENTITY:自增
     * @Column:配置属性和字段的映射关系 name:数据库表中字段的名称
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer stuId;   //学生的主键
    @Column(name = "stu_name")
    private String stuName;  //学生姓名
    @Column(name = "stu_age")
    private Integer stuAge;  //学生年龄
    @Column(name = "stu_sex")
    private Integer stuSex;  //学生性别
    @Column(name = "stu_grade")
    private String stuGrade; //学生年级
    @Column(name = "stu_score")
    private Double stuScore; //学生成绩

    public Integer getStuId() {
        return stuId;
    }

    public void setStuId(Integer stuId) {
        this.stuId = stuId;
    }

    public String getStuName() {
        return stuName;
    }

    public void setStuName(String stuName) {
        this.stuName = stuName;
    }

    public Integer getStuAge() {
        return stuAge;
    }

    public void setStuAge(Integer stuAge) {
        this.stuAge = stuAge;
    }

    public Integer getStuSex() {
        return stuSex;
    }

    public void setStuSex(Integer stuSex) {
        this.stuSex = stuSex;
    }

    public String getStuGrade() {
        return stuGrade;
    }

    public void setStuGrade(String stuGrade) {
        this.stuGrade = stuGrade;
    }

    public Double getStuScore() {
        return stuScore;
    }

    public void setStuScore(Double stuScore) {
        this.stuScore = stuScore;
    }

    @Override
    public String toString() {
        return "Student{" +
                "stuId=" + stuId +
                ", stuName='" + stuName + '\'' +
                ", stuAge=" + stuAge +
                ", stuSex=" + stuSex +
                ", stuGrade='" + stuGrade + '\'' +
                ", stuScore=" + stuScore +
                '}';
    }
}

3.5.4、通过Junit验证JPA数据操作

新建测试包:com.jpa.test
包下新建类:JpaTest
如下图示:
在这里插入图片描述
编写测试方法并运行测试:

package com.jpa.test;

import com.jpa.domain.Student;
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");//myJpa就是persistence.xml文件中持久化单元的名称
        //2、通过实体管理类工厂获得实体管理器
        EntityManager em = factory.createEntityManager();
        //3、获取事务对象,开启事务
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        //4、生成学生数据
        Student student = new Student();
        student.setStuName("小明");
        student.setStuAge(6);
        student.setStuSex(1);
        student.setStuGrade("一年级一班");
        student.setStuScore(100.00);
        //将学生数据保存到数据库
        em.persist(student);
        //5、提交事务
        tx.commit();
        //6、释放资源
        em.close();
        factory.close();
    }
}

运行日志:
在这里插入图片描述通过运行日志可以看出:数据库在插入表数据库之前,先删除了jpa_student再做创建,然后再往表里插入数据,这种操作逻辑是因为在persistence.xml配置文件中我们使用了以下配置所致:

<property name="hibernate.hbm2ddl.auto" value="create"/>

通过MySQL客户端查询,证实程序运行后成功生成了表数据,运行结果:成功。
在这里插入图片描述
我们将persistence.xml文件中的表生成策略改成:

<property name="hibernate.hbm2ddl.auto" value="update"/>

若数据库表已经存在,则不再进行创建,运行测试,查看运行日志:
在这里插入图片描述通过MySQL客户端查询,表数据在原有基础上再多生成了一条:
在这里插入图片描述
注:在使用update时若数据库表不存在,则先创建数据库表再做数据操作。
我们将persistence.xml文件中的表生成策略改成:

<!--value赋值为none,可以不写这行配置-->
<property name="hibernate.hbm2ddl.auto" value="none"/>

若数据库没有可以操作的表,则程序会报错,告知表不存在:
在这里插入图片描述若数据库中存在可以操作的表,则会进行相应的数据操作。

3.5.5、主键生成策略

在4.3.1章节,使用到了@GeneratedValue来配置主键生成策略,用strategy的赋值来指明。
1、IDENTITY自动新增:

strategy = GenerationType.IDENTITY

使用这种策略的前提是数据库底层支持必须支持数自动增长,MySQL数据库支持。
2、SEQUENCE序列

strategy = GenerationType.SEQUENCE

使用这种策略的前提是数据库底层支持必须支序列,Oracle数据库支持(不支持自增)。
3、TABLE表

strategy = GenerationType.TABLE

JPA创建一个新表类记录主键增长,数据库会多出一张临时表。
4、Auto程序自助选择策略

strategy = GenerationType.Auto

TABLE策略是JPA提供的一种独有方式,通过一张新的数据库表完成数据自增。

3.5.6、JPA操作数据库总结

JPA操作数据库的步骤如下:

  1. 加载配置文件创建实体类工厂:
    使用Persistence.createEntityManagerFactory创建实体类工厂
  2. 根据实体管理器工厂,创建实体管理器:
    使用EntityManagerFactory来获取EntityManager对象,在EntityManager提供的createEntityManager内部提供了维护操作数据库内容:维护了数据库信息、维护了缓存信息、维护了实体管理器对象、根据配置创建数据库表,但EntityManagerFactory的创建过程是比较耗费资源的。
  3. 创建事务对象,开启事务:
    EntityManager对象:实体类管理器,提供的方法如下:
     persist:保存
     merge:更新
     remove:删除
     find/getRefrence:根据id查询
    EntityTransaction:事务对象
  4. 增、删、改、查操作
  5. 提交事务
  6. 释放资源

3.5.7、封装JpaUtils工具类

1、创建一个工具类包和JpaUtils类,如下图示:
在这里插入图片描述2、在JpaUtils.java文件中写入如下代码,完成工具类的编写:

package com.jpa.utils;

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

/**
* 解决实体管理器工厂浪费资源和h耗时问题
* 通过静态代码块形式,当第一次访问此工具类时创建一个公共的实体管理器工厂对象
*/
public class JpaUtils {

   private static EntityManagerFactory factory;

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

   /**
    * 获取EntityManager实体对象
    * 第一次访问getEntityManager()方法时,经过静态代码块创建一个factory对象,在调用方法创建EntityManager对象
    * 第二次或以后都每次对getEntityManager()方法的访问就可以直接调用已经创建好的factory对象来创建EntityManager对象,解决了耗时问题
    */
   public static EntityManager getEntityManager(){
       return  factory.createEntityManager();
   }
}

3、Junit测试类改写代码如下:

package com.jpa.test;

import com.jpa.domain.Student;
import com.jpa.utils.JpaUtils;
import org.junit.Test;

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

public class JpaTest {
    @Test
    public void testSave() {
        //1、通过JpaUtils工具类获EntityManager对象
        EntityManager em = JpaUtils.getEntityManager();

        //2、获取事务对象,开启事务
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        //3、生成学生数据
        Student student = new Student();
        student.setStuName("小明");
        student.setStuAge(6);
        student.setStuSex(1);
        student.setStuGrade("一年级一班");
        student.setStuScore(100.00);
        //将学生数据保存到数据库
        em.persist(student);
        //4、提交事务
        tx.commit();
        //5、释放资源
        em.close();
        //factory为静态创建,不需要再做关闭,以备后面继续使用
        //factory.close();
    }
}

如程序备注所说,factory.close()语句在此处不需要在执行,factory对象有由静态方式所创建,会一直保留在内存中供后续调用。

3.5.8、查询指定id的数据

我们可以通过EntityManager对象的find()方法查询指定id值的数据内容,find()需要带入两个参数:

  • class:查询数据的结果需要包装的实体类的字节码(查询出来的数据,需要封装成的对象)
  • id:查询的主键的值

编写测试代码如下:

package com.jpa.test;

import com.jpa.domain.Student;
import com.jpa.utils.JpaUtils;
import org.junit.Test;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;

public class JpaTest {

   @Test
   public void testFind() {
       //1、通过工具类获取EntityManager对象
       EntityManager em = JpaUtils.getEntityManager();
       //2、开启事务
       EntityTransaction tx = em.getTransaction();
       tx.begin();
       //3、执行查询
       /**
        * find方法有两个参数
        * 1、class:查询数据的结果需要包装的实体类的字节码(查询出来的数据,需要封装成的对象)
        * 2、   id:查询的主键的值
        */
       Student student = em.find(Student.class,1);
       System.out.println(student);
       //4、提交事务
       tx.commit();
       //5、释放资源
       em.close();
   }
}

使用System.out.println打印查询结果:
在这里插入图片描述EntityManager对象除提供find()方法进行查询外,还提供了getReference()方法,其入参与find()方法一致。
find()与getReference()的使用方式虽然一致,但有所区别,find()在被调用时就会发送sql进行数据库查询,但getReference()方法属于懒加载方式,在执行该方法时不会立即发送sql作数据库查询。在日常开发中我们使用最多的是getReference()方法,原因是我们可能会做查询数据库的操作,但不一定会立即使用查询结果,如果未使用查询结果,则可以先不执行SQL,在程序真实需要使用查询结果时再做数据库查询,以减少对数据库查询的次数。

3.5.9、依据id删除数据

我们可以通过EntityManager对象的remove()方法删除指定id的数据,remove()方法需要接收一个实体对象,一般情况对象是通过查询语句获得,换言之在做删除动作前,我们需要先进行一次操作对象的获取。
编写测试代码如下:

package com.jpa.test;

import com.jpa.domain.Student;
import com.jpa.utils.JpaUtils;
import org.junit.Test;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;

public class JpaTest {
    @Test
    public void testRemove(){
        EntityManager em = JpaUtils.getEntityManager();
        EntityTransaction tx= em.getTransaction();
        tx.begin();
        //查询需要删除的对象
        Student student = em.getReference(Student.class,1);
        //将查询的对象进行删除
        em.remove(student);
        tx.commit();
        em.close();
    }
}

执行测试查看结果,jpa_student表中id为1的数据被成功删除:
在这里插入图片描述
执行日志输出:
在这里插入图片描述

3.5.10、数据更新

我们可以通过EntityManager对象的merge()方法更新指定对象的数据,merge()方法需要接收一个实体对象,一般情况对象是通过查询语句获得,换言之在做更新动作前,我们需要先进行一次操作对象的获取,对获取到的对象作更新后,再提交到数据库执行。
编写测试代码如下:

package com.jpa.test;

import com.jpa.domain.Student;
import com.jpa.utils.JpaUtils;
import org.junit.Test;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;

public class JpaTest {
    @Test
    public void testUpdate() {
        EntityManager em = JpaUtils.getEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        //查询需要更新的对象
        Student student = em.getReference(Student.class, 2);
        //修改对象数据
        student.setStuName("李四");
        student.setStuAge(7);
        //将新的数据更新到数据库
        em.merge(student);
        tx.commit();
        em.close();
    }
}

执行测试查看结果,jpa_student表中id为2的数据被成功更新:
在这里插入图片描述执行日志输出:
在这里插入图片描述

4、JPQL介绍

JPQL语言,即 Java Persistence Query Language 的简称。JPQL 是一种和 SQL 非常类似的对象化查询语言,它最终会被编译成针对不同底层数据库的 SQL 查询,从而屏蔽不同数据库的差异。

4.1、查询全部

我们可以使用:

from Student

来查询实体类对应数据库表的所有数据(Student实体类<==>jpa_student表),测试代码如下:

package com.jpa.test;

import com.jpa.utils.JpaUtils;
import org.junit.Test;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
import java.util.List;

public class JqlTest {

    @Test
    public void testFindAll() {

        EntityManager em = JpaUtils.getEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        //设置jpql查询语句
        String jpql = "from Student";
        //创建Query查询对象,query是执行jpql语句的对象
        Query query = em.createQuery(jpql);
        //发送查询,并封装结果集
        List list = query.getResultList();
        //将查询出来的结果作for循环输出
        for (Object obj : list) {
            System.out.println(obj);
        }
        tx.commit();
        em.close();
    }
}

程序通过query.getResultList()方法获得查询结果,结果以列表类型作返回。
运行测试,执行日志输出:
在这里插入图片描述

4.2、排序查询

按id倒序排,则需要使用到order by语句,这和SQL语句一致,代码如下:

package com.jpa.test;

import com.jpa.utils.JpaUtils;
import org.junit.Test;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
import java.util.List;

public class JqlTest {
    @Test
    public void testOrder() {

        EntityManager em = JpaUtils.getEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        //设置jpql查询语句
        String jpql = "from Student ORDER BY stuId DESC";
        //创建Query查询对象,query是执行jpql语句的对象
        Query query = em.createQuery(jpql);
        //发送查询,并封装结果集
        List list = query.getResultList();
        //将查询出来的结果作for循环输出
        for (Object obj : list) {
            System.out.println(obj);
        }
        tx.commit();
        em.close();
    }
}

运行测试,执行日志输出:
在这里插入图片描述

4.3、统计查询

在做数量统计时,我们使用query.getSingleResult()来获得查询的数量,并以Object类型做结果返回,代码如下:

package com.jpa.test;

import com.jpa.utils.JpaUtils;
import org.junit.Test;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;

public class JqlTest {

    @Test
    public void testCount() {
        EntityManager em = JpaUtils.getEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        //设置jpql查询语句
        String jpql = "select count(stuId) from Student ";
        //创建Query查询对象,query是执行jpql语句的对象
        Query query = em.createQuery(jpql);
        //发送查询,并封装结果集
        Object result = query.getSingleResult();
        System.out.println(result);
        tx.commit();
        em.close();
    }
}

运行测试,执行日志输出:
在这里插入图片描述

4.4、分页查询

分页查询需要对query对象设置起始索引和查询的条数,需要使用到

query.setFirstResult();
query.setMaxResults();

两个方法入参分别传递索引起始和查询条数,查询的JPQL语句依然使用from Student ,代码如下:

package com.jpa.test;

import com.jpa.utils.JpaUtils;
import org.junit.Test;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
import java.util.List;

public class JqlTest {

    @Test
    public void testPage() {
        EntityManager em = JpaUtils.getEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        //设置jpql查询语句
        String jpql = "from Student";
        //创建Query查询对象,query是执行jpql语句的对象
        Query query = em.createQuery(jpql);
        //设置开始索引起
        query.setFirstResult(1);
        //设置查询的条数
        query.setMaxResults(2);
        //发送查询,并封装结果集
        List list = query.getResultList();
        //将查询出来的结果作for循环输出
        for (Object obj : list) {
            System.out.println(obj);
        }
        tx.commit();
        em.close();
    }
}

以上测试语句是查询jpa_student表从第2个索引开始,后2条的学生数据,下图红框是需要查询出来的数据内容:
在这里插入图片描述
运行测试,执行日志输出:
在这里插入图片描述

4.5、条件查询

我们来查询数据库表中姓名包括”里“字打头的学生信息,这里我们需要用到占位符,我们使用query.setParameter(),来对占位符赋值,该方法接受两个参数,第一个参数为占位符的索引位置,第二个参数为取值。
代码如下:

package com.jpa.test;

import com.jpa.utils.JpaUtils;
import org.junit.Test;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
import java.util.List;

public class JqlTest {

    @Test
    public void testCondition() {
        EntityManager em = JpaUtils.getEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        //设置jpql查询语句
        String jpql = "from Student where stuName like ? ";
        //创建Query查询对象,query是执行jpql语句的对象
        Query query = em.createQuery(jpql);
        //对占位符进行赋值
        query.setParameter(1,"李%");//这里只有一个?,所以占位符位置为1
        //发送查询,并封装结果集
        List list = query.getResultList();
        //将查询出来的结果做for循环输出
        for (Object obj : list) {
            System.out.println(obj);
        }
        tx.commit();
        em.close();
    }
}

运行测试,执行日志输出:
在这里插入图片描述

5、Spring Data JPA介绍

Spring Data JPA是Spring Data家族的一部分,可以轻松实现基于JPA的存储库。 可使开发者使用简的代码就可实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且容易扩展。使用Spring Data JPA能极大的提高开发效率。
Spring Data JPA让我们摆脱了对DAO层的操作,基本所有的CURD都可以依赖它实现,在实际开发过程中,推荐使用Spring Data JPA + ORM(如Hibernate框架)完成操作,这样在切换不同的ORM框架式提供了极大的方便,同时也使数据库层操作更加简单,方便解耦。

5.1、Spring Data JPA特性

使用Spring Data JPA,DAO层中只需要写接口,就自动生成了增删改查、分页查询等方法,它也未做真正的实现,而是把JPA规范的代码再做一次更高级的封装,真正作实现的依然是基于ORM思想的具体框架,如Hibernate。

Java Application
Spring Data JPA
JPA规范
Hibernate
TopLink
其他ORM框架
JDBC 规范
MySQL驱动
Oracle驱动
SQLServer驱动
DB2驱动
...驱动
MySQL数据库
Oracle数据库
SQLServer数据库
DB2数据库
...数据库

5.2、入门案例

5.2.1、搭建环境

5.2.1.1、创建工程【IDEA】
  1. 打开IDEA,点击 +Create New Project 开始创建一个新项目
    在这里插入图片描述
  2. 右侧选择Maven,选择JDK版本,下一步
    在这里插入图片描述
  3. 设置项目名称和存放地址,点击 完成
    在这里插入图片描述以上完成Maven工程的创建工作。
  4. 导入Maven坐标,引入支持的Jra包,pom.xml按如下内容写入:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.itcast</groupId>
    <artifactId>jpa-day2</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 定义公共版本号 -->
    <properties>
        <spring.version>5.0.2.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.12</version>
            <scope>test</scope>
        </dependency>

        <!-- spring APO -->
        <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>
        <!-- spring IOC -->
        <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>

        <!-- spring对orm框架的支持包-->
        <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 数据库连接池 -->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>${c3p0.version}</version>
        </dependency>
        <!-- c3p0 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>

        <!--MySql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <!-- spring data jpa 的坐标-->
        <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>
5.2.1.2、Spring配置文件设置

在resources目录下创建applicationContext.xml文件:
在这里插入图片描述
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="entityManagerFactoty" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--配置的扫描的包(实体类所在的包) -->
        <property name="packagesToScan" value="com.jpa.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.MySQLDialect"/>
                <!--是否显示sql -->
                <property name="showSql" value="true"/>
            </bean>
        </property>

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

    </bean>

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

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

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


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

5.2.2、编写实体

新建包:com.jpa.domain
包下新建类:Student
如下图示:
在这里插入图片描述实体类的具体映射关系见以下代码:

package com.jpa.domain;

import javax.persistence.*;

/**
 * 学生实体类
 */
@Entity                        //1、声明此类是实体类,包名为:javax.persistence.Entity
@Table(name = "jpa_student")   //2、实体类和表的映射关系,name取数据库表的表名称,包名为:javax.persistence.Table
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer stuId;   //学生的主键
    @Column(name = "stu_name")
    private String stuName;  //学生姓名
    @Column(name = "stu_age")
    private Integer stuAge;  //学生年龄
    @Column(name = "stu_sex")
    private Integer stuSex;  //学生性别
    @Column(name = "stu_grade")
    private String stuGrade; //学生年级
    @Column(name = "stu_score")
    private Double stuScore; //学生成绩

    public Integer getStuId() {
        return stuId;
    }

    public void setStuId(Integer stuId) {
        this.stuId = stuId;
    }

    public String getStuName() {
        return stuName;
    }

    public void setStuName(String stuName) {
        this.stuName = stuName;
    }

    public Integer getStuAge() {
        return stuAge;
    }

    public void setStuAge(Integer stuAge) {
        this.stuAge = stuAge;
    }

    public Integer getStuSex() {
        return stuSex;
    }

    public void setStuSex(Integer stuSex) {
        this.stuSex = stuSex;
    }

    public String getStuGrade() {
        return stuGrade;
    }

    public void setStuGrade(String stuGrade) {
        this.stuGrade = stuGrade;
    }

    public Double getStuScore() {
        return stuScore;
    }

    public void setStuScore(Double stuScore) {
        this.stuScore = stuScore;
    }

    @Override
    public String toString() {
        return "Student{" +
                "stuId=" + stuId +
                ", stuName='" + stuName + '\'' +
                ", stuAge=" + stuAge +
                ", stuSex=" + stuSex +
                ", stuGrade='" + stuGrade + '\'' +
                ", stuScore=" + stuScore +
                '}';
    }
}

5.2.3、编写DAO层接口

编写dao层接口规范:
1、需要继承两个接口(JpaRepository、JpaSpecificationExecutor)
2、需要提供相应的泛型
新建包:com.jpa.dao
包下新建接口:StudentDao
如下图示:
在这里插入图片描述StudentDao.java接口文件内容见以下代码:

package com.jpa.dao;


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

/**
 * 符合Spring Data JPA的DAO层规范
 * JpaRepository<操作的实体类类型,实体类中主键属性的类型>,封装了基本的CURD操作
 * JpaSpecificationExecutor<操作的实体类类型>,封装了复杂查询操作(如分页等)
 * 在Student实体类中,实体类类型就为Student,主键为stuId,其类型为Integer
 */
public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {
}

只需要将代码如上编写,就完成了DAO层的代码配置。

5.2.4、简单数据操作

新建测试包:com.jpa.test
包下新建类:StudentDaoTest
如下图示:
在这里插入图片描述

5.2.4.1、查询指定id的数据

我们可以通过findOne()方法查询指定id值的数据内容,findOne()需要带入需要查询的主键id作为参数。
编写测试代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
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;


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

   @Test
   public void testFindOne(){
       Student student = studentDao.findOne(3);
       System.out.println(student);
   }
}

使用System.out.println打印查询结果:
在这里插入图片描述

5.2.4.2、保存或更新数据

我们可以通过save()方法执行数据的保存(新增)或是更新操作,当传递的实体类中不包含主键id时,则为保存操作,若带有主键id则为更新操作。
编写测试保存的代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
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;


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

    /**
     * 保存或更新数据
     * 传递的对象存在主键id时,则为数据更新
     * 如果没有主键id,则为保存(新增)数据
     */
    @Test
    public void testSave() {
        Student student = new Student();
        student.setStuAge(8);
        student.setStuName("王五");
        student.setStuGrade("二年级一班");
        student.setStuScore(90.0);
        student.setStuSex(2);
        studentDao.save(student);
    }
}

执行测试,日志输出:
在这里插入图片描述查看数据库表,发现新的一条数据已经保存成功:
在这里插入图片描述
编写测试更新的代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
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;


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

    /**
     * 保存或更新数据
     * 传递的对象存在主键id时,则为数据更新
     * 如果没有主键id,则为保存(新增)数据
     */
    @Test
    public void testUpdate() {
        //先查询需要修改的数据对象
        Student student = studentDao.findOne(5);
        //对数据对象的部分属性进行修改
        student.setStuName("赵六");
        studentDao.save(student);
    }
}

执行测试,日志输出:
在这里插入图片描述查看数据库表,发现Id为5的数据被更新成功:
在这里插入图片描述

5.2.4.3、依据id删除数据

我们可以通过delete()方法执行对数据的删除操作,delete()需要带入需要删除的主键id作为参数。
编写测试代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
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;


@RunWith(SpringJUnit4ClassRunner.class)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class StudentDaoTest {
    @Autowired
    private StudentDao studentDao;
    
    @Test
    public void testDelete() {
        studentDao.delete(5);
    }
}

执行测试,日志输出:
在这里插入图片描述查看数据库表,发现Id为5的数据已经被删除了:
在这里插入图片描述

5.2.4.4、查询全部数据

我们可以通过findAll()方法执行对数据表所有数据的查询。
编写测试代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
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 StudentDaoTest {
    @Autowired
    private StudentDao studentDao;

    @Test
    public void testFindAll() {
        List<Student> list = studentDao.findAll();
        for(Student student : list){
            System.out.println(student);
        }
    }
}

执行测试,日志输出:
在这里插入图片描述查看数据库表,表中的数据与日志输出的数据一致:
在这里插入图片描述

5.2.5、复杂数据操作

5.2.5.1、统计查询

在做数量统计时,我们使用count()来获得查询的数量,并以long类型做结果返回,代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
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 StudentDaoTest {
    @Autowired
    private StudentDao studentDao;

    @Test
    public void testCount() {
        long count =  studentDao.count();
        System.out.println(count);
    }
}

运行测试,执行日志输出:
在这里插入图片描述

5.2.5.2、查询指定id的数据是否存在

我们可以通过exists()方法来判断指定id对应的数据是否存在,入参为想要查询的id值。
编写测试代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
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;


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

    @Test
    public void testExists() {
        boolean result = studentDao.exists(10);
        System.out.println(result);
    }
}

运行测试,执行日志输出:
在这里插入图片描述

5.2.5.3、getOne()方法查询指定id的数据

我们除了可以使用findOne()方法做指定id数据查询外,还可以使用getOne()方法,依然是传入主键id进行数据的查询,但getOne()和findOne()有这明显的区别,getOne()属于赖加载模式,在使用查询结果的时候才会发送查询语句,而findOne()则是立即进行查询。
在使用GetOne()时,在方法前需加上@Transactional注解,以防止事务报错,编写测试代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;


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

    @Test
    @Transactional
    public void testGetOne() {
        Student student = studentDao.getOne(3);
        System.out.println(student);
    }
}

运行测试,执行日志输出:
在这里插入图片描述

5.3、Spring Data JPA中使用JPQL查询

在Spring Data JPA中也可以使用JPQL方式进行数据库的查询,但需要完成以下配置:
1、需要在Dao接口中写配置方法
2、在接口方法上使用@Query注解写jpql语句
3、在使用查询的地方调用接口方法完成查询操作

5.3.1、按学生姓名查询

在StudentDao.java中加入jpql查询方法,编写代码如下:

package com.jpa.dao;


import com.jpa.domain.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;

public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {

    /**
     * 按学生姓名查询学生数据
     *
     * @param stuName
     * @return
     */
    @Query(value = "from Student where stuName = ?")
    public Student findStudentByName(String stuName);
}

StudentDaoTest.java测试文件编写代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;


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

    @Test
    public void testFindStudentByName() {
        Student student = studentDao.findStudentByName("李四");
        System.out.println(student);
    }
}

运行测试,执行日志输出:
在这里插入图片描述

5.3.2、按学生姓名和Id组合查询

在StudentDao.java中加入jpql查询方法,编写代码如下:

package com.jpa.dao;


import com.jpa.domain.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;

public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {

    /**
     * 按学生姓名和ID查询学生数据
     *
     * @param name
     * @param id
     * @return
     */
    @Query(value = "from Student where stuName = ? and stuId = ?")
    public Student findStudentByNameAndId(String name, Integer id);
}

StudentDaoTest.java测试文件编写代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;


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

    @Test
    public void testFindStudentByNameAndId() {
        Student student = studentDao.findStudentByNameAndId("李四", 2);
        System.out.println(student);
    }
}

运行测试,执行日志输出:
在这里插入图片描述注:在StudentDao.java的findStudentByNameAndId()方法@Query注解上用到了2个?占位符,默认两个?的顺序与下列方法的入参顺序需要保持一致,若非要改变jpql占位符的顺序则需要在?后加上序号,如下列语句所示:

@Query(value = "from Student where stuId = ?2 and stuName = ?1")
public Student findStudentByNameAndId(String name, Integer id);

5.3.3、按id更新学生名称

@Query注解代表的是查询操作,如果要进行数据更新则需要再加上@Modifying注解,编写代码如下:

package com.jpa.dao;


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

public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {
    @Query(value = " update Student set stuName =?2 where stuId = ?1")
    @Modifying
    public void updateStudentNameById(Integer id, String name);
}

在编写StudentDaoTest.java文件时需要注意当前为更新操作,需要在执行方法上添加@Transactional注解,另外默认更新操作执行完后会自动做回滚,就算我们成功执行了语句,也不会对数据库表的数据做修改操作,我们需要使用@Rollback注解来关闭回滚,编写代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
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)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class StudentDaoTest {
    @Autowired
    private StudentDao studentDao;

    @Test
    @Transactional
    @Rollback(value = false)
    public void testUpdateStudentNameById() {
        studentDao.updateStudentNameById(2, "王五");
    }
}

运行测试,执行日志输出:
在这里插入图片描述查看数据库表,表中id为2对应的学生姓名被成功修改为“王五”:
在这里插入图片描述

5.4、Spring Data JPA中使用SQL查询

在Spring Data JPA中也可以使用SQL方式进行数据库的查询,但需要完成以下配置:
1、需要在Dao接口中写配置方法
2、在接口方法上使用@Query注解写jpql语句,需要在注解上写明:

nativeQuery = true //nativeQuery默认为false表示使用jpql方式查询,为ture则表是要sql方式(本地)查询

3、在使用查询的地方调用接口方法完成查询操作

5.4.1、查询全部数据

在StudentDao.java中加入sql查询方法,编写代码如下:

package com.jpa.dao;


import com.jpa.domain.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {

    @Query(value = "select * from jpa_student", nativeQuery = true)
    public List<Student> findSqlAll();
}

StudentDaoTest.java测试文件编写代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
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 StudentDaoTest {
    @Autowired
    private StudentDao studentDao;

    @Test
    public void testFindSqlAll() {
        List<Student> list = studentDao.findSqlAll();
        for (Student student : list) {
            System.out.println(student);
        }
    }
}

运行测试,执行日志输出:
在这里插入图片描述

5.4.2、根据学生姓名模糊查询

在StudentDao.java中加入sql查询方法,编写代码如下:

package com.jpa.dao;


import com.jpa.domain.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {

    @Query(value = "select * from jpa_student where stu_name like ?", nativeQuery = true)
    public List<Student> findSqlAllByName(String name);
}

StudentDaoTest.java测试文件编写代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
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 StudentDaoTest {
    @Autowired
    private StudentDao studentDao;

    @Test
    public void testFindSqlAllByName() {
        List<Student> list = studentDao.findSqlAllByName("小%");
        for (Student student : list) {
            System.out.println(student);
        }
    }
}

运行测试,执行日志输出:
在这里插入图片描述

5.5、Spring Data JPA中方法名规则查询

在执行查询时,Spring Data JPA框架会把方法名进行解析,解析到前缀比如 get、getBy、find、findBy、read、readBy时,会先把这些前缀截取掉,然后对剩下部分进行解析,剩下部分分为两种:一是只有属性名,二是属性名+条件;条件很好解析,解析的关键在于属性名。

5.5.1、findBy精确查询

以findBy开头,后面接属性名称,属性名称以大写开头,如我们要查询学生名称的数据,方法名如下写法:findByStuName(),在StudentDao.java中编写代码如下:

package com.jpa.dao;


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

public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {
    public Student findByStuName(String name);
}

StudentDaoTest.java测试文件编写代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
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 StudentDaoTest {
    @Autowired
    private StudentDao studentDao;

    @Test
    public void testFindByStuName() {
        Student student = studentDao.findByStuName("王五");
        System.out.println(student);
    }
}

运行测试,执行日志输出:
在这里插入图片描述

5.5.2、模糊查询

以findBy+属性名+Like的组合,可以进行属性名的模糊查询操作,在StudentDao.java中编写代码如下:

package com.jpa.dao;


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

import java.util.List;

public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {
    public List<Student> findByStuNameLike(String name);
}

StudentDaoTest.java测试文件编写代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
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 StudentDaoTest {
    @Autowired
    private StudentDao studentDao;

    @Test
    public void testFindByStuNameLike() {
        List<Student> list = studentDao.findByStuNameLike("小%");
        for (Student student : list) {
            System.out.println(student);
        }
    }
}

运行测试,执行日志输出:
在这里插入图片描述

5.5.3、多条件查询

以findBy+属性名+“查询方式”+多条件的连接符(and|or)+属性名+“查询方式”的组合,可以进行属性名的模糊查询操作,如我们查询姓名包含“小”字,且为“一年级二班”的学生信息,我们首先将数据库内容做如下修改:
在这里插入图片描述
在StudentDao.java中编写代码如下:

package com.jpa.dao;


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

import java.util.List;

public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {
    public List<Student> findByStuNameLikeAndStuGrade(String name, String grade);
}

StudentDaoTest.java测试文件编写代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
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 StudentDaoTest {
    @Autowired
    private StudentDao studentDao;

    @Test
    public void testFindByStuNameLikeAndStuGrade() {
        List<Student> list = studentDao.findByStuNameLikeAndStuGrade("小%","一年级二班");
        for (Student student : list) {
            System.out.println(student);
        }
    }
}

运行测试,执行日志输出:
在这里插入图片描述

6、动态查询

6.1、Specifications动态查询介绍

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

T findOne(Specification<T> spec);  //查询单个对象
List<T> findAll(Specification<T> spec);  //查询列表
//查询全部,分页
//pageable:分页参数
//返回值:分页pageBean(page:是springdatajpa提供的)
Page<T> findAll(Specification<T> spec, Pageable pageable);
List<T> findAll(Specification<T> spec, Sort sort);//查询列表,Sort:排序参数
long count(Specification<T> spec);//统计查询

6.2、按学生名称查询

在StudentDao.java中加入jpql查询方法,编写代码如下:

package com.jpa.dao;

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

public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {
}

StudentDaoTest.java测试文件编写代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.persistence.criteria.*;


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

    @Test
    public void testSpec() {

        /**
         * 构造查询条件
         *	root	:Root接口,代表查询的根对象,可以通过root获取实体中的属性
         *	query	:代表一个顶层查询对象,用来自定义查询
         *	cb		:用来构建查询,此对象里有很多条件方法
         **/
        Specification<Student> spec = new Specification<Student>() {
            @Override
            public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                //按学生姓名查询学生数据,则我们如下编写
                //1、获取查询的属性
                Path<Object> stuName = root.get("stuName");
                //2、构造查询条件,equal为精准匹配
                Predicate predicate =  cb.equal(stuName, "王五");
                return predicate;
            }
        };
        Student student = studentDao.findOne(spec);
        System.out.println(student);
    }
}

运行测试,执行日志输出:
在这里插入图片描述

6.3、多条件查询

查询姓名为“小明”,且为“一年级二班”的学生信息,在StudentDao.java中编写代码如下:

package com.jpa.dao;

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

public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {
}

StudentDaoTest.java测试文件编写代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.persistence.criteria.*;


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

    @Test
    public void testSpec() {

        /**
         * 构造查询条件
         *	root	:Root接口,代表查询的根对象,可以通过root获取实体中的属性
         *	query	:代表一个顶层查询对象,用来自定义查询
         *	cb		:用来构建查询,此对象里有很多条件方法
         **/
        Specification<Student> spec = new Specification<Student>() {
            @Override
            public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                //按学生姓名和年级查询学生数据,则我们如下编写
                //1、获取学生姓名属性
                Path<Object> stuName = root.get("stuName");
                //2、获取学生年级属性
                Path<Object> stuGrade = root.get("stuGrade");
                //3、构造查询条件,学生姓名,为精准匹配
                Predicate p1 =  cb.equal(stuName, "小明");
                //4、构造查询条件,学生年级,为精准匹配
                Predicate p2 =  cb.equal(stuGrade, "一年级二班");
                //5、将多个查询条件进行组合(两者都满足使用-与关系,两者满足任意一个使用-或关系)
                Predicate predicate = cb.and(p1,p2); //and以与关系进行组合
                //cb.or(p1,p2); //or以或关系进行组合
                return predicate;
            }
        };
        Student student = studentDao.findOne(spec);
        System.out.println(student);
    }
}

运行测试,执行日志输出:
在这里插入图片描述

6.4、模糊查询

我们查询学生姓名包含“小”字开头的学生信息,在StudentDao.java中编写代码如下:

package com.jpa.dao;

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

public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {
}

StudentDaoTest.java测试文件编写代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
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)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class StudentDaoTest {
    @Autowired
    private StudentDao studentDao;

    @Test
    public void testSpec() {

        /**
         * 构造查询条件
         *	root	:Root接口,代表查询的根对象,可以通过root获取实体中的属性
         *	query	:代表一个顶层查询对象,用来自定义查询
         *	cb		:用来构建查询,此对象里有很多条件方法
         **/
        Specification<Student> spec = new Specification<Student>() {
            @Override
            public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                //1、获取学生姓名属性
                Path<Object> stuName = root.get("stuName");
                //2、构造查询条件,学生姓名,为模糊匹配
                //针对like、gt、lt、ge和le需要在得到path对象后还需指定比较的参数类型,使用as(类型)方式处理
                Predicate predicate = cb.like(stuName.as(String.class), "小%");
                return predicate;
            }
        };
        List<Student> list = studentDao.findAll(spec);
        for (Student student : list) {
            System.out.println(student);
        }
    }
}

运行测试,执行日志输出:
在这里插入图片描述

6.5、查询排序

我们查询学生姓名包含“小”字开头的学生信息,并按id进行倒序排序,在StudentDao.java中编写代码如下:

package com.jpa.dao;

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

public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {
}

StudentDaoTest.java测试文件编写代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
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)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class StudentDaoTest {
    @Autowired
    private StudentDao studentDao;

    @Test
    public void testSpec() {
        Specification<Student> spec = new Specification<Student>() {
            @Override
            public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                Path<Object> stuName = root.get("stuName");
                Predicate predicate = cb.like(stuName.as(String.class), "小%");
                return predicate;
            }
        };
        //需要实例化sort对象,并指明排序方式和排序的属性名称
        Sort sort = new Sort(Sort.Direction.DESC, "StuId");
        //将排序对象作为findAll的第二个参数进行赋值
        List<Student> list = studentDao.findAll(spec, sort);
        for (Student student : list) {
            System.out.println(student);
        }
    }
}

运行测试,执行日志输出:
在这里插入图片描述

6.6、分页查询

我们使用PageRequest对象来进行数据查询的分页功能,在StudentDao.java中编写代码如下:

package com.jpa.dao;

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

public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {
}

StudentDaoTest.java测试文件编写代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.domain.Student;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
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 StudentDaoTest {
    @Autowired
    private StudentDao studentDao;

    @Test
    public void testSpec() {
        Specification spec = null;

        //通过PageRequest对象的pageable接口实现分页功能
        //PageRequest实例化过程中需要传递两个参数
        //参数一:当前查询的分页(从0开始)
        //参数二:每页查询的数量
        PageRequest pageable = new PageRequest(0, 2);
        //将pageable作为findAll的第二个参数进行赋值
        Page<Student> page = studentDao.findAll(spec, pageable);
        System.out.println("Content:" + page.getContent());//获得查询的数据集合
        System.out.println("Total:" + page.getTotalElements()); //获得总条数
        System.out.println("TotalPages:" + page.getTotalPages());//获得总分页数
    }
}

运行测试,执行日志输出:
在这里插入图片描述

7、多表操作

7.1、一对多表操作

我们建立一个学生与教师的关系,一个学生可以包含多个教师。
编写实体类,再实体类中描述表关系(包含关系):

  • 学生:在学生的实体类中包含一个教师的集合
  • 教师:在教师的实体类中包含一个学生的对象

我们使用使用jpa注解来配置一对多映射关系。

7.1.1、一对多表操作环境搭建

7.1.1.1、数据库表准备

我们使用SQL语句创建2张数据表,分别为学生表和教师表:
新建学生表SQL语句:

CREATE TABLE `jpa_student` (
  `stu_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '学生编号(主键)',
  `stu_name` varchar(20) DEFAULT NULL COMMENT '学生姓名',
  `stu_age` int(11) DEFAULT NULL COMMENT '学生年龄',
  `stu_grade` varchar(255) DEFAULT NULL COMMENT '学生年级',
  `stu_score` double DEFAULT NULL COMMENT '学生成绩',
  PRIMARY KEY (`stu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;

填充数据:
在这里插入图片描述新建教师表SQL语句:

CREATE TABLE `jpa_teacher` (
  `tea_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '教师编号(主键)',
  `tea_name` char(20) DEFAULT NULL COMMENT '教师姓名',
  `tea_phone` varchar(11) DEFAULT NULL COMMENT '教师手机号码',
  `tea_stu_id` int(11) DEFAULT NULL COMMENT '学生id(外键)',
  PRIMARY KEY (`tea_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

填充数据:
在这里插入图片描述

7.1.1.2、实体类准备

学生实体类编写代码如下:

package com.jpa.domain;

import javax.persistence.*;

/**
 * 学生实体类
 */
@Entity
@Table(name = "jpa_student")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "stu_id")
    private Integer stuId;   //学生的主键
    @Column(name = "stu_name")
    private String stuName;  //学生姓名
    @Column(name = "stu_age")
    private Integer stuAge;  //学生年龄
    @Column(name = "stu_grade")
    private String stuGrade; //学生年级
    @Column(name = "stu_score")
    private Double stuScore; //学生成绩

    public Integer getStuId() {
        return stuId;
    }

    public void setStuId(Integer stuId) {
        this.stuId = stuId;
    }

    public String getStuName() {
        return stuName;
    }

    public void setStuName(String stuName) {
        this.stuName = stuName;
    }

    public Integer getStuAge() {
        return stuAge;
    }

    public void setStuAge(Integer stuAge) {
        this.stuAge = stuAge;
    }

    public String getStuGrade() {
        return stuGrade;
    }

    public void setStuGrade(String stuGrade) {
        this.stuGrade = stuGrade;
    }

    public Double getStuScore() {
        return stuScore;
    }

    public void setStuScore(Double stuScore) {
        this.stuScore = stuScore;
    }

    @Override
    public String toString() {
        return "Student{" +
                "stuId=" + stuId +
                ", stuName='" + stuName + '\'' +
                ", stuAge=" + stuAge +
                ", stuGrade='" + stuGrade + '\'' +
                ", stuScore=" + stuScore +
                '}';
    }
}

教师实体类编写代码如下:

package com.jpa.domain;

import javax.persistence.*;

/**
 * 教师实体类
 */
@Entity
@Table(name = "jpa_teacher")
public class Teacher {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "tea_id")
    private Integer teaId;   //教师的主键
    @Column(name = "tea_name")
    private String teaName;  //教师姓名
    @Column(name = "tea_phone")
    private String teaPhone;  //教师手机号码

    public Integer getTeaId() {
        return teaId;
    }

    public void setTeaId(Integer teaId) {
        this.teaId = teaId;
    }

    public String getTeaName() {
        return teaName;
    }

    public void setTeaName(String teaName) {
        this.teaName = teaName;
    }

    public String getTeaPhone() {
        return teaPhone;
    }

    public void setTeaPhone(String teaPhone) {
        this.teaPhone = teaPhone;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "teaId=" + teaId +
                ", teaName='" + teaName + '\'' +
                ", teaPhone='" + teaPhone + '\'' +
                '}';
    }
}

在IDEA中实体类截图所示:
在这里插入图片描述

7.1.1.3、DAO层接口准备

学生DAO层编写代码如下:

package com.jpa.dao;

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

public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {

}

教师DAO层编写代码如下:

package com.jpa.dao;

import com.jpa.domain.Student;
import com.jpa.domain.Teacher;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface TeacherDao extends JpaRepository<Teacher, Integer>, JpaSpecificationExecutor<Teacher> {

}

在IDEA中DAO层截图所示:
在这里插入图片描述

7.1.1.4、配置一对多和多对一关系

1、配置学生和教师的关系(一对多),需要在Student.java实体类中进行配置。我们使用注解形式配置表关系,配置步骤如下:
(1)、声明关系,使用@OneToMany配置一对多关系
(2)、配置外键(中间表),使用@JoinColum配置外键,需要输入两个参数,name和referencedColumName。

  • name:表示外键字段名称
  • referencedColumName:表示参照主表的主键字段名称
    如上所述,我们需要对Student.java进一步进行改造:
package com.jpa.domain;

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


/**
 * 学生实体类
 */
@Entity
@Table(name = "jpa_student")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "stu_id")
    private Integer stuId;   //学生的主键
    @Column(name = "stu_name")
    private String stuName;  //学生姓名
    @Column(name = "stu_age")
    private Integer stuAge;  //学生年龄
    @Column(name = "stu_grade")
    private String stuGrade; //学生年级
    @Column(name = "stu_score")
    private Double stuScore; //学生成绩

    //使用Set集合处理一对多关系
    @OneToMany(targetEntity = Teacher.class)
    @JoinColumn(name = "tea_stu_id", referencedColumnName = "stu_id")
    private Set<Teacher> teachers = new HashSet<Teacher>();

    public Integer getStuId() {
        return stuId;
    }

    public void setStuId(Integer stuId) {
        this.stuId = stuId;
    }

    public String getStuName() {
        return stuName;
    }

    public void setStuName(String stuName) {
        this.stuName = stuName;
    }

    public Integer getStuAge() {
        return stuAge;
    }

    public void setStuAge(Integer stuAge) {
        this.stuAge = stuAge;
    }

    public String getStuGrade() {
        return stuGrade;
    }

    public void setStuGrade(String stuGrade) {
        this.stuGrade = stuGrade;
    }

    public Double getStuScore() {
        return stuScore;
    }

    public void setStuScore(Double stuScore) {
        this.stuScore = stuScore;
    }

    public Set<Teacher> getTeachers() {
        return teachers;
    }

    public void setTeachers(Set<Teacher> teachers) {
        this.teachers = teachers;
    }

    @Override
    public String toString() {
        return "Student{" +
                "stuId=" + stuId +
                ", stuName='" + stuName + '\'' +
                ", stuAge=" + stuAge +
                ", stuGrade='" + stuGrade + '\'' +
                ", stuScore=" + stuScore +
                ", teachers=" + teachers +
                '}';
    }
}

2、配置教师和学生的关系(多对一),需要在Teacher.java实体类中进行配置。我们使用注解形式配置表关系,配置步骤如下:
(1)、声明关系,使用@ManyToOne配置多对一关系
(2)、配置外键(中间键),使用@JoinColum配置外键,需要输入两个参数,name和referencedColumName。

  • name:表示外键字段名称
  • referencedColumName:表示参照主表的主键字段名称
    如上所述,我们需要对Teacher.java进一步进行改造:
package com.jpa.domain;

import javax.persistence.*;

/**
 * 教师实体类
 */
@Entity
@Table(name = "jpa_teacher")
public class Teacher {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "tea_id")
    private Integer teaId;   //教师的主键
    @Column(name = "tea_name")
    private String teaName;  //教师姓名
    @Column(name = "tea_phone")
    private String teaPhone;  //教师手机号码

    //配置多对一关系
    @ManyToOne(targetEntity = Student.class)
    @JoinColumn(name = "tea_stu_id", referencedColumnName = "stu_id")
    private Student student;

    public Integer getTeaId() {
        return teaId;
    }

    public void setTeaId(Integer teaId) {
        this.teaId = teaId;
    }

    public String getTeaName() {
        return teaName;
    }

    public void setTeaName(String teaName) {
        this.teaName = teaName;
    }

    public String getTeaPhone() {
        return teaPhone;
    }

    public void setTeaPhone(String teaPhone) {
        this.teaPhone = teaPhone;
    }


    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "teaId=" + teaId +
                ", teaName='" + teaName + '\'' +
                ", teaPhone='" + teaPhone + '\'' +
                ", student=" + student +
                '}';
    }
}

7.1.2、创建学生和教师并建立映射关系

在编写测试时需注意添加@Transactional注解,也因为事务默认是回滚当前操作的性质,我们还需要添加@Rollback(value = false)注解。为了在生成学生时自动将主键id传给教师表(为tea_stu_id字段填值),我们需要使用以下语句进行关联:

student.getTeachers().add(teacher);

完整的测试代码如下编写:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.dao.TeacherDao;
import com.jpa.domain.Student;
import com.jpa.domain.Teacher;
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)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class OneToManyTest {
    @Autowired
    private StudentDao studentDao;
    @Autowired
    private TeacherDao teacherDao;

    @Test
    @Transactional
    @Rollback(value = false)
    public void testAdd() {
        //新建一个学生对象
        Student student = new Student();
        student.setStuName("小王");
        student.setStuAge(7);
        student.setStuScore(80.0);
        student.setStuGrade("一年级三班");
        //新建一个教师对象
        Teacher teacher = new Teacher();
        teacher.setTeaName("胡老师");
        teacher.setTeaPhone("18083876541");
        //将学生和教师作关联(一对多)
        student.getTeachers().add(teacher);
        //保存学生和教师信息
        studentDao.save(student);
        teacherDao.save(teacher);
    }
}

运行测试,执行日志输出:
在这里插入图片描述数据库表新增数据成功:
在这里插入图片描述在这里插入图片描述

7.1.3、级联操作

所谓级联操作就是指在操作主表数据是对从表的数据也做响应的操作,如添加一个学生时,可以将关联到学生上的教师一并作添加;在删除一个学生时也能将学生关联的教师做删除。
在做此操作前我们需将学生实体类设置为放弃外键维护权。具体将以下注解进行修改:

@OneToMany(targetEntity = Teacher.class)
@JoinColumn(name = "tea_stu_id", referencedColumnName = "stu_id")

修改为:

/**
* 放弃外键维护权
* mappedBy的取值名称为对方配置关系的属性名称
* cascade表示级联操作,默认使用CascadeType.ALL
*/
@OneToMany(mappedBy = "student",cascade = CascadeType.ALL)

完整的Student.java代码如下修改:

package com.jpa.domain;

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


/**
 * 学生实体类
 */
@Entity
@Table(name = "jpa_student")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "stu_id")
    private Integer stuId;   //学生的主键
    @Column(name = "stu_name")
    private String stuName;  //学生姓名
    @Column(name = "stu_age")
    private Integer stuAge;  //学生性别
    @Column(name = "stu_grade")
    private String stuGrade; //学生年级
    @Column(name = "stu_score")
    private Double stuScore; //学生成绩

    /**
     * 放弃外键维护权
     * mappedBy的取值名称为对方配置关系的属性名称
     * cascade表示级联操作,默认使用CascadeType.ALL
     */
    @OneToMany(mappedBy = "student",cascade = CascadeType.ALL)
    private Set<Teacher> teachers = new HashSet<Teacher>();

    public Integer getStuId() {
        return stuId;
    }

    public void setStuId(Integer stuId) {
        this.stuId = stuId;
    }

    public String getStuName() {
        return stuName;
    }

    public void setStuName(String stuName) {
        this.stuName = stuName;
    }

    public Integer getStuAge() {
        return stuAge;
    }

    public void setStuAge(Integer stuAge) {
        this.stuAge = stuAge;
    }

    public String getStuGrade() {
        return stuGrade;
    }

    public void setStuGrade(String stuGrade) {
        this.stuGrade = stuGrade;
    }

    public Double getStuScore() {
        return stuScore;
    }

    public void setStuScore(Double stuScore) {
        this.stuScore = stuScore;
    }

    public Set<Teacher> getTeachers() {
        return teachers;
    }

    public void setTeachers(Set<Teacher> teachers) {
        this.teachers = teachers;
    }

    @Override
    public String toString() {
        return "Student{" +
                "stuId=" + stuId +
                ", stuName='" + stuName + '\'' +
                ", stuAge=" + stuAge +
                ", stuGrade='" + stuGrade + '\'' +
                ", stuScore=" + stuScore +
                ", teachers=" + teachers +
                '}';
    }
}
7.1.3.1、级联添加

我们可以在保存一个学生数据的同时将对应的教师数据一并作添加操作。编写测试代码如下:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.dao.TeacherDao;
import com.jpa.domain.Student;
import com.jpa.domain.Teacher;
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)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class OneToManyTest {
    @Autowired
    private StudentDao studentDao;
    @Autowired
    private TeacherDao teacherDao;

    @Test
    @Transactional
    @Rollback(value = false)
    public void testAdd() {
        //新建一个学生对象
        Student student = new Student();
        student.setStuName("小唐");
        student.setStuAge(7);
        student.setStuScore(80.0);
        student.setStuGrade("一年级四班");
        //新建一个教师对象
        Teacher teacher = new Teacher();
        teacher.setTeaName("孙老师");
        teacher.setTeaPhone("18000000001");

        teacher.setStudent(student);
        student.getTeachers().add(teacher);

        //只保存学生,但会将教师数据一并作添加
        studentDao.save(student);
    }
}

运行测试,执行日志输出:
在这里插入图片描述数据库表新增数据成功:
在这里插入图片描述在这里插入图片描述

7.1.3.1、级联删除

我们将学生号为“1”的数据删除时,jpa_teacher表中该学生对应的教师数据也需要一并删除,测试代码如下编写:

package com.jpa.test;

import com.jpa.dao.StudentDao;
import com.jpa.dao.TeacherDao;
import com.jpa.domain.Student;
import com.jpa.domain.Teacher;
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)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class OneToManyTest {
    @Autowired
    private StudentDao studentDao;
    @Autowired
    private TeacherDao teacherDao;

    @Test
    @Transactional
    @Rollback(value = false)
    public void testRemove() {
        //1、查询学号为1的学生
        Student student = studentDao.findOne(1);
        //2、删除该学生
        studentDao.delete(student);
    }
}

测试执行后查看数据库:
在这里插入图片描述在这里插入图片描述

7.2、多对多表操作

7.2.1.、多对多表操作环境准备
7.2.1.1、数据库表准备

我们使用SQL语句创建3张数据表,分别为用户表、角色表和中间表:
新建用户表SQL语句:

CREATE TABLE `jpa_user` (
  `user_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户编号(主键)',
  `user_name` varchar(20) DEFAULT NULL COMMENT '用户名称',
  `user_age` int(11) DEFAULT NULL COMMENT '用户年龄',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

新建角色表SQL语句:

CREATE TABLE `jpa_role` (
  `role_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '角色编号(主键)',
  `role_name` char(20) DEFAULT NULL COMMENT '角色名称',
  PRIMARY KEY (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

新建中间表SQL语句:

CREATE TABLE `jpa_user_role` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `relation_user_id` int(11) DEFAULT NULL COMMENT '用户id',
  `relation_role_id` int(11) DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
7.2.1.2、实体类准备

用户实体类编写代码如下:

package com.jpa.domain;

import javax.persistence.*;


/**
 * 用户实体类
 */
@Entity
@Table(name = "jpa_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Integer userId;   //用户的主键
    @Column(name = "user_name")
    private String userName;  //用户名称
    @Column(name = "user_age")
    private Integer userAge;  //用户年龄

    public Integer getUserId() {
        return userId;
    }

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

    public String getUserName() {
        return userName;
    }

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

    public Integer getUserAge() {
        return userAge;
    }

    public void setUserAge(Integer userAge) {
        this.userAge = userAge;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", userAge=" + userAge +
                '}';
    }
}

角色实体类编写代码如下:

package com.jpa.domain;

import javax.persistence.*;

/**
 * 角色实体类
 */
@Entity
@Table(name = "jpa_role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Integer roleId;   //角色的主键
    @Column(name = "role_name")
    private String roleName;  //角色名称

    public Integer getRoleId() {
        return roleId;
    }

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

    public String getRoleName() {
        return roleName;
    }

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

    @Override
    public String toString() {
        return "Role{" +
                "roleId=" + roleId +
                ", roleName='" + roleName + '\'' +
                '}';
    }
}

在IDEA中实体类截图所示:
在这里插入图片描述

7.2.1.3、DAO层接口准备

用户DAO层编写代码如下:

package com.jpa.dao;

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

public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {

}

角色DAO层编写代码如下:

package com.jpa.dao;

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

public interface RoleDao extends JpaRepository<Role, Integer>, JpaSpecificationExecutor<Role> {

}

在IDEA中DAO层截图所示:
在这里插入图片描述

7.2.1.4、配置多对多关系

1、配置用户和角色的关系(多对多),需要在User.java实体类中进行配置。我们使用注解形式配置表关系,配置步骤如下:
(1)、声明关系,使用@ManyToMany配置一对多关系
(2)、配置中间表与user及role表的关系:

@JoinTable(name="jpa_user_role",//name为中间表的名称
//joinColumns当前对象在中间表的外键
joinColumns = {@JoinColumn(name="relation_user_id",referencedColumnName = "user_id")},
//inverseJoinColumns对方对象在中间表的外键
inverseJoinColumns = {@JoinColumn(name="relation_role_id",referencedColumnName = "role_id")})

如上所述,我们需要对User.java进一步进行改造:

package com.jpa.domain;

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


/**
 * 用户实体类
 */
@Entity
@Table(name = "jpa_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Integer userId;   //用户的主键
    @Column(name = "user_name")
    private String userName;  //用户名称
    @Column(name = "user_age")
    private Integer userAge;  //用户年龄

    /**
     * 配置用户到角色的多对多关系
     * 1、声明表关系的配置
     * 2、配置中间表(包含两个外键)
     */
    @ManyToMany(targetEntity = Role.class)
    @JoinTable(name = "jpa_user_role",//name为中间表的名称
            //joinColumns当前对象在中间表的外键
            joinColumns = {@JoinColumn(name = "relation_user_id", referencedColumnName = "user_id")},
            //inverseJoinColumns对方对象在中间表的外键
            inverseJoinColumns = {@JoinColumn(name = "relation_role_id", referencedColumnName = "role_id")})
    private Set<Role> roles = new HashSet<Role>();


    public Integer getUserId() {
        return userId;
    }

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

    public String getUserName() {
        return userName;
    }

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

    public Integer getUserAge() {
        return userAge;
    }

    public void setUserAge(Integer userAge) {
        this.userAge = userAge;
    }

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

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

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", userAge=" + userAge +
                ", roles=" + roles +
                '}';
    }
}

2、配置角色和用户的关系(多对多),需要在Teacher.java实体类中进行配置。我们使用注解形式配置表关系,配置步骤如下:
(1)、声明关系,使用@ManyToMany配置多对一关系
(2)、配置中间表与user及role表的关系:

 @JoinTable(name = "jpa_user_role",//name为中间表的名称
//joinColumns当前对象在中间表的外键
joinColumns = {@JoinColumn(name = "relation_role_id", referencedColumnName = "role_id")},
//inverseJoinColumns对方对象在中间表的外键
inverseJoinColumns = {@JoinColumn(name = "relation_user_id", referencedColumnName = "user_id")})

如上所述,我们需要对Role.java进一步进行改造:

package com.jpa.domain;

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

/**
 * 角色实体类
 */
@Entity
@Table(name = "jpa_role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Integer roleId;   //角色的主键
    @Column(name = "role_name")
    private String roleName;  //角色名称

    /**
     * 配置角色到用户的多对多关系
     * 1、声明表关系的配置
     * 2、配置中间表(包含两个外键)
     */
    @ManyToMany(targetEntity = User.class)
    @JoinTable(name = "jpa_user_role",//name为中间表的名称
            //joinColumns当前对象在中间表的外键
            joinColumns = {@JoinColumn(name = "relation_role_id", referencedColumnName = "role_id")},
            //inverseJoinColumns对方对象在中间表的外键
            inverseJoinColumns = {@JoinColumn(name = "relation_user_id", referencedColumnName = "user_id")})
    private Set<User> users = new HashSet<User>();

    public Integer getRoleId() {
        return roleId;
    }

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

    public String getRoleName() {
        return roleName;
    }

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

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

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

    @Override
    public String toString() {
        return "Role{" +
                "roleId=" + roleId +
                ", roleName='" + roleName + '\'' +
                ", users=" + users +
                '}';
    }
}

7.2.2、创建用户和角色并建立映射关系

在编写测试时需注意添加@Transactional注解,也因为事务默认是回滚当前操作的性质,我们还需要添加@Rollback(value = false)注解。为了在生成用户和角色时对中间表也进行设值,我们需要使用以下语句进行关联:

user.getRoles().add(role);

完整的测试代码如下编写:

package com.jpa.test;

import com.jpa.dao.UserDao;
import com.jpa.dao.RoleDao;
import com.jpa.domain.Role;
import com.jpa.domain.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;


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

    @Test
    @Transactional
    @Rollback(value = false)
    public void testAdd() {
        //1、创建一个user
        User user = new User();
        user.setUserName("小方");
        user.setUserAge(35);
        //2、创建一个role
        Role role = new Role();
        role.setRoleName("管理员");

        //配置用户到角色的关系
        user.getRoles().add(role);

        userDao.save(user);
        roleDao.save(role);
    }
}

运行测试,执行日志输出:
在这里插入图片描述数据库表新增数据成功:
jpa_user表:
在这里插入图片描述jpa_role表:
在这里插入图片描述jpa_user_role中间表:
在这里插入图片描述

7.2.3、级联操作

我们可以在保存一个用户的同时,保存角色并关联。要实现这个功能需要将角色放弃维护权,具体将以下注解进行修改:

@ManyToMany(targetEntity = User.class)
@JoinTable(name = "jpa_user_role",//name为中间表的名称
//joinColumns当前对象在中间表的外键
joinColumns = {@JoinColumn(name = "relation_role_id", referencedColumnName = "role_id")},
//inverseJoinColumns对方对象在中间表的外键
inverseJoinColumns = {@JoinColumn(name = "relation_user_id", referencedColumnName = "user_id")})

修改为:

@ManyToMany(mappedBy = "roles")

我们还需要在用户roles属性的@ManyToMany主机上配置:

cascade = CascadeType.ALL

完成以上操作后,我们将User.java和Role.java分别修改为了如下代码:
User.java:

package com.jpa.domain;

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


/**
 * 用户实体类
 */
@Entity
@Table(name = "jpa_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Integer userId;   //用户的主键
    @Column(name = "user_name")
    private String userName;  //用户名称
    @Column(name = "user_age")
    private Integer userAge;  //用户年龄

    /**
     * 配置用户到角色的多对多关系
     * 1、声明表关系的配置
     * 2、配置中间表(包含两个外键)
     */
    @ManyToMany(targetEntity = Role.class, cascade = CascadeType.ALL)
    @JoinTable(name = "jpa_user_role",//name为中间表的名称
            //joinColumns当前对象在中间表的外键
            joinColumns = {@JoinColumn(name = "relation_user_id", referencedColumnName = "user_id")},
            //inverseJoinColumns对方对象在中间表的外键
            inverseJoinColumns = {@JoinColumn(name = "relation_role_id", referencedColumnName = "role_id")})
    private Set<Role> roles = new HashSet<Role>();


    public Integer getUserId() {
        return userId;
    }

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

    public String getUserName() {
        return userName;
    }

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

    public Integer getUserAge() {
        return userAge;
    }

    public void setUserAge(Integer userAge) {
        this.userAge = userAge;
    }

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

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

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", userAge=" + userAge +
                ", roles=" + roles +
                '}';
    }
}

Role.java:

package com.jpa.domain;

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

/**
 * 角色实体类
 */
@Entity
@Table(name = "jpa_role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Integer roleId;   //角色的主键
    @Column(name = "role_name")
    private String roleName;  //角色名称
    /**
     * 配置角色到用户的多对多关系
     * 设置实现放弃维护权
     */
    @ManyToMany(mappedBy = "roles")
    private Set<User> users = new HashSet<User>();

    public Integer getRoleId() {
        return roleId;
    }

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

    public String getRoleName() {
        return roleName;
    }

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

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

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

    @Override
    public String toString() {
        return "Role{" +
                "roleId=" + roleId +
                ", roleName='" + roleName + '\'' +
                ", users=" + users +
                '}';
    }
}
7.2.3.1、级联添加

我们可以在保存一个用户数据的同时将对应的角色数据和中间表数据一并作添加操作。编写测试代码如下:

package com.jpa.test;

import com.jpa.dao.UserDao;
import com.jpa.dao.RoleDao;
import com.jpa.domain.Role;
import com.jpa.domain.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;


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

    @Test
    @Transactional
    @Rollback(value = false)
    public void testAdd() {
        //1、创建一个user
        User user = new User();
        user.setUserName("小方");
        user.setUserAge(35);
        //2、创建一个role
        Role role = new Role();
        role.setRoleName("管理员");

        //配置用户到角色的关系
        user.getRoles().add(role);
        role.getUsers().add(user);

        //只做用户保存,检测是否新增了角色和中间表数据
        userDao.save(user);
    }
}

运行测试,查看日志输出:
在这里插入图片描述数据库表新增数据成功:
jpa_user表:
在这里插入图片描述jpa_role表:
在这里插入图片描述jpa_user_role中间表:
在这里插入图片描述

7.2.3.2、级联删除

我们在用户表中user_id为1的用户进行删除时,要求将jpa_role表和jpa_user_role表中对应的关联数据一并作删除,测试代码如下编写:

package com.jpa.test;

import com.jpa.dao.UserDao;
import com.jpa.dao.RoleDao;
import com.jpa.domain.Role;
import com.jpa.domain.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;


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

    @Test
    @Transactional
    @Rollback(value = false)
    public void testRemove() {
        //1、查询user_id为1的用户
        User user = userDao.findOne(1);
        //2、删除查询出来的user_id为1的用户
        userDao.delete(user);
    }
}

运行测试,查看日志输出:
在这里插入图片描述测试执行后查看数据库,记录用户id为1且对应关系表的数据都已被删除:
jpa_user表:
在这里插入图片描述jpa_role表:
在这里插入图片描述jpa_user_role中间表:
在这里插入图片描述
感谢您阅读本教程,有不足之处请留言指出。本教程是由本人在学习Spring Data JPA时的总结归纳,希望对读者提供一定的帮助。
在此特别备注:本教程实例及绝大部分内容来自黑马程序员培训网络视频课件讲解内容。

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值