跳至主要內容

JPA

wangdx大约 66 分钟

JPA 简介

基于 JPA 标准开发数据库应用

  • 项目应用中为了便于数据的有效管理都会基于关系型数据库实现相关业务数据的存储为了可以进一步统一数据操作的标准,在 JavaEE5 标准化规范中定义了 JPA(JavaPersistence AP1、Java 持久化 API)操作标准,JPA 吸取了目前 Java 持久化技术的优点可以方便的利用 Java 对象实现持久层开发

JPA 发展历史

  • 1.早期版本的 EJB,定义持久层结合使用 javax.ejb.EntityBean 接口作为业务逻辑层
  • 2.引入 EJB3.0 的持久层分离,并指定为 JPA1.0。这个 API 规范随着 JAVA EE5 对 2006 年 5 月 11 日使用 JSR220 规范发布;
  • 3、JPA2.0 规范发布于 2009 年 12 月 10 日并成 JavaCommunity Process JSR317 的- 部分;
  • 4、JPA2.1 使用 JSR338 的 JAVA EE7 的规范发布于 2013 年 4 月 22 日

ORMapping

  • 在 JPA 推广的早期时代由于各类 ORMapping(Object Relational Mapping、对象关系映射)框架较多,导致项目的开发与维护闲难,,开发者不得不面对多种不同名称但是功能相同的操作模式。为了解决这样的开发与维护问题,Gavin King 制定了 EJB 3.0 技术规范,同时伴随着 EJB 3.0 的推出,JPA 标准也正式落地,并且基于 IPA 标准提供了多种不同的实现框架,例如:Hibernate(从 3.2 版本之后开始兼容 JPA 标准)OpenJPA、TOPLink、EclipseLink,而随着时间的发展,现在国内使用较多的 JPA 实现为 Hibernate

Hibernate 与数据库可移植性

  • 在 JPA 出现之前市面上使用最多的是 Hibernate 开发框架,早期的 Hibernate 框架是模拟 EJB 2.X 完全基于对象的形式实现数据库操作,同时具有中的实体 Bean(Entity Bean)技术实现的,强大的可移植性,该移植性是基于数据库方言实现的,即:只需要修改 JDBC 配置和方言类型就可以在数据层代码不更改的前提下实现不同的数据库之中任意移植

但是随着国内互联网开发环境的不断发展,很多追求性能的公司不再有这种可移植性的需要所以当不需要考虑数据库移植时一般都使用 MyBatis/MyBatisPlus 实现数据层应用开发

JPA 编程起步

JPA 关联映射 JPA 的开发实现主要是基于实体类实例实现的数据管理,在实际的开发中每一个实体类的结构要与最终操作的数据表有所关联,这样在使用 EntityManager 类进行操作时就可以直接用实体类的对象实例来描述数据表中的一行完整记录。

1、
implementation group: 'jakarta.persistence', name: 'jakarta.persistence-api', version: '3.1.0'
implementation group: 'jakarta.persistence', name: 'jakarta.persistence-api', version: '3.2.0-M1'

implementation group: 'org.hibernate.orm', name: 'hibernate-core', version: '6.1.0.Final'
implementation group: 'org.hibernate.orm', name: 'hibernate-core', version: '6.4.4.Final'

implementation group: 'org.hibernate.orm', name: 'hibernate-hikaricp', version: '6.1.0.Final'
implementation group: 'org.hibernate.orm', name: 'hibernate-hikaricp', version: '6.4.4.Final'

implementation group: 'org.hibernate', name: 'hibernate-core-jakarta', version: '5.6.9.Final'
implementation group: 'org.hibernate', name: 'hibernate-core-jakarta', version: '5.6.15.Final'


    'jakarta.persistence-api'       : "jakarta.persistence:jakarta.persistence-api:${versions.jpa}",
    'hibernate-core'                : "org.hibernate.orm:hibernate-core:${versions.hibernateORM}",
    'hibernate-hikaricp'                : "org.hibernate.orm:hibernate-hikaricp:${versions.hibernateORM}",
    'hibernate-core-jakarta'        : "org.hibernate:hibernate-core-jakarta:${versions.hibernate}"


2、
DROP DATABASE IF EXISTS yootk;
CREATE DATABASE yootk CHARACTER SET UTF8;
USE yootk;
CREATE TABLE course (
   cid		BIGINT		AUTO_INCREMENT	comment '课程ID',
   cname		VARCHAR(50) 			comment '课程名称',
   start		DATE				comment '课程开始日期',
   end		DATE				comment '课程结束日期',
   credit	INT				comment '课程学分',
   num		INT				comment '课程人数',
   CONSTRAINT pk_cid PRIMARY KEY(cid)
)engine=innodb;

3、
package com.yootk.po;

import jakarta.persistence.*;

import java.util.Date;

@Entity // 表示当前的类是一个实体类
@Table(name = "course") // 如果此时的类名称与表名称一致,该注解可以不定义的
public class Course {
    @Id // 定义主键标记
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 交由数据库生成
    private Long cid; // 主键字段,映射cid字段信息
    @Column(name = "cname") // 如果属性名称和字段名称不对应,需要进行注解配置
    private String cname; // 属性名称与列名称相同
    @Temporal(TemporalType.DATE)
    private java.util.Date start; // 属性名称与列名称相同
    @Temporal(TemporalType.DATE)
    private java.util.Date end; // 属性名称与列名称相同
    private Integer credit; // 属性名称与列名称相同
    private Integer num; // 属性名称与列名称相同
    public Course() {} // 反射操作必须要有无参
    public Course(Long cid, String cname, Date start, Date end, Integer credit, Integer num) {
        this.cid = cid;
        this.cname = cname;
        this.start = start;
        this.end = end;
        this.credit = credit;
        this.num = num;
    }
    public Long getCid() {
        return cid;
    }
    public void setCid(Long cid) {
        this.cid = cid;
    }
    public String getCname() {
        return cname;
    }
    public void setCname(String cname) {
        this.cname = cname;
    }
    public Date getStart() {
        return start;
    }
    public void setStart(Date start) {
        this.start = start;
    }
    public Date getEnd() {
        return end;
    }
    public void setEnd(Date end) {
        this.end = end;
    }
    public Integer getCredit() {
        return credit;
    }
    public void setCredit(Integer credit) {
        this.credit = credit;
    }
    public Integer getNum() {
        return num;
    }
    public void setNum(Integer num) {
        this.num = num;
    }
    @Override
    public String toString() {
        return "【课程信息】ID:" + this.cid + "、名称:" + this.cname + "、学分:" + this.credit +
                "、人数:" + this.num + "、开始日期:" + this.start + "、结束日期:" + this.end;
    }
}


4、
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
                        http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="YootkJPA"> 			<!-- 持久化单元 -->
       <class>com.yootk.po.Course</class> 			<!-- 实体类 -->
        <properties> <!-- 使用Hikari连接池实现数据库连接管理 -->
            <property name="hibernate.connection.provider_class"
                      value="org.hibernate.hikaricp.internal.HikariCPConnectionProvider"/>
            <property name="hibernate.dialect"
                      value="org.hibernate.dialect.MySQLDialect"/>    <!-- 数据库方言 -->
            <property name="hibernate.hikari.dataSourceClassName"
                      value="com.zaxxer.hikari.HikariDataSource"/> <!-- Hikari数据源 -->
            <property name="hibernate.hikari.minimumIdle"
	    value="5"/> 				<!-- 空闲时连接池数量 -->
            <property name="hibernate.hikari.maximumPoolSize"
                      value="10"/> 				<!-- 连接池最大数量 -->
            <property name="hibernate.hikari.idleTimeout"
                      value="3000"/> 				<!-- 连接最小维持时长 -->
            <property name="hibernate.hikari.dataSource.driverClassName"
                      value="com.mysql.cj.jdbc.Driver"/> 	<!-- 驱动程序 -->
            <property name="hibernate.hikari.dataSource.jdbcUrl"
                      value="jdbc:mysql://localhost:3306/yootk"/>    <!-- 连接地址 -->
            <property name="hibernate.hikari.dataSource.username"
                      value="root"/> 				<!-- 用户名 -->
            <property name="hibernate.hikari.dataSource.password"
                      value="mysqladmin"/> 			<!-- 密码 -->
		                  <property name="hibernate.show_sql"
                      value="true"/>    			<!-- 显示执行SQL -->
            <property name="hibernate.format_sql"
                      value="false"/>    			<!-- 格式化SQL -->
        </properties>
    </persistence-unit>
</persistence>


5、
package com.yootk.util;

import java.util.Date;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

public class DateUtil {
    private static final String DATE_PATTERN = "yyyy-MM-dd"; // 转换格式
    private static final DateTimeFormatter DATE_FORMATTER =
            DateTimeFormatter.ofPattern(DATE_PATTERN); // 日期格式化
    private static final ZoneId ZONE_ID = ZoneId.systemDefault();
    public static Date stringToDate(String date) {
        LocalDate localDate = LocalDate.parse(date, DATE_FORMATTER);
        Instant instant = localDate.atStartOfDay().atZone(ZONE_ID).toInstant();
        return Date.from(instant);
    }
}


6、
package com.yootk.test;

import com.yootk.po.Course;
import com.yootk.util.DateUtil;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
import org.junit.jupiter.api.Test;

public class TestCoursePersistence {
    @Test
    public void testAdd() {
        EntityManagerFactory factory = Persistence
                .createEntityManagerFactory("YootkJPA");
        EntityManager entityManager = factory.createEntityManager(); // 创建JPA操作对象
        entityManager.getTransaction().begin(); // 开启JPA事务
        Course course = new Course();
        course.setCname("Spring就业编程实战");
        course.setCredit(2);
        course.setNum(10);
        course.setStart(DateUtil.stringToDate("2008-06-26"));
        course.setEnd(DateUtil.stringToDate("2008-07-27"));
        entityManager.persist(course); // 数据存储
        entityManager.getTransaction().commit();
        entityManager.close();
        factory.close();
    }
}

JPA 连接工厂

EntityManager 与数据库会话

  • JPA 的核心实现在于 EntityManager,而 EntityManager 接口对象实例都需要通过 EntityManagerFactory 接口实例来进行创建,在实际开发中每一个 EntityManager 实例都对应一个数据库会话,所以围绕着数据库会话,EntityManager 才可以实现更新事务控制以及数据处理操作。

JPA 连接工厂类

  • 每一次在项目中进行 EntityManager 接口对象创建时都需要大量且重复的操作步骤,所以可以创建一个专属的 JPA 连接工厂类在该工厂类中基于 ThreadLocal 来实现每一不同操作线程的 EntityManager 实例管理
1、
package com.yootk.util;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;

public class JPAEntityFactory { // JPA的连接工厂
    private static final String PERSISTENCE_UNIT = "YootkJPA"; // 持久化单元名称
    private static EntityManagerFactory entityManagerFactory; // 连接工厂类的核心
    private static ThreadLocal<EntityManager> entityManagerThreadLocal =
            new ThreadLocal<>(); // 保存全部的EntityManager接口实例
    static { // 静态代码块执行,只执行一次
        rebuildEntityManagerFactory(); // 获取EntityManagerFactory实例
    }
    private JPAEntityFactory() {} // 取消构造方法的定义
    public static EntityManager getEntityManager() { // 获取操作对象
        EntityManager entityManager = entityManagerThreadLocal.get(); // 有可能是重复调用
        if (entityManager == null) { // 当前没有指定的对象实例
            if (entityManagerFactory == null) { // 没有连接工厂
                rebuildEntityManagerFactory(); // 构建连接工厂
            }
            entityManager = entityManagerFactory.createEntityManager(); // 创建EntityManager实例
            entityManagerThreadLocal.set(entityManager); // 实例存储
        }
        return entityManager;
    }
    public static void close() {    // 关闭EntityManager
        EntityManager entityManager = entityManagerThreadLocal.get(); // 获取已经保存的实例
        if (entityManager != null) { // 实例存在
            entityManager.close(); // 关闭当前的数据库操作
            entityManagerThreadLocal.remove(); // 移除集合
        }
    }
    public static EntityManagerFactory getEntityManagerFactory() { // 获取EntityManagerFactor实例
        if (entityManagerFactory == null) { // 没有对象的实例
            rebuildEntityManagerFactory(); // 重新构建对象实例
        }
        return entityManagerFactory;
    }
    // 一旦调用了此方法,那么就表示要重新实例化一个新的EntityManagerFactory接口对象
    public static void rebuildEntityManagerFactory() {
        entityManagerFactory = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT);
    }
}


2、
package com.yootk.test;

import com.yootk.po.Course;
import com.yootk.util.DateUtil;
import com.yootk.util.JPAEntityFactory;
import org.junit.jupiter.api.Test;

public class TestCoursePersistence {
    @Test
    public void testAdd() {
        JPAEntityFactory.getEntityManager().getTransaction().begin(); // 开启JPA事务
        Course course = new Course();
        course.setCname("Spring就业编程实战");
        course.setCredit(2);
        course.setNum(10);
        course.setStart(DateUtil.stringToDate("2008-06-26"));
        course.setEnd(DateUtil.stringToDate("2008-07-27"));
        JPAEntityFactory.getEntityManager().persist(course); // 数据存储
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
}


3、
package com.yootk.test;

import com.yootk.po.Course;
import com.yootk.util.DateUtil;
import com.yootk.util.JPAEntityFactory;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestCoursePersistence {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestCoursePersistence.class);
    @Test
    public void testAdd() {
        JPAEntityFactory.getEntityManager().getTransaction().begin(); // 开启JPA事务
        Course course = new Course();
        course.setCname("Spring就业编程实战");
        course.setCredit(2);
        course.setNum(10);
        course.setStart(DateUtil.stringToDate("2008-06-26"));
        course.setEnd(DateUtil.stringToDate("2008-07-27"));
        JPAEntityFactory.getEntityManager().persist(course); // 数据存储
        LOGGER.info("数据更新成功,当前所更新的数据ID为:{}", course.getCid()); // 日志输出
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
}

DDL 自动更新

DDL 自动更新

  • 在传统的项目开发之中,常规的做法是先进行数据表的创建,而后在围绕数据表进行业也是先进行表结构的修改,而后再进行程序的务功能的实现。在每次业务发生改变时,变更,这样的数据库维护是非常繁琐的,考虑到数据库更新以及数据库移植方面的设计在 Hibernate 之中提供了 DDL 自动创建以及表更新策略

1、
<property name="hibernate.hbm2ddl.auto" value="create"/>
2、
<property name="hibernate.hbm2ddl.auto" value="update"/>

JPA 主键生成策略

JPA 主键生成策略

  • 实体类中作为主键列的属性,可以通过“@Id”注解进行声明,由于不同的数据表中会有不同的主键处理的方式,如果现在希望可以由 JPA 自动实现主键数据的处理,可以在对应的属性上,使用“@GeneratedValue”注解进行主键生成策略的配置

JPA 主键生成策略

