Hibernate
Hibernate入门
认识Hibernate
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 缓存机制
Hibernate 提供了缓存机制(一级缓存、二级缓存、查询缓存),我们可以将那些经常使用的数据存放到 Hibernate 缓存中。当 Hibernate 在查询数据时,会优先到缓存中查找,如果找到则直接使用,只有在缓存中找不到指定的数据时,Hibernate 才会到数据库中检索,因此 Hibernate 的缓存机制能够有效地降低应用程序对数据库访问的频次,提高应用程序的运行性能。
Hibernate 特点
- 代码精简,开发成本较低。较于JDBC。
- 使用HQL (Hibernate query Language) 语言。
- Hibernate通过操作Java对象实现对数据库的操作。
- 提供三种缓存,将常用的数据放到缓存,不必每次都使用数据库查询,对提升性能大有裨益。
- 可移植性好,能够屏蔽数据库之间的差异。更换数据库通常只需要修改配置文件hibernate.cfg.xml中的hibernate.dialect(方言)属性,指定数据库类型。hibernate能够根据指定的方言自动生成合适的sql。
- 缺点是性能较差。
- 扩展性良好。
SpringBoot集成Hibernate
添加依赖
<!--jpa就是hibernate-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
Hibernate核心配置
spring:
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5InnoDBDialect # 方言
new_generator_mappings: false
format_sql: true # 格式化sql
database: mysql
show-sql: true # 打印sql
# 当写的实体类中属性,如果对应表没有该属性的字段,会自动创建一个该属性的字段,
# 规则是属性中大写字母处变成_小写字母的字段,比如userName变成user_name
hibernate:
ddl-auto: update
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC
Entity
package com.hibernate;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
@Data
@Entity
@Table(name = "books")
public class Books implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "book_id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer bookId;
@Column(name = "title")
private String title;
@Column(name = "category")
private String category;
}
实现类
package com.hibernate.respository.Impl;
import com.hibernate.respository.BookDao;
import com.hibernate.entity.Book;
import org.springframework.stereotype.Component;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
@Component
public class IBookDao implements BookDao {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<Book> getAllBooks() {
String sql = "FROM Book as book ORDER BY book.bookId";
return (List<Book>) entityManager.createQuery(sql).getResultList();
}
@Override
public Book getBookById(int bookId) {
return entityManager.find(Book.class, bookId);
}
@Override
public void addBook(Book book) {
entityManager.persist(book);
}
@Override
public void updateBook(Book book) {
Book book1 = getBookById(book.getBookId());
book1.setTitle(book.getTitle());
book1.setCategory(book.getCategory());
entityManager.flush();
entityManager.clear();
}
@Override
public void deleteBook(int bookId) {
entityManager.remove(getBookById(bookId));
}
@Override
public boolean bookExists(String title, String category) {
String sql = "FROM Book as book WHERE book.title=?0 and book.category=?1";
int count = entityManager.createQuery(sql)
.setParameter(0, title)
.setParameter(1, category)
.getResultList().size();
return count > 0;
}
}
Hibernate运行时的执行流程如下图
Hibernate 工作流程一般分为如下 7 步:
- Hibernate 启动,Configruation 会读取并加载 Hibernate 核心配置文件和映射文件钟的信息到它实例对象中。
- 通过 Configuration 对象读取到的配置文件信息,创建一个 SessionFactory 对象,该对象中保存了当前数据库的配置信息、映射关系等信息。
- 通过 SessionFactory 对象创建一个 Session 实例,建立数据库连接。Session 主要负责执行持久化对象的增、删、改、查操作,创建一个 Session 就相当于创建一个新的数据库连接。
- 通过 Session 对象创建 Transaction(事务)实例对象,并开启事务。Transaction 用于事务管理,一个 Transaction 对象对应的事务可以包含多个操作。需要注意的是,Hibernate 的事务默认是关闭的,需要手动开启和关闭。
- Session 接口提供了各种方法,可以对实体类对象进行持久化操作(即对数据库进行操作),例如 get()、load()、save()、update()、saveOrUpdate() 等等,除此之外,Session 对象还可以创建Query 对象 或 NativeQuery 对象,分别使用 HQL 语句或原生 SQL 语句对数据库进行操作。
- 对实体对象持久化操作完成后,必须提交事务,若程序运行过程中遇到异常,则回滚事务。
- 关闭 Session 与 SessionFactory,断开与数据库的连接,释放资源。
Hibernate核心配置文件
配置文件是 hibernate.cfg.xml
包含连接数据库的相关信息。
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映射文件
配置文件:
<?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>
- Class属性配置
属性名 | 描述 | 是否必需 |
---|---|---|
name | 实体类的完全限定名(包名+类名),若根元素 |
否 |
table | 对应的数据库表名。 | 否 |
catalog | 指定映射文件所对应的数据库 catalog 名称,若根元素 |
否 |
schema | 指定映射文件所对应的数据库 schema 名称,若根元素 |
否 |
lazy | 指定是否使用延迟加载。 | 否 |
- Hibernate 提供了以下 7 种主键生成策略
主键生成策略 | 说明 |
---|---|
increment | 自动增长策略之一,适合 short、int、long 等类型的字段。该策略不是使用数据库的自动增长机制,而是使用 Hibernate 框架提供的自动增长方式,即先从表中查询主键的最大值, 然后在最大值的基础上+1。该策略存在多线程问题,一般不建议使用。 |
identity | 自动增长策略之一,适合 short、int、long 等类型的字段。该策略采用数据库的自动增长机制,但该策略不适用于 Oracle 数据库。 |
sequence | 序列,适合 short、int、long 等类型的字段。该策略应用在支持序列的数据库,例如 Oracle 数据库,但不是适用于 MySQL 数据库。 |
uuid | 适用于字符串类型的主键,采用随机的字符串作为主键。 |
native | 本地策略,Hibernate 会根据底层数据库不同,自动选择适用 identity 还是 sequence 策略,该策略也是最常用的主键生成策略。 |
assigned | Hibernate 框架放弃对主键的维护,主键由程序自动生成。 |
foreign | 主键来自于其他数据库表(应用在多表一对一的关系)。 |
- property属性配置
属性名 | 描述 |
---|---|
name | 实体类属性的名称 |
column | 数据表字段名 |
type | 用于指定数据表中的字段需要转化的类型,这个类型既可以是 Hibernate 类型,也可以是 Java 类型 |
length | 数据表字段的长度 |
lazy | 该属性使用延迟加载,默认值是 false |
unique | 是否对该字段使用唯一性约束。 |
not-null | 是否允许该字段为空 |
Hibernate核心接口
1.Configuration
实例之后,可以获得核心配置文件的配置信息,如数据库URL、用户名、密码、驱动程序、方言等。
Configuration configuration = new Configuration().configure();
// 手动传入一个参数指定映射文件位置
configuration.addResource("net/biancheng/www/mapping/User.hbm.xml");
// 传入映射文件对应的实体类
configuration.addClass(User.class);
Configuration 对象只存在于系统的初始化阶段,将 SessionFactory 创建完成后,就会被销毁。
2.SessionFactory
SessionFactory 对象用来读取和解析映射文件,并负责创建和管理 Session 对象。
SessionFactory 对象中保存了当前的数据库配置信息、所有映射关系以及 Hibernate 自动生成的预定义 SQL 语句,同时它还维护了 Hibernate 的二级缓存。
一个 SessionFactory 实例对应一个数据库存储源,Hibernate 应用可以从 SessionFactory 实例中获取 Session 实例。
SessionFactory 具有以下特点:
- SessionFactory 是线程安全的,它的同一个实例可以被应用多个不同的线程共享。
- SessionFactory 是重量级的,不能随意创建和销毁它的实例。如果应用只访问一个数据库,那么在应用初始化时就只需创建一个 SessionFactory 实例;如果应用需要同时访问多个数据库,那么则需要为每一个数据库创建一个单独的 SesssionFactory 实例。
SessionFactory sessionFactory = configuration.buildSessionFactory();
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();
}
}
3. Session
Session 是 Hibernate 应用程序与数据库进行交互时,使用最广泛的接口,它也被称为 Hibernate 的持久化管理器,所有持久化对象必须在 Session 的管理下才可以进行持久化操作。持久化类只有与 Session 关联起来后,才具有了持久化的能力。
Session 具有以下特点:
- 不是线程安全的,因此应该避免多个线程共享同一个 Session 实例;
- Session 实例是轻量级的,它的创建和销毁不需要消耗太多的资源。通常我们会将每一个Session 实例和一个数据库事务绑定,每执行一个数据库事务,不论执行成功与否,最后都因该调用 Session 的 Close() 方法,关闭 Session 释放占用的资源。
创建Session的两种方式:
// 采用 openSession() 方法获取 Session 对象
Session session = sessionFactory.openSession();
// 使用完成后需要调用 close() 方法进行手动关闭。
// 采用 getCurrentSession() 方法获取 Session 对象
Session session = sessionFactory.getCurrentSession();
// 它在事务提交或回滚后会自动关闭。
方法 | 描述 |
---|---|
save() | 执行插入操作 |
update() | 执行修改操作 |
saveOrUpdate() | 根据参数,执行插入或修改操作 |
delete() | 执行删除操作 |
get() | 根据主键查询数据(立即加载) |
load() | 根据主键查询数据(延迟加载) |
createQuery() | 获取 Hibernate 查询对象 |
createSQLQuery() | 获取 SQL 查询对象 |
4. Transaction
Transaction 是 Hibernate 提供的数据库事务管理接口,它对底层的事务接口进行了封装。所有的持久化操作(即使是只读操作)都应该在事务管理下进行,因此在进行 CRUD 持久化操作之前,必须获得 Trasaction 对象并开启事务。
获取 Transaction 对象的两种方式
// 是在根据 Session 获得一个 Transaction 对象后,又继续调用 Transaction 的 begin() 方法,开启了事务。
Transaction transaction = session.beginTransaction();
// 根据 Session 获得一个 Transaction 对象,但是并没有开启事务。
Transaction transaction1 = session.getTransaction();
使用创景:
@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 对象进行查询,通常需要以下步骤:
- 获得 Hibernate 的 Sesssin 对象;
- 定义 HQL 语句;
- 调用 Session 接口提供的 createQuery() 方法创建 Query 对象,并将 HQL 语句以参数的形式传入到该方法中;
- 若 HQL 语句中包含参数,则调用 Query 接口提供的 setXxx() 方法设置参数;
- 调用 Query 的 getResultList() 方法,进行查询,并获取查询结果集。
除了数据库查询外,HQL 语句还可以进行更新和删除操操作,需要注意的是 HQL 并不支持 insert 操作,想要保存数据请使用 Session 接口提供的 save() 或 saveOrUpate() 方法。
@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 通过操作持久化对象即可实现对数据库表的 CRUD 操作。
持久化对象的状态
Hibernate 是一款持久层的 ORM 框架,专注于数据的持久化工作。在进行数据持久化操作时,持久化对象可能处于不同的状态当中,这些状态可分为三种,分别为瞬时态、持久态和脱管态,如下表。
状态 | 别名 | 产生时机 | 特点 |
---|---|---|---|
瞬时态(transient) | 临时态或自由态 | 由 new 关键字开辟内存空间的对象(即使用 new 关键字创建的对象) | 没有唯一标识 OID;未与任何 Session 实例建立关联关系;数据库中也没有与之相关的记录; |
持久态(persistent) | - | 当对象加入到 Session 的一级缓存中时,与 Session 实例建立关联关系时 | 存在唯一标识 OID,且不为 null;已经纳入到 Session 中管理;数据库中存在对应的记录;持久态对象的任何属性值的改动,都会在事务结束时同步到数据库表中。 |
脱管态(detached) | 离线态或游离态 | 持久态对象与 Session 断开联系时 | 存在唯一标识 OID;与 Session 断开关联关系,未纳入 Session 中管理;一旦有 Session 再次关联该脱管对象,那么该对象就可以立马变为持久状态;脱管态对象发生的任何改变,都不能被 Hibernate 检测到,更不能提交到数据库中。 |
在 Hibernate 运行时,持久化对象的三种状态可以通过 Session 接口提供的 一系列方法进行转换。这三种状态之间的转换关系具体如下图。
持久化对象的状态转换遵循以下规则:
- 当一个实体类对象通过 new 关键字创建时,此时该对象就处于瞬时态。
- 当执行 Session 接口提供的 save() 或 saveOrUpate() 方法,将瞬时态对象保存到 Session 的一级缓存中时,该对象就从瞬时态转换为了持久态。
- 当执行 Session 接口提供的 evict()、close() 或 clear() 方法,将持久态对象与 Session 断开关联关系时,该对象就从持久态转换为了脱管态。
- 当执行 Session 接口提供的 update()、saveOrUpdate() 方法,将脱管态对象重新与 Session 建立关联关系时,该对象会从脱管态转换为持久态。
- 直接执行 Session 接口提供的 get()、load() 或 find() 方法从数据库中查询出的对象,是处于持久态的。
- 当执行 Session 接口提供的 delete() 方法时,持久态对象就会从持久态转换为瞬时态。
- 由于瞬时态和脱管态对象都不在 Session 的管理范围内,因此一段时间后,它们就会被 JVM 回收。
缓存
缓存是位于应用程序和永久性数据存储源(例如硬盘上的文件或者数据库)之间,用于临时存放备份数据的内存区域,通过它可以降低应用程序读写永久性数据存储源的次数,提高应用程序的运行性能。
缓存具有以下特点:
- 缓存中的数据通常是数据库中数据的备份,两者中存放的数据完全一致,因此应用程序运行时,可以直接读写缓存中的数据,而不再对数据库进行访问,可以有效地降低应用程序对数据库的访问频率。
- 缓存的物理介质通常是内存,永久性数据存储源的物理介质为硬盘或磁盘,而应用程序读取内存的速度要明显高于硬盘,因此使用缓存能够有效的提高数据读写的速度,提高应用程序的性能。
- 由于应用程序可以修改(即“写”)缓存中的数据,为了保证缓存和数据库中的数据保持一致,应用程序通常会在在某些特定时刻,将缓存中的数据同步更新到数据库中。
Hibernate 一级缓存
Hibernate 一级缓存是 Session 级别的缓存,它是由 Hibernate 管理的,不可卸载。
Hibernate 一级缓存是由 Session 接口实现中的一系列 Java 集合构成的,其生命周期与 Session 保持一致。
Hibernate 一级缓存中存放的数据是数据库中数据的备份,在数据库中数据以数据库记录的形式保存,而在 Hibernate 一级缓存中数据是以对象的形式存放的。
当使用 Hibernate 查询对象时,会首先从一级缓存中查找,若在一级缓存中找到了匹配的对象,则直接取出并使用;若没有在一级缓存中找到匹配的对象,则去数据库中查询对应的数据,并将查询到的数据添加到一级缓存中。由此可见,Hibernate 的一级缓存机制能够在 Session 范围内,有效的减少对数据库的访问次数,优化 Hibernate 的性能。
一旦对象被存入一级缓存中,除非用户手动清除,不然只要 Session 实例的生命周期没有结束,存放在其中的对象就会一直存在。当 Session 关闭时,Session 的生命周期结束,该 Session 所管理的一级缓存也会立即被清除;
一级缓存的特点
- 一级缓存是 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 提供的方法,来管理一级缓存中的内容,如下表。
返回值类型 | 方法 | 描述 |
---|---|---|
void | clear() | 该方法用于清空一级缓存中的所有对象。 |
void | evict(Object var1) | 该方法用于清除一级缓存中某一个对象。 |
void | flush() throws HibernateException | 该方法用于刷出缓存,使数据库与一级缓存中的数据保持一致。 |
快照区
Hibernate 的缓存机制,可以有效的减少应用程序对数据库的访问次数,但该机制有一个非常重要的前提,那就是必须确保一级缓存中的数据域与数据库的数据保持一致,为此 Hibernate 中还提供了快照技术。
Hibernate 的 Session 中,除了一级缓存外,还存在一个与一级缓存相对应的快照区。当 Hibernate 向一级缓存中存入数据(持久态对象)时,还会复制一份数据存入快照区中,使一级缓存和快照区的数据完全相同。
当事务提交时,Hibernate 会检测一级缓存中的数据和快照区的数据是否相同。若不同,则 Hibernate 会自动执行 update() 方法,将一级缓存的数据同步更新到数据库中,并更新快照区,这一过程被称为刷出缓存(flush);反之,则不会刷出缓存。
默认情况下,Session 会在以下时间点刷出缓存:
- 当应用程序调用 Transaction 的 commit() 方法时, 该方法先刷出缓存(session.flush()),然后再向数据库提交事务(tx.commit());
- 当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,会先刷出缓存,以保证查询结果能够反映持久化对象的最新状态;
- 手动调用 Session 的 flush() 方法。