文章目录

  • Hibernate框架入门教程
    • 读者
    • 阅读条件
  • ORM是什么
    • ORM 的缺点
    • ORM 框架
    • 总结
  • Hibernate是什么
        • 什么是 ORM?
        • 什么是持久化?
    • Hibernate 支持的数据库
    • Hibernate 是一种全自动的 ORM 框架
    • Hibernate 提供了缓存机制
    • JDBC vs Hibernate
  • Hibernate项目创建流程(IDEA版)
    • 1. 下载 Hibernate 开发包
        • 目录介绍
    • 2. 新建工程
    • 3. 创建数据库表
    • 4. 创建实体类
    • 5. 创建映射文件
    • 6. 创建 Hibernate 核心配置文件
    • 7. 测试
  • Hibernate增删改查操作(CRUD)
    • 插入记录
    • 修改记录
    • 删除记录
    • 查询数据
        • HQL 查询
        • QBC 查询
        • SQL 查询
  • Hibernate工作原理(图解)
  • hibernate.cfg.xml(Hibernate核心配置文件)
        • 元素
        • 元素
  • Hibernate映射文件(Xxx.hbm.xml)
        • 元素
        • 元素
        • 元素
        • 元素
  • Hibernate核心接口(5个)
    • 1. Configuration
        • 创建 Configuration 实例
        • 加载映射文件
    • 2. SessionFactory
        • 创建 SessionFactory 对象
        • 抽取工具类
    • 3. Session
        • 创建 Session 对象
        • Session 中的持久化方法
    • 4. Transaction
        • 获取 Transaction 对象
        • 提交或回滚事务
    • 5. Query
  • Hibernate持久化类详解
      • 持久化类的规范
      • 持久化对象
        • 持久化对象的状态
  • Hibernate一级缓存详解
    • 缓存
    • Hibernate 一级缓存
        • 一级缓存的特点
        • 示例 1
    • 快照区
        • 示例 2
  • Hibernate关联映射(非常详细)
    • 关联映射
      • 一对多
        • 创建实体类
        • 建立映射
        • 测试
      • 多对多
        • 创建实体类
        • 建立映射
        • 将映射文件加入核心配置文件
        • 测试
    • 反转
        • 示例
    • 级联
        • 级联保存
        • 级联删除
        • 孤儿删除

转载于: http://c.biancheng.net/mybatis/

Hibernate框架入门教程

Hibernate 是 Gavin King 和他的开发团队在 2001 年推出一个开源免费的、基于 ORM 技术的 Java 持久化框架。Hibernate 框架对 JDBC 进行了轻量级的封装,可以应用在任何使用 JDBC 的场合,无论是在 Java 客户端程序中,还是 Java Web 程序中,都可以使用它进行开发。

Hibernate 将 Java 对象与数据库表之间建立起映射关系,并提供了一系列数据访问接口,Java 开发人员可以通过这些接口随心所欲地使用面向对象思想对数据库进行操作。

Hibernate 框架具有良好的可移植性,它支持几乎所有主流的关系型数据库,例如 MySQL、Oracle、SQL Server和 DB2 等等,开发人员只要在配置文件中指定好当前使用的数据库,就不需要操心不同数据库之间的差异。

Hibernate 是一款全自动的 ORM 框架,它能够自动生成的 SQL 语句并自动执行,实现对数据库进行操作,整个过程完全不需要人工干预,大大降低了开发成本。

读者

该 Hibernate 教程适用于 Java 开发人员,主要讲解了 Hibernate 框架的基本功能及使用,并为每个知识点都提供了大量实例,以帮助读者对所学内容进行巩固练习,达到融会贯通、灵活运用的目的。

阅读条件

阅读 Hibernate 教程之前,您应该已经掌握了 Java 基础、Java 高级特性、基于 JDBC 的 JavaWeb 开发等知识。另外,由于该 Hibernate 教程中的所有实例都是使用 IntelliJ IDEA 编写编译的,所以您还需要对 IntelliJ IDEA 有基本的了解。

ORM是什么

ORM 是 Object Relational Mapping 的缩写,译为“对象关系映射”,它解决了对象和关系型数据库之间的数据交互问题。

使用面向对象编程时,数据很多时候都存储在对象里面,具体来说是存储在对象的各个属性(也称成员变量)中。例如有一个 User 类,它的 id、username、password、email 属性都可以用来记录用户信息。当我们需要把对象中的数据存储到数据库时,按照传统思路,就得手动编写 SQL 语句,将对象的属性值提取到 SQL 语句中,然后再调用相关方法执行 SQL 语句。

而有了 ORM 技术以后,只要提前配置好对象和数据库之间的映射关系,ORM 就可以自动生成 SQL 语句,并将对象中的数据自动存储到数据库中,整个过程不需要人工干预。在 Java 中,ORM 一般使用 XML 或者注解来配置对象和数据库之间的映射关系。

ORM logo
图1:ORM 图标

和自动生成 SQL 语句相比,手动编写 SQL 语句的缺点是非常明显的,主要体现在以下两个方面:

  • 对象的属性名和数据表的字段名往往不一致,我们在编写 SQL 语句时需要非常小心,要逐一核对属性名和字段名,确保它们不会出错,而且彼此之间要一一对应。
  • 此外,当 SQL 语句出错时,数据库的提示信息往往也不精准,这给排错带来了不小的困难。

ORM 的出现,恰好解决了这些难题。

面向对象编程和关系型数据库都是广泛使用的两种技术,ORM 使得两者之间的数据交互变得自动化,解放了程序员的双手,同时也让源代码中不再出现 SQL 语句。

需要说明的是,ORM 是一种双向数据交互技术,它不仅可以将对象中的数据存储到数据库中,也可以反过来将数据库中的数据提取到对象中。

下表说明了关系型数据库和对象之间的对应关系:

数据库类/对象
表(table)类(class)
表中的记录(record,也称行)对象(object)
表中的字段(field,也称列)对象中的属性(attribute)

例如,现在有一张 user 表,它包含 id、user_id 和 user_name 三个字段,另外还有一个 Java User 类,它包含 id、userId 和 userName 三个属性,下图演示了它们之间的对应关系:

img
图2:user 表和 User 类之间的对应关系

你看,数据表和类用来描述数据的表现形式,它们之间是相互对应的;记录和对象用来真正地存储数据,它们之间也是相互对应的。

ORM 的缺点

ORM 在提高开发效率的同时,也带来了以下几个缺点:

  • ORM 增加了大家的学习成本,为了使用 ORM 技术,您至少需要掌握一种 ORM 框架。
  • 自送生成 SQL 语句会消耗计算资源,这势必会对程序性能造成一定的影响。
  • 对于复杂的数据库操作,ORM 通常难以处理,即使能处理,自动生成的 SQL 语句在性能方面也不如手写的原生 SQL。
  • 生成 SQL 语句的过程是自动进行的,不能人工干预,这使得开发人员无法定制一些特殊的 SQL 语句。

ORM 框架

ORM 技术通常使用单独的框架,或者框架的某个模块来实现,下面列出了常用的 ORM 框架:

  • 常用的 Java ORM 框架有 Hibernate 和 Mybatis。
  • 常用的 Python ORM 实现有 SQLAlchemy 框架、Peewee 框架、Django 框架的 ORM 模块等。
  • 常用的 PHP ORM 实现有 Laravel 框架、Yii 框架的 ORM 模块、ThinkPHP 框架的 ORM 模块等。

总结

ORM 是一种自动生成 SQL 语句的技术,它实现了对象和关系型数据库之间的数据交互,提高了开发效率。在实际开发中,常见的增删改查(CRUD)操作都可以交给 ORM,避免了手写 SQL 语句的麻烦。

Hibernate是什么

Hibernate 是一个开源免费的、基于 ORM 技术的 Java 持久化框架。通俗地说,Hibernate 是一个用来连接和操作数据库的 Java 框架,它最大的优点是使用了 ORM 技术。

2001 年,27 岁的程序员 Gavin King 首次发布了 Hibernate。Gavin King 是一名来自澳大利亚的愤青,他非常厌恶当时流行的 EJB 框架,因为 EJB 框架的实体 bean 让数据库操作变得异常麻烦,于是他自己动手开发了一个符合 ORM 理论的 Java 框架,并于 2001 年 11 月首次发布,这个框架就叫 Hibernate。

2003 年 9 月,Gavin King 及其开发团队加入 JBoss 公司,开始全职开发 Hibernate,从此 Hibernate 获得了突飞猛进的发展和普及。

什么是 ORM?

ORM 是一种自动生成 SQL 语句的技术,它可以将对象中的数据自动存储到数据库中,也可以反过来将数据库中的数据自动提取到对象中,整个过程不需要人工干预,避免了手写 SQL 带来的麻烦。

学习 Hibernate 必须先了解 ORM,我们已经在上一节中详细介绍了 ORM 技术,不了解的读者请转到:ORM 是什么?

什么是持久化?

所谓持久化,就是将程序中的数据存储到数据库中。

程序必须加载到内存才能运行,程序运行过程中产生的数据也位于内存中,而内存是一种临时性存储介质,当程序关闭或者计算机断电后数据就会消失,为了让数据永久存储,必须将它们写入硬盘,而数据库就是数据在硬盘上的一种高效的存储和组织形式,非常便于数据的插入、删除、查找、更新等。

将程序中的数据写入数据库,数据就可以永久存储,不会随着程序的关闭而消失了,所以将这个过程称为数据的持久化。

Hibernate 支持的数据库

Hibernate 支持几乎所有主流的关系型数据库,只要在配置文件中设置好当前正在使用的数据库,程序员就不需要操心不同数据库之间的差异。

以下常见数据库都被 Hibernate 支持:

  • MySQL
  • Oracle
  • Microsoft SQL Server
  • DB2
  • PostgreSQL
  • FrontBase
  • Sybase

Hibernate 是一种全自动的 ORM 框架

Hibernate 是一款全自动的 ORM 框架。之所以将 Hibernate 称为全自动的 ORM 框架,这其实是相对于 MyBatis 来说的。

我们知道,ORM 思想的核心是将 Java 对象与数据表之间建立映射关系。所谓的映射关系,简单点说就是一种对应关系,这种对应关系是双向的:

  • 将数据表对应到 Java 对象上,这样数据表中的数据就能自动提取到 Java 对象中;
  • 将 Java 对象对应到数据表上,这样 Java 对象中的数据就能自动存储到数据表中。

MyBatis 虽然是一种 ORM 框架,但它建立的映射关系是不完整的。Mybatis 只能将数据表映射到 Java 对象上,却不能将 Java 对象映射到数据表上,所以数据只能从数据表自动提取到 Java 对象中,反之则不行。要想将 Java 对象中的数据存储数据表中,开发人员需要手动编写 SQL 语句,依然非常麻烦,这就是 MyBatis 被称为半自动 ORM 框架的原因。

与 MyBatis 相比,Hibernate 建立了完整的映射关系,它不仅能将数据表中的数据自动提取到 Java 对象中,还能自动生成并执行 SQL 语句,将 Java 对象中的数据存储到数据表中,整个过程不需要人工干预,因此 Hibernate 被称为全自动的 ORM 框架。

Hibernate 和 MyBatis 都是业界主流的 ORM 框架和持久层解决方案,但两者存在一些区别,详情请参考 MyBatis 和 Hibernate 的区别。

Hibernate 提供了缓存机制

Hibernate 提供了缓存机制(一级缓存、二级缓存、查询缓存),我们可以将那些经常使用的数据存放到 Hibernate 缓存中。当 Hibernate 在查询数据时,会优先到缓存中查找,如果找到则直接使用,只有在缓存中找不到指定的数据时,Hibernate 才会到数据库中检索,因此 Hibernate 的缓存机制能够有效地降低应用程序对数据库访问的频次,提高应用程序的运行性能。

JDBC vs Hibernate

我们知道,JDBC 是 Java 领域中出现最早一种数据库访问技术,而 Hibernate 则是对 JDBC 的轻量级封装,它们都可以完成对数据库的增删改查(CRUD)操作,但两者存在以下差异,如下表。

区别JDBCHibernate
代码量代码繁琐,开发成本较高。代码精简,开发成本较低。
语言JDBC 使用的是基于关系型数据库的标准 SQL 语句。Hibernate 使用的是 HQL(Hibernate query language)语句。
操作的对象JDBC 操作的是数据,将数据通过 SQL 语句直接传送到数据库中执行。Hibernate 操作的是 Java 对象。Hibernate 使用了 ORM 思想,在 Java 对象与数据库表之间建立映射关系,通过操作 Java 对象来实现对数据库的操作。
缓存JDBC 没有提供缓存机制。Hibernate 提供了缓存机制(例如一级缓存、二级缓存、查询缓存),对于那些经常使用的数据,可以将它们放到缓存中,不必在每次使用时都去数据库查询,缓存机制对提升性能大有裨益。
可移植性JDBC 可移植性差,JDBC 中使用的 SQL 语句是以字符串的形式嵌入到代码中的,但不同数据库所使用的的 SQL 语法不尽相同,如果切换数据库(例如从 MySQL 数据库切换到 Oracle 数据库),需要修改大量代码。Hibernate 可移植性好,能够屏蔽不同数据库之间的差异。当需要更换数据库时,通常只需要修改其配置文件(hibernate.cfg.xml)中的 hibernate.dialect(方言) 属性,指定数据库类型即可,Hibernate 会根据指定的方言自动生成适合的 SQL 语句。
读写性能JDBC 是 Java 领域内性能最好的数据库访问技术。Hibernate 性能较差。
可扩展性JDBC 不具有可扩展性Hibernate 框架开源免费,用户可以根据自身需要进行功能定制,具有良好的可扩展性。

Hibernate 和 JDBC 都是十分优秀的数据库访问技术,它们都各有优缺点,但两者不存在孰优孰劣的问题,我们只需要根据自身需求和应用场景选择即可。

Hibernate项目创建流程(IDEA版)

本节我们将演示如何搭建一个 Hibernate 工程。

搭建 Hibernate 工程需要以下 7 步:

  1. 下载 Hibernate 开发包
  2. 新建工程
  3. 创建数据库表
  4. 创建实体类
  5. 创建映射文件
  6. 创建 Hibernate 核心配置文件
  7. 测试

1. 下载 Hibernate 开发包

浏览器访问 Hibernate 官网 下载 Hibernate(以 hibernate-release-5.5.3.Final 为例)开发包。