  • 为了便于主键生成策略的管理,在 IPA 中提供了一个 jakarta.persistence.GenerationType 枚举类,该类中定义了五种主键生成策略

数据表管理主键

  • 不同的数据库类型存在有不同的主键生成方式,例如:在 MYSQL 中可以使用"IDENTITY",而在 Oracle 中可以使用“SEOUENCE'所以在主键生成策略的选择上容易出现数据库移植的设计冲突。在 JPA 规范设计时,充分的考虑到了这一需要,提供了 “GenerationType.TABLE'主键生成策略,该策略的主要模式就是通过一张数据表保存要生成的主键数据

1、
package com.yootk.po;

import jakarta.persistence.*;

import java.util.Date;

@Entity // 表示当前的类是一个实体类
@Table(name = "course") // 如果此时的类名称与表名称一致,该注解可以不定义的
public class Course {
    @Id // 定义主键标记
    @GeneratedValue(strategy = GenerationType.UUID) // 交由数据库生成
    private String cid; // 主键字段,映射cid字段信息
    @Column(name = "cname") // 如果属性名称和字段名称不对应,需要进行注解配置
    private String cname; // 属性名称与列名称相同
    @Temporal(TemporalType.DATE)
    private java.util.Date start; // 属性名称与列名称相同
    @Temporal(TemporalType.DATE)
    private java.util.Date end; // 属性名称与列名称相同
    private Integer credit; // 属性名称与列名称相同
    private Integer num; // 属性名称与列名称相同
    public Course() {} // 反射操作必须要有无参
    public Course(String cid, String cname, Date start, Date end, Integer credit, Integer num) {
        this.cid = cid;
        this.cname = cname;
        this.start = start;
        this.end = end;
        this.credit = credit;
        this.num = num;
    }
    public String getCid() {
        return cid;
    }
    public void setCid(String cid) {
        this.cid = cid;
    }
    public String getCname() {
        return cname;
    }
    public void setCname(String cname) {
        this.cname = cname;
    }
    public Date getStart() {
        return start;
    }
    public void setStart(Date start) {
        this.start = start;
    }
    public Date getEnd() {
        return end;
    }
    public void setEnd(Date end) {
        this.end = end;
    }
    public Integer getCredit() {
        return credit;
    }
    public void setCredit(Integer credit) {
        this.credit = credit;
    }
    public Integer getNum() {
        return num;
    }
    public void setNum(Integer num) {
        this.num = num;
    }
    @Override
    public String toString() {
        return "【课程信息】ID:" + this.cid + "、名称:" + this.cname + "、学分:" + this.credit +
                "、人数:" + this.num + "、开始日期:" + this.start + "、结束日期:" + this.end;
    }
}


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

3、
DROP DATABASE IF EXISTS yootk;
CREATE DATABASE yootk CHARACTER SET UTF8;
USE yootk;
CREATE TABLE dept (
   deptno     BIGINT    AUTO_INCREMENT 	comment '部门编号',
   dname      VARCHAR(50)  			comment '部门名称',
   CONSTRAINT pk_deptno PRIMARY KEY(deptno)
) engine=innodb;
CREATE TABLE table_id_generate(
   digid     BIGINT AUTO_INCREMENT  	comment '主键管理ID',
   id_key    VARCHAR(50) 			comment '主键识别KEY',
   id_value   BIGINT(50) 			comment '当前主键数据',
   CONSTRAINT pk_digid PRIMARY KEY(digid)
) engine = innodb ;
-- 向table_id_generate表中保存若干主键配置项以及对应的主键内容
INSERT INTO table_id_generate(id_key,id_value) VALUES ('COMPANY_ID', 3000) ;
INSERT INTO table_id_generate(id_key,id_value) VALUES ('DEPT_ID', 6666) ;
INSERT INTO table_id_generate(id_key,id_value) VALUES ('EMP_ID', 7777) ;


4、
package com.yootk.po;

import jakarta.persistence.*;

@Entity // 定义JPA实体类
@Table(name = "dept") // 定义映射的表名称
public class Dept {
    @Id // 当前的列为主键
    @TableGenerator(name = "DEPT_GENERATOR", // 定义当前主键生成配置的名称
            table = "table_id_generate", // 定义主键数据存放表
            pkColumnName = "id_key", // 定义主键列名称
            pkColumnValue = "DEPT_ID", // 定义主键行的名称
            valueColumnName = "id_value", // 定义主键值的字段
            allocationSize = 1) // 每一次的主键增长步长为1
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "DEPT_GENERATOR")
    private Long deptno; // 主键
    @Column(name = "dname")
    private String dname; // 普通列
    public Dept() {}
    public Dept(Long deptno, String dname) {
        this.deptno = deptno;
        this.dname = dname;
    }
    public Long getDeptno() {
        return deptno;
    }
    public void setDeptno(Long deptno) {
        this.deptno = deptno;
    }
    public String getDname() {
        return dname;
    }
    public void setDname(String dname) {
        this.dname = dname;
    }
}


5、
package com.yootk.test;

import com.yootk.po.Dept;
import com.yootk.util.JPAEntityFactory;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestDeptPersistence {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestDeptPersistence.class);
    @Test
    public void testAdd() {
        JPAEntityFactory.getEntityManager().getTransaction().begin(); // 开启JPA事务
        Dept dept = new Dept();
        dept.setDname("沐言科技教学研发部"); // 设置属性
        JPAEntityFactory.getEntityManager().persist(dept); // 数据存储
        LOGGER.info("数据更新成功,当前所更新的数据ID为:{}", dept.getDeptno()); // 日志输出
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
}

EntityManager 数据操作

EntityManager 接口

  • EntityManager 接口是 JPA 规范中进行数据处理的主要接口,在该接口中提供了实体类、ID 查询的基本支持对象的更新、删除、

JPA 合并操作

  • 使用了 merge()方法实现新建的 Course 实体类对象的合并处理,在进行合并之前首先会通过 SELECT 语句根据操作数据的 ID 进行查询,如果发现此时的数据不存在则会执行 INSERT 增加处理,而如果此时数据已经存在,并且和数据库中的数据不匹配,JPA 会判断当前字段内容有更新,则会执行 UPDATE 更新处理,如果没有任何的修改,则 merge()方法不进行任何处理
package com.yootk.test;

