关于后端:Hibernate-基本操作懒加载以及缓存

4次阅读

共计 11496 个字符,预计需要花费 29 分钟才能阅读完成。

前言

上一篇咱们介绍了 Hibernate 以及写了一个 Hibernate 的工具类,疾速入门体验了一波 Hibernate 的应用,咱们只需通过 Session 对象就能实现数据库的操作了。

当初,这篇介绍应用 Hibernate 进行根本的 CRUD、懒加载以及缓存的常识。

提醒:如果你还没看上一篇,那么倡议你看完上一篇再来看这篇。

上一篇:一文疾速入门体验 Hibernate

根本的 CRUD

以下代码均写在测试类 HibernateTest 中

插入操作

这个在上一篇曾经演示过,这里便不再演示。

查问操作

查问有 2 种形式,通过 Session 对象的 get 办法 或者 load 办法来实现查问,次要将查问的数据后果封装到一个 Java 对象中。

  1. get 办法
    @Test
    public void queryByGet() {
        // 获取 Session 对象
        Session session = HibernateUtil.getSession();
        try {// 应用 get() 办法,第一个参数是长久化类的类型参数,第二个参数是主键标识参数,如果没有匹配的记录,那么会返回 null
            User user = session.get(User.class, new Integer("1"));
            System.out.println("用户 ID:" + user.getId());
        } catch (Exception e) {System.out.println("查问 User 数据失败!");
            e.printStackTrace();} finally{
            // 敞开 Session 对象
            HibernateUtil.closeSession();}
    }

控制台输入:能够看到,执行了查问 SQL,并打印了用户 ID。

INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
五月 08, 2023 11:38:59 下午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.name as name2_0_0_,
        user0_.password as password3_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
用户 ID:1
  1. load 办法
    @Test
    public void queryByLoad() {
        // 获取 Session 对象
        Session session = HibernateUtil.getSession();
        try {// 应用 load() 办法,它返回对象的代理,只有该代理被调用时,Hibernate 才会真正去执行 SQL 查问
            User user = session.load(User.class, new Integer("1"));
            // ID 是已知的,不必进行查问
            System.out.println("用户 ID:" + user.getId());
            // 此时该代理被调用,就执行 SQL 语句,失去真正的数据记录
            System.out.println("用户名称:" + user.getName());
        } catch (Exception e) {System.out.println("查问 User 数据失败!");
            e.printStackTrace();} finally{
            // 敞开 Session 对象
            HibernateUtil.closeSession();}
    }

控制台输入:

五月 08, 2023 11:40:13 下午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
五月 08, 2023 11:40:14 下午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
用户 ID:1
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.name as name2_0_0_,
        user0_.password as password3_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
用户名称:god23bin

能够看到,是先打印用户 ID 的,这里还没有执行查问 SQL,直到下一条语句中的 user.getName() 的执行,查问的 SQL 语句才被 Hibernate 执行

批改操作

想对某条数据进行批改操作,那么须要将它先查问进去,而后进行批改。这里就执行了两条 SQL,保险起见,开启事务,而后执行这两条 SQL,接着提交事务。当然,这两条 SQL,Hibernate 帮咱们写的啦!

    @Test
    public void update() {
        // 获取 Session 对象
        Session session = HibernateUtil.getSession();
        try {
            // 开启事务
            session.beginTransaction();
            // 进行查问,将后果封装成 user 对象
            User user = session.get(User.class, new Integer("1"));
            // 对 user 对象进行批改
            user.setName("公众号:god23bin");
            user.setPassword("456789");
            // 提交事务
            session.getTransaction().commit();
        } catch (Exception e) {
            // 产生异样,则回滚事务
            session.getTransaction().rollback();
            System.out.println("批改 User 数据失败!");
            e.printStackTrace();} finally{
            // 敞开 Session 对象
            HibernateUtil.closeSession();}
    }

控制台输入:

五月 09, 2023 12:00:16 上午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
五月 09, 2023 12:00:17 上午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.name as name2_0_0_,
        user0_.password as password3_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
Hibernate: 
    update
        user 
    set
        name=?,
        password=? 
    where
        id=?
        

能够看到运行前和运行后,数据的变动,如图:

如果屏幕前的小伙伴是依照我的步骤一步一步跟下来,那么你可能会遇到中文乱码的问题,此时须要在 hibernate.cfg.xml 配置文件中批改 URL,加上两个参数 useUnicode=true&characterEncoding=UTF-8,如下:

<property name="connection.url">jdbc:mysql://localhost:3306/demo_hibernate?useUnicode=true&amp;characterEncoding=UTF-8</property>

删除操作

删除操作须要先把数据查问进去,而后通过 Session 对象的 delete 办法将其删除。代码如下:

    @Test
    public void delete() {
        // 获取 Session 对象
        Session session = HibernateUtil.getSession();
        try {session.beginTransaction();
            User user = session.get(User.class, new Integer("1"));
            // 删除操作
            session.delete(user);
            session.getTransaction().commit();
        } catch (Exception e) {session.getTransaction().rollback();
            System.out.println("删除 User 数据失败!");
            e.printStackTrace();} finally{
            // 敞开 Session 对象
            HibernateUtil.closeSession();}
    }

控制台输入:

五月 09, 2023 12:10:09 上午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
五月 09, 2023 12:10:10 上午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.name as name2_0_0_,
        user0_.password as password3_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
Hibernate: 
    delete 
    from
        user 
    where
        id=?

对于 Hibernate 中对象的状态

在 Hibernate 中,对象的状态有 4 种,别离为 Transient、Persistent、Detached、Removed,译名就比拟多,不便起见,我抉择 3 个字的译名:

  1. 刹时态(Transient):当一个对象被实例化后,它处于刹时态,简略了解,就是 new 操作之后。刹时态的对象没有与之关联的数据库记录,并且没有被 Hibernate 的 Session 治理。当将刹时态的对象关联到长久态对象或通过 Session 对象的 savepersist 等办法进行长久化操作后,该对象的状态会发生变化,转成长久态。
  2. 长久态(Persistent):当一个对象与 Hibernate 的 Session 关联后,它就处于长久态。长久态的对象有与之对应的数据库记录,并且被 Hibernate 的 Session 治理。对长久态对象的任何更改都会主动同步到数据库。长久态对象能够通过 Session 的 getload 等办法从数据库中获取,或者通过 saveupdatepersist 等办法进行长久化操作。
  3. 游离态(Detached):当一个长久态对象与 Hibernate 的 Session 拆散后,它处于游离态。游离态的对象依然有与之对应的数据库记录,但不再受 Hibernate 的 Session 治理。对游离态对象的更改不会主动同步到数据库。能够通过 Session 的 evictclear 等办法将长久态对象转变为游离态对象,或者通过 Session 的 merge 办法将游离态对象从新关联到 Session 中。
  4. 删除态(Removed):当一个长久态对象被从 Hibernate 的 Session 中删除后,它处于删除态。删除态的对象依然有与之对应的数据库记录,但行将被从数据库中删除。删除态对象能够通过 Session 的delete 办法进行删除操作。

Hibernate 通过跟踪对象的状态变动,实现了对象与数据库的同步。在 Hibernate 的事务管理中,对象的状态转换是主动进行的,咱们无需手动操作,Hibernate 会依据对象的状态进行相应的数据库操作,保障对象与数据库的一致性。

须要留神的是,Hibernate 的对象状态与数据库的操作并不是一一对应的,Hibernate 提供了一系列的长久化办法和操作,咱们能够依据具体的需要抉择适合的办法来进行对象状态的转换和数据库操作。对于简单的业务逻辑和数据处理,须要认真了解和治理对象的状态,以防止数据不统一的问题。

懒加载

Hibernate 的懒加载(Lazy Loading)是一种提早加载策略,它容许程序在须要拜访相干数据时才从数据库中加载关联对象的属性或汇合。

在 Hibernate 中,懒加载是通过应用代理对象来实现的。实际上,咱们在演示 Session 对象的 load() 办法时,就是懒加载了,一开始返回的是代理对象,并没有间接查询数据库,而是直到该代理对象的属性或办法被调用时,Hibernate 会依据须要主动执行额定的数据库查问,从而提早加载关联的数据。

这就是懒加载,等到须要的时候才去加载。