目录介绍

下载完成后,解压 hibernate-release-5.5.3.Final.zip,可得到以下目录结构。

img

图1:Hibernate 目录结构图

Hibernate 文件夹中,包含以下 3 个目录:

  • documentation:Hibernate 的开发文档;
  • lib:Hibernate 相关的依赖;
  • project:Hibernate 官方给出的示例工程。

在 lib 目录下有一个 required 文件夹,其中包含了 Hibernate 5 必须依赖的 jar 包,如下图。

img
图2:Hibernate 必须 jar 包

2. 新建工程

\1. 以 IntelliJ IDEA 为例,新建一个名为 hibernate-demo 的 Java 工程,并在该项目根目录下新建一个 lib 目录,如下图。

IDEA 新建 java 工程
图3:新建 java 项目

\2. 将 required 目录中的所有 jar 包拷贝到 Hibernate 工程下的 lib 目录中,除此之外,为了与数据库进行连接,我们还需要下载 MySQL 数据库驱动程序和 junit 的 Jar 包,并将它们都拷贝到 lib 目录中,如下图。

IDEA Hibernate 依赖包
图4:IDEA Hibernate 依赖包

\3. 在 IDEA 上方的菜单栏点击 File,选择 Project Structure…,进入工程结构视图。

img
图5:Project Structure

\4. 在工程结构视图左侧选中 Modules,点击右侧的 Dependencise 标签,并点击该标签页下方的“+”,选择“JARs or Directories…” ,如下图。

img
图6:工程结构依赖视图

\5. 在弹出的“Attach Files or Directories”视图中,选择 hibernate-demo 工程下的 lib 目录,然后点击 OK 按钮,如下图。

img
图7:Attach Files or Directories

\6. 返回 Dependencise 页后可以看到,lib 目录中依赖已经引入到了该工程之中,点击下方的 Apply 按钮保存,最后再点击 OK 按钮,如下图。

img
图8:保存配置

3. 创建数据库表

执行以下 SQL 语句,在 bianchengbang_jdbc 数据库中创建 user 表,并向该表中添加测试数据。

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (`id` int NOT NULL AUTO_INCREMENT,`user_id` varchar(255) DEFAULT NULL,`user_name` varchar(255) DEFAULT NULL,`password` varchar(255) DEFAULT NULL,`email` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;INSERT INTO `user` VALUES ('1', '001', 'admin', 'admin', '12345678@qq.com');
INSERT INTO `user` VALUES ('2', '002', 'user', '123456', '98765432@qq.com');

4. 创建实体类

在 hibernate-demo 工程的 net.biancheng.www.po 包下,创建一个与 user 表对应的实体类 User,代码如下。

package net.biancheng.www.po;
/**
* 实体类
* 与 bianchegnbang_jdbc 数据库中的 user 表对应
*/
public class User {private int id;private String userId;private String userName;private String password;private String email;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getUserId() {return userId;}public void setUserId(String userId) {this.userId = userId;}public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}@Overridepublic String toString() {return "net.biancheng.www.po.User{" +"id=" + id +", userId='" + userId + '\'' +", userName='" + userName + '\'' +", password='" + password + '\'' +", email='" + email + '\'' +'}';}
}

5. 创建映射文件

单单一个实体类 User 是不具备持久化操作的能力的,为了使该类具备这种能力,就需要将 User 实体类映射到数据库的某一张表中,并将实体类中的属性与数据表的字段相对应,这些都需要在映射文件中配置。

Hibernate 的映射文件的命名规则如下:

[实体类名].hbm.xml

例如,实体类 User 的映射文件就可以命名为 User.hbm.xml。

在 net.biancheng.www.mapping 包下,创建 User 的映射文件 User.hbm.xml,配置如下。

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping><!-- name:类的全路径:--><!-- table:表的名称:(可以省略的.使用类的名称作为表名.)--><class name="net.biancheng.www.po.User" table="user" schema="bianchengbang_jdbc"><!-- 主键--><id name="id" column="id"><!--主键生成策略--><generator class="native"></generator></id><!--type:三种写法--><!--Java类型 :java.lang.String--><!--Hibernate类型:string--><!--SQL类型 :不能直接使用type属性,需要子标签<column>--><!--<column name="name" sql-type="varchar(20)"/>--><property name="userId" column="user_id" type="java.lang.String"/><property name="userName" column="user_name"/><property name="password" column="password"/><property name="email" column="email"/></class>
</hibernate-mapping>

6. 创建 Hibernate 核心配置文件

Hibernate 框架是对 JDBC 的封装,也需要对 JDBC 连接数据库所需的 url、driver、username 和 password 等 4 个基本参数进行配置,Hibernate 一般是通过配置文件 hibernate.cfg.xml 来指定的,该文件被称为 Hibernate 的核心配置文件。

在 Hibernate 工程的 src 目录下,新建一个 hibernate.cfg.xml 文件,配置如下。

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC"-//Hibernate/Hibernate Configuration DTD//EN""http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration><session-factory><!--使用 Hibernate 自带的连接池配置--><property name="connection.url">jdbc:mysql://localhost:3306/bianchengbang_jdbc</property><property name="hibernate.connection.username">root</property><property name="hibernate.connection.password">root</property><property name="connection.driver_class">com.mysql.jdbc.Driver</property><!--hibernate 方言--><property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property><!--打印sql语句--><property name="hibernate.show_sql">true</property><!--格式化sql--><property name="hibernate.format_sql">true</property><!-- 加载映射文件 --><mapping resource="net/biancheng/www/mapping/User.hbm.xml"/></session-factory>
</hibernate-configuration>

7. 测试

\1. 在 net.biancheng.www.test 包下,创建一个名为 MyTest 的测试类,并在该测试类中创建一个测试方法 testQuery(),使用 Hibernate 实现对数据库的查询,代码如下。

package net.biancheng.www.test;
import net.biancheng.www.po.User;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.Query;
import org.junit.Test;
import java.util.List;
public class MyTest {/*** 查询数据库数据*/@Testpublic void testQuery() {//Hibernate 加载核心配置文件(有数据库连接信息)Configuration configuration = new Configuration().configure();//创建一个 SessionFactory 用来获取 Session 连接对象SessionFactory sessionFactory = configuration.buildSessionFactory();//获取session 连接对象Session session = sessionFactory.openSession();//开始事务Transaction transaction = session.beginTransaction();//根据主键查询 user 表中的记录User user = session.get(User.class, 1);System.out.println(user);//提交事务transaction.commit();//释放资源session.close();sessionFactory.close();}
}

\2. 运行测试方法 testQuery(),控制台输出如下。

七月 19, 2021 12:24:28 下午 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate ORM core version 5.5.3.Final
七月 19, 2021 12:24:29 下午 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
七月 19, 2021 12:24:30 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
七月 19, 2021 12:24:30 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/bianchengbang_jdbc]
七月 19, 2021 12:24:30 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
七月 19, 2021 12:24:30 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
七月 19, 2021 12:24:30 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections <init>
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Mon Jul 19 12:24:30 CST 2021 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
七月 19, 2021 12:24:31 下午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
七月 19, 2021 12:24:32 下午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate:selectuser0_.id as id1_0_0_,user0_.user_id as user_id2_0_0_,user0_.user_name as user_nam3_0_0_,user0_.password as password4_0_0_,user0_.email as email5_0_0_fromuser user0_whereuser0_.id=?
net.biancheng.www.po.User{id=1, userId='001', userName='admin', password='admin', email='12345678@qq.com'}
七月 19, 2021 12:24:32 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PoolState stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost:3306/bianchengbang_jdbc]

从控制台输出可以看到,Hibernate 自动执行查询语句,并得到了查询结果。

Hibernate增删改查操作(CRUD)

接下来,我们将介绍 Hibernate 是如何实现对数据库的增删改查(CRUD)操作的。

插入记录

Hibernate 在 Session 接口中为我们提供了一个 save() 方法,该方法可以向据库表中插入记录。

\1. 在测试类 MyTest 中,创建一个名称为 testInsert 的方法,代码如下。

/**
* 保存数据
*/
@Test
public void testInsert() {//加载 Hibernate 核心配置文件Configuration configuration = new Configuration().configure();//创建一个 SessionFactory 用来获取 Session 连接对象SessionFactory sessionFactory = configuration.buildSessionFactory();//获取session 连接对象Session session = sessionFactory.openSession();//开始事务Transaction transaction = session.beginTransaction();//创建实体类对象User user = new User();user.setUserId("003");user.setUserName("编程帮新增用户");user.setPassword("654321");user.setEmail("14234567@qq.com");//向 user 表中插入数据,返回值为新增数据的主键 idSerializable save = session.save(user);System.out.println("新增数据的主键 id:"+save);//提交事务transaction.commit();//释放资源session.close();sessionFactory.close();
}

\2. 运行测试方法 testInsert(),控制台输出如下。

Hibernate:insertintouser(user_id, user_name, password, email)values(?, ?, ?, ?)
新增数据的主键 id:3

\3. 查询数据库 user 表中的数据,结果如下表。

iduser_iduser_namepasswordemail
1001adminadmin12345678@qq.com
2002user12345698765432@qq.com
3003编程帮新增用户65432114234567@qq.com

由上表可以看出,我们成功地使用 Hibernate 向 user 表中添加了一条记录。

修改记录

Hibernate 在 Session 接口中为我们提供了一个 update() 方法,该方法可以用来修改数据库表中的记录。

\1. 在 MyTest 类中,添加一个 testUpdate() 方法,代码如下。

/**
* 修改记录
*/
@Test
public void testUpdate() {//加载 Hibernate 核心配置文件Configuration configuration = new Configuration().configure();//创建一个 SessionFactory 用来获取 Session 连接对象SessionFactory sessionFactory = configuration.buildSessionFactory();//获取session 连接对象Session session = sessionFactory.openSession();//开始事务Transaction transaction = session.beginTransaction();//创建实体对象User user = new User();user.setId(3);//设置需要修改的字段user.setUserName("更新用户名");//直接调用 update() 方法进行修改session.update(user);//提交事务transaction.commit();//释放资源session.close();sessionFactory.close();
}

\2. 执行测试方法 testUpdate(),控制台输出如下。

Hibernate:updateusersetuser_id=?,user_name=?,password=?,email=?whereid=?

\3. 查询数据库 user 表中的数据,结果如下图。

img
图1:直接使用 update() 方法修改记录

从图 9 和控制台输出可知,update() 方法会更新指定记录的全部字段,对于我们没有指定的字段,Hibernate 则将其修改为默认值,这显然不是我们想要的结果。

通常我们会采用“先查询再修改”的方式来避免此问题的发生,即先将需要修改的记录查询出来,获得该条记录的实体对象,然后重新给该对象中的字段重新赋值,最后再使用该对象进行更新操作。

\4. 修改 testUpdate() 方法的代码,采用“先查询再修改”的方式进行更新,代码如下。

/**
* 修改记录
*/
@Test
public void testUpdate() {//加载 Hibernate 核心配置文件Configuration configuration = new Configuration().configure();//创建一个 SessionFactory 用来获取 Session 连接对象SessionFactory sessionFactory = configuration.buildSessionFactory();//获取session 连接对象Session session = sessionFactory.openSession();//开始事务Transaction transaction = session.beginTransaction();//现将需要修改的记录查询出来User user = session.get(User.class, 3);//设置需要修改的字段user.setUserName("更新用户名");//直接调用 update() 方法进行修改session.update(user);//提交事务transaction.commit();//释放资源session.close();sessionFactory.close();
}

\5. 重新执行测试方法 testUpdate() ,控制台输出如下。

Hibernate:selectuser0_.id as id1_0_0_,user0_.user_id as user_id2_0_0_,user0_.user_name as user_nam3_0_0_,user0_.password as password4_0_0_,user0_.email as email5_0_0_fromuser user0_whereuser0_.id=?
Hibernate:updateusersetuser_id=?,user_name=?,password=?,email=?whereid=?

\6. 查询数据库 user 表中的数据,如下图。

img
图2:先查询在修改

从上图可知,我们成功地使用 Hibernate 修改了 user 表中的一条记录。

删除记录

Hibernate 在 Session 接口中,为我们提供了一个 delete() 方法,该方法用于删除数据库表中的记录。

\1. 在 MyTest 类中,添加一个 testDelete() 方法,代码如下。

/**
* 删除记录
*/
@Test
public void testDelete() {//加载 Hibernate 核心配置文件Configuration configuration = new Configuration().configure();//创建一个 SessionFactory 用来获取 Session 连接对象SessionFactory sessionFactory = configuration.buildSessionFactory();//获取session 连接对象Session session = sessionFactory.openSession();//开始事务Transaction transaction = session.beginTransaction();User user = new User();user.setId(3);//删除指定的记录session.delete(user);//提交事务transaction.commit();//释放资源session.close();sessionFactory.close();
}

\2. 执行测试方法 testDelete() ,控制台输出如下。

Hibernate:deletefromuserwhereid=?

\3. 查询数据 user 表中的数据,结果如下图。

img
图3:删除记录

从上图可知,我们成功地使用 Hibernate 删除了 user 表中的一条记录。

查询数据

我们知道,Hibernate 通过 Session 接口提供的 get() 方法能够查询出一条指定的数据,但该方法无法查询多条或所有数库数据。

Hibernate 为用户提供了以下 3 种查询方式,它们都可以用来查询多条数据。

  • HQL 查询
  • QBC 查询
  • SQL 查询

HQL 查询

HQL 全称:Hibernate Query Language,它是一种面向对象的查询语言,它和 SQL 的语法相似,但 HQL 操作的是实体类对象及其中的属性,而不是数据库表中的字段。在 Hibernate 提供的各种检索方式中,HQL 是使用最广的一种检索方式。

\1. 在数据库中执行以下 SQL 语句,向 user 表中添加 3 条使用 163 邮箱的用户数据。

INSERT INTO `user`(user_id, user_name, password, email) VALUES ( '003', 'user2', 'user2', '9dfasdfa@163.com');
INSERT INTO `user`(user_id, user_name, password, email) VALUES ( '004', 'user3', 'user3', '76543345@163.com');
INSERT INTO `user`(user_id, user_name, password, email) VALUES ( '005', 'user4', 'user4', '234543546@163.com');

\2. 在 MyTest 类中,添加一个 testHqlQuery() 方法,该方法用于查询 user 表中的所有使用 163 邮箱的用户数据,代码如下。