import com.yootk.po.Course;
import com.yootk.util.DateUtil;
import com.yootk.util.JPAEntityFactory;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestCoursePersistence {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestCoursePersistence.class);
    @Test
    public void testAdd() {
        JPAEntityFactory.getEntityManager().getTransaction().begin(); // 开启JPA事务
        Course course = new Course();
        course.setCname("Spring就业编程实战");
        course.setCredit(2);
        course.setNum(10);
        course.setStart(DateUtil.stringToDate("2008-06-26"));
        course.setEnd(DateUtil.stringToDate("2008-07-27"));
        JPAEntityFactory.getEntityManager().persist(course); // 数据存储
        LOGGER.info("数据更新成功,当前所更新的数据ID为:{}", course.getCid()); // 日志输出
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
    @Test
    public void testEdit() {
        JPAEntityFactory.getEntityManager().getTransaction().begin(); // 开启JPA事务
        Course course = new Course();
        course.setCid(3L); // 要修改的课程编号
        course.setCname("SpringBook就业编程实战"); // 信息修改
        course.setCredit(3); // 信息修改
        // course.setNum(10); // 如果此处没有设置,则内容为空
        // course.setStart(DateUtil.stringToDate("2008-06-26"));
        // course.setEnd(DateUtil.stringToDate("2008-07-27"));
        JPAEntityFactory.getEntityManager().merge(course); // 数据合并(修改)
        LOGGER.info("数据更新成功,当前所更新的数据ID为:{}", course.getCid()); // 日志输出
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
    @Test
    public void testFind2Edit() {
        JPAEntityFactory.getEntityManager().getTransaction().begin(); // 开启JPA事务
        Course course = JPAEntityFactory.getEntityManager().find(Course.class, 2L); // 根据ID查询
        LOGGER.info("【数据查询】{}", course);
        course.setCname("Netty就业编程实战"); // 修改课程内容
        course.setNum(5); // 设置课程学分
        JPAEntityFactory.getEntityManager().merge(course); // 数据合并(修改)
        LOGGER.info("数据更新成功,当前所更新的数据ID为:{}", course.getCid()); // 日志输出
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
    @Test
    public void testFind() {
        Course course = JPAEntityFactory.getEntityManager().find(Course.class, 6L); // 根据ID查询
        LOGGER.info("【数据查询】{}", course);
        JPAEntityFactory.close();
    }
    @Test
    public void testReference() {
        Course course = JPAEntityFactory.getEntityManager().getReference(Course.class, 6L); // 根据ID查询
        LOGGER.info("【数据查询】{}", course);
        JPAEntityFactory.close();
    }
    @Test
    public void testRemove() {
        Course course = JPAEntityFactory.getEntityManager().getReference(Course.class, 4L); // 根据ID查询
        LOGGER.info("【数据查询】{}", course);
        JPAEntityFactory.getEntityManager().getTransaction().begin();
        JPAEntityFactory.getEntityManager().remove(course); // 删除实体对象
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
    @Test
    public void testRemoveEntity() {
        Course course = new Course(); // 创建PO对象实例
        course.setCid(3L); // 要删除的数据
        JPAEntityFactory.getEntityManager().getTransaction().begin();
        JPAEntityFactory.getEntityManager().remove(course); // 删除实体对象
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
}

JPQL 语句

JPQL 语法结构

  • 在数据库的数据操作过程中,查询操作是最为繁琐也是最为重要的功能,为了更好的满足用户开发的各种需要,在 JPA 中提供了 JPQL(Java Persistence Query Language、Java 持久化查询语言)语法支持,该语法的基本结构如下所示:
    • SELECT 子句 FROM 子句 [WHERE 子句][GROUP BY 子句][HAVING 子句][ORDER BY 子句]
    • 在该语法中可以通过 SELECT 语句定义要查询的 PO 属性名称,而后通过 FROM 设置关联的 PO 类型如果有需要也可以使用 WHERE、GROUP BY、HAVING、ORDER BY 等子句进行查询的限定与排序处理。

JPQL 与 SQL

  • JPQL 和 SQL 的执行是有所不同的,SQL 命令是关系型数据库原生支持的,而 JPQL 是工作在 JPA 组件之中,在最终执行时要根据编写的需要转化为 SQL 语句
  • 在进行 JPQL 编写时,采用的是 PO 类的结构定义,而最终在执行时,也会由 JPA 将 JPQL 的结构转为 SQL 结构,JPQL 的出现使得开发人员可以以更加熟悉的方式进行数据操作,

Query 数据查询接口

  • 在 JPA 中执行 JPQL 时,需要通过 EntityManager 接口中的 createQuery()方法创建一个 Query 接口或相关子接口的实例,进行相关查询的配置后,就可以利用 Query 接口实现数据的更新或查询操作。

Query 接口常用方法

  • Query 接口除了提供有数据查询的操作之外,还包含有数据更新的需要,同时在查询的过程中,还提供了与 JDBC 技术中 PreparedStatement 操作接口同样的占位符定义支持以及数据库的分页支持
1、
package com.yootk.test;

import com.yootk.po.Course;
import com.yootk.util.DateUtil;
import com.yootk.util.JPAEntityFactory;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

public class TestCourseQuery {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestCourseQuery.class);

    @Test
    public void testSelectAll() {
        // 此时查询之中的Course不是表名称,而是PO类的类名称
        String jpql = "SELECT c FROM Course AS c"; // JPQL查询语句
        Query query = JPAEntityFactory.getEntityManager().createQuery(jpql);
        List<Course> all = query.getResultList(); // 发出最终的查询指令
        for (Course course : all) { // 数据迭代输出
            LOGGER.info("查询结果:{}", course);
        }
        JPAEntityFactory.close();
    }

    @Test
    public void testSelectSplit() {
        int currentPage = 2; // 当前所在页
        int lineSize = 2; // 每页显示的数据行
        String keyword = "%就业编程实战%";
        // 使用PO类中的属性(不是cname的数据字段)进行模糊匹配
        String jpql = "SELECT c FROM Course AS c WHERE c.cname LIKE ?1"; // JPQL查询语句
        TypedQuery<Course> query = JPAEntityFactory.getEntityManager().createQuery(jpql, Course.class);
        query.setFirstResult((currentPage - 1) * lineSize); // 开始查询行
        query.setMaxResults(lineSize); // 长度
        query.setParameter(1, keyword); // 绑定占位符的数据
        List<Course> all = query.getResultList(); // 发出最终的查询指令
        for (Course course : all) { // 数据迭代输出
            LOGGER.info("查询结果:{}", course);
        }
        JPAEntityFactory.close();
    }
    @Test
    public void testSelectCount() {
        String keyword = "%就业编程实战%";
        // 使用PO类中的属性(不是cname的数据字段)进行模糊匹配
        String jpql = "SELECT COUNT(c) FROM Course AS c WHERE c.cname LIKE ?1"; // JPQL查询语句
        TypedQuery<Course> query = JPAEntityFactory.getEntityManager().createQuery(jpql, Course.class);
        query.setParameter(1, keyword); // 绑定占位符的数据
        LOGGER.info("数据量统计:{}", query.getSingleResult());
        JPAEntityFactory.close();
    }
    @Test
    public void testSelectByID() {
        String jpql = "SELECT c FROM Course AS c WHERE c.cid=?1"; // JPQL查询语句
        Query query = JPAEntityFactory.getEntityManager().createQuery(jpql);
        query.setParameter(1, 1L); // 设置占位符数据
        // 这种查询操作里面需要进行对象的转型处理,本身就存在有安全隐患,Query不支持泛型类型的标记
        Course course = (Course) query.getSingleResult(); // 查询但行数据
        LOGGER.info("查询结果:{}", course);
        JPAEntityFactory.close();
    }
    @Test
    public void testSelectByID2Typed() {
        String jpql = "SELECT c FROM Course AS c WHERE c.cid=?1"; // JPQL查询语句
        TypedQuery<Course> query = JPAEntityFactory.getEntityManager().createQuery(jpql, Course.class);
        query.setParameter(1, 1L); // 设置占位符数据
        // 这种查询操作里面需要进行对象的转型处理,本身就存在有安全隐患,Query不支持泛型类型的标记
        Course course = query.getSingleResult(); // 查询单行数据
        LOGGER.info("查询结果:{}", course);
        JPAEntityFactory.close();
    }
}

JPQL 数据更新

EntityManager 更新问题

  • EntityManager 接口提供了 merge()、remove()方法实现数据的修改和删除操作,但是这两个操作方法内部都需要通过 PO 类的对象实例才可以完成,但是这两个操作方法会存在有如下的设计问题:
    • merge()数据修改:在该方法操作时会对当前传入的 PO 类对象实例进行更新,但是很多时候可能只是更新表中的某几个字段。所以为了防止不更新的字段不被设置为 nul,就需要将所有不更新的数据也重复进行设置,这样必然带来额外的开发负担。并且很多时候为了简化这一操作,往往会先进行查询,修改所需属性后再更新,这样又会造成严重的性能浪费。

remove() 数据删除:在进行删除时需要将删除的 ID 封装在其对应的 PO 类实例之中,这样每一次操作都会额外的产生一个新的 PO 实例,占用不必要的内存空间。

1、
package com.yootk.test;

import com.yootk.po.Course;
import com.yootk.util.JPAEntityFactory;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.List;

public class TestCourseUpdate {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestCourseUpdate.class);

    @Test
    public void testUpdate() {
        JPAEntityFactory.getEntityManager().getTransaction().begin();
        // 所有的JPQL在编写的时候一定不是表名称,都是PO类的名称,包括属性也都是PO类中的属性名称
        String jpql = "UPDATE Course AS c SET c.credit=?1 WHERE c.credit<?2";
        Query query = JPAEntityFactory.getEntityManager().createQuery(jpql);
        query.setParameter(1, 5); // 学分修改为5
        query.setParameter(2, 3); // 学分低于3分
        LOGGER.info("数据更新结果:{}", query.executeUpdate()); // 更新处理
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
    @Test
    public void testRemoveById() {
        JPAEntityFactory.getEntityManager().getTransaction().begin();
        // 所有的JPQL在编写的时候一定不是表名称,都是PO类的名称,包括属性也都是PO类中的属性名称
        String jpql = "DELETE FROM Course AS c WHERE c.cid=?1";
        Query query = JPAEntityFactory.getEntityManager().createQuery(jpql);
        query.setParameter(1, 1);
        LOGGER.info("数据更新结果:{}", query.executeUpdate()); // 更新处理
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
    @Test
    public void testRemoveEnd() {
        JPAEntityFactory.getEntityManager().getTransaction().begin();
        // 所有的JPQL在编写的时候一定不是表名称,都是PO类的名称,包括属性也都是PO类中的属性名称
        String jpql = "DELETE FROM Course AS c WHERE c.end<?1";
        Query query = JPAEntityFactory.getEntityManager().createQuery(jpql);
        query.setParameter(1, new java.util.Date());
        LOGGER.info("数据更新结果:{}", query.executeUpdate()); // 更新处理
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
}

SQL 原生操作

JPA 执行原生 SQL

  • JPA 设计时首先要考虑的就是数据库的可移植性标准的设计,但是这样的设计就有可能会丧失掉一些数据库特有的个性化处理操作的支持,为了可以发挥出不同数据库的语法特点,在 JPA 的开发中开发者也可以利用原生 SQL 实现数据的 CRUD 操作,并且基于 JPA 的支持实现结果集与 PO 实例之间的转换
1、
package com.yootk.test;

import com.yootk.po.Course;
import com.yootk.util.JPAEntityFactory;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

public class TestNativeSQL {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestNativeSQL.class);

    @Test
    public void testSelectById() {
        String sql = "SELECT cid, cname, credit, num, start, end FROM course WHERE cid=?1";
        Query query = JPAEntityFactory.getEntityManager().createNativeQuery(sql); // 创建原生SQL
        query.setParameter(1, 8); // 设置占位符
        LOGGER.info("查询结果:{}", query.getSingleResult());
        JPAEntityFactory.close();
    }
    @Test
    public void testSelectSplit() {
        int currentPage = 1;
        int lineSize = 3;
        String keyword = "%开发实战%";
        String sql = "SELECT cid, cname, credit, num, start, end FROM course WHERE cname LIKE :kw";
        Query query = JPAEntityFactory.getEntityManager().createNativeQuery(sql); // 创建原生SQL
        query.setParameter("kw", keyword); // 设置占位符
        query.setFirstResult((currentPage - 1) * lineSize);
        query.setMaxResults(lineSize);
        List all = query.getResultList();
        for (Object course : all) {
            LOGGER.info("查询结果:{}", course);
        }
        JPAEntityFactory.close();
    }
    @Test
    public void testSelectCount() {
        String keyword = "%开发实战%";
        String sql = "SELECT COUNT(cid) FROM course WHERE cname LIKE :kw";
        Query query = JPAEntityFactory.getEntityManager().createNativeQuery(sql); // 创建原生SQL
        query.setParameter("kw", keyword); // 设置占位符
        LOGGER.info("数据统计结果:{}", query.getSingleResult());
        JPAEntityFactory.close();
    }
    @Test
    public void testDelete() {
        JPAEntityFactory.getEntityManager().getTransaction().begin();
        String sql = "DELETE FROM course WHERE cid=?1";
        Query query = JPAEntityFactory.getEntityManager().createNativeQuery(sql); // 创建原生SQL
        query.setParameter(1, 3); // 设置占位符
        LOGGER.info("数据删除结果:{}", query.executeUpdate());
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
    @Test
    public void testUpdate() {
        JPAEntityFactory.getEntityManager().getTransaction().begin();
        String sql = "UPDATE course SET cname=?1, credit=?2 WHERE cid=?3";
        Query query = JPAEntityFactory.getEntityManager().createNativeQuery(sql); // 创建原生SQL
        query.setParameter(1, "Netty开发实战");
        query.setParameter(2, 5);
        query.setParameter(3, 11); // 设置占位符
        LOGGER.info("数据修改结果:{}", query.executeUpdate());
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
}

Criteria 数据查询

Criteria 数据查询

  • JPQL 采用了字符串结构实现了数据操作的定义,这样的做法虽然简单直观,但是并不符合于面向对象的开发要求,所以在 JPA 中又提供了 Criteria 数据操作支持,该操作将要执行的数据操作命令进行了封装,开发者可以通过指定类的方法实现数据查询或更新所需的配置

CriteriaBuider 接口

  • 在 Criteria 处理结构中,开发者需要通过 CriteriaBuilder 接口实现所有查询对象的构建同时在该接口内部提供了的关系运算、逻辑运算以及统计运算的处理方法
1、
package com.yootk.test;

import com.yootk.po.Course;
import com.yootk.util.DateUtil;
import com.yootk.util.JPAEntityFactory;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.*;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class TestCriteria {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestCriteria.class);

    @Test
    public void testSelectAll() {
        // 创建Criteria查询构建器,利用此构建器可以创建查询以及更新的处理操作
        CriteriaBuilder builder = JPAEntityFactory.getEntityManager().getCriteriaBuilder();
        // 在创建查询的时候需要先明确的设置当前要匹配的PO类对象类型(FROM子句就是通过此出来生成的)
        CriteriaQuery<Course> criteriaQuery = builder.createQuery(Course.class);
        // 面向对象的开发过程里面,最为头疼的问题就是每一步的操作都需要通过明确的方法进行调用
        Root<Course> root = criteriaQuery.from(Course.class); // 创建查询
        TypedQuery<Course> query = JPAEntityFactory.getEntityManager()
                .createQuery(criteriaQuery); // 创建查询对象
        List<Course> all = query.getResultList();
        for (Course course : all) {
            LOGGER.info("数据查询:{}", course);
        }
        JPAEntityFactory.close();
    }
    @Test
    public void testSelectRoot() {
        // 创建Criteria查询构建器,利用此构建器可以创建查询以及更新的处理操作
        CriteriaBuilder builder = JPAEntityFactory.getEntityManager().getCriteriaBuilder();
        // 在创建查询的时候需要先明确的设置当前要匹配的PO类对象类型(FROM子句就是通过此出来生成的)
        CriteriaQuery<Course> criteriaQuery = builder.createQuery(Course.class);
        // 面向对象的开发过程里面,最为头疼的问题就是每一步的操作都需要通过明确的方法进行调用
        Root<Course> root = criteriaQuery.from(Course.class); // 创建查询
        LOGGER.info("CID路径:{}", root.get("cid"));
        LOGGER.info("PO类型:{}", root.getJavaType());
        JPAEntityFactory.close();
    }
    @Test
    public void testSelectSingleCondition() {
        // 创建Criteria查询构建器,利用此构建器可以创建查询以及更新的处理操作
        CriteriaBuilder builder = JPAEntityFactory.getEntityManager().getCriteriaBuilder();
        // 在创建查询的时候需要先明确的设置当前要匹配的PO类对象类型(FROM子句就是通过此出来生成的)
        CriteriaQuery<Course> criteriaQuery = builder.createQuery(Course.class);
        // 面向对象的开发过程里面,最为头疼的问题就是每一步的操作都需要通过明确的方法进行调用
        Root<Course> root = criteriaQuery.from(Course.class); // 创建查询
        Predicate predicate = builder.equal(root.get("credit"), 2); // 创建查询条件
        criteriaQuery.where(predicate); // 追加WHERE查询子句
        TypedQuery<Course> query = JPAEntityFactory.getEntityManager()
                .createQuery(criteriaQuery); // 创建查询对象
        List<Course> all = query.getResultList();
        for (Course course : all) {
            LOGGER.info("数据查询:{}", course);
        }
        JPAEntityFactory.close();
    }
    @Test
    public void testSelectMoreCondition() {
        // 创建Criteria查询构建器,利用此构建器可以创建查询以及更新的处理操作
        CriteriaBuilder builder = JPAEntityFactory.getEntityManager().getCriteriaBuilder();
        // 在创建查询的时候需要先明确的设置当前要匹配的PO类对象类型(FROM子句就是通过此出来生成的)
        CriteriaQuery<Course> criteriaQuery = builder.createQuery(Course.class);
        // 面向对象的开发过程里面,最为头疼的问题就是每一步的操作都需要通过明确的方法进行调用
        Root<Course> root = criteriaQuery.from(Course.class); // 创建查询
        List<Predicate> predicates = new ArrayList<>(); // 保存有多个查询条件
        predicates.add(
                builder.or(builder.gt(root.get("credit"), 1L),
                        builder.between(root.get("end"),
                                DateUtil.stringToDate("2007-10-10"),
                                DateUtil.stringToDate("2009-09-09")))); // 追加查询条件
        predicates.add(builder.like(root.get("cname"), "%开发%")); // 追加查询条件
        predicates.add(builder.gt(root.get("num"), 9));
        criteriaQuery.where(predicates.toArray(new Predicate[] {})); // 追加WHERE查询子句
        TypedQuery<Course> query = JPAEntityFactory.getEntityManager()
                .createQuery(criteriaQuery); // 创建查询对象
        List<Course> all = query.getResultList();
        for (Course course : all) {
            LOGGER.info("数据查询:{}", course);
        }
        JPAEntityFactory.close();
    }
    @Test
    public void testSelectINCondition() { // 重要的操作
        // 创建Criteria查询构建器,利用此构建器可以创建查询以及更新的处理操作
        CriteriaBuilder builder = JPAEntityFactory.getEntityManager().getCriteriaBuilder();
        // 在创建查询的时候需要先明确的设置当前要匹配的PO类对象类型(FROM子句就是通过此出来生成的)
        CriteriaQuery<Course> criteriaQuery = builder.createQuery(Course.class);
        // 面向对象的开发过程里面,最为头疼的问题就是每一步的操作都需要通过明确的方法进行调用
        Root<Course> root = criteriaQuery.from(Course.class); // 创建查询
        Set<Long> cids = Set.of(8L, 9L, 10L, 50L, 99L); // 要查询的ID数据
        // 如果此时使用的是JPQL的方式处理,则此时的查询需要通过循环的方式动态修改字符串
        Predicate predicate = root.get("cid").in(cids); // IN查询配置
        criteriaQuery.where(predicate); // 追加WHERE查询子句
        TypedQuery<Course> query = JPAEntityFactory.getEntityManager()
                .createQuery(criteriaQuery); // 创建查询对象
        List<Course> all = query.getResultList();
        for (Course course : all) {
            LOGGER.info("数据查询:{}", course);
        }
        JPAEntityFactory.close();
    }
    @Test
    public void testDelete() {
        JPAEntityFactory.getEntityManager().getTransaction().begin();
        CriteriaBuilder builder = JPAEntityFactory.getEntityManager().getCriteriaBuilder();
        CriteriaDelete<Course> delete = builder.createCriteriaDelete(Course.class); // 删除操作
        Set<Long> cids = Set.of(8L, 9L, 10L, 50L, 99L); // 要删除的ID数据
        Root<Course> root = delete.from(Course.class); // 创建根操作
        Predicate predicate = root.get("cid").in(cids); // 设置查询条件
        delete.where(predicate); // 设置WHERE子句
        Query query = JPAEntityFactory.getEntityManager().createQuery(delete);
        LOGGER.info("数据删除:{}", query.executeUpdate()); // 数据删除
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
}

JPA 一级缓存

一级缓存处理逻辑

  • 一级缓存指的是 EntityManager 接口上的缓存操作,用户在使用 find()方法查询时,如果在缓存中不存在有指定 ID 的数据,则会通过 JPA 发出数据查询操作,而第 2 次获取同一 ID 实例时,就可以通过缓存直接进行数据加载,这样就减少了一次查询操作所带来的性能损耗
1、
package com.yootk.test;

import com.yootk.po.Course;
import com.yootk.util.DateUtil;
import com.yootk.util.JPAEntityFactory;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.*;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class TestFirstLevelCache {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestFirstLevelCache.class);

    @Test
    public void testFindCache() { // 使用EntityManager接口提供的查询方法
        Course courseA = JPAEntityFactory.getEntityManager()
                .find(Course.class, 11L); // 数据查询
        LOGGER.info("第一次使用find()方法查询数据:{}", courseA);
        Course courseB = JPAEntityFactory.getEntityManager()
                .find(Course.class, 11L); // 数据查询
        LOGGER.info("第二次使用find()方法查询数据:{}", courseB);
        JPAEntityFactory.close();
    }
    @Test
    public void testFindUpdateCache() { // 使用EntityManager接口提供的查询方法
        Course courseA = JPAEntityFactory.getEntityManager()
                .find(Course.class, 11L); // 数据查询
        courseA.setCredit(-99); // 修改了当前的缓存数据
        LOGGER.info("第一次使用find()方法查询数据:{}", courseA);
        Course courseB = JPAEntityFactory.getEntityManager()
                .find(Course.class, 11L); // 数据查询
        LOGGER.info("第二次使用find()方法查询数据:{}", courseB);
        JPAEntityFactory.close();
    }
    @Test
    public void testFindRefreshCache() { // 使用EntityManager接口提供的查询方法
        Course courseA = JPAEntityFactory.getEntityManager()
                .find(Course.class, 11L); // 数据查询
        courseA.setCredit(-99); // 修改了当前的缓存数据
        LOGGER.info("第一次使用find()方法查询数据:{}", courseA);
        JPAEntityFactory.getEntityManager().refresh(courseA); // 强制性刷新操作
        Course courseB = JPAEntityFactory.getEntityManager()
                .find(Course.class, 11L); // 数据查询
        LOGGER.info("第二次使用find()方法查询数据:{}", courseB);
        JPAEntityFactory.close();
    }
}

JPA 对象状态

JPA 对象状态

  • 在 JPA 中提供的一级缓存处理机制,主要是为了实现 JPA 状态的维护处理,考虑到数据增加、修改、删除等一系列的操作方式,在 JPA 中一共提供了四种状态:
  • 1、瞬时态(New):新实例化的实体对象,此对象并未实现持久化存储(也可能还未分配 ID)没有与持久化上下文(Persistence context)建立任何的关联:
  • 2、持久态(Managed):数据库中存在有相关 ID 的数据,该对象保存在一级缓存之中,由于该对象已经与持久化上下文建立了联系,所以对象属性的修改可以直接影响到数据库中已有的数据项(需要进行事务提交)
  • 3、游离态(Datached):数据库中存在有相关 ID 数据,但是该对象未与持久化上下文建立联系 (EntityManager 实例已经关闭),此时对该对象所做的修改不会影响到数据库中的已有数据项:
  • 4、删除态(Removed):该对象与持久化上下文有关联,但其对应的数据库中的数据已经被删除。.

JPA 对象状态转换

  • JPA 提供的四种对象状态并不是固定的,开发者在使用时可以通过 EntityManager 接口或 EntityTransaction 接口提供的方法实现状态的转换处理
1、
    @Test
    public void testManaged() {
        Course course = JPAEntityFactory.getEntityManager().find(Course.class, 11L);
        JPAEntityFactory.getEntityManager().getTransaction().begin();
        course.setCname("Redis开发实战"); // 持久态属性修改
        course.setNum(100); // 持久态属性修改
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }

2、
    @Test
    public void testNew() {
        JPAEntityFactory.getEntityManager().getTransaction().begin(); // 开启JPA事务
        Course course = new Course(); // 瞬时态对象
        course.setCname("SpringCloud开发实战");
        course.setCredit(2);
        course.setNum(500);
        course.setStart(DateUtil.stringToDate("2008-06-26"));
        course.setEnd(DateUtil.stringToDate("2008-07-27"));
        JPAEntityFactory.getEntityManager().persist(course); // 持久态对象
        LOGGER.info("数据更新成功,当前所更新的数据ID为:{}", course.getCid()); // 日志输出
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        // 在没有关闭EntityManager的时候要重新根据已有的ID数据进行数据查询
        Course courseA = JPAEntityFactory.getEntityManager().find(Course.class, course.getCid());
        LOGGER.info("数据查询:{}", courseA);
        JPAEntityFactory.close();
    }

3、
    @Test
    public void testBatch() { // 数据批量增加
        JPAEntityFactory.getEntityManager().getTransaction().begin();
        for (int x = 0; x < 1000; x++) {
            Course course = new Course(); // 瞬时态对象
            course.setCname("李兴华公益编程直播课 - " + x); // 属性设置
            course.setCredit(2);
            course.setNum(500);
            course.setStart(DateUtil.stringToDate("2008-06-26"));
            course.setEnd(DateUtil.stringToDate("2008-07-27"));
            JPAEntityFactory.getEntityManager().persist(course); // 存储表示加入到一级缓存之中
            if (x % 10 == 0) {  // 每10条的数据进行一次缓存刷新
                JPAEntityFactory.getEntityManager().flush(); // 强制刷新
                JPAEntityFactory.getEntityManager().clear(); // 清空缓存
            }
        }
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }

JPA 二级缓存

二级缓存

  • 在常规的开发过程中,每一个用户的处理线程一般都只会包含有一个 EntityManager 接口对象实例,所以每一个线程的内部都会维持着一级缓存,然而不同线程之间的一级缓存数据是无发进行共享的,所以为了解决不同线程之间的数据共享问题,在 JPA 中又提供了二级缓存配置支持
  • 由于二级缓存可以被不同的线程所访问,那么此时就需要在应用中提供有一个完整的缓存数据空间,该数据空间可以是一块指定的内存,或者是一块磁盘空间,也可能是专属的分布式缓存服务器,为了简化模型,本次将使用最为常规的 EHCache 缓存组件来实现二级缓存的配置

缓存驱逐算法

  • 数据的缓存空间是有限的,所以为了可以及时清理掉那些不再使用的缓存数据项,考虑到数据清除方式的不同,在 EHCache 中提供有三种缓存清除策略,
    • 第一种:LRU(Least Recently Used、最近最少使用)策略。该策略会将使用次数较少的缓存项进行定期清除,从而只保留缓存热项。
    • 第二种:LFU(Least Frequently Used、最近最不用使用)策略。该策略会将最近一段时间内使用最少的缓存项进行定期清除。
    • 第三种:FIFO(First Input First Output、先进先出)策略。只要缓存空间不足时会将最早缓存的数据清除,该算法有可能会造成缓存热项被清除,同时还有可能产生缓存穿透问题,甚至可能造成应用程序崩溃。

缓存穿透

  • 使用缓存的目的是提高程序高并发的处理性能,毕竟通过数据库进行数据查询一定会带来严重的性能问题,而利用缓存的存储可以减少重复数据的查询,如果现在采用了 FIFO 设计,那么有可能早先的一个数据已经被多线程频繁访问,但是缓存项一旦删除后,所有的线程就会大规模的通过数据库查询,这样就会造成程序的崩溃
1、
    @Test
    public void testThreadQuery() {
        EntityManager managerA = JPAEntityFactory.getEntityManagerFactory().createEntityManager();
        LOGGER.info("【第一次数据查询】{}", managerA.find(Course.class, 11L));
        managerA.close();
        EntityManager managerB = JPAEntityFactory.getEntityManagerFactory().createEntityManager();
        LOGGER.info("【第二次数据查询】{}", managerB.find(Course.class, 11L));
        managerB.close();
    }

2、
implementation group: 'org.hibernate.orm', name: 'hibernate-jcache', version: '6.1.0.Final'
implementation group: 'org.hibernate.orm', name: 'hibernate-jcache', version: '6.4.4.Final'
implementation group: 'org.ehcache', name: 'ehcache', version: '3.10.0'
implementation group: 'org.ehcache', name: 'ehcache', version: '3.10.8'
implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1'
implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1'
implementation group: 'com.sun.xml.bind', name: 'jaxb-impl', version: '2.3.0'
implementation group: 'com.sun.xml.bind', name: 'jaxb-impl', version: '4.0.4'
implementation group: 'com.sun.xml.bind', name: 'jaxb-core', version: '4.0.4'
implementation group: 'com.sun.xml.bind', name: 'jaxb-core', version: '2.3.0'


3、
<!-- 定义JCache的工厂类,通过该类获取JSR-107实现类 -->
<property name="hibernate.cache.region.factory_class"
          value="org.hibernate.cache.jcache.internal.JCacheRegionFactory"/>
<!-- 在应用中启用二级缓存,即:EntityManagerFactory级缓存 -->
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<!-- EHCache组件提供的CachingProvider接口实现类,用于接入EHCache缓存 -->
<property name="hibernate.javax.cache.provider"
          value="org.ehcache.jsr107.EhcacheCachingProvider"/>


4、
<!-- 定义JCache的工厂类,通过该类获取JSR-107实现类 -->
<property name="hibernate.cache.region.factory_class"
          value="org.hibernate.cache.jcache.internal.JCacheRegionFactory"/>
<!-- 在应用中启用二级缓存,即:EntityManagerFactory级缓存 -->
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<!-- EHCache组件提供的CachingProvider接口实现类,用于接入EHCache缓存 -->
<property name="hibernate.javax.cache.provider"
          value="org.ehcache.jsr107.EhcacheCachingProvider"/>
@Entity // 表示当前的类是一个实体类
@Table(name = "course") // 如果此时的类名称与表名称一致,该注解可以不定义的
@Cacheable // 该类的对象可以实现二级缓存
public class Course {}


5、
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>

6、
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <diskStore path="java.io.tmpdir/yootk"/>		<!-- 设置临时缓存路径 -->
    <defaultCache
        maxElementsInMemory="10000"
        eternal="true"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        maxElementsOnDisk="10000000"
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU"/>
</ehcache>


7、
    @Test
    public void testQueryCache() {
        String jpql = "SELECT c FROM Course AS c WHERE c.cid=:cid";
        EntityManager managerA = JPAEntityFactory.getEntityManagerFactory().createEntityManager();
        Query queryA = managerA.createQuery(jpql);
        queryA.setParameter("cid", 12L);
        LOGGER.info("【第一次数据查询】{}", queryA.getSingleResult());
        managerA.close();
        EntityManager managerB = JPAEntityFactory.getEntityManagerFactory().createEntityManager();
        Query queryB = managerB.createQuery(jpql);
        queryB.setParameter("cid", 12L);
        LOGGER.info("【第二次数据查询】{}", queryB.getSingleResult());
        managerB.close();
    }

JPA 查询缓存

是二级缓存的一种拓展机制

1、
@Test
public void testQueryCache() {
    String jpql = "SELECT c FROM Course AS c WHERE c.cid=:cid";
    EntityManager managerA = JPAEntityFactory.getEntityManagerFactory().createEntityManager();
    Query queryA = managerA.createQuery(jpql);
    queryA.setParameter("cid", 12L);
    LOGGER.info("【第一次数据查询】{}", queryA.getSingleResult());
    managerA.close();
    EntityManager managerB = JPAEntityFactory.getEntityManagerFactory().createEntityManager();
    Query queryB = managerB.createQuery(jpql);
    queryB.setParameter("cid", 12L);
    LOGGER.info("【第二次数据查询】{}", queryB.getSingleResult());
    managerB.close();
}


2、
<property name="hibernate.cache.use_query_cache" value="true" />

3、
public class Course implements Serializable { }

4、
    @Test
    public void testQueryCache() {
        String jpql = "SELECT c FROM Course AS c WHERE c.cid=:cid";
        EntityManager managerA = JPAEntityFactory.getEntityManagerFactory().createEntityManager();
        Query queryA = managerA.createQuery(jpql); // 获取Query接口实例
        queryA.setHint(AvailableHints.HINT_CACHEABLE, true); // 缓存配置
        queryA.setParameter("cid", 12L);
        LOGGER.info("【第一次数据查询】{}", queryA.getSingleResult());
        managerA.close();
        EntityManager managerB = JPAEntityFactory.getEntityManagerFactory().createEntityManager();
        Query queryB = managerB.createQuery(jpql); // 获取Query接口实例
        queryB.setHint(AvailableHints.HINT_CACHEABLE, true); // 缓存配置
        queryB.setParameter("cid", 12L);
        LOGGER.info("【第二次数据查询】{}", queryB.getSingleResult());
        managerB.close();
    }

CacheMode

CacheMode 缓存模式

  • 在默认情况下,程序中只要启用了二级缓存,不同的用户线程进行数据查询时都可以实现缓存数据的读写处理。但是在一些特殊环境下,有可能某些线程只需要设置缓存数据而某些线程只需要读取缓存数据,甚至不需要进行缓存操作,为了解决这一设计问题在 JPA 中提供了五种缓存操作类型

CacheMode 缓存类型

  • 在进行缓存模式设置时,开发者可以通过 Query 接口提供的 setHint()方法进行完成,在 AvailableHints 接口中提供了一个 HINT CACHE MODE(对应字符串为 org.hibernate.cacheMode"")的常量,而具体的缓存分类,是由 CacheMode 枚举类定义的,该类分别为五种缓存类型,定义了五个常量

设置 JPA 缓存类型

  • 在 AvailableHints 接口中提供了一个 HINT_CACHE_MODE(对应字符串为 org.hibernate.cacheMode"")的常量,而具体的缓存分类,是由 CacheMode 枚举类定义的,该类分别为五种缓存类型,定义了五个常量

1、

    @Test
    public void testQueryCache() {
        String jpql = "SELECT c FROM Course AS c WHERE c.cid=:cid";
        EntityManager managerA = JPAEntityFactory.getEntityManagerFactory().createEntityManager();
        Query queryA = managerA.createQuery(jpql); // 获取Query接口实例
        queryA.setHint(AvailableHints.HINT_CACHE_MODE, CacheMode.PUT); // 缓存数据的写入
        queryA.setHint(AvailableHints.HINT_CACHEABLE, true); // 缓存配置
        queryA.setParameter("cid", 12L);
        LOGGER.info("【第一次数据查询】{}", queryA.getSingleResult());
        managerA.close();
        EntityManager managerB = JPAEntityFactory.getEntityManagerFactory().createEntityManager();
        Query queryB = managerB.createQuery(jpql); // 获取Query接口实例
        queryB.setHint(AvailableHints.HINT_CACHE_MODE, CacheMode.REFRESH); // 缓存数据的写入
        queryB.setHint(AvailableHints.HINT_CACHEABLE, true); // 缓存配置
        queryB.setParameter("cid", 12L);
        LOGGER.info("【第二次数据查询】{}", queryB.getSingleResult());
        managerB.close();
    }
2、
    @Test
    public void testQueryCache() {
        String jpql = "SELECT c FROM Course AS c WHERE c.cid=:cid";
        EntityManager managerA = JPAEntityFactory.getEntityManagerFactory().createEntityManager();
        Query queryA = managerA.createQuery(jpql); // 获取Query接口实例
        queryA.setHint(AvailableHints.HINT_CACHE_MODE, CacheMode.IGNORE); // 缓存数据的写入
        queryA.setHint(AvailableHints.HINT_CACHEABLE, true); // 缓存配置
        queryA.setParameter("cid", 12L);
        LOGGER.info("【第一次数据查询】{}", queryA.getSingleResult());
        managerA.close();
        EntityManager managerB = JPAEntityFactory.getEntityManagerFactory().createEntityManager();
        Query queryB = managerB.createQuery(jpql); // 获取Query接口实例
        queryB.setHint(AvailableHints.HINT_CACHE_MODE, CacheMode.GET); // 缓存数据的写入
        queryB.setHint(AvailableHints.HINT_CACHEABLE, true); // 缓存配置
        queryB.setParameter("cid", 12L);
        LOGGER.info("【第二次数据查询】{}", queryB.getSingleResult());
        managerB.close();
    }

JPA 数据锁

多线程更新同一数据

  • Java 是一门多线程编程语言,这样在进行项目开发时,有可能会有同一条数据被多个不同的线程同时读取,并且这些线程都有可能会执行该条数据的更新操作,此时如果没有进行合理的控制,那么就会出现数据更新覆盖的问题,从而造成业务数据的更新错误。

JPA 锁实现机制

  • 如果要想解决多线程更新的数据同步问题,就需要使用 JPA 的锁机制,考虑到不同的业务需要以及性能要求,JPA 提供了悲观锁(PessimisticLock)与乐观锁(OptimisticLock)两种处理机制,这两种锁的使用都通过 EntityManager 接口提供的方法完成

JPA 悲观锁

JPA 悲观锁

  • 悲观锁认为所有的数据操作一定都会存在并发更新的问题,所以只要是在业务中进行数会自动采用锁机制对当前数据行进行锁定,这样其他的事务就无法进行该行据获取时,数据的更新处理

悲观锁分类

  • 悲观锁分为读锁和写锁,这样在进行悲观锁定义时,需要通过 LockModeType 枚举类定义的常量进行配置,这些常量如下所示:
  • PESSIMISTIC_READ:只要事务读实体,实体管理器就锁定实体,直到事务完成(提交或回滚)后锁才会解锁处理,这种锁模式不会阻碍其它事务读取数据;只要事务更新实体,实体管理器就会锁定实体,这种锁模式强制尝试修改
  • PESSIMISTIC_WRITE:当多个并发更新事务出现更新失败几率较高时使用这种锁模式;实体数据的事务串行化,
  • PESSIMISTIC_FORCE_INCREMENT:当事务读实体时,实体管理器就锁定实体,当事务结束时会增加实体的版本属性,即使实体没有修改;
1、
package com.yootk.test;

import com.yootk.po.Course;
import com.yootk.util.DateUtil;
import com.yootk.util.JPAEntityFactory;
import jakarta.persistence.LockModeType;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

public class TestPessmisticLock { // 悲观锁的测试
    private static final Logger LOGGER = LoggerFactory.getLogger(TestPessmisticLock.class);
    public static final Long CID = 12L; // 待更新数据编号
    @Test
    public void testLock() throws Exception {
        startLockThread(); // 此时进行数据更新线程的启用
        TimeUnit.MILLISECONDS.sleep(10);
        startWriteThread(); // 此时进行数据更新线程的启用
        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
    }

    // 如果没有环境去发现并发访问问题,那么就自己编写多线程的操作来进行控制
    public void startLockThread() { // 开启锁定的处理线程
        // 【坑内】有一个线程已经提前入坑了,并且已经通过悲观锁锁定了所需要更新的资源
        Thread thread = new Thread(() -> {
            // 如果现在要想关注悲观锁的操作,那么一定要启动好一个完整的事务
            JPAEntityFactory.getEntityManager().getTransaction().begin();
            Course course = JPAEntityFactory.getEntityManager().find(Course.class, CID,
                    LockModeType.PESSIMISTIC_WRITE); // 查询数据,并使用悲观写锁
            LOGGER.info("【{}】课程名称:{}、课程学分:{}", Thread.currentThread().getName(),
                    course.getCname(), course.getCredit()); // 日志记录
            course.setCname("Spring就业编程实战");
            course.setCredit(1);
            try {
                TimeUnit.SECONDS.sleep(10); // 强制性的休眠10秒
            } catch (InterruptedException e) {}
            LOGGER.info("【{}】休眠完成,进行事务更新的回滚操作...", Thread.currentThread().getName());
            // 对于悲观锁来讲,只有在提交事务或者回滚事务之后才可以进行数据的行解锁(其他的线程才可以继续访问)
            JPAEntityFactory.getEntityManager().getTransaction().rollback(); // 此时回滚操作
            JPAEntityFactory.close();
        }, "ReadThread - 读取线程");
        thread.start(); // 启动线程
    }

    public void startWriteThread() { // 开启更新的处理线程
        // 【坑外】有其他线程来晚了,等待第一个坑内的线程出坑,才好进入继续执行
        Thread thread = new Thread(()->{
            JPAEntityFactory.getEntityManager().getTransaction().begin();
            // 此时的更新操作并不是采用JPQL的方式完成的,而直接利用了持久化状态的方式进行了PO的更新
            Course course = JPAEntityFactory.getEntityManager().find(Course.class, CID,
                    LockModeType.PESSIMISTIC_WRITE); // 查询数据,并使用悲观写锁
            course.setCname("Dubbo就业编程实战");
            course.setCredit(9);
            JPAEntityFactory.getEntityManager().getTransaction().commit(); // 事务提交
            JPAEntityFactory.close();
        }, "WriteThread - 更新线程");
        thread.start(); // 启动线程
    }
}


2、
package com.yootk.test;

import com.yootk.po.Course;
import com.yootk.util.DateUtil;
import com.yootk.util.JPAEntityFactory;
import jakarta.persistence.LockModeType;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

public class TestPessmisticLock { // 悲观锁的测试
    private static final Logger LOGGER = LoggerFactory.getLogger(TestPessmisticLock.class);
    public static final Long CID = 12L; // 待更新数据编号
    @Test
    public void testLock() throws Exception {
        startLockThread(); // 此时进行数据更新线程的启用
        TimeUnit.MILLISECONDS.sleep(10);
        startReadThread(); // 此时进行数据更新线程的启用
        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
    }

    // 如果没有环境去发现并发访问问题,那么就自己编写多线程的操作来进行控制
    public void startLockThread() { // 开启锁定的处理线程
        // 【坑内】有一个线程已经提前入坑了,并且已经通过悲观锁锁定了所需要更新的资源
        Thread thread = new Thread(() -> {
            // 如果现在要想关注悲观锁的操作,那么一定要启动好一个完整的事务
            JPAEntityFactory.getEntityManager().getTransaction().begin();
            Course course = JPAEntityFactory.getEntityManager().find(Course.class, CID,
                    LockModeType.PESSIMISTIC_WRITE); // 查询数据,并使用悲观写锁
            LOGGER.info("【{}】课程名称:{}、课程学分:{}", Thread.currentThread().getName(),
                    course.getCname(), course.getCredit()); // 日志记录
            course.setCname("Spring就业编程实战");
            course.setCredit(1);
            try {
                TimeUnit.SECONDS.sleep(10); // 强制性的休眠10秒
            } catch (InterruptedException e) {}
            LOGGER.info("【{}】休眠完成,进行事务更新的回滚操作...", Thread.currentThread().getName());
            // 对于悲观锁来讲,只有在提交事务或者回滚事务之后才可以进行数据的行解锁(其他的线程才可以继续访问)
            JPAEntityFactory.getEntityManager().getTransaction().rollback(); // 此时回滚操作
            JPAEntityFactory.close();
        }, "LockThread - 读取线程");
        thread.start(); // 启动线程
    }

    public void startReadThread() { // 开启更新的处理线程
        // 【坑外】有其他线程来晚了,等待第一个坑内的线程出坑,才好进入继续执行
        Thread thread = new Thread(()->{
            JPAEntityFactory.getEntityManager().getTransaction().begin();
            // 此时的更新操作并不是采用JPQL的方式完成的,而直接利用了持久化状态的方式进行了PO的更新
            Course course = JPAEntityFactory.getEntityManager().find(Course.class, CID,
                    LockModeType.PESSIMISTIC_READ); // 查询数据,并使用悲观写锁
            LOGGER.info("【{}】课程名称:{}、课程学分:{}", Thread.currentThread().getName(),
                    course.getCname(), course.getCredit()); // 日志记录
            JPAEntityFactory.getEntityManager().getTransaction().rollback(); // 事务提交
            JPAEntityFactory.close();
        }, "ReadThread - 读取线程");
        thread.start(); // 启动线程
    }
    public void startWriteThread() { // 开启更新的处理线程
        // 【坑外】有其他线程来晚了,等待第一个坑内的线程出坑,才好进入继续执行
        Thread thread = new Thread(()->{
            JPAEntityFactory.getEntityManager().getTransaction().begin();
            // 此时的更新操作并不是采用JPQL的方式完成的,而直接利用了持久化状态的方式进行了PO的更新
            Course course = JPAEntityFactory.getEntityManager().find(Course.class, CID,
                    LockModeType.PESSIMISTIC_WRITE); // 查询数据,并使用悲观写锁
            course.setCname("Dubbo就业编程实战");
            course.setCredit(9);
            JPAEntityFactory.getEntityManager().getTransaction().commit(); // 事务提交
            JPAEntityFactory.close();
        }, "WriteThread - 更新线程");
        thread.start(); // 启动线程
    }
}


3、

package com.yootk.test;

import com.yootk.po.Course;
import com.yootk.util.DateUtil;
import com.yootk.util.JPAEntityFactory;
import jakarta.persistence.LockModeType;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

public class TestPessmisticLock { // 悲观锁的测试
    private static final Logger LOGGER = LoggerFactory.getLogger(TestPessmisticLock.class);
    public static final Long CID = 12L; // 待更新数据编号
    @Test
    public void testLock() throws Exception {
        startLockThread(); // 此时进行数据更新线程的启用
        TimeUnit.MILLISECONDS.sleep(10);
        startReadThread(); // 此时进行数据更新线程的启用
        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
    }

    // 如果没有环境去发现并发访问问题,那么就自己编写多线程的操作来进行控制
    public void startLockThread() { // 开启锁定的处理线程
        // 【坑内】有一个线程已经提前入坑了,并且已经通过悲观锁锁定了所需要更新的资源
        Thread thread = new Thread(() -> {
            // 如果现在要想关注悲观锁的操作,那么一定要启动好一个完整的事务
            JPAEntityFactory.getEntityManager().getTransaction().begin();
            Course course = JPAEntityFactory.getEntityManager().find(Course.class, CID,
                    LockModeType.PESSIMISTIC_READ); // 查询数据,并使用悲观写锁
            LOGGER.info("【{}】课程名称:{}、课程学分:{}", Thread.currentThread().getName(),
                    course.getCname(), course.getCredit()); // 日志记录
            course.setCname("Spring就业编程实战");
            course.setCredit(1);
            try {
                TimeUnit.SECONDS.sleep(10); // 强制性的休眠10秒
            } catch (InterruptedException e) {}
            LOGGER.info("【{}】休眠完成,进行事务更新的回滚操作...", Thread.currentThread().getName());
            // 对于悲观锁来讲,只有在提交事务或者回滚事务之后才可以进行数据的行解锁(其他的线程才可以继续访问)
            JPAEntityFactory.getEntityManager().getTransaction().rollback(); // 此时回滚操作
            JPAEntityFactory.close();
        }, "LockThread - 读取线程");
        thread.start(); // 启动线程
    }

    public void startReadThread() { // 开启更新的处理线程
        // 【坑外】有其他线程来晚了,等待第一个坑内的线程出坑,才好进入继续执行
        Thread thread = new Thread(()->{
            JPAEntityFactory.getEntityManager().getTransaction().begin();
            // 此时的更新操作并不是采用JPQL的方式完成的,而直接利用了持久化状态的方式进行了PO的更新
            Course course = JPAEntityFactory.getEntityManager().find(Course.class, CID,
                    LockModeType.PESSIMISTIC_READ); // 查询数据,并使用悲观写锁
            LOGGER.info("【{}】课程名称:{}、课程学分:{}", Thread.currentThread().getName(),
                    course.getCname(), course.getCredit()); // 日志记录
            JPAEntityFactory.getEntityManager().getTransaction().rollback(); // 事务提交
            JPAEntityFactory.close();
        }, "ReadThread - 读取线程");
        thread.start(); // 启动线程
    }
    public void startWriteThread() { // 开启更新的处理线程
        // 【坑外】有其他线程来晚了,等待第一个坑内的线程出坑,才好进入继续执行
        Thread thread = new Thread(()->{
            JPAEntityFactory.getEntityManager().getTransaction().begin();
            // 此时的更新操作并不是采用JPQL的方式完成的,而直接利用了持久化状态的方式进行了PO的更新
            Course course = JPAEntityFactory.getEntityManager().find(Course.class, CID,
                    LockModeType.PESSIMISTIC_WRITE); // 查询数据,并使用悲观写锁
            course.setCname("Dubbo就业编程实战");
            course.setCredit(9);
            JPAEntityFactory.getEntityManager().getTransaction().commit(); // 事务提交
            JPAEntityFactory.close();
        }, "WriteThread - 更新线程");
        thread.start(); // 启动线程
    }
}

JPA 乐观锁

乐观锁

  • 乐观锁的实现是基于程序逻辑处理的,在 JPA 中如果要想启动乐观锁的支持,则需要基于 LockModeType 枚举类中定义的枚举项进行配置,JPA 包括有如下几个乐观锁模式定义:
  • OPTIMISTIC:乐观读锁,与“READ”枚举项作用相同;
  • OPTIMISTIC_FORCE_INCREMENT:乐观写锁,在每次更新时会同时进行版本号的更新,与“WRITE”枚举项的作用相同。

乐观锁流程

  • 在使用乐观锁时需要在指定的数据表中追加有一个版本编号(类型为整型),每一次数据读取时,除了会读取到核心的数据之外,还会将当前的版本号读取过来。在更新数据时,需要传递当 ID 和版本号都匹配的时候才可以更新,如果发现此时不匹配要更新数据的 ID 以及版本号,:则表示该数据已经被其他线程更新过了,则当前线程的数据无法更新
    • 多个线程同时读取同一数据
    • 一个线程依据正确版本号更新
    • 其他版本号不匹配更新失败
1、
ALTER TABLE course ADD vseq INT DEFAULT 1;

2、
@Version
private Integer vseq; // 版本号


3、

    @Test
    public void testBaseUpdate() throws Exception {
        JPAEntityFactory.getEntityManager().getTransaction().begin();
        Course course = JPAEntityFactory.getEntityManager().find(Course.class, CID,
                LockModeType.OPTIMISTIC_FORCE_INCREMENT); // 查询数据,并使用乐观锁
        LOGGER.info("【{}】课程名称:{}、课程学分:{}", Thread.currentThread().getName(),
                course.getCname(), course.getCredit()); // 日志记录
        course.setCname("Spring就业编程实战");
        course.setCredit(1);
        JPAEntityFactory.getEntityManager().getTransaction().commit(); // 此时回滚操作
        JPAEntityFactory.close();
    }
4、
package com.yootk.test;

import com.yootk.po.Course;
import com.yootk.util.JPAEntityFactory;
import jakarta.persistence.LockModeType;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

public class TestOptimisticLock { // 悲观锁的测试
    private static final Logger LOGGER = LoggerFactory.getLogger(TestOptimisticLock.class);
    public static final Long CID = 12L; // 待更新数据编号
    @Test
    public void testBaseUpdate() throws Exception {
        startWriteThreadA();
        TimeUnit.MILLISECONDS.sleep(10);
        startWriteThreadB();
        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
    }

    public void startWriteThreadA() { // 开启更新的处理线程
        // 【坑外】有其他线程来晚了,等待第一个坑内的线程出坑,才好进入继续执行
        Thread thread = new Thread(()->{
            JPAEntityFactory.getEntityManager().getTransaction().begin();
            Course course = JPAEntityFactory.getEntityManager().find(Course.class, CID,
                    LockModeType.OPTIMISTIC_FORCE_INCREMENT); // 查询数据,并使用乐观锁
            LOGGER.info("【{}】课程名称:{}、课程学分:{}", Thread.currentThread().getName(),
                    course.getCname(), course.getCredit()); // 日志记录
            course.setCname("SpringBoot就业编程实战");
            course.setCredit(1);
            try {
                TimeUnit.SECONDS.sleep(5); // 追加延迟
            } catch (InterruptedException e) {}
            JPAEntityFactory.getEntityManager().getTransaction().commit(); // 此时回滚操作
            JPAEntityFactory.close();
        }, "WriteThread - A - 更新线程");
        thread.start(); // 启动线程
    }
    public void startWriteThreadB() { // 开启更新的处理线程
        // 【坑外】有其他线程来晚了,等待第一个坑内的线程出坑,才好进入继续执行
        Thread thread = new Thread(()->{
            JPAEntityFactory.getEntityManager().getTransaction().begin();
            Course course = JPAEntityFactory.getEntityManager().find(Course.class, CID,
                    LockModeType.OPTIMISTIC_FORCE_INCREMENT); // 查询数据,并使用乐观锁
            LOGGER.info("【{}】课程名称:{}、课程学分:{}", Thread.currentThread().getName(),
                    course.getCname(), course.getCredit()); // 日志记录
            course.setCname("SpringCloud就业编程实战");
            course.setCredit(1);
            JPAEntityFactory.getEntityManager().getTransaction().commit(); // 此时回滚操作
            JPAEntityFactory.close();
        }, "WriteThread - B - 更新线程");
        thread.start(); // 启动线程
    }
}

一对一数据关联

一对一表关联

  • 一对一数据关联是数据表垂直拆分的一种技术实现手段,当某张数据表过多时,就可以考虑根据其数据的作用将其拆分为不同的表进行存储,这样即可以保证数据的存储性能又可以提高数据的安全性。例如:在用户登录系统设计的结构中,可以创建一张 Login 登录信息表,该表中只保存有用户登录 ID 和密码,而用户的详细信息定义可以保存在 Details 详情表中

一对一关联设计

  • 在 JPA 中如果要想进行一对一关联的结构实现,那么首先就需要进行两个实体类之间的关联定义此时需要提供有 Login 与 Details 两个类,而后 Login.details 为 Details 类实例,Details.login 为 Login 类实例,而后还需要利用“@OneToOne”注解进行一对一关联的标记,在该注解中开发者可以根据需要定义级联数据抓取模式(数据查询时使用)以及关联结构的级联配置(数据更新时使用),为便于读者理解下面将通过具体的步骤实现该关联结构的开发,
1、
DROP DATABASE IF EXISTS yootk;
CREATE DATABASE yootk CHARACTER SET UTF8;
USE yootk;
CREATE TABLE login (
   uid    	VARCHAR(50)    		comment '用户ID',
   password    	VARCHAR(32) 		comment '登录密码',
   CONSTRAINT pk_uid1 PRIMARY KEY(uid)
) engine=innodb;
CREATE TABLE details (
    uid 		VARCHAR(50) 		comment '用户id',
    name 		VARCHAR(50) 		comment '用户姓名',
    age 		INT 			comment '用户年龄',
    CONSTRAINT pk_uid2 PRIMARY KEY(uid),
    CONSTRAINT fk_uid FOREIGN KEY(uid) REFERENCES login(uid) ON DELETE CASCADE
) engine=innodb;
INSERT INTO login(uid, password) VALUES ('muyan', 'hello');
INSERT INTO login(uid, password) VALUES ('yootk', 'hello');
INSERT INTO details(uid, name, age) VALUES ('muyan', '沐言', 18);
INSERT INTO details(uid, name, age) VALUES ('yootk', '优拓', 19);


2、
package com.yootk.po;

import jakarta.persistence.*;

import java.io.Serializable;

@Entity
public class Login implements Serializable {
    @Id
    private String uid; // 此时的ID手工设置
    private String password;
    @OneToOne( // 定义一对一关联结构
            mappedBy = "login", // 映射Details类中的成员属性
            cascade = CascadeType.ALL, // 全部进行数据的级联操作
            fetch = FetchType.EAGER) // 数据同步抓取
    private Details details; // 一对一关联
    public String getUid() {
        return uid;
    }
    public void setUid(String uid) {
        this.uid = uid;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public Details getDetails() {
        return details;
    }
    public void setDetails(Details details) {
        this.details = details;
    }
    @Override
    public String toString() {
        return "【用户登录】用户ID:" + this.uid + "、密码:" + this.password;
    }
}


3、
package com.yootk.po;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;

import java.io.Serializable;

@Entity
public class Details implements Serializable {
    @Id
    private String uid; // 此时的ID手工设置
    private String name;
    private Integer age;
    @OneToOne // 一对一
    @JoinColumn( // 进行关联字段的配置
            name = "uid", // 与Login类之间的关联成员属性配置
            referencedColumnName = "uid", // 与login数据表之间关联字段的配置
            unique = true) // 数据唯一
    private Login login; // 一对一关联
    public String getUid() {
        return uid;
    }
    public void setUid(String uid) {
        this.uid = uid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public Login getLogin() {
        return login;
    }
    public void setLogin(Login login) {
        this.login = login;
    }
    @Override
    public String toString() {
        return "【用户详情】姓名:" + this.name + "、年龄:" + this.age;
    }
}


4、
        <class>com.yootk.po.Login</class>            <!-- 实体类 -->
        <class>com.yootk.po.Details</class>            <!-- 实体类 -->

5、
package com.yootk.test;

import com.yootk.po.Details;
import com.yootk.po.Login;
import com.yootk.util.JPAEntityFactory;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestOneToOne {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestOneToOne.class);
    @Test
    public void testAdd() {
        JPAEntityFactory.getEntityManager().getTransaction().begin(); // 开启JPA事务
        Login login = new Login(); // 用户登录表
        login.setUid("lee");
        login.setPassword("hello");
        Details details = new Details(); // 用户详情表
        details.setName("李兴华");
        details.setUid("lee"); // 与Login.uid成员属性一致
        details.setAge(18);
        // 本次的操作是同时实现了login与details表数据的设置,实际的开发中可能只会设置login数据
        login.setDetails(details); // 设置用户登录与用户详情之间的关联
        details.setLogin(login); // 设置用户详情与用户登录之间的关联
        JPAEntityFactory.getEntityManager().persist(login); // 只存储login实例
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
}


6、
    @Test
    public void testFind() {
        Login login = JPAEntityFactory.getEntityManager().find(Login.class, "muyan"); // 数据查询
    }

7、
@OneToOne( // 定义一对一关联结构
        mappedBy = "login", // 映射Details类中的成员属性
        cascade = CascadeType.ALL, // 全部进行数据的级联操作
        fetch = FetchType.LAZY) // 数据同步抓取
private Details details; // 一对一关联
@OneToOne(fetch = FetchType.LAZY) // 一对一
@JoinColumn( // 进行关联字段的配置
        name = "uid", // 与Login类之间的关联成员属性配置
        referencedColumnName = "uid", // 与login数据表之间关联字段的配置
        unique = true) // 数据唯一
private Login login; // 一对一关联


8、
    @Test
    public void testFind() throws Exception {
        Login login = JPAEntityFactory.getEntityManager().find(Login.class, "muyan"); // 数据查询
        LOGGER.info("第一次查询结果:{}", login);
//        TimeUnit.SECONDS.sleep(5);
        LOGGER.info("第二次查询结果:{}", login.getDetails()); // 加载数据
    }

一对多数据关联

一对多数据关联

  • 为了更有效的进行数据管理,往往会依据不同的类型进行数据的归类,例如:一个人事管理系一个部门会有多个雇员,而雇员的分类就可以依靠部门编号实现。该结构就属于一对多统中的数据关联,而这一关联结构也是在实际的数据库设计与开发中最为常见的一种。

实体类一对多关联

  • JPA 中的数据关联需要实体类定义的支持,对于“一"方直接采用引用关联即可,而多”方则需要通过集合的形式进行配置。以下图为例,依据表的定义设置了 Dept 与 Emp 两个实体类,在 Emp 实体类中提供了 Dept 的引用关联,而在 Dept 实体类中利用了“List<Emp>”集合实现了多个雇员信息的存储。

“1+N”次查询

  • 在当前的 Dept 实体类中并没有配置抓取策略,而根据源代码的观察可以发现,@OneToMany 中默认的抓取模式定义为“FetchType fetch() default FetchType.LAZy"所以采用的是延迟加载模式,而如果此时将数据抓取模式修改为 EARGE 则会同时查询出部门和雇员数据,
1、
DROP DATABASE IF EXISTS yootk;
CREATE DATABASE yootk CHARACTER SET UTF8;
USE yootk;
CREATE TABLE dept (
   deptno  	BIGINT 		comment '部门编号',
   dname   	VARCHAR(50) 	comment '部门名称',
   loc 		VARCHAR(50) 	comment '部门位置',
   CONSTRAINT pk_deptno PRIMARY KEY(deptno)
)engine=innodb;
CREATE TABLE emp (
    empno 	BIGINT 		comment '雇员编号',
    ename 	VARCHAR(50) 	comment '雇员姓名',
    sal 		DOUBLE 		comment '基本工资',
    deptno 	BIGINT 		comment '部门编号',
    CONSTRAINT pk_empno PRIMARY KEY(empno),
    CONSTRAINT fk_deptno FOREIGN KEY(deptno) REFERENCES dept(deptno) ON DELETE CASCADE
)engine=innodb;
INSERT INTO dept(deptno, dname, loc) VALUES (10, '教学部', '北京');
INSERT INTO dept(deptno, dname, loc) VALUES (20, '市场部', '上海');
INSERT INTO emp(empno, ename, sal, deptno) VALUES (7369, '沐言-10-1', 800.0, 10);
INSERT INTO emp(empno, ename, sal, deptno) VALUES (7379, '沐言-10-2', 850.0, 10);
INSERT INTO emp(empno, ename, sal, deptno) VALUES (7389, '沐言-10-3', 900.0, 10);
INSERT INTO emp(empno, ename, sal, deptno) VALUES (7566, '沐言-20-1', 950.0, 20);
INSERT INTO emp(empno, ename, sal, deptno) VALUES (7569, '沐言-20-2', 980.0, 20);


2、
package com.yootk.po;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;

import java.util.List;

@Entity
public class Dept {
    @Id
    private Long deptno; // 自己设置部门编号
    private String dname;
    private String loc;
    @OneToMany( // 部门作为“一”方,所以要编写一对多的注解
            mappedBy = "dept", // 雇员的成员属性包含有dept的内容
            cascade = CascadeType.ALL) // 更新和查询全部级联
    private List<Emp> emps; // 一个部门有多个雇员
    public Dept() {}
    public Dept(Long deptno, String dname, String loc) {
        this.deptno = deptno;
        this.dname = dname;
        this.loc = loc;
    }
    public Long getDeptno() {
        return deptno;
    }
    public void setDeptno(Long deptno) {
        this.deptno = deptno;
    }
    public String getDname() {
        return dname;
    }
    public void setDname(String dname) {
        this.dname = dname;
    }
    public String getLoc() {
        return loc;
    }
    public void setLoc(String loc) {
        this.loc = loc;
    }
    public List<Emp> getEmps() {
        return emps;
    }
    public void setEmps(List<Emp> emps) {
        this.emps = emps;
    }
}


3、
package com.yootk.po;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;

@Entity
public class Emp {
    @Id
    private Long empno;
    private String ename;
    private Double sal;
    @ManyToOne // 多个雇员属于一个部门
    // 定义关联字段,如果现在属性和字段一致,写一个就可以了
    @JoinColumn(name = "deptno")
    private Dept dept; // 一位雇员属于一个部门
    public Emp() {}
    public Emp(Long empno, String ename, Double sal, Dept dept) {
        this.empno = empno;
        this.ename = ename;
        this.sal = sal;
        this.dept = dept;
    }
    public Long getEmpno() {
        return empno;
    }
    public void setEmpno(Long empno) {
        this.empno = empno;
    }
    public String getEname() {
        return ename;
    }
    public void setEname(String ename) {
        this.ename = ename;
    }
    public Double getSal() {
        return sal;
    }
    public void setSal(Double sal) {
        this.sal = sal;
    }
    public Dept getDept() {
        return dept;
    }
    public void setDept(Dept dept) {
        this.dept = dept;
    }
}


4、
        <class>com.yootk.po.Dept</class>            <!-- 实体类 -->
        <class>com.yootk.po.Emp</class>            <!-- 实体类 -->

5、
package com.yootk.test;

import com.yootk.po.Dept;
import com.yootk.po.Emp;
import com.yootk.util.JPAEntityFactory;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

public class TestOneToMany {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestOneToMany.class);
    @Test
    public void testAdd() {
        JPAEntityFactory.getEntityManager().getTransaction().begin(); // 开启JPA事务
        Dept dept = new Dept(55L, "开发部", "洛阳"); // 创建部门对象实例
        dept.setEmps(List.of(
                new Emp(7839L, "李兴华", 910.0, dept),
                new Emp(7859L, "李兴华", 840.0, dept),
                new Emp(7879L, "李兴华", 760.0, dept))); // 部门和雇员之间的关系
        JPAEntityFactory.getEntityManager().persist(dept); // 数据持久化
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
}


6、
    @Test
    public void testFind() {
        Dept dept = JPAEntityFactory.getEntityManager().find(Dept.class, 55L);
    }

7、
    @Test
    public void testFind() {
        Dept dept = JPAEntityFactory.getEntityManager().find(Dept.class, 55L);
        LOGGER.info("【第一次查询】部门编号:{}、部门名称:{}、部门位置:{}",
                dept.getDeptno(), dept.getDname(), dept.getLoc());
        for (Emp emp : dept.getEmps()) {
            LOGGER.info("【第二次查询】雇员编号:{}、雇员姓名:{}、基本工资:{}、部门编号:{}",
                    emp.getEmpno(), emp.getEname(), emp.getSal(), emp.getDept().getDeptno());
        }
    }

8、
    @Test
    public void testLazyFind() {
        Dept dept = JPAEntityFactory.getEntityManager().find(Dept.class, 55L);
        JPAEntityFactory.close(); // 模拟业务关闭的操作
        dept.getEmps(); // 【JSP、控制层】加载雇员信息
    }

9、
    @Test
    public void testList() {
        String jpql = "SELECT d FROM Dept AS d";
        TypedQuery<Dept> query = JPAEntityFactory.getEntityManager().createQuery(jpql, Dept.class);
        for (Dept dept : query.getResultList()) {
            LOGGER.info("【部门数据】部门编号:{}、部门名称:{}、员工数量:{}",
                    dept.getDeptno(), dept.getDname(), dept.getEmps().size());
        }
        JPAEntityFactory.close();
    }

多对多数据关联

用户认证授权设计

  • 多对多的数据关联可以理解为两个“一对多”设计结构的整合,以常见的用户认证和授权操作为例,一个用户在系统中可能会有多个角色,一个角色也可能同时被多个用户而一所拥有,这个时候除了要定义用户和角色的数据表之外,还需要创建一张“用户_角色的关联表”

多对多关联实体类

  • 在 JPA 实现多对多的开发过程中,开发者只需要提供有用户和角色两个实体类定义即可,在 Member 实体类中提供了 List<Role>集合,而在 Role 实体类中也提供了 List<Member>集合,这两个集合属性在定义时都需要使用 JPA 提供的“@ManyToMany”注解进行多对多的结构配置。
1、
DROP DATABASE IF EXISTS yootk;
CREATE DATABASE yootk CHARACTER SET UTF8;
USE yootk;
CREATE TABLE member (
   mid 		VARCHAR(50) 	comment '用户编号',
   password    	VARCHAR(50) 	comment '登录密码',
   CONSTRAINT pk_mid PRIMARY KEY(mid)
)engine=innodb;
CREATE TABLE role (
    rid 		VARCHAR(50) 	comment '角色编号',
    name 		VARCHAR(50) 	comment '角色姓名',
    CONSTRAINT pk_rid PRIMARY KEY(rid)
)engine=innodb;
CREATE TABLE member_role (
    mid 		VARCHAR(50)    	comment '用户编号',
    rid VARCHAR(50) comment '角色编号',
    CONSTRAINT fk_mid FOREIGN KEY(mid) REFERENCES member(mid) ON DELETE CASCADE,
    CONSTRAINT fk_rid FOREIGN KEY(rid) REFERENCES role(rid) ON DELETE CASCADE
)engine=innodb;
INSERT INTO member (mid, password) VALUES ('yootk', 'jixianit.com');
INSERT INTO role(rid, name) VALUES ("dept", "部门管理");
INSERT INTO role(rid, name) VALUES ("emp", "雇员管理");
INSERT INTO member_role(mid, rid) VALUES ('yootk', 'dept');
INSERT INTO member_role(mid, rid) VALUES ('yootk', 'emp');


2、
package com.yootk.po;

import jakarta.persistence.*;

import java.io.Serializable;
import java.util.List;

@Entity
public class Member implements Serializable {
    @Id
    private String mid;
    private String password;
    @ManyToMany // 多对多的数据关联
    @JoinTable( // 关系表的维护
            name = "member_role", // 定义关联表名称
            // member表与member_role表依靠mid关联
            joinColumns = {@JoinColumn(name = "mid")},
            // 在进行多对多处理的使用,需要提供有一个主要的操作方,例如:现在要通过用户找到角色
            inverseJoinColumns = {@JoinColumn(name = "rid")} // 通过member找到role
    )
    private List<Role> roles; // 一个用户对应多个角色信息
    public Member() {}
    public Member(String mid, String password, List<Role> roles) {
        this.mid = mid;
        this.password = password;
        this.roles = roles;
    }
    public String getMid() {
        return mid;
    }
    public void setMid(String mid) {
        this.mid = mid;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public List<Role> getRoles() {
        return roles;
    }
    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }
    public String toString() {
        return "【Member数据】用户ID:" + this.mid + "、登录密码:" + this.password;
    }
}


3、
package com.yootk.po;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;

import java.io.Serializable;
import java.util.List;

@Entity
public class Role implements Serializable {
    @Id
    private String rid;
    private String name;
    @ManyToMany // 多对多的数据关联
    private List<Member> members; // 一个角色的同时属于多个用户
    public Role() {}
    public Role(String rid) {
        this.rid = rid;
    }
    public Role(String rid, String name) {
        this.rid = rid;
        this.name = name;
    }
    public String getRid() {
        return rid;
    }
    public void setRid(String rid) {
        this.rid = rid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public List<Member> getMembers() {
        return members;
    }
    public void setMembers(List<Member> members) {
        this.members = members;
    }
    public String toString() {
        return "【Role数据】角色编号:" + this.rid + "、角色名称:" + this.name;
    }
}


4、
package com.yootk.test;

import com.yootk.po.Member;
import com.yootk.po.Role;
import com.yootk.util.JPAEntityFactory;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;

public class TestManyToMany {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestManyToMany.class);
    @Test
    public void testAdd() {
        JPAEntityFactory.getEntityManager().getTransaction().begin(); // 开启JPA事务
        Member member = new Member("lee", "edu.yootk.com", new ArrayList<>()); // 创建新数据
        for (String rid : new String[]{"dept", "emp"}) { // 循环设置rid数据
            member.getRoles().add(new Role(rid)); // 保存角色编号
        }
        JPAEntityFactory.getEntityManager().persist(member); // 数据持久化
        JPAEntityFactory.getEntityManager().getTransaction().commit();
        JPAEntityFactory.close();
    }
}


5、
    @Test
    public void testFind() {
        Member member = JPAEntityFactory.getEntityManager()
                .find(Member.class, "yootk"); // 查询用户数据
        LOGGER.info("第一次数据查询:用户ID = {}、登录密码 = {}", member.getMid(), member.getPassword());
        LOGGER.info("第二次数据查询:用户角色:{}", member.getRoles());
    }

SpringDataJPA

SpringDataJPA

  • 在项目的开发中 JPA 仅仅能够实现数据层的开发,然而在标准的设计分层中数据层的方法是需要根据业务层的需要来定义的,然而在实际的开发中就会出现这样一种情况,不同的数据层可能提供有相同功能的方法。例如:现在所有的数据层都要求提供数据增加、修改、删除、ID 查询的支持,那么按照以往的设计,此时肯定要在不同的数据层接口进行重复定义,同时还需要在对应的数据层实现子类中进行方法覆写,很明显这样的开发设计,一定会带来大量重复逻辑的代码的定义,不仅开发低效,同时也造成了代码维护的困难。

SpringDataJPA 实现结构

  • 为了进一步实现数据层的定义抽象,Spring 提供了 SpringDataJPA 的处理技术,开发人员按照该技术的定义规范开发,不仅可以减少重复功能逻辑的方法定义,同时也可以取消了数据层实现子类的定义,在与业务层进行依赖注入时,数据层的子类会由 Spring 容器帮助用户动态生成

LocalContainerEntityManagerFactoryBean 核心配置方法

LocalContainerEntityManagerFactoryBean 配置关联

  • JPA 的操作主要是围绕着 EntityManagerFactor 与 EntityManager 两个接口实现的,在 SpringDataJPA 中开发者不再需要手工进行这两个接口实例的操作,会由 LocalContainerEntityManagerFactoryBean 配置类定义自行在内部完成接口的方法调用。该类在进行配置时需要明确的配置项目中所使用的数据源持久化实现提供者(PersistenceProvider)、JPA 整合适配器(JpaVendorAdapter)(Datasource)以及 JPA 实现方言(JpaDialect)、实体 Bean 的扫描包、JPA 属性定义
1、
https://spring.io/projects/spring-data

2、
// https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa
implementation group: 'org.springframework.data', name: 'spring-data-jpa', version: '3.0.0-M3'


3、
project(":data") {
    dependencies{
        implementation(libraries.'mysql-connector-java')
        implementation(libraries.'jakarta.persistence-api')
        implementation(libraries.'hibernate-core')
        implementation(libraries.'hibernate-hikaricp')
        implementation(libraries.'hibernate-core-jakarta')
        implementation(libraries.'hibernate-jcache')
        implementation(libraries.'ehcache')
        implementation(libraries.'HikariCP')
        implementation(libraries.'spring-data-jpa')
    }
}

4、
# 【JDBC】配置JDBC驱动程序
yootk.database.driverClassName=com.mysql.cj.jdbc.Driver
# 【JDBC】配置JDBC连接地址
yootk.database.jdbcUrl=jdbc:mysql://localhost:3306/yootk
# 【JDBC】数据库用户名
yootk.database.username=root
# 【JDBC】配置数据库密码
yootk.database.password=mysqladmin
# 【HikariCP】定义连接超时时间
yootk.database.connectionTimeOut=3000
# 【HikariCP】是否为只读操作
yootk.database.readOnly=false
# 【HikariCP】定义一个连接最小维持的时长
yootk.database.pool.idleTimeOut=3000
# 【HikariCP】定义一个连接最大的保存时间
yootk.database.pool.maxLifetime=6000
# 【HikariCP】定义数据库连接池最大长度
yootk.database.pool.maximumPoolSize=60
# 【HikariCP】定义数据库连接池最小的维持数量
yootk.database.pool.minimumIdle=20

5、
package com.yootk.config;

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;

@Configuration // 配置类
@PropertySource("classpath:config/database.properties") // 读取资源文件
public class DataSourceConfig {
    @Value("${yootk.database.driverClassName}") // 绑定资源文件中的配置数据项
    private String driverClassName; // JDBC驱动程序
    @Value("${yootk.database.jdbcUrl}") // 绑定资源文件中的配置数据项
    private String jdbcUrl; // JDBC连接地址
    @Value("${yootk.database.username}") // 绑定资源文件中的配置数据项
    private String username; // JDBC连接用户名
    @Value("${yootk.database.password}") // 绑定资源文件中的配置数据项
    private String password; // JDBC连接密码
    @Value("${yootk.database.connectionTimeOut}") // 绑定资源文件中的配置数据项
    private long connectionTimeout; // JDBC连接超时时间
    @Value("${yootk.database.readOnly}") // 绑定资源文件中的配置数据项
    private boolean readOnly; // 是否为只读数据库
    @Value("${yootk.database.pool.idleTimeOut}") // 绑定资源文件中的配置数据项
    private long idleTimeOut; // 连接池之中的,一个连接的存活最小时间
    @Value("${yootk.database.pool.maxLifetime}") // 绑定资源文件中的配置数据项
    private long maxLifetime; // 连接池之中的,一个连接的存活最长时间
    @Value("${yootk.database.pool.maximumPoolSize}") // 绑定资源文件中的配置数据项
    private int maximumPoolSize; // 连接池最大维持的长度
    @Value("${yootk.database.pool.minimumIdle}") // 绑定资源文件中的配置数据项
    private int minimumIdle; // 连接池最小维持的长度
    @Bean("dataSource") // 配置Bean
    public DataSource dataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setDriverClassName(this.driverClassName);
        dataSource.setJdbcUrl(this.jdbcUrl);
        dataSource.setUsername(this.username);
        dataSource.setPassword(this.password);
        dataSource.setConnectionTimeout(this.connectionTimeout);
        dataSource.setReadOnly(this.readOnly);
        dataSource.setIdleTimeout(this.idleTimeOut);
        dataSource.setMaxLifetime(this.maxLifetime);
        dataSource.setMaximumPoolSize(this.maximumPoolSize);
        dataSource.setMinimumIdle(this.minimumIdle);
        return dataSource;
    }
}


6、
package com.yootk.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

@Configuration // 配置类
public class TransactionConfig {
    @Bean // 事务的注册
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        // PlatformTransactionManager是一个事务的控制标准,而后不同的数据库组件需要实现该标准。
        JpaTransactionManager transactionManager = new JpaTransactionManager(); // 事务实现子类
        transactionManager.setDataSource(dataSource); // 设置数据源
        return transactionManager;
    }
}

SpringDataJPA 编程起步

SpringDataJPA 项目结构

  • 在本此的开发中,所有的实体类需要保存在“com.yootk.po”包中、数据层 DAO 接口保存在"com.vootk.dao”包中。由于本次使用 Annotation 注解的方式实现 Spring 容器的启动,所以还需要提供有一个 StartSpringDataJPA 的核心配置类,进行 JPA 注解以及扫描包的定义
1、
DROP DATABASE IF EXISTS yootk;
CREATE DATABASE yootk CHARACTER SET UTF8;
USE yootk;
CREATE TABLE company (
   cid    	BIGINT 	AUTO_INCREMENT  	comment '公司ID',
   name    	VARCHAR(50) 		comment '公司名称',
   capital 	DOUBLE 			comment '注册资金',
   place 		VARCHAR(50) 		comment '注册位置',
   num 		INT 			comment '员工数量',
   CONSTRAINT pk_cid PRIMARY KEY(cid)
)engine=innodb;
INSERT INTO company(name, capital, place, num) VALUES ('沐言科技', 1000000, '北京', 50);
INSERT INTO company(name, capital, place, num) VALUES ('沐言优拓', 500000, '天津', 70);
INSERT INTO company(name, capital, place, num) VALUES ('李兴华编程训练营', 500000, '上海', 100);


2、
# 【JPA使用环境】开启DDL自动更换操作机制,如果发现数据表的结构有问题时,自动进行更新
hibernate.hbm2ddl.auto=update
# 【JPA使用环境】是否要显示出每次执行的SQL命令
hibernate.show_sql=true
# 【JPA使用环境】是否采用格式化的方式进行SQL显示
hibernate.format_sql=false
# 【JPA二级缓存】启用二级缓存配置
hibernate.cache.use_second_level_cache=true
# 【JPA二级缓存】二级缓存的工厂类,本次为jcache
hibernate.cache.region.factory_class=org.hibernate.cache.jcache.internal.JCacheRegionFactory
# 【JPA二级缓存】配置JSR-107缓存标准实现子类
hibernate.javax.cache.provider=org.ehcache.jsr107.EhcacheCachingProvider


3、
package com.yootk.config;

import jakarta.persistence.SharedCacheMode;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;

import javax.sql.DataSource;Error creating bean with name 'entityManagerFactory' defined in class path resource [com/yix/config/SpringDataJPAConfig.class]: EntityManagerFactory interface [interface org.hibernate.SessionFactory] seems to conflict with Spring's EntityManagerFactoryInfo mixin - consider resetting the 'entityManagerFactoryInterface' property to plain [jakarta.persistence.EntityManagerFactory]


EntityManagerFactory interface [interface org.hibernate.SessionFactory] seems to conflict with Spring's EntityManagerFactoryInfo mixin - consider resetting the 'entityManagerFactoryInterface' property to plain [jakarta.persistence.EntityManagerFactory]
@Configuration
@PropertySource("classpath:config/jpa.properties") // 资源文件加载
public class SpringDataJPAConfig { // SpringDataJPA配置类
    @Value("${hibernate.hbm2ddl.auto}")
    private String hbm2ddlAuto;
    @Value("${hibernate.show_sql}")
    private boolean showSql;
    @Value("${hibernate.format_sql}")
    private String formatSql;
    @Value("${hibernate.cache.use_second_level_cache}")
    private boolean secondLevelCache;
    @Value("${hibernate.cache.region.factory_class}")
    private String factoryClass;
    @Value("${hibernate.javax.cache.provider}")
    private String cacheProvider;
    @Bean("entityManagerFactory") // 进行Bean注册
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(
            DataSource dataSource,
            HibernatePersistenceProvider provider,
            HibernateJpaVendorAdapter adapter,
            HibernateJpaDialect dialect) {
        LocalContainerEntityManagerFactoryBean factoryBean =
                new LocalContainerEntityManagerFactoryBean(); // 工厂配置
        factoryBean.setDataSource(dataSource);
        factoryBean.setPersistenceProvider(provider);
        factoryBean.setJpaVendorAdapter(adapter);
        factoryBean.setJpaDialect(dialect);
        // 在当前应用之中是直接提供了二级缓存的配置,所以应该继续启用
        factoryBean.setSharedCacheMode(SharedCacheMode.ENABLE_SELECTIVE); // 启用二缓存
        factoryBean.setPackagesToScan("com.yootk.po"); // 实体类扫描包
        factoryBean.setPersistenceUnitName("YootkJPA"); // 持久化单元名称
        factoryBean.getJpaPropertyMap().put("hibernate.hbm2ddl.auto", this.hbm2ddlAuto);
        factoryBean.getJpaPropertyMap().put("hibernate.format_sql", this.formatSql);
        factoryBean.getJpaPropertyMap().put("hibernate.cache.use_second_level_cache", this.secondLevelCache);
        factoryBean.getJpaPropertyMap().put("hibernate.cache.region.factory_class", this.factoryClass);
        factoryBean.getJpaPropertyMap().put("hibernate.javax.cache.provider", this.cacheProvider);
        return factoryBean;
    }
    @Bean
    public HibernatePersistenceProvider provider() { // JPA持久化实现类
        return new HibernatePersistenceProvider();
    }
    @Bean
    public HibernateJpaVendorAdapter adapter() {    // JPA适配器
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setShowSql(this.showSql); // 进行是否显示SQL的配置
        return adapter;
    }
    @Bean
    public HibernateJpaDialect dialect() { // JPA方言
        return new HibernateJpaDialect();
    }
}


4、

package com.yootk;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@ComponentScan("com.yootk")
@EnableJpaRepositories("com.yootk.dao") // 直接决定了数据层代码的简化
public class StartSpringDataJPA {}

5、
package com.yootk.po;

import jakarta.persistence.*;

import java.io.Serializable;
@Entity
@Cacheable
public class Company implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 自动增长
    private Long cid;
    private String name;
    private Double capital;
    private String place;
    private Integer num;

    public Long getCid() {
        return cid;
    }

    public void setCid(Long cid) {
        this.cid = cid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getCapital() {
        return capital;
    }

    public void setCapital(Double capital) {
        this.capital = capital;
    }

    public String getPlace() {
        return place;
    }

    public void setPlace(String place) {
        this.place = place;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }
}


6、
package com.yootk.dao;

import com.yootk.po.Company;
import org.springframework.data.repository.RepositoryDefinition;

import java.util.List;

// 定义当前数据层对应的持久化类的类型以及对应的主键数据类型
@RepositoryDefinition(domainClass = Company.class, idClass = Long.class)
public interface ICompanyDAO {
    public Company save(Company company); // 实现数据增加
    public List<Company> findAll(); // 查询全部
}


7、
package com.yootk.test;

import com.yootk.StartSpringDataJPA;
import com.yootk.dao.ICompanyDAO;
import com.yootk.po.Company;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.util.List;

@ContextConfiguration(classes = StartSpringDataJPA.class) // 基于Bean的方式启动
@ExtendWith(SpringExtension.class)
public class TestCompanyDAO {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestCompanyDAO.class);
    @Autowired
    private ICompanyDAO companyDAO;
    @Test
    public void testSave() {
        Company company = new Company();
        company.setName("小李和他的朋友们");
        company.setCapital(300000.0);
        company.setNum(999);
        company.setPlace("洛阳");
        Company result = this.companyDAO.save(company);
        LOGGER.info("数据增加操作,增加后的数据ID为:{}", result.getCid());
    }
    @Test
    public void testFindAll() {
        List<Company> all = this.companyDAO.findAll(); // 查询全部数据
        for (Company company : all) {
            LOGGER.info("【公司数据】公司名称:{}、公司注册地:{}",
                    company.getName(), company.getPlace());
        }
    }
}

Repository 接口

Repository 接口

  • 使用 SpringDataJPA 可以极大的简化数据层的实现代码,但是数据层接口定义的方法名称以及返回值类型等都是有严格定义标准的,而如果要想理解此标准的使用,就需要首先掌握 Repositor 及其相关子接口的使用。在之前所开发的数据层接口中使用了“@RepositoryDefinition”注解进行数据层定义,使用该注解配置时与继承 Repository 父接口定义的效果是完全相同的。
1、
package com.yootk.dao;

import com.yootk.po.Company;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.RepositoryDefinition;

import java.util.List;

// 定义当前数据层对应的持久化类的类型以及对应的主键数据类型
//@RepositoryDefinition(domainClass = Company.class, idClass = Long.class)
public interface ICompanyDAO extends Repository<Company, Long> {
    public Company save(Company company); // 实现数据增加
    public List<Company> findAll(); // 查询全部
}


2、
package com.yootk.dao;

import com.yootk.po.Company;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.RepositoryDefinition;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Set;

// 定义当前数据层对应的持久化类的类型以及对应的主键数据类型
//@RepositoryDefinition(domainClass = Company.class, idClass = Long.class)
public interface ICompanyDAO extends Repository<Company, Long> {
    public Company save(Company company); // 实现数据增加

    // 在进行数据更新的时候,一般都会在更新完成后返回数据更新影响行数,所以此时的方法直接返回了int数据
    @Query("UPDATE Company AS c SET c.capital=:#{#param.capital}, c.num=:#{#param.num}" +
            " WHERE c.cid=:#{#param.cid}") // 更新的JPQL语句
    @Transactional // 采用注解事务
    @Modifying(clearAutomatically = true) // 更新缓存数据
    public int editBase(@Param(value = "param") Company po); // 更新基础信息

    @Transactional // 采用注解事务
    @Modifying(clearAutomatically = true) // 更新缓存数据
    @Query("DELETE FROM Company AS c WHERE c.cid=:pid")
    public int removeById(@Param("pid") Long cid); // 根据ID删除数据

    @Transactional // 采用注解事务
    @Modifying(clearAutomatically = true) // 更新缓存数据
    @Query("DELETE FROM Company AS c WHERE c.cid IN :pids")
    public int removeBatch(@Param("pids") Set<Long> ids); // 批量删除

    @Query("SELECT c FROM Company AS c")
    public List<Company> findAll(); // 查询全部

    @Query("SELECT c FROM Company AS c WHERE c.cid=?#{[0]}")
    public Company findById(Long id);

    @Query("SELECT c FROM Company AS c WHERE c.cid IN :pids")
    public List<Company> findByIds(@Param("pids") Set<Long> ids);

    @Query("SELECT c FROM Company AS c WHERE c.cid=:#{#param.cid} AND name=:#{#param.name}")
    public Company findByIdAndName(@Param("param") Company company);
}


3、
package com.yootk.test;

import com.yootk.StartSpringDataJPA;
import com.yootk.dao.ICompanyDAO;
import com.yootk.po.Company;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.util.List;
import java.util.Set;

@ContextConfiguration(classes = StartSpringDataJPA.class) // 基于Bean的方式启动
@ExtendWith(SpringExtension.class)
public class TestCompanyDAO {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestCompanyDAO.class);
    @Autowired
    private ICompanyDAO companyDAO;
    @Test
    public void testSave() {
        Company company = new Company();
        company.setName("小李和他的朋友们");
        company.setCapital(300000.0);
        company.setNum(999);
        company.setPlace("洛阳");
        Company result = this.companyDAO.save(company);
        LOGGER.info("数据增加操作,增加后的数据ID为:{}", result.getCid());
    }
    @Test
    public void testFindAll() {
        List<Company> all = this.companyDAO.findAll(); // 查询全部数据
        for (Company company : all) {
            LOGGER.info("【公司数据】公司名称:{}、公司注册地:{}",
                    company.getName(), company.getPlace());
        }
    }

    @Test
    public void testFindById() {
        Company company = this.companyDAO.findById(3L);
        LOGGER.info("【公司数据】公司ID:{}、公司名称:{}、公司注册地:{}",
                company.getCid(), company.getName(), company.getPlace());
    }

    @Test
    public void testFindByIdAndName() {
        Company param = new Company();
        param.setCid(3L);
        param.setName("李兴华编程训练营");
        Company company = this.companyDAO.findByIdAndName(param);
        LOGGER.info("【公司数据】公司ID:{}、公司名称:{}、公司注册地:{}",
                company.getCid(), company.getName(), company.getPlace());
    }
    @Test
    public void testFindByIds() {
        List<Company> all = this.companyDAO.findByIds(Set.of(1L, 2L, 3L));
        for (Company company : all) {
            LOGGER.info("【公司数据】公司名称:{}、公司注册地:{}",
                    company.getName(), company.getPlace());
        }
    }
    @Test
    public void testEditBase() {
        Company company = new Company();
        company.setCid(3L);
        company.setCapital(99999.99);
        company.setNum(80);
        LOGGER.info("数据更新操作,影响的数据行数:{}", this.companyDAO.editBase(company));
    }
    @Test
    public void testRemoveById() {
        LOGGER.info("数据删除操作,影响的数据行数:{}", this.companyDAO.removeById(1L));
    }
    @Test
    public void testRemoveByIds() {
        LOGGER.info("数据删除操作,影响的数据行数:{}", this.companyDAO.removeBatch(Set.of(9L, 8L, 2L)));
    }
}

Repository 方法映射

springdatajpaopen in new window

CrudRepository 数据接口

1、
package com.yootk.dao;

import com.yootk.po.Company;
import org.springframework.data.repository.CrudRepository;

public interface ICompanyDAO extends CrudRepository<Company, Long> {
}


2、
package com.yootk.test;

import com.yootk.StartSpringDataJPA;
import com.yootk.dao.ICompanyDAO;
import com.yootk.po.Company;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@ContextConfiguration(classes = StartSpringDataJPA.class) // 基于Bean的方式启动
@ExtendWith(SpringExtension.class)
public class TestCompanyDAO {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestCompanyDAO.class);
    @Autowired
    private ICompanyDAO companyDAO;
    @Test
    public void testSave() {
        Company company = new Company();
        company.setName("小李和他的朋友们");
        company.setCapital(300000.0);
        company.setNum(999);
        company.setPlace("洛阳");
        Company result = this.companyDAO.save(company);
        LOGGER.info("数据增加操作,增加后的数据ID为:{}", result.getCid());
    }
    @Test
    public void testFindAll() {
        for (Company company : this.companyDAO.findAll()) { // 查询全部数据
            LOGGER.info("【公司数据】公司名称:{}、公司注册地:{}",
                    company.getName(), company.getPlace());
        }
    }
    @Test
    public void testGet() throws Exception {
        Thread threadA = new Thread(()->{
            Optional<Company> optional = companyDAO.findById(3L);
            if (optional.isPresent()) { // 有数据返回
                Company company = optional.get(); // 获取数据
                LOGGER.info("【{}】公司编号:{}、公司名称:{}", Thread.currentThread().getName(),
                        company.getCid(), company.getName());
            }
        }, "查询线程A");
        Thread threadB = new Thread(()->{
            Optional<Company> optional = companyDAO.findById(3L);
            if (optional.isPresent()) { // 有数据返回
                Company company = optional.get(); // 获取数据
                LOGGER.info("【{}】公司编号:{}、公司名称:{}", Thread.currentThread().getName(),
                        company.getCid(), company.getName());
            }
        }, "查询线程B");
        threadA.start();
        TimeUnit.SECONDS.sleep(1); // 延缓一下,给写入缓存时间
        threadB.start();
        TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
    }
    @Test
    public void testDelete() {
        this.companyDAO.deleteById(5L); // 删除之后的结果是一个void
    }
}

PagingAndSortingRepository 数据接

PagingAndSortingRepository 子接

  • 在数据查询处理中,分页加载是最为常见的基础性功能,SpringDataJPA 提供了一个 PagingAndSortingRepository 子接口,该接口在查询时可以通过 Sort 设置排序,并利用 Pageable 进行页码的配置
1、
package com.yootk.dao;

import com.yootk.po.Company;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;

public interface ICompanyDAO extends PagingAndSortingRepository<Company, Long>, CrudRepository<Company, Long> {
}


2、
    @Test
    public void testSplitPage() {
        int currentPage = 1; // 当前所在页
        int lineSize = 1; // 每页显示一条数据
        Sort sort = Sort.by(Sort.Direction.DESC, "capital"); // 数据排序配置
        Pageable pageable = PageRequest.of(currentPage - 1, lineSize, sort); // 构建分页处理
        Page<Company> page = this.companyDAO.findAll(pageable); // 数据分页查询处理
        LOGGER.info("总记录数:{}、总页数:{}", page.getTotalElements(), page.getTotalPages());
        for (Company company : page.getContent()) {
            LOGGER.info("【公司数据】公司ID:{}、公司名称:{}、注册资本:{}",
                    company.getCid(), company.getName(), company.getCapital());
        }
    }

JpaRepository 数据接

JpaRepository 接

  • SpringDataJPA 中为了便于数据层的开发,提供了一系列的 Repository 相关接口,这样开发老在使用时就可以直接根据需要进行所需接口的继承,但是大部分情况下数据层的操作方法都比较繁琐,为了简化定义提供了 JpaRepository 子接口,该接口拥有 CrudRepository 子接口与 PagingAndSortingRepository 子接口的全部功能,开发者只需要继承一个父接口即可,同时在 JpaRepository 接口中有提供了-些与 JPA 有关的操作方法
1、
package com.yootk.dao;

import com.yootk.po.Company;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ICompanyDAO extends JpaRepository<Company, Long>{
}


2、
    @Test
    public void testInsertBatch() {
        List<Company> all = new ArrayList<>();
        for (int x = 0 ; x < 1000 ; x ++) {
            Company company = new Company();
            company.setName("小李公开课 - " + x);
            company.setCapital(300000.0);
            company.setNum(100 + x);
            company.setPlace("洛阳");
            all.add(company);
        }
        this.companyDAO.saveAllAndFlush(all); // 数据批量存储
    }

3、

    @Test
    public void testGetID(){
        Optional<Company> company = this.companyDAO.findById(5L); // 数据查询
    }
4、
    @Test
    public void testReferenceID(){
        Company company = this.companyDAO.getReferenceById(5L);
    }

5、
    @Test
    public void testReferenceID(){
        Company company = this.companyDAO.getReferenceById(5L);
        LOGGER.info("公司注册地:{}", company.getPlace());
    }

demo


上次编辑于: