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 步:

  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核心配置文件

配置文件是 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 实体类的完全限定名(包名+类名),若根元素 中已经指定了 package 属性,则该属性可以省略包名
table 对应的数据库表名。
catalog 指定映射文件所对应的数据库 catalog 名称,若根元素 中已经指定 catalog 属性,则该属性会覆盖根元素中的配置。
schema 指定映射文件所对应的数据库 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 对象进行查询,通常需要以下步骤:

  1. 获得 Hibernate 的 Sesssin 对象;
  2. 定义 HQL 语句;
  3. 调用 Session 接口提供的 createQuery() 方法创建 Query 对象,并将 HQL 语句以参数的形式传入到该方法中;
  4. 若 HQL 语句中包含参数,则调用 Query 接口提供的 setXxx() 方法设置参数;
  5. 调用 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() 方法。
posted @ 2023-09-24 17:12  凉冰24  阅读(84)  评论(0)    收藏  举报