/**
* 使用 HQL 查询
*/
@Test
public void testHqlQuery() {//加载 Hibernate 核心配置文件Configuration configuration = new Configuration().configure();//创建一个 SessionFactory 用来获取 Session 连接对象SessionFactory sessionFactory = configuration.buildSessionFactory();//获取session 连接对象Session session = sessionFactory.openSession();//开始事务Transaction transaction = session.beginTransaction();//创建 HQL 语句,语法与 SQL 类似,但操作的是实体类及其属性Query query = session.createQuery("from User where email like ?1");//查询所有使用 163 邮箱的用户query.setParameter(1, "%@163.com%");//获取结果集List<User> resultList = query.getResultList();//遍历结果集for (User user : resultList) {System.out.println(user);}//提交事务transaction.commit();//释放资源session.close();sessionFactory.close();
}

\3. 执行测试方法 testHqlQuery() ,控制台输出如下。

Hibernate:selectuser0_.id as id1_0_,user0_.user_id as user_id2_0_,user0_.user_name as user_nam3_0_,user0_.password as password4_0_,user0_.email as email5_0_fromuser user0_whereuser0_.email like ?
net.biancheng.www.po.User{id=4, userId='003', userName='user2', password='user2', email='9dfasdfa@163.com'}
net.biancheng.www.po.User{id=5, userId='004', userName='user3', password='user3', email='76543345@163.com'}
net.biancheng.www.po.User{id=6, userId='005', userName='user4', password='user4', email='234543546@163.com'}

从以上控制台输出可知,我们成功地通过 HQL 查询到了 user 表中的多条数据。

QBC 查询

QBC 全称 Query By Criteria,是一种完全面向对象(比 HQL 更加面向对象)的对数据库查询技术。通过它对数据库进行查询时,应用程序不需要提供查询语句,而是通过 QBC API 中的相关的接口和类来设定需要查询的数据、查询条件等。

在 Hibernate 的早期版本中,通常通过以下方式使用 Criteria 查询数据库中的数据。

Criteria criteria = session.createCriteria(User.class);
criteria.add(Restrictions.eq("userName", "admin"));
List<Customer> list = criteria.list();

但 Hibernate5.2 之后,出于安全性考虑,这种方式已经基本废弃,现在 QBC 查询基本上都是使用 JPA(javax.persistence-api-xxx.jar)包中的 CriteriaBuilder(一个工厂类),来创建 CriteriaQuery 对象,以实现对数据库的操作。

QBC API 位于 javax.persistence.criteria 包中,主要包括以下接口:

接口功能描述
CriteriaBuilder用来生成 CriteriaQuery 实例对象的工厂。
CriteriaQueryQBC 主要的查询接口,通过它可以设定需要查询的数据。
Root指定需要检索的对象图的根节点对象。
Selection用来指定查询语句。
ExpressionSelection 接口的子接口,用来指定查询表达式。
PredicateExpression 接口的子接口,用来指定查询条件。

使用 QBC 查询时,需要以下步骤:

  1. 通过 Session 对象创建一个 CriteriaBuilder 实例;
  2. 通过 CriteriaBuilder 的实例对象创建一个 CriteriaQuery 的实例对象;
  3. 通过 CriteriaBuilder 对象创建一个 Root 的实例对象,并指定需要查询的对象图的根节点对象,Hibernate 会根据它来决定查询语句中的主表。
  4. 通过 CriteriaBuilder 提供的方法指定查询条件,并返回 Predicate 对象,Hibernate 根据它来决定查询语句中 where 子句的内容。
  5. 通过 Query 接口查询数据。

CriteriaBuilder 接口提供了一系列设定查询条件的方法,这些方法都返回 Predicate 对象,常用的设定条件查询的方法如下表。

方法描述
equal()等于
notEqual()不等于
gt()大于
ge()大于等于
lt()小于
le()小于等于
between()在……之间
like()相似
isNotEmpty()不为空
and()
or()

\1. 在 MyTest 类中,添加一个 testQbcQuery() 方法,在该方法中,使用 QBC 查询 user 表中的所有使用 163 邮箱的用户数据,代码如下。

/**
* QBC 查询
*/
@Test
public void testQbcQuery() {//加载 Hibernate 核心配置文件Configuration configuration = new Configuration().configure();//创建一个 SessionFactory 用来获取 Session 连接对象SessionFactory sessionFactory = configuration.buildSessionFactory();//获取session 连接对象Session session = sessionFactory.openSession();//开始事务Transaction transaction = session.beginTransaction();//获得 CriteriaBuilder 对象CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();//构建 CriteriaQuery 查询对象CriteriaQuery<User> criteria = criteriaBuilder.createQuery(User.class);//添加查询条件Root<User> from = criteria.from(User.class);Predicate like = criteriaBuilder.like(from.get("email"), "%@163.com%");criteria.where(criteriaBuilder.and(like));//获取结果集List<User> list = session.createQuery(criteria).getResultList();//遍历结果集for (User user : list) {System.out.println(user);}//提交事务transaction.commit();//释放资源session.close();sessionFactory.close();
}

\2. 执行测试方法 testQbcQuery() ,控制台输出如下。

Hibernate:selectuser0_.id as id1_0_,user0_.user_id as user_id2_0_,user0_.user_name as user_nam3_0_,user0_.password as password4_0_,user0_.email as email5_0_fromuser user0_whereuser0_.email like ?
net.biancheng.www.po.User{id=4, userId='003', userName='user2', password='user2', email='9dfasdfa@163.com'}
net.biancheng.www.po.User{id=5, userId='004', userName='user3', password='user3', email='76543345@163.com'}
net.biancheng.www.po.User{id=6, userId='005', userName='user4', password='user4', email='234543546@163.com'}

从以上控制台输出可知,我们成功地通过 QBC 查询到了 user 表中的多条数据。

SQL 查询

Hibernate 同样支持使用原生的 SQL 语句对数据库进行查询。

\1. 在 MyTest 类中,添加一个 testSqlQuery() 方法,该方法使用 SQL 查询 user 表中所有使用 163 邮箱的用户数据,代码如下。

/**
* SQL 查询
*/
@Test
public void testSqlQuery() {//加载 Hibernate 核心配置文件Configuration configuration = new Configuration().configure();//创建一个 SessionFactory 用来获取 Session 连接对象SessionFactory sessionFactory = configuration.buildSessionFactory();//获取session 连接对象Session session = sessionFactory.openSession();//开始事务Transaction transaction = session.beginTransaction();//构建 sql 查询NativeQuery sqlQuery = session.createSQLQuery("select * from user where email like '%163.com%'");sqlQuery.addEntity(User.class);//获得结果集List<User> resultList = sqlQuery.getResultList();//遍历结果集for (User user : resultList) {System.out.println(user);}//提交事务transaction.commit();//释放资源session.close();sessionFactory.close();
}

\2. 执行测试方法 testSqlQuery() ,控制台输出如下。

Hibernate:select*fromuserwhereemail like '%163.com%'
net.biancheng.www.po.User{id=4, userId='003', userName='user2', password='user2', email='9dfasdfa@163.com'}
net.biancheng.www.po.User{id=5, userId='004', userName='user3', password='user3', email='76543345@163.com'}
net.biancheng.www.po.User{id=6, userId='005', userName='user4', password='user4', email='234543546@163.com'}

从以上控制台输出可知,我们成功地使用 Hibernate 通过 SQL 查询到了 user 表中的多条数据。

Hibernate工作原理(图解)

在 Hibernate操作数据库一节的学习中,我们主要涉及到了 Configuration、SessionFactory、Session、Transaction 和 Query 等多个接口,这些接口在 Hibernate 运行时都扮演着十分重要的角色,本节我们就来介绍以一下 Hibernate 运行时的工作原理。

关于 Configuration、SessionFactory、Session、Transaction 和 Query 等接口的使用,我们会在 Hibernate 的核心接口一节中进行详细的讲解,

Hibernate 运行时的执行流程如下图。

Hibernate 工作流程
图1:Hibernate 工作流程图

由上图可知,Hibernate 工作流程一般分为如下 7 步:

  1. Hibernate 启动,Configruation 会读取并加载 Hibernate 核心配置文件和映射文件钟的信息到它实例对象中。
  2. 通过 Configuration 对象读取到的配置文件信息,创建一个 SessionFactory 对象,该对象中保存了当前数据库的配置信息、映射关系等信息。
  3. 通过 SessionFactory 对象创建一个 Session 实例,建立数据库连接。Session 主要负责执行持久化对象的增、删、改、查操作,创建一个 Session 就相当于创建一个新的数据库连接。
  4. 通过 Session 对象创建 Transaction(事务)实例对象,并开启事务。Transaction 用于事务管理,一个 Transaction 对象对应的事务可以包含多个操作。需要注意的是,Hibernate 的事务默认是关闭的,需要手动开启和关闭。
  5. Session 接口提供了各种方法,可以对实体类对象进行持久化操作(即对数据库进行操作),例如 get()、load()、save()、update()、saveOrUpdate() 等等,除此之外,Session 对象还可以创建Query 对象 或 NativeQuery 对象,分别使用 HQL 语句或原生 SQL 语句对数据库进行操作。
  6. 对实体对象持久化操作完成后,必须提交事务,若程序运行过程中遇到异常,则回滚事务。
  7. 关闭 Session 与 SessionFactory,断开与数据库的连接,释放资源。

hibernate.cfg.xml(Hibernate核心配置文件)

Hibernate 的常用配置文件主要分为 2 种:核心配置文件(hibernate.cfg.xml)和映射文件(Xxx.hbm.xml),它们主要用于配置数据库连接、事务管理、Hibernate 本身的配置信息以及 Hibernate 映射文件信息。

本节我们只讲解 Hibernate 核心配置文件,也即 hibernate.cfg.xml,后续将在《Hibernate 映射文件》一节中继续讲解 Hibernate 映射文件。

hibernate.cfg.xml 被称为 Hibernate 的核心配置文件,它包含了数据库连接的相关信息以及映射文件的基本信息。通常情况下,该配置文件默认放在项目的 src 目录下,当项目发布后,该文件会在项目的 WEB-INF/classes 路径下。

hibernate.cfg.xml 中通常可以进行以下配置,这些配置中有些是必需配置,有些则是可选配置。

<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-configuration PUBLIC        "-//Hibernate/Hibernate Configuration DTD//EN"        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"><hibernate-configuration>    <session-factory>        <!--使用 Hibernate 自带的连接池配置-->        <property name="connection.url">jdbc:mysql://localhost:3306/bianchengbang_jdbc</property>        <property name="hibernate.connection.username">root</property>        <property name="hibernate.connection.password">root</property>        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>        <!--hibernate 方言-->        <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>        <!--打印sql语句-->        <property name="hibernate.show_sql">true</property>        <!--格式化sql-->        <property name="hibernate.format_sql">true</property>        <!-- 加载映射文件 -->        <mapping resource="net/biancheng/www/mapping/User.hbm.xml"/>    </session-factory></hibernate-configuration>

我们知道,在 XML 配置文件中 dtd 信息十分重要,它规定了 XML 中的语法和格式。Hibernate 核心配置的 dtd 信息,可以在 Hibernate 核心 Jar 包(hibernate-core-xxx.jar)下的 org.hibernate.hibernate-configuration-3.0.dtd 中找到,初学者只需要复制并使用该 dtd 信息即可。

Hibernate 核心配置文件的根元素是 ,该元素中包含一个 子元素。

元素

在 元素中,包含了多个 子元素,这些元素用于配置 Hibernate 连接数据库的各种信息,例如,数据库的方言、驱动、URL、用户名、密码等。这些 property 属性中,有些是 Hibernate 的必需配置,有些则是可选配置,如下表。

property 属性名描述是否为必需属性
connection.url指定连接数据库 URL
hibernate.connection.username指定数据库用户名
hibernate.connection.password指定数据库密码
connection.driver_class指定数据库驱动程序
hibernate.dialect指定数据库使用的 SQL 方言,用于确定 Hibernate 自动生成的 SQL 语句的类型
hibernate.show_sql用于设置是否在控制台输出 SQL 语句
hibernate.format_sql用于设置是否格式化控制台输出的 SQL 语句
hibernate.hbm2ddl.auto当创建 SessionFactory 时,是否根据映射文件自动验证表结构或自动创建、自动更新数据库表结构。 该参数的取值为 validate 、update、create 和 create-drop
hibernate.connection.autocommit事务是否自动提交

Hibernate 能够访问多种关系型数据库,例如 MySQL、Oracle 等等,尽管多数关系型数据库都支持标准的 SQL 语言,但它们往往都还存在一些它们各自的独特的 SQL 方言,就像在不同地区的人既会说普通话,还能说他们各自的方言一样。hibernate.dialect 用于指定被访问的数据库的 SQL 方言,当 Hibernate 自动生成 SQL 语句或者使用 native 策略成主键时,都会参看该属性设置的方言。

元素

在 元素中,除了 property 元素外,还可以包含一个或多个 元素,它们用来指定 Hibernate 映射文件的信息,加载映射文件。

<mapping resource="net/biancheng/www/mapping/User.hbm.xml"/>

通常情况下,Hibernate 项目中存在多少映射文件,在核心配置文件中就配置多少个 元素。

但需要注意的是, 元素指定的是映射文件的路径,而不是包结构。

Hibernate映射文件(Xxx.hbm.xml)

Hibernate 的常用配置文件主要分为 2 种:核心配置文件(hibernate.cfg.xml)和映射文件(Xxx.hbm.xml),它们主要用于配置数据库连接、事务管理、Hibernate 本身的配置信息以及 Hibernate 映射文件信息。

上节《hibernate.cfg.xml》中讲解了 Hibernate 核心配置文件,本节我们继续讲解 Hibernate 映射文件。

Hibernate 映射文件用于在实体类对象与数据表之间建立映射关系,每个映射文件的结构基本相同,示例代码如下所示。

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping schema="bianchengbang_jdbc"><!-- name:类的全路径:--><!-- table:表的名称:(可以省略的.使用类的名称作为表名.)--><class name="net.biancheng.www.po.User" table="user"><!--主键--><id name="id" column="id"><!--主键生成策略--><generator class="native"></generator></id><property name="userId" column="user_id" type="java.lang.String"/><property name="userName" column="user_name"/><property name="password" column="password"/><property name="email" column="email"/></class>
</hibernate-mapping>