懒加载的次要长处是能够进步零碎性能和缩小不必要的数据库查问。如果一个对象关联的属性或汇合在业务逻辑中很少被应用,懒加载能够防止不必要的数据库拜访,加重数据库负载。

除了 load 办法实现的懒加载,咱们还能够通过设置映射文件中的 <property> 标签的 lazy 属性实现懒加载:

<property name="name" type="string" lazy="true" /> <!-- name 属性被设置成懒加载 -->

缓存

缓存是一种长期存储数据的形式,将数据保留在更疾速的存储介质(如内存)中,以便未来可能快速访问和检索。

Hibernate 提供了缓存的技术,次要用于存储实体对象以及查问的后果集。缓存分为 一级缓存(Session 缓存)和二级缓存(Session Factory 缓存)

一级缓存

一级缓存是与 Session 相关联的缓存,它存储了从数据库中读取的实体对象。在同一个 Session 中,当屡次查问雷同的数据时,Session 首先会依据对应的长久化类和唯一性标识(个别指的是 ID)去缓存中查找是否存在该数据。如果存在,则间接从缓存中获取,而不再拜访数据库;如果不存在,则持续向二级缓存种查找。

一级缓存是 默认开启 的,能够进步读取性能。

示例:

    @Test
    public void testFirstLevelCache() {Session session = HibernateUtil.getSession();
        try {System.out.println("第一次查问:");
            User user = session.get(User.class, new Integer("2"));
            System.out.println("用户名:" + user.getName());
            
            System.out.println("第二次查问:");
            User user2 = session.get(User.class, new Integer("2"));
            System.out.println("用户名:" + user2.getName());
        } catch (Exception e) {System.out.println("查问 User 数据失败!");
            e.printStackTrace();} finally{
            // 敞开 Session 对象
            HibernateUtil.closeSession();}
    }

控制台输入:

五月 09, 2023 9:35:31 下午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
五月 09, 2023 9:35:32 下午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
第一次查问:Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.name as name2_0_0_,
        user0_.password as password3_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
用户名:god23bin
第二次查问:用户名:god23bin

能够看到,第二次查问是没有执行 SQL 的,间接从一级缓存中获取。

二级缓存

二级缓存是在 SessionFactory 级别上的缓存,用于缓存多个 Session 之间共享的数据。它能够缩小对数据库的拜访次数,进步性能和扩展性。二级缓存能够存储实体对象、汇合对象以及查问后果集。

因为 Hibernate 自身并未提供二级缓存的具体实现,所以须要借助其余缓存插件或者说策略来实现二级缓存。比方 Ehcache、Redis 等。

咱们这里间接应用 Ehcache。

  1. 引入依赖项
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
    <version>5.6.14.Final</version>
</dependency>

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.0</version>
</dependency>
  1. 开启二级缓存

二级缓存默认是敞开的,咱们须要手动开启。在 hibernate.cfg.xml 中开启二级缓存:

<?xml version="1.0" encoding="UTF-8"?>
        <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        ...
        <!-- 开启二级缓存 -->
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <!-- 开启查问缓存 -->
        <property name="hibernate.cache.use_query_cache">true</property>
        <!-- 指定应用的缓存实现类 -->
        <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.internal.SingletonEhcacheRegionFactory</property>
    </session-factory>
</hibernate-configuration>
  1. 创立缓存配置文件