Hibernate 映射文件的 dtd 信息可以在 Hibernate 的核心 Jar 包(hibernate-core-xxx.jar)下的 org.hibernate.hibernate-mapping-3.0.dtd 文件中找到,初学者只需要复制并使用即可。

元素

是 Hibernate 映射文件的根元素,在该元素中定义的配置在整个映射文件中都有效。

元素中常用的属性配置及其功能描述如下表。

属性名描述是否必需
schema指定映射文件所对应的数据库名字空间
package为映射文件对应的实体类指定包名
catalog指定映射文件所对应的数据库目录
default-access指定 Hibernate 用于访问属性时所使用的策略,默认为 property。当 default-access=“property” 时,使用 getter 和 setter 方法访问成员变量;当 default-access = "field"时,使用反射访问成员变量。
default-cascade指定默认的级联风格
default-lazy指定 Hibernate 默认使用的延迟加载策略

元素

元素是 Hibernate 映射文件的根元素 的子元素,它主要用来定义一个实体类与数据库表之间的映射关系,该元素中包含的常用属性如下表。

属性名描述是否必需
name实体类的完全限定名(包名+类名),若根元素 中已经指定了 package 属性,则该属性可以省略包名
table对应的数据库表名。
catalog指定映射文件所对应的数据库 catalog 名称,若根元素 中已经指定 catalog 属性,则该属性会覆盖根元素中的配置。
schema指定映射文件所对应的数据库 schema 名称,若根元素 中已经指定 schema 属性,则该属性会覆盖根元素中的配置。
lazy指定是否使用延迟加载。

元素

通常情况下,Hibernate 推荐我们在持久化类(实体类)中定义一个标识属性,用于唯一地标识一个持久化实例(实体类对象),且标识属性需要映射到底层数据库的主键上。

在 Hibernate 映射文件中,标识属性通过 元素来指定,它是 元素的子元素,该元素中包含的常用属性如下表。

属性名描述是否必需
name与数据库表主键向对应的实体类的属性
column数据库表主键的字段名
type用于指定数据表中的字段需要转化的类型,这个类型既可以是 Hibernate 类型,也可以是 Java 类型

元素中通常还包含一个 元素,该元素通过 class 属性来指定 Hibernate 的主键生成策略。

<id name="id" column="id" type="integer"><!--主键生成策略--><generator class="native" ></generator>
</id>

Hibernate 提供了以下 7 主键生成策略,如下表。

主键生成策略说明
increment自动增长策略之一,适合 short、int、long 等类型的字段。该策略不是使用数据库的自动增长机制,而是使用 Hibernate 框架提供的自动增长方式,即先从表中查询主键的最大值, 然后在最大值的基础上+1。该策略存在多线程问题,一般不建议使用。
identity自动增长策略之一,适合 short、int、long 等类型的字段。该策略采用数据库的自动增长机制,但该策略不适用于 Oracle 数据库。
sequence序列,适合 short、int、long 等类型的字段。该策略应用在支持序列的数据库,例如 Oracle 数据库,但不是适用于 MySQL 数据库。
uuid适用于字符串类型的主键,采用随机的字符串作为主键。
native本地策略,Hibernate 会根据底层数据库不同,自动选择适用 identity 还是 sequence 策略,该策略也是最常用的主键生成策略。
assignedHibernate 框架放弃对主键的维护,主键由程序自动生成。
foreign主键来自于其他数据库表(应用在多表一对一的关系)。

元素

元素中可以包含一个或多个 子元素,它用于表示实体类的普通属性(除与数据表主键字段对应的属性之外的其他属性)和数据表中非主键字段的映射关系。该元素中包含的常用属性如下表。

属性名描述
name实体类属性的名称
column数据表字段名
type用于指定数据表中的字段需要转化的类型,这个类型既可以是 Hibernate 类型,也可以是 Java 类型
length数据表字段的长度
lazy该属性使用延迟加载,默认值是 false
unique是否对该字段使用唯一性约束。
not-null是否允许该字段为空

此外,在 Hibernate 映射文件中,父元素中子元素必须遵循一定的配置顺序,例如在 元素中必须先定义 元素,再定义 元素,否则 Hibernate 的 XML 解析器在运行时会报错。

Hibernate核心接口(5个)

在 Hibernate 中有 5 个常用的核心接口,它们分别是 Configuration 接口、SessionFactory 接口、Session 接口、Transaction 接口和 Query 接口。本节,我们就对这 5 个核心接口进行详细讲解。

1. Configuration

正如其名,Configuration 主要用于管理 Hibernate 配置信息,并在启动 Hibernate 应用时,创建 SessionFactory 实例。

在 Hibernate 应用启动时,需要获取一些基本信息,例如,数据库 URL、数据库用户名、数据库用户密码、数据库驱动程序和数据库方言等等。这些属性都可以在 Hibernate 的核心配置文件(hiberntae.cfg.xml)中进行设置。

创建 Configuration 实例

Hibernate 通常使用以下方式创建 Configuration 实例,此时 Hibernate 会自动在当前项目的类路径(CLASSPATH)中,搜索核心配置文件 hibernate.cfg.xml 并将其加载到内存中,作为后续操作的基础配置 。

Configuration configuration = new Configuration().configure();

若 Hibernate 核心配置文件没有在项目的类路径( src 目录)下,则需要在 configure() 方法中传入一个参数指定配置文件位置,示例代码如下:

Configuration configuration = new Configuration().configure("/net/biancheng/www/mapping/hibernate.cfg.xml");

加载映射文件

我们知道,在 Hibernate 的核心配置文件中, 元素用来指定 Hibernate 映射文件的位置信息,加载映射文件,但该元素并非核心配置文件中的必须元素,即我们可以不在 hibernate.cfg.xml 中指定映射文件的位置。此时,我们可以通过 2 种方式加载映射文件。

\1. 调用 Configuration 提供的 addResource() 方法,并在该方法中传入一个参数指定映射文件的位置,示例代码如下。

configuration.addResource("net/biancheng/www/mapping/User.hbm.xml");

\2. 调用 Configuration 提供的 addClass() 方法,并将与映射文件对应的实体类以参数的形式传入该方法中,示例代码如下。

configuration.addClass(User.class);

使用 addClass() 方法加载映射文件时,需要同时满足以下 2 个条件,否则将无法加载映射文件。

  • 映射文件的命名要规范,即映射文件要完全按照“实体类.hbm.xml ”的形式命名;
  • 映射文件要与实体类在同一个包下。

需要注意的是,Configuration 对象只存在于系统的初始化阶段,将 SessionFactory 创建完成后,它的使命就完成了。

2. SessionFactory

SessionFactory 对象用来读取和解析映射文件,并负责创建和管理 Session 对象。

SessionFactory 对象中保存了当前的数据库配置信息、所有映射关系以及 Hibernate 自动生成的预定义 SQL 语句,同时它还维护了 Hibernate 的二级缓存。

一个 SessionFactory 实例对应一个数据库存储源,Hibernate 应用可以从 SessionFactory 实例中获取 Session 实例。

SessionFactory 具有以下特点:

  • SessionFactory 是线程安全的,它的同一个实例可以被应用多个不同的线程共享。
  • SessionFactory 是重量级的,不能随意创建和销毁它的实例。如果应用只访问一个数据库,那么在应用初始化时就只需创建一个 SessionFactory 实例;如果应用需要同时访问多个数据库,那么则需要为每一个数据库创建一个单独的 SesssionFactory 实例。

SessionFactory 之所以是重量级的,是因为它需要一个很大的缓存,用来存放预定义 SQL 语句以及映射关系数据等内容。我们可以为 SessionFactory 配置一个缓存插件,它被称为 Hiernate 的二级缓存,此处了解即可,我们会在后面详细介绍。

创建 SessionFactory 对象

通常情况下,我们通过 Configuration 的 buildSessionFactory() 方法来创建 SessionFactory 的实例对象,示例代码如下。

SessionFactory sessionFactory = configuration.buildSessionFactory();

抽取工具类

由于 SessionFactory 是重量级的,它的实例不能随意创建和销毁,因此在实际开发时,我们通常会抽取出一个工具类,将 SessionFactory 对象的创建过程放在静态代码快中,以避免 SessionFactory 对象被多次创建。

例如,在 hibernate-demo 项目的 net.biancheng.www.utils 包下,创建一个工具类 HibernateUtils,代码如下。

package net.biancheng.www.utils;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
/**
* 工具类
*/
public class HibernateUtils {private static final Configuration configuration;private static final SessionFactory sessionFactory;//在静态代码块中创建 SessionFactory 对象static {configuration = new Configuration().configure();sessionFactory = configuration.buildSessionFactory();}//通过 SessionFactory 对象创建 Session 对象public static Session openSession() {return sessionFactory.openSession();}public static void main(String[] args) {
//        openSession();HibernateUtils hibernateUtils = new HibernateUtils();}
}

由以上代码可知,我们直接调用 HibernateUtils 的 getSession() 的方式,便可以获取 Session 对象。

3. Session

Session 是 Hibernate 应用程序与数据库进行交互时,使用最广泛的接口,它也被称为 Hibernate 的持久化管理器,所有持久化对象必须在 Session 的管理下才可以进行持久化操作。持久化类只有与 Session 关联起来后,才具有了持久化的能力。

Session 具有以下特点:

  • 不是线程安全的,因此应该避免多个线程共享同一个 Session 实例;
  • Session 实例是轻量级的,它的创建和销毁不需要消耗太多的资源。通常我们会将每一个Session 实例和一个数据库事务绑定,每执行一个数据库事务,不论执行成功与否,最后都因该调用 Session 的 Close() 方法,关闭 Session 释放占用的资源。

Session 对象维护了 Hibernate 的一级缓存,在显式执行 flush 之前,所有的持久化操作的数据都缓存在 Session 对象中。

这里的持久化指的是对数据库数据的保存、更新、删除和查询。持久化类指与数据库表建立了映射关系的实体类,持久化对象指的是持久化类的对象。

关于持久化以及一级缓存的概念,这里了解即可,我们会在 Hibernate 持久化类和 Hibernate 一级缓存中详细讲解。

创建 Session 对象

我们可以通过以下 2 个方法创建 Session 对象:

1.调用 SessionFactrory 提供的 openSession() 方法,来获取 Session 对象,示例代码如下。

//采用 openSession() 方法获取 Session 对象
Session session = sessionFactory.openSession();

\2. 调用SessionFactrory 提供的 getCurrentSession() 方法,获取 Session 对象,示例代码如下。

//采用 getCurrentSession() 方法获取 Session 对象
Session session = sessionFactory.getCurrentSession();

以上 2 种方式都能获取 Session 对象,但两者的区别在于:

  • 采用 openSession() 方法获取 Session 对象时,SessionFactory 直接创建一个新的 Session 实例,且在使用完成后需要调用 close() 方法进行手动关闭。
  • 采用 getCurrentSession() 方法创建的 Session 实例会被绑定到当前线程中,它在事务提交或回滚后会自动关闭。

Session 中的持久化方法

在 Session 中,提供了多个持久化的操作方法,其常用方法如下表。

方法描述
save()执行插入操作
update()执行修改操作
saveOrUpdate()根据参数,执行插入或修改操作
delete()执行删除操作
get()根据主键查询数据(立即加载)
load()根据主键查询数据(延迟加载)
createQuery()获取 Hibernate 查询对象
createSQLQuery()获取 SQL 查询对象

注意:Hibernate 中的 Session 与 Java Web 中的 HttpSession 没有任何关系,如无特别说明,本教程中的 Session 都指的是 Hibernate 中的 Session。

4. Transaction

Transaction 是 Hibernate 提供的数据库事务管理接口,它对底层的事务接口进行了封装。所有的持久化操作(即使是只读操作)都应该在事务管理下进行,因此在进行 CRUD 持久化操作之前,必须获得 Trasaction 对象并开启事务。

获取 Transaction 对象

我们可以通过 Session 提供的以下两个方法来获取 Transaction 对象:

\1. 调用 Session 提供的 beginTransaction() 方法获取 Transaction 对象,示例代码如下。

Transaction transaction = session.beginTransaction();

\2. 调用 Session 提供的 getTransaction() 方法获取 Transaction 对象,示例代码如下。

Transaction transaction1 = session.getTransaction();

以上两个方法均可以获取 Transaction 对象,但两者存在以下不同:

  • getTransaction() 方法:根据 Session 获得一个 Transaction 对象,但是并没有开启事务。
  • beginTransaction() 方法:是在根据 Session 获得一个 Transaction 对象后,又继续调用 Transaction 的 begin() 方法,开启了事务。

Transation 接口中提供了管理事务的方法,常用方法如下表。

方法描述
begin()该方法用于开启事务
commit()该方法用于提交事务
rollback()该方法用于回滚事务

提交或回滚事务

持久化操作执行完成后,需要调用了 Transaction 接口的 commit() 方法进行事务提交,只有在事务提交后,才能真正地将数据同步到数据库中。当发生异常时,则需要调用 rollback() 方法进行事务回滚,以避免数据发生错误,示例代码如下。

@Test
public void test() {User user = new User();Session session = HibernateUtils.openSession();//获取事务对象Transaction transaction = session.getTransaction();//开启事务transaction.begin();try {//执行持久化操作Serializable save = session.save(user);//提交事务transaction.commit();} catch (Exception e) {//发生异常回滚事务transaction.rollback();} finally {//释放资源session.close();}
}

5. Query

Query 是 Hibernate 提供的查询接口,主要用执行 Hinernate 的查询操作。Query 对象中通常包装了一个 HQL(Hibernate Query Language)语句,HQL 语句与 SQL 语句存在相似之处,但 HQL 语句是面向对象的,它使用的是类名和类的属性名,而不是表名和表中的字段名。HQL 能够提供更加丰富灵活、更为强大的查询能力,因此 Hibernate 官方推荐使用 HQL 进行查询。

在 Hibernate 应用中使用 Query 对象进行查询,通常需要以下步骤:

  1. 获得 Hibernate 的 Sesssin 对象;
  2. 定义 HQL 语句;
  3. 调用 Session 接口提供的 createQuery() 方法创建 Query 对象,并将 HQL 语句以参数的形式传入到该方法中;
  4. 若 HQL 语句中包含参数,则调用 Query 接口提供的 setXxx() 方法设置参数;
  5. 调用 Query 的 getResultList() 方法,进行查询,并获取查询结果集。

HQL 查询的实例,请参考 Hibernate 增删改查操作中的 HQL 查询,这里就不再赘述。

除了 getResultList() 方法外,Query 接口中还包含一些其他的常用方法,如下表。

方法描述
setXxx()Query 接口中提供了一系列 setXxx() 方法,用于设置查询语句中的参数。这些方法都需要两个参数,分别是:参数名或占位符位置、参数值。我们需要根据参数类型的不同,分别调用不同的 setXxx() 方法,例如 setString()、setInteger()、setLong()、setBoolean() 和 setDate() 等等。
Iterator iterate();该方法用于执行查询语句,并返回一个 Iterator 对象。我们可以通过返回的 Iterator 对象,遍历得到结果集。
Object uniqueResult();该方法用户执行查询,并返回一个唯一的结果。使用该方法时,需要确保查询结果只有一条数据,否则就会报错。
int executeUpdate();该方法用于执行 HQL 的更新和删除操作。
Query setFirstResult(int var1);该方法用户设置结果集第一条记录的位置,即设置从第几条记录开始查询,默认从 0 开始。
Query setMaxResults(int var1);该方法用于设置结果集的最大记录数,通常与 setFirstResult() 方法结合使用,用于限制结果集的范围,以实现分页功能。

除了数据库查询外,HQL 语句还可以进行更新和删除操操作,需要注意的是 HQL 并不支持 insert 操作,想要保存数据请使用 Session 接口提供的 save() 或 saveOrUpate() 方法。

下面我们通过一个实例,演示如何使用 HQL 进行更新和删除操作,代码如下。

@Test
public void testHqlInsert() {//获取 Session 对象Session session = HibernateUtils.openSession();//获取事务对象Transaction transaction = session.getTransaction();//开启事务transaction.begin();//更新操作Query query = session.createQuery("update User SET userName=:username,userId=:userId,email=:email where id=:id");//为更新语句设置参数query.setParameter("username", "HQL");query.setParameter("userId", "HQL");query.setParameter("email", "HQL");query.setParameter("id", 4);//执行更新操作int i = query.executeUpdate();if (i > 0) {System.out.println("成功修改了 " + i + " 条记录");}//删除操作Query query1 = session.createQuery("delete from User WHERE userId=?1");//为删除语句设置参数query1.setParameter(1, "005");//执行删除操作int d = query1.executeUpdate();if (d > 0) {System.out.println("成功删除了 " + d + " 条记录");}//提交事务transaction.commit();//释放资源session.close();
}

执行该测试方法,控制台输出如下。

Hibernate:updateusersetuser_name=?,user_id=?,email=?whereid=?
成功修改了 1 条记录
Hibernate:deletefromuserwhereuser_id=?
成功删除了 1 条记录

查看数据库 user 表,结果如下。

HQL 更新删除结果
图1:HQL 更新删除结果

从上图可知,我们成功地使用 HQL 进行更新和删除操作。

Hibernate持久化类详解

持久化类(Persistent Object )简称 PO,在 Hibernate 中, PO 是由 POJO(即 java 类或实体类)和 hbm 映射配置组成。

简单点说,持久化类本质上就是一个与数据库表建立了映射关系的普通 Java 类(实体类),例如 User 类与数据库中 user 表通过映射文件 User.hbm.xml 建立了映射关系,此时 User 就是一个持久化类。

持久化类的规范

持久化类需要遵守一定的规范,具体如下:

  • 持久化类中需要提供一个使用 public 修饰的无参构造器;
  • 持久化类中需要提供一个标识属性 OID,与数据表主键字段向对应,例如实体类 User 中的 id 属性。为了保证 OID 的唯一性,OID 应该由 Hibernate 进行赋值,尽量避免人工手动赋值;
  • 持久化类中所有属性(包括 OID)都要与数据库表中的字段相对应,且都应该符合 JavaBean 规范,即属性使用 private 修饰,且提供相应的 setter 和 getter 方法;
  • 标识属性应尽量使用基本数据类型的包装类型,例如 Interger,目的是为了与数据库表的字段默认值 null 保持一致;
  • 不能用 final 修饰持久化类。

持久化对象

“持久化对象”就是持久化类的实例对象,它与数据库表中一条记录相对应,Hibernate 通过操作持久化对象即可实现对数据库表的 CRUD 操作。

在 Hibernate 中,持久化对象是存储在一级缓存中的,一级缓存指 Session 级别的缓存,它可以根据缓存中的持久化对象的状态改变同步更新数据库,这里了解即可,我们会在后面的 Hibernate 一级缓存中详细介绍。

持久化对象的状态

Hibernate 是一款持久层的 ORM 框架,专注于数据的持久化工作。在进行数据持久化操作时,持久化对象可能处于不同的状态当中,这些状态可分为三种,分别为瞬时态、持久态和脱管态,如下表。

状态别名产生时机特点
瞬时态(transient)临时态或自由态由 new 关键字开辟内存空间的对象(即使用 new 关键字创建的对象)没有唯一标识 OID;未与任何 Session 实例建立关联关系;数据库中也没有与之相关的记录;
持久态(persistent)-当对象加入到 Session 的一级缓存中时,与 Session 实例建立关联关系时存在唯一标识 OID,且不为 null;已经纳入到 Session 中管理;数据库中存在对应的记录;持久态对象的任何属性值的改动,都会在事务结束时同步到数据库表中。
脱管态(detached)离线态或游离态持久态对象与 Session 断开联系时存在唯一标识 OID;与 Session 断开关联关系,未纳入 Session 中管理;一旦有 Session 再次关联该脱管对象,那么该对象就可以立马变为持久状态;脱管态对象发生的任何改变,都不能被 Hibernate 检测到,更不能提交到数据库中。

在 Hibernate 运行时,持久化对象的三种状态可以通过 Session 接口提供的 一系列方法进行转换。这三种状态之间的转换关系具体如下图。

持久化对象状态转换
图1:Hibernate 持久化对象状态转化图

通过上图可知,持久化对象的状态转换遵循以下规则:

  • 当一个实体类对象通过 new 关键字创建时,此时该对象就处于瞬时态。
  • 当执行 Session 接口提供的 save() 或 saveOrUpate() 方法,将瞬时态对象保存到 Session 的一级缓存中时,该对象就从瞬时态转换为了持久态。
  • 当执行 Session 接口提供的 evict()、close() 或 clear() 方法,将持久态对象与 Session 断开关联关系时,该对象就从持久态转换为了脱管态。
  • 当执行 Session 接口提供的 update()、saveOrUpdate() 方法,将脱管态对象重新与 Session 建立关联关系时,该对象会从脱管态转换为持久态。
  • 直接执行 Session 接口提供的 get()、load() 或 find() 方法从数据库中查询出的对象,是处于持久态的。
  • 当执行 Session 接口提供的 delete() 方法时,持久态对象就会从持久态转换为瞬时态。
  • 由于瞬时态和脱管态对象都不在 Session 的管理范围内,因此一段时间后,它们就会被 JVM 回收。

Hibernate一级缓存详解

Hibernate 是一款全自动 ORM 框架,它会在应用程序访问数据时,自动生成 SQL 语句并执行,因此开发人员不需要自己编写 SQL 语句,但这也造成它无法像 MyBatis 一样,能够直接从 SQL 层面严格控制其执行性能以及对数据库的访问频率,所以很容易出现性能不佳的情况。

为此,Hibernate 提供了多种性能优化手段(例如 HQL、懒加载策略、抓取策略以及缓存机制),其中缓存机制是 Hibernate 重要的优化手段之一,它可以有效地减少应用程序对数据库的访问的次数,提高应用程序的运行性能。

缓存

缓存是位于应用程序和永久性数据存储源(例如硬盘上的文件或者数据库)之间,用于临时存放备份数据的内存区域,通过它可以降低应用程序读写永久性数据存储源的次数,提高应用程序的运行性能。

缓存在系统中位置
图1:缓存在系统中位置

注:永久性数据存储源一般包括两种,数据库和硬盘上的文件,它们都可以永久的保存数据,但本教程中的永久性数据库存储源则是特指数据库,因此在后面的教程中,我们将使用数据库来代替永久性数据存储源的书法,特此说明。

缓存具有以下特点:

  • 缓存中的数据通常是数据库中数据的备份,两者中存放的数据完全一致,因此应用程序运行时,可以直接读写缓存中的数据,而不再对数据库进行访问,可以有效地降低应用程序对数据库的访问频率。
  • 缓存的物理介质通常是内存,永久性数据存储源的物理介质为硬盘或磁盘,而应用程序读取内存的速度要明显高于硬盘,因此使用缓存能够有效的提高数据读写的速度,提高应用程序的性能。
  • 由于应用程序可以修改(即“写”)缓存中的数据,为了保证缓存和数据库中的数据保持一致,应用程序通常会在在某些特定时刻,将缓存中的数据同步更新到数据库中。

Hibernate 也提供了缓存机制,当查询数据时,首先 Hibernate 会到缓存中查找,如果找到就直接使用,找不到时才从永久性数据存储源(通常指的是数据库)中检索,因此,把频繁使用的数据加载到缓存中,可以减少应用程序对数据库的访问频次,使应用程序的运行性能得以提升。

Hibernate 提供了两种缓存机制:一级缓存和二级缓存。下面,我们就对一级缓存进行介绍。

Hibernate 一级缓存

Hibernate 一级缓存是 Session 级别的缓存,它是由 Hibernate 管理的,不可卸载。

Hibernate 一级缓存是由 Session 接口实现中的一系列 Java 集合构成的,其生命周期与 Session 保持一致。

Hibernate 一级缓存中存放的数据是数据库中数据的备份,在数据库中数据以数据库记录的形式保存,而在 Hibernate 一级缓存中数据是以对象的形式存放的。

当使用 Hibernate 查询对象时,会首先从一级缓存中查找,若在一级缓存中找到了匹配的对象,则直接取出并使用;若没有在一级缓存中找到匹配的对象,则去数据库中查询对应的数据,并将查询到的数据添加到一级缓存中。由此可见,Hibernate 的一级缓存机制能够在 Session 范围内,有效的减少对数据库的访问次数,优化 Hibernate 的性能。

一旦对象被存入一级缓存中,除非用户手动清除,不然只要 Session 实例的生命周期没有结束,存放在其中的对象就会一直存在。当 Session 关闭时,Session 的生命周期结束,该 Session 所管理的一级缓存也会立即被清除;

一级缓存的特点

Hibernate 一级缓存具有以下特点:

  • 一级缓存是 Hibernate 自带的,默认是开启状态,无法卸载。
  • Hibernate 一级缓存中只能保存持久态对象。
  • Hibernate 一级缓存的生命周期与 Session 保持一致,且一级缓存是 Session 独享的,每个 Session 不能访问其他的 Session 的缓存区,Session 一旦关闭或销毁,一级缓存中的所有对象将全部丢失。
  • 当通过 Session 接口提供的 save()、update()、saveOrUpdate() 和 lock() 等方法,对对象进行持久化操作时,该对象会被添加到一级缓存中。
  • 当通过 Session 接口提供的 get()、load() 方法,以及 Query 接口提供的 getResultList、list() 和 iterator() 方法,查询某个对象时,会首先判断缓存中是否存在该对象,如果存在,则直接取出来使用,而不再查询数据库;反之,则去数据库查询数据,并将查询结果添加到缓存中。
  • 当调用 Session 的 close() 方法时,Session 缓存会被清空。
  • 一级缓存中的持久化对象具有自动更新数据库能力。
  • 一级缓存是由 Hibernate 维护的,用户不能随意操作缓存内容,但用户可以通过 Hibernate 提供的方法,来管理一级缓存中的内容,如下表。
返回值类型方法描述
voidclear()该方法用于清空一级缓存中的所有对象。
voidevict(Object var1)该方法用于清除一级缓存中某一个对象。
voidflush() throws HibernateException该方法用于刷出缓存,使数据库与一级缓存中的数据保持一致。

示例 1

下面,我们通过一个实例,来验证 Hibernate 一级缓存是否真的存在。

\1. 在 hibernate-demo 的单元测试类 MyTest 中,添加一个名为 testCacheExist 的方法,代码如下。

@Test
public void testCacheExist() {Session session = HibernateUtils.openSession();Transaction transaction = session.getTransaction();transaction.begin();//第一次查询User user = session.get(User.class, 1);System.out.println("第一次查询:" + user);//第二次查询User user2 = session.get(User.class, 1);System.out.println("第二次查询:" + user2);transaction.commit();session.close();
}

\2. 执行该测试方法,控制台输出如下。

Hibernate:selectuser0_.id as id1_0_0_,user0_.user_id as user_id2_0_0_,user0_.user_name as user_nam3_0_0_,user0_.password as password4_0_0_,user0_.email as email5_0_0_fromuser user0_whereuser0_.id=?第一次查询:net.biancheng.www.po.User{id=1, userId='001', userName='admin', password='admin', email='12345678@qq.com'}
第二次查询:net.biancheng.www.po.User{id=1, userId='001', userName='admin', password='admin', email='12345678@qq.com'}

从控制台输出可以看出,只有第一次查询时,去数据库查询并执行了 SQL,第二次查询时,虽然没有执行任何 SQL,但也依然得到了查询结果。

这是因为第一次到数据库查询数据时,就将查询结果添加到了一级缓存中,当第二次查询时,就会直接从一级缓存中获取结果,而并没有到数据库中查询,因此没有任何 SQL 输出。

快照区

Hibernate 的缓存机制,可以有效的减少应用程序对数据库的访问次数,但该机制有一个非常重要的前提,那就是必须确保一级缓存中的数据域与数据库的数据保持一致,为此 Hibernate 中还提供了快照技术。

Hibernate 的 Session 中,除了一级缓存外,还存在一个与一级缓存相对应的快照区。当 Hibernate 向一级缓存中存入数据(持久态对象)时,还会复制一份数据存入快照区中,使一级缓存和快照区的数据完全相同。

当事务提交时,Hibernate 会检测一级缓存中的数据和快照区的数据是否相同。若不同,则 Hibernate 会自动执行 update() 方法,将一级缓存的数据同步更新到数据库中,并更新快照区,这一过程被称为刷出缓存(flush);反之,则不会刷出缓存。