咱们在 /src/main/resources 目录下创立缓存配置文件 ehcache.xml:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">

    <!-- 硬盘存储:将缓存中临时不应用的对象,长久化到硬盘 -->
    <!-- path 属性:指定在硬盘上存储对象的门路 -->
    <!-- java.io.tmpdir 是默认的临时文件门路。能够通过左边的形式打印出具体的文件门路:System.out.println(System.getProperty("java.io.tmpdir")); -->
    <diskStore path="java.io.tmpdir"/>


    <!-- defaultCache:默认的缓存策略 -->
    <!-- eternal 属性:设定缓存的 elements 是否永远不过期。如果为 true,则缓存的数据始终无效,如果为 false 那么还要依据 timeToIdleSeconds,timeToLiveSeconds 判断 -->
    <!-- maxElementsInMemory 属性:在内存中缓存的 element 的最大数目 -->
    <!-- overflowToDisk 属性:如果内存中数据超过内存限度,是否要缓存到硬盘上 -->
    <!-- diskPersistent 属性:是否在硬盘上长久化。指重启 JVM 后,数据是否无效。默认为 false -->
    <!-- timeToIdleSeconds 属性:对象闲暇工夫(单位:秒),指对象在多长时间没有被拜访就会生效。只对 eternal 为 false 的无效。默认值 0,示意始终能够拜访 -->
    <!-- timeToLiveSeconds 属性:对象存活工夫(单位:秒),指对象从创立到生效所须要的工夫。只对 eternal 为 false 的无效。默认值 0,示意始终能够拜访 -->
    <!-- memoryStoreEvictionPolicy 属性:缓存的 3 种革除策略,因为缓存区域是肯定的,满了之后就须要革除不须要的数据 -->
    <!-- 1. FIFO:first in first out (先进先出). 先缓存的数据会先被革除 -->
    <!-- 2. LFU:Less Frequently Used (起码应用). 意思是始终以来起码被应用的。缓存的元素有一个 hit 属性,hit 值最小的将会被革除 -->
    <!-- 3. LRU:Least Recently Used(最近起码应用). (ehcache 默认值). 缓存的元素有一个工夫戳,当缓存容量满了,而又须要腾出中央来缓存新的元素的时候,那么现有缓存元素中工夫戳离以后工夫最远的元素将被革除 -->

    <defaultCache eternal="false"
                  maxElementsInMemory="1000"
                  overflowToDisk="false"
                  diskPersistent="false"
                  timeToIdleSeconds="0"
                  timeToLiveSeconds="600"
                  memoryStoreEvictionPolicy="LRU"/>

    <!-- name:Cache 的名称,必须是惟一的(ehcache 会把这个 cache 放到 HashMap 里)-->
    <cache name="userCache"
           eternal="false"
           maxElementsInMemory="100"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="0"
           timeToLiveSeconds="300"
           memoryStoreEvictionPolicy="LRU"/>
</ehcache>
  1. 在长久化类的映射文件中指定缓存策略

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>
    <class name="cn.god23bin.demo.domain.entity.User" table="user">
        <!-- 指定缓存策略 -->
        <cache usage="read-only"/>
        ...
    </class>
</hibernate-mapping>

测试二级缓存:

    @Test
    public void testSecondLevelCache() {Session session1 = HibernateUtil.getSession();
        Session session2 = HibernateUtil.getSession();
        try {System.out.println("第一个 Session 去查问数据并封装成对象");
            User user1 = session1.get(User.class, new Integer("2"));
            System.out.println("用户名:" + user1.getName());

            System.out.println("第二个 Session 去查问同一数据并封装成对象");
            User user2 = session2.get(User.class, new Integer("2"));
            System.out.println("用户名:" + user1.getName());
        } catch (Exception e) {System.out.println("查问 User 数据失败!");
            e.printStackTrace();} finally{
            // 敞开 Session 对象
            HibernateUtil.closeSession();}
    }

控制台输入:

五月 09, 2023 11:18:31 下午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
第一个 Session 去查问数据并封装成对象
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.name as name2_0_0_,
        user0_.password as password3_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
用户名:god23bin
第二个 Session 去查问同一数据并封装成对象
用户名:god23bin

总结

本篇文章次要讲了根本的 CRUD 操作,都是通过 Session 去操作的,依据一个长久化类的类型以及一个惟一标识进行相干操作,而后讲了 Hibernate 中的对象的状态,有 4 种,别离是刹时、长久、游离、删除。

接着说了 Hibernate 的懒加载,有利于升高数据库的开销,当然缓存也是,除了放慢咱们的访问速度,也升高了间接拜访数据库的开销,缓存就两种,一级和二级,一级默认是开启的,二级须要引入相干的依赖项,而后进行配置,开启二级缓存,配置缓存策略。

这里附上整个我的项目的目录构造,便于对照:

以上,就是本篇的内容,这些都应该把握。咱们下期再见。

最初的最初

心愿各位屏幕前的 靓仔靓女们 给个三连!你轻轻地点了个赞,那将在我的心里世界削减一颗亮堂而夺目的星!

咱们下期再见!

正文完
 0