默认情况下,Session 会在以下时间点刷出缓存:

  • 当应用程序调用 Transaction 的 commit() 方法时, 该方法先刷出缓存(session.flush()),然后再向数据库提交事务(tx.commit());
  • 当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,会先刷出缓存,以保证查询结果能够反映持久化对象的最新状态;
  • 手动调用 Session 的 flush() 方法。

示例 2

下面,我们通过一个实例验证缓存刷出。

在单元测试类 MyTest 中,添加一个名为 testCacheFlush 的测试方法,代码如下。

@Test
public void testCacheFlush() {Session session = HibernateUtils.openSession();Transaction transaction = session.getTransaction();transaction.begin();//查询,并将结果对象添加到一级缓存和快照区中User user = session.get(User.class, 1);System.out.println("查询结果为:" + user);//修改结果对象user.setUserName("缓存刷出 name");//提交事务transaction.commit();//释放资源session.close();
}

执行该测试方法,控制台输出如下。

Hibernate:selectuser0_.id as id1_0_0_,user0_.user_id as user_id2_0_0_,user0_.user_name as user_nam3_0_0_,user0_.password as password4_0_0_,user0_.email as email5_0_0_fromuser user0_whereuser0_.id=?查询结果为:net.biancheng.www.po.User{id=1, userId='001', userName='admin', password='admin', email='12345678@qq.com'}Hibernate:updateusersetuser_id=?,user_name=?,password=?,email=?whereid=?

查看数据库中的 user 表,结果如下图。

img
图2:刷出缓存结果

Hibernate关联映射(非常详细)

在前面的学习中,我们所涉及的都是基于单表的操作,但在实际的开发过程中,基本上都是同时对多张表的操作,且这些表都存在一定的关联关系。

Hibernate 是一款基于 ORM 设计思想的框架,它将关系型数据库中的表与我们 Java 实体类进行映射,表中的记录对应实体类的对象,而表中的字段对应着实体类中的属性。Hibernate 进行增删改查等操作时,不再直接操作数据库表,而是对与之对应的实体类对象进行处理。那么,Hibernate 是如何处理多表关联问题的呢?本节我们针对此问题进行介绍。

关联映射

在关系型数据库中,多表之间存在着三种关联关系,分别为一对一、一对多和多对多,如图 1 所示。

Hibernate 关联关系映射
图1:多表关联关系

关联关系描述的是数据库表之间的引用关系,而 Hibernate 关联映射指的就是与数据库表对应的实体类之间的引用关系。

在数据库中,如果两张表想要建立关联关系,就需要外键来连接它们,数据库表之间的关系是没有方向性的,且彼此是透明的。而在 Java 中,如两个类想要建立关系的话,那就需要这两个类都通过属性(变量)来管理对方的引用,以达到建立关联关系的目的,Hibernate 关联映射也是通过这种方式实现的。

一对多

在三种关联关系中,一对多(或者多对一)是最常见的一种关联关系。

在关系型数据库中,一对多映射关系通常是由“多”的一方指向“一”的一方。在表示“多”的一方的数据表中增加一个外键,指向“一”的一方的数据表的主键,“一”的一方称为主表,而“多”的一方称为从表。

img

图2:数据库映射一对多关联关系

图 1 说明如下:

  • student 表为学生表,id 为学生表的主键,name 表示学生名称;
  • grade 表为班级表,id 为班级表的主键,name 表示班级名称;
  • gid 为学生表的外键,指向班级表的主键 id;

使用 Hibernate 映射“一对多”关联关系时,需要如下步骤:

  • 在“多”的一方的实体类中,引入“一”的一方实体类对象作为其属性,并在映射文件中通过 标签进行映射;
  • 在“一”的一方的实体类中,以 Set 集合的形式引入“多”的一方实体类对象作为其属性,并在映射文件中通过 标签进行映射。

学生和班级之间的关系,是典型的一对多关联关系,一个学生只能属于一个班级,而一个班级可以有多个学生,下面我们以学生(Student)和班级(grade)为例,演示如何使用 Hibernate 建立一对多关联映射。

创建实体类

\1. 在 net.biancheng.www.po 包中,创建一个名为 Student 的实体类,代码如下。

package net.biancheng.www.po;public class Student {    private Integer id;    private String name;    //持有实体类 Grade 的一个引用,维护多对一关系    private Grade grade;    public Student() {    }    public Student(Integer id, String name) {        this.id = id;        this.name = name;    }    public Integer getId() {        return id;    }    public void setId(Integer id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Grade getGrade() {        return grade;    }    public void setGrade(Grade grade) {        this.grade = grade;    }    @Override    public String toString() {        return "Student{" +                "id=" + id +                ", name='" + name + '\'' +                ", grade=" + grade +                '}';    }}

\2. 在 net.biancheng.www.po 包中,创建一个名为 Grade 的实体类,代码如下。

package net.biancheng.www.po;import java.util.HashSet;import java.util.Set;public class Grade {    private Integer Id;    private String name;    //持有 Student 引用的集合,来维护一对多关联关系    private Set<Student> students = new HashSet<>();    public Integer getId() {        return Id;    }    public void setId(Integer id) {        Id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Set<Student> getStudents() {        return students;    }    public void setStudents(Set<Student> students) {        this.students = students;    }    @Override    public String toString() {        return "Grade{" +                "Id=" + Id +                ", name='" + name +                '}';    }}

建立映射

\1. 在 net.biancheng.www.mapping 包中,创建 Student 的映射文件 Student.hbm.xml,配置如下。

<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-mapping PUBLIC        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping>    <class name="net.biancheng.www.po.Student" table="student" schema="bianchengbang_jdbc" lazy="true">        <!--主键映射-->        <id name="id" column="id" type="java.lang.Integer">            <!--主键生成策略-->            <generator class="native"></generator>        </id>        <property name="name" column="name" length="100" type="java.lang.String"></property>        <!--维护关联关系-->        <many-to-one name="grade" class="net.biancheng.www.po.Grade" column="gid"/>    </class></hibernate-mapping>

从 Student(学生)的角度看,Student 和 Grade 的关系为多对一,因此 Student 映射文件需要通过 来维护 Student 与 Grade 的多对一关联关系。

标签通常会包含以下常用属性,如下表。

属性名描述
name用来设置关联对象的属性名称
column用来设置实体类所对应的数据库表的外键的字段名称
class用来设置关联对象类的完全限定名(包名+类名)

\2. 在 net.biancheng.www.mapping 包中,创建 Grade 的映射文件 Grade.hbm.xml,配置如下。

<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-mapping PUBLIC        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping>    <class name="net.biancheng.www.po.Grade" table="grade" schema="bianchengbang_jdbc">        <id name="id" column="id" type="java.lang.Integer">            <generator class="native"></generator>        </id>        <property name="name" column="name" length="100" type="java.lang.String"/>        <!--使用 set 元素维护一对多关联关系-->        <set name="students">            <key column="gid"></key>            <one-to-many class="net.biancheng.www.po.Student"></one-to-many>        </set>    </class></hibernate-mapping>

从 Grade(班级)的角度看,Student 和 Grade 是一对多的关系,因此 Grade 的映射文件中,需要通过 标签来维护 Grade 与 Student 的一对多关联关系。

标签中通常会包含以下属性和子标签,如下表。

名称类型描述
name属性关联对象引用(集合类型)的属性名称;
子标签用于设置关联对象所对应的数据库表的外键,其 colum 属性用于指定外键的字段名称。
子标签用于维护一对多关系,其 class 属性用来设置关联对象类的完全限定名(包名+类名)。

\5. 在 Hibernate 核心配置文件 hibernate.cfg.xml 中,使用 元素来指定映射文件 Student.hbm.xml 和 Grade.hbm.xml 的位置信息,配置代码如下。

<mapping resource="net/biancheng/www/mapping/Grade.hbm.xml "/><mapping resource="net/biancheng/www/mapping/Student.hbm.xml" />

测试

\1. 在测试类 MyTest 中,添加一个 addCascade() 方法,代码如下。

/*** 一对多*/@Testpublic void addCascade() {    Session session = HibernateUtils.openSession();    Transaction transaction = session.beginTransaction();    //创建一个班级对象    Grade grade = new Grade();    grade.setName("一年级");    //创建学生对象    Student student = new Student();    student.setName("小明");    //设置学生的班级    student.setGrade(grade);    //创建学生对象    Student student2 = new Student();    student2.setName("小红");    //设置学生的班级    student2.setGrade(grade);    //设置班级内有哪些学生    grade.getStudents().add(student);    grade.getStudents().add(student2);    //保存学生信息    session.save(student);    session.save(student2);    //保存班级信息    session.save(grade);    //提交事务    transaction.commit();    //释放资源    session.close();}

\2. 执行该测试方法,控制台输出如下。

Hibernate:create table grade (id integer not null auto_increment,name varchar(100),primary key (id)) engine=MyISAM
Hibernate:create table student (id integer not null auto_increment,name varchar(100),gid integer,primary key (id)) engine=MyISAMHibernate:alter table studentadd constraint FKrahcsicxucn7bhee8empkb42cforeign key (gid)references grade (id)Hibernate:insertintograde(name)values(?)Hibernate:insertintostudent(name, gid)values(?, ?)Hibernate:insertintostudent(name, gid)values(?, ?)Hibernate:updatestudentsetgid=?whereid=?Hibernate:updatestudentsetgid=?whereid=?

从控制台的输出可以看到,除了建表操作外,数据库还执行了 3 条 insert 语句和 2 条 update 语句。

\3. 查询数据库表 student 中的数据,结果如下。

idnamegid
1小明1
2小红1

\4. 查询数据库表 grade 中的数据,结果如下。

idname
1一年级

多对多

在实际的应用中,“多对多”也是一种常见的关联关系,例如学生和课程的关系,一个学生可以选修多门课程,一个课程可以被多名学生选修。

在关系型数据库中,是无法直接表达“多对多”关联关系的,我们一般会采用新建一张中间表,将一个“多对多”关联拆分为两个“一对多”关联解决此问题,如下图。

img
图3:多对多:数据库表设计方案

图 1 说明如下:

  • student 表为学生表,id 为学生表的主键,name 表示学生名称;
  • course 表为课程表,id 为课程表的主键,name 表示课程名称;
  • student_course 表为中间表,其中字段 cid 和 sid 是中间表的两个外键,分别指向 student 表和 course 表的主键 id;
  • 在 student_course 表中,sid 和 cid 是该表的联合主键。

Hibernate 在映射“多对多”关联关系时,需要在两个实体类中,分别以 Set 集合的方式引入对方的对象,并在映射文件中通过 标签进行映射。

下面我们以学生(Student)和课程(Course)为例,演示如何使用 Hibernate 建立多对多关联映射。

创建实体类

\1. 在 net.biancheng.www.po 包下创建一个名为 Course 的实体类,并以 Set 集合的形式引入 Student 对象作为其属性,来维护 Course与 Student 之间的多对多关联关系,代码如下。

package net.biancheng.www.po;import java.util.HashSet;import java.util.Set;/*** 课程实体类*/public class Course {    private Integer id;    private String name;    //学生 Student 的集合作为其属性,维护多对多关联关系    private Set<Student> students = new HashSet<>();    public Integer getId() {        return id;    }    public void setId(Integer id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Set<Student> getStudents() {        return students;    }    public void setStudents(Set<Student> students) {        this.students = students;    }    @Override    public String toString() {        return "Course{" +                "id=" + id +                ", name='" + name +                '}';    }}

\2. 修改 Student 类的代码,在 Student 类中以 Set 的形式引入 Course 对象作为其属性,来维护 Student 与 Course 之间的多对多关联关系,代码如下。

package net.biancheng.www.po;import java.util.HashSet;import java.util.Set;/*** 学生实体类*/public class Student {    private Integer id;    private String name;    private Grade grade;    //将 Course 对象的集合作为其属性,以维护它们之间的多对多关联关系    private Set<Course> courses = new HashSet<>();    public Student() {    }    public Set<Course> getCourses() {        return courses;    }    public void setCourses(Set<Course> courses) {        this.courses = courses;    }    public Student(Integer id, String name) {        this.id = id;        this.name = name;    }    public Integer getId() {        return id;    }    public void setId(Integer id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Grade getGrade() {        return grade;    }    public void setGrade(Grade grade) {        this.grade = grade;    }    @Override    public String toString() {        return "Student{" +                "id=" + id +                ", name='" + name + '\'' +                ", grade=" + grade +                '}';    }}

建立映射

\1. 在 net.biancheng.www.mapping 包中,创建 Course 的映射文件 Coures.hbm.xml,具体配置如下。

<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-mapping PUBLIC        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping>    <class name="net.biancheng.www.po.Course" table="course" schema="bianchengbang_jdbc">        <id name="id" column="id" >            <generator class="native"></generator>        </id>        <property name="name" column="name" length="100"/>               <set name="students" table="student_course" cascade="save-update">            <key column="cid"></key>            <many-to-many class="net.biancheng.www.po.Student" column="sid"></many-to-many>        </set>    </class></hibernate-mapping>

\2. 修改 Student.hbm.xml 的配置,在其中添加 标签映射关联关系,配置如下。

<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-mapping PUBLIC        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping>    <class name="net.biancheng.www.po.Student" table="student" schema="bianchengbang_jdbc" lazy="true">        <!--主键映射-->        <id name="id" column="id" type="java.lang.Integer">            <!--主键生成策略-->            <generator class="native"></generator>        </id>        <property name="name" column="name" length="100" type="java.lang.String"></property>        <!--维护关联关系-->        <many-to-one name="grade" class="net.biancheng.www.po.Grade" column="gid"/>        <set name="courses" table="student_course" lazy="false">            <key column="sid"></key>            <many-to-many class="net.biancheng.www.po.Course" column="cid"></many-to-many>        </set>    </class></hibernate-mapping>

Hibernate 在映射文件中,通过 标签来映射“多对多”关联关系,其常用属性和子标签说明如下表。

名称类型说明
name属性关联对象的名称
table属性中间表的表明
lazy属性是否启用懒加载
子标签 标签的子标签,用于映射中间表的外键字段;其 colum 属性,用于指定中间表的哪个外键指向当前数据库表的主键。
子标签 标签的子标签,用于关联实体类,该子标签包含以下属性: class 属性:用于指定关联实体类的完全限定名(包名+类名);column 属性:用于指定中间表的哪个外键指向关联数据库表的主键。

将映射文件加入核心配置文件

在 Hibernate 核心配置文件 hibernate.cfg.xml 中,使用 元素来指定映射文件 Course.hbm.xml 的位置信息。

<!--指定课程的映射文件--><mapping resource="net/biancheng/www/mapping/Course.hbm.xml" />

测试

\1. 在测试类 MyTest 中,添加一个 testManyToManySave() 方法,代码如下。

/*** 多对多* 保存操作*/@Testpublic void testManyToManySave() {    Session session = HibernateUtils.openSession();    Transaction transaction = session.beginTransaction();       //新建学生和班级信息    Grade grade = new Grade();    grade.setName("三年级");    Student student = new Student();    student.setName("选课学生1");    student.setGrade(grade);    Student student2 = new Student();    student2.setName("选课学生2");    student2.setGrade(grade);    grade.getStudents().add(student);    grade.getStudents().add(student2);       //新建三个课程    Course course = new Course();    course.setName("Java");    Course course2 = new Course();    course2.setName("PHP");    Course course3 = new Course();    course3.setName("C++");    //学生选课    course.getStudents().add(student);    course.getStudents().add(student2);    course3.getStudents().add(student);    course3.getStudents().add(student2);       //保存操作    session.save(student);    session.save(student2);    session.save(grade);    session.save(course);    session.save(course2);    session.save(course3);    //提交事务    transaction.commit();    //释放资源    session.close();}

\2. 执行该测试方法,控制台输出如下。

Hibernate:alter table studentadd constraint FKrahcsicxucn7bhee8empkb42cforeign key (gid)references grade (id)
Hibernate:alter table student_courseadd constraint FKkx4bkddvbfs0ese9v7hc5rycgforeign key (cid)references course (id)
Hibernate:alter table student_courseadd constraint FK8era63dfxi3csvjpresf6fdguforeign key (sid)references student (id)Hibernate:insertintostudent(name, gid)values(?, ?)Hibernate:insertintostudent(name, gid)values(?, ?)Hibernate:insertintograde(name)values(?)Hibernate:insertintocourse(name)values(?)Hibernate:insertintocourse(name)values(?)Hibernate:insertintocourse(name)values(?)Hibernate:updatestudentsetname=?,gid=?whereid=?Hibernate:updatestudentsetname=?,gid=?whereid=?Hibernate:updatestudentsetgid=?whereid=?Hibernate:updatestudentsetgid=?whereid=?Hibernate:insertintostudent_course(cid, sid)values(?, ?)Hibernate:insertintostudent_course(cid, sid)values(?, ?)Hibernate:insertintostudent_course(cid, sid)values(?, ?)Hibernate:insertintostudent_course(cid, sid)values(?, ?)

从控制台输出可知,除了建表和 insert 语句外,Hibernate 还执行了 update 语句,而这些 update 语句正是由于使用双向关联而产生的多余 SQL 语句,它们会导致程序的运行效率降低。

\3. 到数据库中,分别查询学生表(stduent)、班级表(grade)、课程表(course)和中间表(student_course)中的数据,结果如下。

img

图4:多对多-查询结果

反转

在前面介绍“一对多”还是“多对多”关联关系时,我们都采用是双向关联,即关联的双方都对关联关系进行了维护,例如在学生和班级这种一对多关联中,既描述了学生与班级的关系,又描述了班级与学生的关系。

这种双向关联的方式可以让 Hibernate 同时控制双方的关系,但在程序运行时,却很容易产生多余的 SQL 语句,造成重复操作的问题。此时,我们可以使用 Hibernate 提供的反转(inverse)功能,来解决此问题。

在映射文件的 标签中,有一个 inverse(反转)属性,它的作用是控制关联的双方由哪一方管理关联关系。

inverse 属性的取值是 boolean 类型的,当 inverse 属性取值为 false(默认值)时,表示由当前这一方管理双方的关联关系,如果双方 inverse 属性都为 false,双方将同时管理关联关系;取值为 true 时,表示当前一方放弃控制权,由对方管理双方的关联关系。

在一对多关联关系中,通常我们会将“一”的一方的 inverse 属性取值为 true,即由“多”的一方来维护关联关系;而在多对多关联关系中,则任意设置一方的 inverse 属性为 true 即可。

由于学生和班级之间的关联关系是一对多,而学生和课程之间的关联关系是多对多,因此我们可以将这两种关联关系的控制权都交给学生(Student)。下面我们就通过一个实例来演示反转功能。

示例

\1. 修改映射文件 Grade.hbm.xml 的配置,在其 标签中,使用 inverse(反转)属性,并将该属性的值设置为 true,使其丧失对关联关系的控制权,由 Student 来管理它们之间的关联关系,配置如下。

<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-mapping PUBLIC        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping>    <class name="net.biancheng.www.po.Grade" table="grade" schema="bianchengbang_jdbc">        <id name="id" column="id" type="java.lang.Integer">            <generator class="native"></generator>        </id>        <property name="name" column="name" length="100" type="java.lang.String"/>        <!--设置 inverse 属性为 true,使其丧失对关联关系的控制权,由 Student 来管理关联关系-->        <set name="students" inverse="true">            <key column="gid"></key>            <one-to-many class="net.biancheng.www.po.Student"></one-to-many>        </set>    </class></hibernate-mapping>

\2. 修改映射文件 Course.hbm.xml 的配置,在其 标签中,使用 inverse(反转)属性,并将该属性的值设置为 true,使其丧失对关联关系的控制权,由 Student 来管理它们之间的关联关系,配置如下。

<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-mapping PUBLIC        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping>    <class name="net.biancheng.www.po.Course" table="course" schema="bianchengbang_jdbc">        <id name="id" column="id">            <generator class="native"></generator>        </id>        <property name="name" column="name" length="100"/>        <!--设置 inverse 属性为 true,使其丧失对关联关系的控制权,由 Student 来管理关联关系-->        <set name="students" table="student_course" inverse="true">            <key column="cid"></key>            <many-to-many class="net.biancheng.www.po.Student" column="sid"></many-to-many>        </set>    </class></hibernate-mapping>

\3. 由于所有的关联关系都已经交给了 Student 进行管理,因此我们在测试类中,只能通过 Student 对关联关系进行维护。修改测试类 MyTest 的 testManyToManySave() 方法,代码如下。

/*** 多对多* 保存操作*/@Testpublic void testManyToManySave() {    Session session = HibernateUtils.openSession();    Transaction transaction = session.getTransaction();    transaction.begin();       //新建一个班级信    Grade grade = new Grade();    grade.setName("三年级(反转)");       //新建两个学生    Student student = new Student();    student.setName("选课学生1(反转)");    Student student2 = new Student();    student2.setName("选课学生2(反转)");       //新建三个课程    Course course = new Course();    course.setName("Java(反转)");    Course course2 = new Course();    course2.setName("PHP(反转)");    Course course3 = new Course();    course3.setName("C++(反转)");    //由于所有的关联关系控制权都交给了Student,因此只能由Stduent对关联关系进行维护    student.setGrade(grade);    student.getCourses().add(course);    student.getCourses().add(course2);    student.getCourses().add(course3);    student2.setGrade(grade);    student2.getCourses().add(course);    student2.getCourses().add(course3);    //保存数据    session.save(course);    session.save(course2);    session.save(course3);    session.save(grade);    session.save(student);    session.save(student2);    //提交事务    transaction.commit();    session.close();}

执行该测试方法,控制台输出如下。

Hibernate:alter table studentadd constraint FKrahcsicxucn7bhee8empkb42cforeign key (gid)references grade (id)
Hibernate:alter table student_courseadd constraint FKkx4bkddvbfs0ese9v7hc5rycgforeign key (cid)references course (id)
Hibernate:alter table student_courseadd constraint FK8era63dfxi3csvjpresf6fdguforeign key (sid)references student (id)Hibernate:insertintocourse(name)values(?)Hibernate:insertintocourse(name)values(?)Hibernate:insertintocourse(name)values(?)Hibernate:insertintograde(name)values(?)Hibernate:insertintostudent(name, gid)values(?, ?)Hibernate:insertintostudent(name, gid)values(?, ?)Hibernate:insertintostudent_course(sid, cid)values(?, ?)Hibernate:insertintostudent_course(sid, cid)values(?, ?)Hibernate:insertintostudent_course(sid, cid)values(?, ?)Hibernate:insertintostudent_course(sid, cid)values(?, ?)Hibernate:insertintostudent_course(sid, cid)values(?, ?)

从控制台输出可以看出,使用了 inverse(反转)功能后,并没有产生多余的 update 语句。

级联

Hibernate 还提供了级联功能,当拥有关联关系控制权的一方,对数据库进行操作时,为了使数据保持同步,其关联对象也执行同样的操作。

在 Hibernate 的映射文件中有一个 cascade 属性,该属性用于设置关联对象是否进行级联操作,其常用属性值,如下表。

属性描述
save-update在进行保存或更新时,进行级联操作。
delete在进行删除操作时,进行级联操作。
delete-orphan孤儿删除,删除和当前对象解除关联关系的对象。 该属性值仅在关联关系为一对多时有效,因此只有此时才会存在父子关系,其中“一”的一方为“父方”,“多”的一方为“子方”; 例如,班级和学生是一对多的关系,其中班级为“父方”,学生为“子方”,当学生与班级解除了关联关系后,其外键被重置为了 null,那么这个学生也就失去了他的学生身份,这种记录就需要从学生表中删除。
all所有情况下均进行级联操作, delete-orphan (孤儿删除)除外。
all-delete-orphan所有情况下均进行级联操作, 包括 delete-orphan (孤儿删除)。
none默认值,表示所有情况下,均不进行级联操作。

下面我们以“班级-学生-课程”为例,演示级联保存、级联删除以及孤儿删除等。

级联保存

班级和学生的关联关系为一对多,而学生与课程的关联关系则为多对多,基于这些关联关系,我们可以在保存班级信息时,级联保存学生和课程信息。

\1. 修改映射文件 Grade.hbm.xml,在 标签中,设置 cascade 属性的值为 save-update,以达到保存班级信息时,级联保存学生信息的目的,具体配置如下。

<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-mapping PUBLIC        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping>    <class name="net.biancheng.www.po.Grade" table="grade" schema="bianchengbang_jdbc">        <id name="id" column="id" type="java.lang.Integer">            <generator class="native"></generator>        </id>        <property name="name" column="name" length="100" type="java.lang.String"/>        <!--设置 cascade 属性为 save-update,在保存班级信息时,级联保存学生信息-->        <set name="students" inverse="false" cascade="save-update">            <key column="gid"></key>            <one-to-many class="net.biancheng.www.po.Student"></one-to-many>        </set>    </class></hibernate-mapping>

注意:使用 cascade 属性进行级联操作时,其必须具备管理或控制关联关系的能力,即其 inverse 属性必须取值为 false(默认值)。

\2. 修改映射文件 Student.hbm.xml,在 标签中,设置 cascade 属性的值为 save-update,以达到保存学生信息时,级联保存选课信息的目的,具体配置如下。

<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-mapping PUBLIC        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping>    <class name="net.biancheng.www.po.Student" table="student" schema="bianchengbang_jdbc" lazy="true">        <id name="id" column="id" type="java.lang.Integer">            <generator class="native"></generator>        </id>        <property name="name" column="name" length="100" type="java.lang.String"></property>        <many-to-one name="grade" class="net.biancheng.www.po.Grade" column="gid" />        <!--设置 cascade 属性为 save-update,通过 student 进行级联保存-->        <set name="courses" table="student_course" cascade="save-update">            <key column="sid"></key>            <many-to-many class="net.biancheng.www.po.Course" column="cid"></many-to-many>        </set>    </class></hibernate-mapping>

\3. 在测试类 MyTest 中,添加一个 testCascadeSave() 用来测试级联保存,代码如下。

@Testpublic void testCascadeSave() {    Session session = HibernateUtils.openSession();    Transaction transaction = session.getTransaction();    //开启事务    transaction.begin();    //创建班级    Grade grade = new Grade();    grade.setName("三年级");    Grade grade2 = new Grade();    grade2.setName("四年级");    //新建三个课程    Course course = new Course();    course.setName("Java 基础");    Course course2 = new Course();    course2.setName("Java 面向对象 ");    Course course3 = new Course();    course3.setName("Java 高级特性");    //创建是三个学生    Student student = new Student();    student.setName("张三");    //学生选课    student.getCourses().add(course);    student.getCourses().add(course2);    student.getCourses().add(course3);    Student student2 = new Student();    student2.setName("李四");    //学生选课    student2.getCourses().add(course);    student2.getCourses().add(course2);    Student student3 = new Student();    student3.setName("赵六");    //学生选课    student3.getCourses().add(course2);    student3.getCourses().add(course3);    //将学生分配到班级中    grade.getStudents().add(student);    grade.getStudents().add(student2);    grade2.getStudents().add(student3);    //只保存班级信息,学生和选课信息则通过级联保存    session.save(grade);    session.save(grade2);    //提交事务    transaction.commit();    //释放资源    session.close();}

\4. 为了防止其他数据干扰,我们可以在核心配置文件 hibernate.cfg.xml 中,设置 hibernate.hbm2ddl.auto 属性的值为 create,使数据库表在每次运行时,会删除并重新创建,保证表中只有本次运行的数据(选配)。

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

\5. 运行测试方法,执行成功后到数据库中,分别对 student 表、grade 表、course 表和中间表 student_course 进行查询,结果如下图。

img

图5:级联保存结果

从以上查询结果可知,虽然我们只保存班级(grade)信息,但 Hibernate 仍然通过级联操作,将学生信息、课程信息以及选课信息(中间表)都保存到了数据库中。

级联删除

基于班级和学生的“一对多”关联关系,我们可以在删除班级信息时,级联删除班级内的学生信息。

\1. 修改映射文件 Grade.hbm.xml,并在 标签中的 cascade 属性新增属性值“delete”,配置如下。

<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-mapping PUBLIC        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping>    <class name="net.biancheng.www.po.Grade" table="grade" schema="bianchengbang_jdbc">        <id name="id" column="id" type="java.lang.Integer">            <generator class="native"></generator>        </id>        <property name="name" column="name" length="100" type="java.lang.String"/>        <!--设置 cascade 属性为 save-update,delete-->        <set name="students" inverse="false" cascade="save-update,delete">            <key column="gid"></key>            <one-to-many class="net.biancheng.www.po.Student"></one-to-many>        </set>    </class></hibernate-mapping>

注意:cascade 属性可以包含多个值,中间用“,”隔开。

\2. 在测试类 MyTest 中,添加一个名为 testCascadeDelete 测试方法,删除“四年级”的信息,代码如下。

@Testpublic void testCascadeDelete() {    Session session = HibernateUtils.openSession();    Transaction transaction = session.getTransaction();    //开启事务    transaction.begin();    // HQL 查询四年级信息    Query query = session.createQuery("from Grade WHERE name=?1");    query.setParameter(1, "四年级");    List<Grade> resultList = query.getResultList();    for (Grade grade : resultList) {        //删除四年级的信息        session.delete(grade);    }    //提交事务    transaction.commit();    //释放资源    session.close();}

\3. 执行该测试方法,然后到数据库中分别对 student 表、grade 表、course 表和中间表 student_course 进行查询,结果如下图。

级联删除
图6:级联删除结果

从查询结果可以看出,我们在删除班级信息时,还同时级联删除了相关的 student(学生)和中间表(student_course)的信息。

Student.hbm.xml 在维护 Student 和 Course 关联映射时,并没有通过 cascade 属性开启级联删除功能,因此课程信息没有被级联删除。

级联操作是具有方向性的,在决定级联删除的方向时,需要以现实需求为标准,否则会出现不符合常理的错误,例如,级联删除的方向是从 student 到 grade 的话,则就会出现因为一个学生而删掉整个班级的尴尬局面。

孤儿删除

班级和学生的关联关系是“一对多”,此时它们是存在父子关系的,其中班级为“父方”,学生为“子方”。如果学生表中某条数据的外键(gid)就会被重置为 null,即该学生与班级解除了关联关系,不再属于任何一个班级,那么这个学生也就没有了存在的意义,这种数据就被称为孤儿数据,孤儿删除就是用来删除这种“孤儿数据”的。

\1. 修改映射文件 Grade.hbm.xml,设置 cascade 属性的取值为 delete-orphan,配置如下。

<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-mapping PUBLIC        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping>    <class name="net.biancheng.www.po.Grade" table="grade" schema="bianchengbang_jdbc">        <id name="id" column="id" type="java.lang.Integer">            <generator class="native"></generator>        </id>        <property name="name" column="name" length="100" type="java.lang.String"/>        <!--设置 cascade 属性为 delete-orphan(孤儿删除)-->        <set name="students" inverse="false" cascade="save-update,delete-orphan">            <key column="gid"></key>            <one-to-many class="net.biancheng.www.po.Student"></one-to-many>        </set>    </class></hibernate-mapping>

\2. 在测试类 MyTest 中,添加一个名为 testDeleteOrphan 的测试方法,代码如下。

@Testpublic void testDeleteOrphan() {    Session session = HibernateUtils.openSession();    Transaction transaction = session.getTransaction();    //开启事务    transaction.begin();    // HQL 查询三年级信息    Query query = session.createQuery("from Grade WHERE name=?1");    query.setParameter(1, "三年级");    List<Grade> gradeList = query.getResultList();    //遍历结果集    for (Grade grade : gradeList) {        //HQL 查询名为张三的学生信息        Query query1 = session.createQuery("from Student where name=?1");        query1.setParameter(1, "张三");        List<Student> studentList = query1.getResultList();        //将学生张三与班级解除关系,使之称为孤儿        grade.getStudents().removeAll(studentList);    }    //提交事务    transaction.commit();    //释放资源    session.close();}

\3. 执行该测试方法,然后到数据库中分别对 student 表、grade 表、course 表和中间表 student_course 进行查询,结果如下图。

img
图7:孤儿删除结果

从图 3 可以看到,由于学生表中的“张三”与班级表解除了关联关系(外键重置为 null),当我们在班级的映射文件中设置了孤儿删除后,该学生信息及其相关表中数据就被自动删除了。

注意,在介绍级联保存时,我们为了防止其他数据的干扰,而将核心配置文件 hibernate.cfg.xml 中 hibernate.hbm2ddl.auto 属性设置为了 create,这会导致每次执行程序时,都重新创建数据库表。在执行级联删除或孤儿删除时,需要我们重新将 hibernate.hbm2ddl.auto 属性设置为 update。

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 域名和自定义DNS解析规则

    主机记录 1、不能超过5级。 2、不能以 . 和 - 开头或结尾&#xff0c;主机记录值不能以"符号"单独存在。 3、*只允许在首位 4、.分割的每个字符串长度不能超过63字符 5、合法字符包含a--z、A--Z、0--9、- 、_ 、.、*、、中文汉字。 6.*只允许在首位、和"*"…...

    2024/4/7 21:22:51
  2. 深入浅出Docker 1-6章 学习笔记

    docker深入浅出 docker概览 略 docker安装 启动Hyper-V和容器特性 开始菜单-设置-搜索并选择“启用或关闭windows功能”-勾选Hyper-V和容器 没有的话参考这个blog去官网下载安装&#xff0c;结束以后重启如果提示WSL2有问题&#xff0c;去这里下载更新输入docker verison&…...

    2024/4/16 1:12:48
  3. 第12届蓝桥杯 第一题:《直线》

    2021年第12届蓝桥杯竞赛 第一题&#xff1a;《直线》 题目大意 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 在平面直角坐标系中&#xff0c;两点可以确定一条直线。 给定平面上 20 212021 个整点 {(x, y)|0 ≤ x &l…...

    2024/5/10 15:46:26
  4. 软件妙妙招——如何去除惠普战66的扬声器杂音

    问题 新买了惠普战66第四代&#xff0c;感觉一切ok。但是今天偶然播放网易云音乐&#xff0c;发现音质好拉&#xff0c;杂音很明显&#xff01; PS&#xff1a;因为之前没播放过带人声的音乐&#xff0c;所以一直没发现这个问题。 解决方法 打开控制面板&#xff0c;点击“硬…...

    2024/5/9 22:31:14
  5. 【蓝桥杯c++(Python)每日练习】每日刷题day5:快排问题,抽签,方格填数

    文章目录一&#xff0c;快排问题二&#xff0c;抽签三&#xff0c;方格填数四&#xff0c;END&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f31f;&#x1f31f;&#x1f31f;Hello&#xff0c;大家好我是 上进小菜猪&#xff0c;一个有趣的全栈博主&#xff0c;欢迎关注&am…...

    2024/4/13 14:11:46
  6. 神经网络的学习-搭建神经网络实现mnist数据集分类

    文章目录四、神经网络的学习1.损失函数2.损失函数的意义3.数值微分4.梯度法5.学习算法的实现四、神经网络的学习 这一章通过两层神经网络实现对mnist手写数据集的识别&#xff0c;本文是源于《深度学习入门》的学习笔记 若理解困难&#xff0c;参考上一章笔记&#xff1a;深度…...

    2024/5/3 4:22:46
  7. 纯正弦波逆变器前级后级方案pcb反激电源方案正弦波程序烧录资料

    纯正弦波逆变器前级后级方案pcb反激电源方案正弦波程序烧录资料 编号:99128648765096518恒盛电子科技...

    2024/5/5 12:54:33
  8. Python学习之字典常用方法

    目录 1、增加&#xff1a;直接通过键名增加即可 2、修改&#xff1a; &#xff08;1&#xff09;直接修改&#xff1a; &#xff08;2&#xff09;方法update&#xff08;&#xff09;&#xff1a;新建一个字典&#xff0c;通过方法update&#xff08;&#xff09;将新的字典…...

    2024/4/13 14:12:11
  9. 编程初学者对于python,anaconda环境,tensorflow以及pychram相互之间联系的理解

    作为一个编程小白&#xff0c;最初学习python和深度学习相关的知识&#xff0c;可以说除了理论之外&#xff0c;还有各种包要下&#xff0c;还有各种路径要了解。特别是深度学习相关的包和一些pycharm上没有的包的下载方法需要对路径以及这几个软件之间的关系有着清晰的认知 下…...

    2024/4/13 14:11:41
  10. go 语言学习笔记(15)——字符串常用函数

    文章目录go 语言学习笔记&#xff08;15&#xff09;——字符串常用函数字字符串操作ContainsJoinIndexRepeatReplaceSplitTrimFields字符串转换AppenFormatgo 语言学习笔记&#xff08;15&#xff09;——字符串常用函数 字字符串操作 Contains func Contains(s , substr s…...

    2024/4/18 15:29:09
  11. chromium浏览器定制 | 高匿名爬虫随机指纹

    JavaScript的逆向内卷愈发严重&#xff0c;瑞数搞了许久也我头痛不已。我在《零基础一站式高级网络爬虫就业班》中讲了浏览器的指纹检测&#xff0c;也有不少网站要求补环境&#xff0c;相信不少同学都有所了解。补环境&#xff0c;补来补去就是模拟浏览器&#xff0c;爬虫总归…...

    2024/4/5 2:50:49
  12. Linux远程终端工具安装(xshell和xtfp)

    目录3.1 Xshell5安装和配置3.1.1 安装Xshell53.1.2 配置Xshell终端3.2 Xftp传输工具3.2.1 安装Xftp3.2.2 配置Xftp通常在工作过程中&#xff0c;公司中使用的真实服务器或者是云服务器&#xff0c;都不允许除运维人员之外的员工直接接触&#xff0c;因此就需要通过远程登录的方…...

    2024/4/13 14:12:01
  13. docker-compose: 未找到命令

    问题&#xff1a; 安装 wget https://github.com/docker/compose/releases/download/1.23.0-rc3/docker-compose-Linux-x86_64 --2022-02-06 15:04:54-- https://github.com/docker/compose/releases/download/1.23.0-rc3/docker-compose-Linux-x86_64安装之后 移动到 mv doc…...

    2024/4/13 14:12:01
  14. 使用Nodepad++编写JAVA代码并运行

    文件后缀名是 .java 在Nodepad里面编写好代码后保存然后找到该地址并在其上方的资源栏的最前面加cmd空格然后回车进入cmd后面就按照下图编写即可 public class Hello { public static void main(String[] args) { System.out.print("Hello World!"); } } 注意事项&a…...

    2024/4/13 14:13:01
  15. 【JavaSE】面向对象(二)

    六、抽象类 被abstract修饰的类&#xff0c;称为抽象类。被abstract修饰的方法&#xff0c;称为抽象方法。 抽象类和抽象方法都必须使用abstract修饰&#xff0c;抽象方法不能有方法体。抽象类不能实例化&#xff0c;无法使用new关键字来调用抽象类的构造器来创建抽象类的实例…...

    2024/4/16 13:46:52
  16. php进销存系统php仓库管理系统源码php源码

    php进销存系统php仓库管理系统源码php源码 基于php开发的进销存管理系统&#xff0c;仓库管理系统 编号:749.90648335869659俊语先生...

    2024/4/25 16:16:16
  17. 基于智能软开关的配电网优化调度matlab

    基于智能软开关的配电网优化调度matlab 采用matlab编程&#xff0c;分析得到了含智能软开关下的配电网故障恢复能力&#xff0c;包括恢复负荷、失电节点以及节点电压等&#xff0c;程序选择标准ieee33节点系统作为分析对象&#xff0c;采用yalmip编程&#xff0c;运行稳定 编号…...

    2024/4/13 14:12:46
  18. 仓库-进销存后台管理系统 SpringBoot+MybatisPlus+Thymeleaf框架的简单的进销存后台管理系统

    仓库-进销存后台管理系统 SpringBootMybatisPlusThymeleaf框架的简单的进销存后台管理系统&#xff0c;功能有&#xff1a;客户管理、供应商管理、商品管理、进货管理、退货管理、商品销售管理、部门管理、用户管理、公告管理等。代码写得很整洁编号:1624.99647990000692项目都…...

    2024/4/16 1:13:29
  19. cmake:基于MDK(Keil)的Nationstech.N32G45x平台交叉编译工具链定义

    Keil MDK是非常常用的单片机开发集成环境&#xff0c;Keil公司2005年由ARM公司收购&#xff0c;现在是ARM主要的嵌入系统开发平台(ARM的另一个开发环境ds-5早在九年前就停止更新了)。 Keil虽然是个集成开发环境&#xff0c;但Keil本身其实是由μVision IDE和arm编译器构成。cma…...

    2024/4/13 14:13:06
  20. 搭建机器人电控系统——通信协议——SPI通信及其实例

    通信协议 计算机与外界的信息交互称为通信。 基本的通信方式分为两种&#xff1a; 串行通信&#xff1a;所传送的数据各位按顺序一位一位地发送或接受&#xff0c;占用资源少&#xff0c;速度相对较慢。 并行通信&#xff1a;所传送的数据的各个位是同时发送或接受。速度快&a…...

    2024/4/13 14:12:56

最新文章

  1. 有什么方便实用的黏土特效教程?6个软件教你快速进行特效制作

    有什么方便实用的黏土特效教程&#xff1f;6个软件教你快速进行特效制作 作为时尚小达人&#xff0c;你自己是否想要制作出属于自己的黏土特效照片呢&#xff1f;比如下面几种。 看到这些黏土特效软件有没有心动&#xff0c;下面我也为大家详细的介绍一下可以制作出对应特效的…...

    2024/5/10 19:07:07
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/9 21:23:04
  3. 02 OSI和TCP/IP参考模型

    OSI参考模型优点&#xff1a; 1. 分层结构&#xff1a;OSI参考模型将网络通信划分为七个不同的层次&#xff0c;每个层次都有特定的功能和责任。这样的分层结构使得网络通信变得模块化&#xff0c;易于理解、设计和维护。 2. 标准化&#xff1a;OSI参考模型的制定依据了通信领…...

    2024/5/10 0:11:23
  4. [蓝桥杯 2014 省 A] 波动数列

    容我菜菲说一句&#xff0c;全网前排题解都是rubbish&#xff0c;当然洛谷某些也是litter 不好意思&#xff0c;最近背单词背了很多垃圾的英文&#xff0c;正题开始 [蓝桥杯 2014 省 A] 波动数列 题目描述 输入格式 输入的第一行包含四个整数 n , s , a , b n,s,a,b n,s,a…...

    2024/5/10 0:07:41
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/10 12:36:12
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/9 15:10:32
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/9 4:20:59
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/7 11:36:39
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/6 1:40:42
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/8 20:48:49
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/7 9:26:26
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/8 19:33:07
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/8 20:38:49
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/10 10:22:18
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/9 17:11:10
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57