hibernate学习笔记之一(下)

本文涉及的产品
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用版 2核4GB 50GB
简介: hibernate学习笔记之一(下)

添加 C3P0 的配置信息


    <!-- C3P0连接池设定 -->
    <!-- 使用 C3P0连接池配置连接池提供的供应商 -->
    <property name="connection.provider_class">
      org.hibernate.c3p0.internal.C3P0ConnectionProvider
    </property>
    <!--在连接池中可用的数据库连接的最少数目 -->
    <property name="c3p0.min_size">5 </property>
    <!--在连接池中所有数据库连接的最大数目 -->
    <property name="c3p0.max_sizen">20 </property>
    <!--设定数据库连接的过期时间,以ms为单位,如果连接池中的某个数据库连接空闲状态的时间 超过timeout时间,则会从连接池中清除 -->
    <property name="c3p0.timeout">120 </property>
    <!--每3000s检查所有连接池中的空闲连接以s为单位 -->
    <property name="c3p0.idle_test_period">3000 </property>
    <!-- 映射文件配置 -->
    <mapping resource="com/hibernate/User.hbm.xml"/>


首先声明了 C3P0 的供应商信息,然后配置了连接池中的连接最小数目和最大数目等。配置完这些信息后,C3P0 连接池就可以使用了。


hibernate的核心接口


有六个常用的核心接口,它们分别是 Configuration、SessionFactory、Session、Transaction、Query 和 Criteria


Configuration


主要用于启动、加载和管理 Hibernate 的配置文件信息,在启动 Hibernate 的过程中,Configuration 实例首先确定 Hibernate 文件的位置,然后读取相关配置,最后创建一个唯一的 SessionFactory 实例。


Configuration config=new Configuration().configure(); 
Configuration config = new Configuration().configure("文件的位置");


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


SessionFactory


SessionFactory 接口负责读取并解析映射文件,以及建立 Session 对象,它在 Hibernate 中起到一个缓冲区的作用,会将 Configuration 对象中的所有配置信息、Hibernate 自动生成的 SQL 语句以及某些可重复利用的数据加载到缓冲区中。同时,它还维护了 Hibernate 的二级缓存。


SessionFactory 实例是通过 Configuration 对象获取的.


SessionFactory sessionFactory = config.buildSessionFactory();


SessionFactory 具有以下特点。


  • 它是线程安全的,它的同一个实例能够供多个线程共享。
  • 它是重量级的,不能随意创建和销毁它的实例。


SessionFactory 是一个重量级的对象,占用的内存空间较大,所以通常情况下,一个应用程序只需要一个 SessionFactory 实例,只有应用中存在多个数据源时,才为每个数据源建立一个 SessionFactory 实例。为此,在实际开发时,通常会抽取出一个工具类提供 Session 对象。


public class HibernateUtils {
    // 声明一个私有的静态final类型的Configuration对象
    private static final Configuration config;
    // 声明一个私有的静态的final类型的SessionFactory对象
    private static final SessionFactory factory;
    // 通过静态代码块构建SessionFactory
    static {
        config = new Configuration().configure();
        factory = config.buildSessionFactory();
    }
    // 提供一个公有的静态方法供外部获取,并返回一个session对象
    public static Session getSession() {
        return factory.openSession();
    }
}


Session


主要用于读取、创建和删除映射对象的实例,这一系列的操作将被转换为数据表中的增加、修改、查询和删除操作。


Session 是轻量级的,实例的创建和销毁不需要消耗太多的资源,同时它还是 Hibernate 的一级缓存,这个缓存主要用于存放当前工作单元加载的对象

获取session


//采用openSession方法创建Session
Session session = sessionFactory.openSession();
//采用getCurrentSession()方法创建Session
Session session = sessionFactory.getCurrentSession();


openSession() 方法获取 Session 实例时,SessionFactory 直接创建一个新的 Session 实例,并且在使用完成后需要调用 close() 方法进行手动关闭;


getCurrentSession() 方法创建的 Session 实例会被绑定到当前线程中,它在提交或回滚操作时会自动关闭。


提供的持久化操作方法


名称 描述
save() 用于执行添加对象操作
update() 用于执行修改对象操作
saveOrUpdate() 用于执行添加或修改对象操作
delete() 用于执行删除对象操作
get() 根据主键查询数据
load() 根据主键查询数据
createQuery() 用于数据库操作对象
createSQLQuery() 用于数据库操作对象
createCriteria() 面向对象的条件查询



Session 是线程不安全的,当多个并发线程同时操作一个 Session 实例时,就可能导致 Session 数据存取的混乱(当方法内部定义和使用 Session 时,不会出现线程问题)。因此设计软件架构时,应避免多个线程共享一个 Session 实例。


Transaction


Transaction 接口主要是用于管理事务,它是 Hibernate 的数据库事务接口,且对底层的事务接口进行了封装。


Transaction transaction = session.beginTransaction();



在 Transaction 接口中,提供了事务管理的常用方法,具体如下。


  • commit() 方法:提交相关联的 session 实例。
  • rollback() 方法:撤销事务操作。
  • wasCommitted() 方法:检查事务是否提交。


使用实例


try{
    transaction = session.beginTransaction();   //开启事务
    session.save(user); //执行操作
    transaction.commit();   //提交事务
}catch(Exception e) {
    transaction.rollback(); //回滚事务
}finally{
    session.close();    //关闭资源
}

Query


Query 接口是 Hibernate 的查询接口,主要用于执行 Hibernate 的查询操作。


Query 中包装了一个 HQL(Hibernate Query Language)查询语句,该语句采用了面向对象的查询方式,具有丰富灵活的查询特征。因此,Hibernate 官方推荐使用 HQL 语言进行查询。


@Test
public void testFindAll() {
    Configuration config = new Configuration().configure();
    SessionFactory sessionFactory = config.buildSessionFactory();
    // 1.得到一个Session
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    // 2.编写 HQL,其中的User代表的是类
    String hql = "from User";
    // 3.创建Query查询对象
    Query query = session.createQuery(hql);
    // 4.使用query.list()方法查询数据,并放入list集合
    List<User> list = query.list();
    for (User u : list) {
        System.out.println(u);
    }
    transaction.commit();
    session.close();
    sessionFactory.close();
}


常用方法


1669112053198.png


Criteria


Criteria 接口是 Hibernate 提供的一个面向对象的查询条件接口,通过它完全不需要考虑数据库底层如何实现,以及 SQL 语句如何编写。Criteria 查询又称为 QBC 查询(Query By Criteria)


1669112069550.png


使用


@Test
    public void testQBC() {
        Configuration config = new Configuration().configure();
        SessionFactory sessionFactory = config.buildSessionFactory();
        // 1.得到一个Session
        Session session = sessionFactory.openSession();
        Transaction transaction = session.beginTransaction();
        // 2.通过session获得Criteria对象
        Criteria criteria = session.createCriteria(User.class);
        // 3.使用Restrictions的eq方法设定查询条件为name="zhangsan"
        // 4.向Criteria对象中添加查询条件
        criteria.add(Restrictions.eq("name", "zhangsan"));
        // 5.执行Criterita的list()方法获得结果
        List<User> list = criteria.list();
        for (User u : list) {
            System.out.println(u);
        }
        transaction.commit();
        session.close();
        sessionFactory.close();
    }


Hibernate持久化对象的状态及状态转换


在 Hibernate中,持久化对象是存储在一级缓存当中的,一级缓存指 Session 级别的缓存,它可以根据缓存中的持久化对象的状态改变同步更新数据库。


Hibernate 是持久层的 ORM 框架,专注于数据的持久化工作。在进行数据持久化操作时,持久化对象可能处于不同的状态当中。这些状态可分为三种:


1)瞬时态(transient)


瞬时态也称为临时态或者自由态,瞬时态的对象是由 new 关键字开辟内存空间的对象,不存在持久化标识 OID(相当于主键值),且未与任何的 Session 实例相关联,在数据库中也没有记录,失去引用后将被 JVM 回收。瞬时对象在内存孤立存在,它是携带信息的载体,不和数据库的数据有任何关联关系。


2)持久态(persistent)


持久态的对象存在一个持久化标识 OID,当对象加入到 Session 缓存中时,就与 Session 实例相关联。它在数据库中存在与之对应的记录,每条记录只对应唯一的持久化对象。需要注意的是,持久态对象是在事务还未提交前变成持久态的。


3)脱管态(detached)


脱管态也称离线态或者游离态,当持久化对象与 Session 断开时就变成了脱管态,但是脱管态依然存在持久化标识 OID,只是失去了与当前 Session 的关联。需要注意的是,脱管态对象发生改变时 Hibernate 是不能检测到的。(当new一个对象并且设id值,而id在数据库中存在的,此时对对象处于脱管态)


注意:


oid是持久化类中的一个属性,与数据库中的id属性对应(user类中的id)


hibernate底层是通过持久化类的反射方法操作的,所以持久化类中的每一个属性都应该有set和get方法,oid也不例外


持久化类对象是否有oid,是该对象是瞬时态还是持久态的标志。


总结:


**瞬时态:**没有持久化标识OID,没有被纳入Session对象的管理


**持久态:**有持久化标识OID,已经被纳入Session对象的管理


**托管态:**有持久化标识OID,但没有被Session对象管理


①瞬时态(临时态、自由态):不存在持久化标识OID,尚未与Hibernate Session关联对象,被认为处于瞬时态,失去引用将被JVM回收

②持久态:存在持久化标识OID,与当前session有关联,并且相关联的session没有关闭 ,并且事务未提交

③脱管态(离线态、游离态):存在持久化标识OID,但没有与当前session关联,脱管状态改变hibernate不能检测到


持久化对象的转换

10c765a762da4245b272b5db1cacd192.png


当一个对象通过 new 关键字创建后,该对象处于瞬时态;当对瞬时态对象执行 Session 的 save() 或 saveOrUpdate() 方法后,该对象将被放入 Session 的一级缓存中,此时该对象处于持久态。


当对持久态对象执行 evict()、close() 或 clear() 操作后,对象会进入脱管态。


当直接执行 Session 的 get()、load()、find() 或 iterate() 等方法从数据库中查询出对象时,查询到的对象也会处于持久态。


当对数据库中的纪录进行 update()、saveOrUpdate() 以及 lock() 等操作后,此时脱管态的对象就过渡到持久态;由于瞬时态和脱管态的对象不在 session 的管理范围内,所以会在一段时间后被 JVM 回收。


实例:


pom.xml文件


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>hibernatedemo02</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>hibernatedemo02 Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.20</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
    </dependency>
    <dependency>
      <groupId>c3p0</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.1.2</version>
    </dependency>
    <!-- 添加Hibernate依赖 -->
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-annotations</artifactId>
      <version>3.2.1.ga</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>3.2.1.ga</version>
    </dependency>
    <!--    SLF4J-->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.32</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-nop</artifactId>
      <version>1.7.2</version>
    </dependency>
    <!-- 添加javassist -->
    <dependency>
      <groupId>javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.12.0.GA</version>
    </dependency>
    <!-- mysql数据库的驱动包 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
  </dependencies>
  <build>
    <!--配置Maven 对resource文件 过滤 -->
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.properties</include>
          <include>**/*.xml</include>
          <include>**/*.xls</include>
          <include>**/*.xlsx</include>
        </includes>
        <filtering>true</filtering>
      </resource>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>true</filtering>
      </resource>
    </resources>
  </build>
</project>


核心配置文件:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <!-- 指定方言 -->
        <property name="dialect">
            org.hibernate.dialect.MySQL5Dialect
        </property>
        <!-- 链接数据库url -->
        <property name="connection.url">
            jdbc:mysql://localhost:3306/hibernatelearn?useUnicode=true&amp;characterEncoding=utf-8
        </property>
        <!-- 连接数据库的用户名 -->
        <property name="connection.username">
            root
        </property>
        <!-- 数据库的密码 -->
        <property name="connection.password">
            root
        </property>
        <!-- 数据库驱动 -->
        <property name="connection.driver_class">
            com.mysql.jdbc.Driver
        </property>
        <!-- 显示sql语句 -->
        <property name="show_sql">
            true
        </property>
        <!-- 格式化sql语句 -->
        <property name="format_sql">true</property>
        <!-- 自动建表 -->
        <property name="hbm2ddl.auto">update</property>
        <!-- 映射文件配置 -->
        <mapping resource="Goods.hbm.xml" />
    </session-factory>
</hibernate-configuration>


工具类:


import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
/**
 * TODO 类描述
 *
 * @author qijian.
 * @date 2021/8/23 19:35
 */
public class HibernateUtils {
    /***
     * 声明一个私有的静态final类型的Configuration对象
     */
    private static final Configuration config;
    /**
     * 声明一个私有的静态的final类型的SessionFactory对象
     */
    private static final SessionFactory factory;
    /**
     * 通过静态代码块构建SessionFactory
     */
    static {
        config = new Configuration().configure();
        factory = config.buildSessionFactory();
    }
    /**
     * 提供一个公有的静态方法供外部获取,并返回一个session对象
     * @return 返回session
     */
    public static Session getSession() {
        return factory.openSession();
    }
}


实体类


public class Goods {
    private Integer id; // 标识id
    private String name; // 商品名称
    private Double price; // 商品价格
    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 Double getPrice() {
        return price;
    }
    public void setPrice(Double price) {
        this.price = price;
    }
    // 重写toString()方法
    @Override
    public String toString() {
        return "Goods[id=" + id + ",name=" + name + ",price=" + price + "]";
    }
}


配置文件


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.mengma.domain.Goods" >
        <id name="id"   type="integer">
            <generator class="native" />
        </id>
        <property name="name"   />
        <property name="price"  />
    </class>
</hibernate-mapping>


测试:


// 演示持久化对象的三种状态
@Test
public void test1() {
    Session session = HibernateUtils.getSession(); // 得到session对象
    Transaction tx = session.beginTransaction();
    Goods goods = new Goods();
    goods.setName("铅笔");
    goods.setPrice(0.5);
    //此时goods处于Transient态
    session.save(goods);
    //此时对象已由Hibernate纳入管理容器,处于持久态
    tx.commit();
    //事务提交后,库表中已经插入一条Goods的记录
    Transaction tx1 = session.beginTransaction();
    goods.setPrice(1.0);
    tx1.commit;
    //虽然这个事务中我们没有显示的调用Session.save方法保存goods对象
    //但是由于处于Persistent状态的对象将自动被固华到数据库中,因此
    //对象的变化也会被同步到数据库中
    //也就是说数据库中goods的记录数据已被更新
    session.close();
    System.out.println(goods);
}


使用 new 关键字创建 Goods 对象时,该对象是没有标识(OID)的,也没有与 Session 进行关联,它处于瞬时状态。


执行 save() 方法后,Goods 对象已经处于 Session 的管理范围了,并且有了自己的 OID,此时的 Goods 对象已转换为持久态。当执行 commit() 方法并关闭 Session 后,Goods 对象就已不在 Session 的管理范围,此时 Goods 对象从持久态转换为托管态。


接下来我们进行debug调试

2097b70ec44446dd80726a3f47f1372d.png


可以看出此时id的值是null。接着进入下一步操作。


6f44a3eae7634acdb7a554f44912d54c.png


此时id已被赋值。


也就是说:执行完 save() 方法后,Good 对象的 id 属性已被赋值,该值就是 Goods 对象的持久化标识 OID,这说明持久化对象在事务提交前就已经变成了持久态,也说明了瞬时态对象和持久态对象的区别就是,持久态对象与 Session 进行了关联并且 OID 有值。


当执行完 close() 方法后,Goods 对象与 Session 不再存在关联关系,此时的 Goods 对象会由持久态对象转换为脱管态,但通过控制台的输出内容可以发现 Goods 对象的 OID 属性依然是存在的,这说明脱管态对象与持久态对象的区别就是脱管状态对象没有了 Session 关联。


hibernate一级缓存


CPU缓存(Cache Memory)是位于CPU与内存之间的临时存储器,它的容量比内存小得多但是交换速度却比内存要快得多。


缓存的工作原理是当CPU要读取一个数据时,首先从缓存中查找,如果找到就立即读取并送给CPU处理;如果没有找到,就用相对慢的速度从内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。 正是这样的读取机制使CPU读取缓存的命中率非常高(大多数CPU可达90%左右),也就是说CPU下一次要读取的数据90%都在缓存中,只有大约10%需要从内存读取。这大大节省了CPU直接读取内存的时间,也使CPU读取数据时基本无需等待。总的来说,CPU读取数据的顺序是先缓存后内存。—百度百科


hibernate一级缓存其实就是 Session 缓存。Session 缓存是一块内存空间,用于存储与管理 Java对象。


在使用 Hibernate 查询对象时,首先会使用对象的 OID 值在 Hibernate 的一级缓存中查找,如果找到匹配的对象,则直接将该对象从一级缓存中取出使用;如果没有找到匹配的对象,则会去数据库中查询对应的数据。当从数据库中查询到所需数据时,该数据信息会存储到一级缓存中。由此可知,Hibernate 一级缓存的作用就是减少对数据库的访问次数。


Hibernate 的一级缓存具有如下特点。


1)当应用程序调用 Session 接口的 save()、update()、saveOrUpdate() 时,如果 Session 缓存中没有相应的对象,则 Hibernate 就会自动把从数据库中查询到的相应对象信息加入到一级缓存中。


2)当调用 Session 接口的 load()、get() 方法,以及 Query 接口的 list()、iterator() 方法时,会判断缓存中是否存在该对象,有则返回,不会查询数据库,如果缓存中没有要查询的对象,则再去数据库中查询对应对象,并添加到一级缓存中。


3)当调用 Session 的 close() 方法时,Session 缓存会被清空。


4)Session 能够在某些情况下,按照缓存中对象的变化,执行相关的 SQL 语句同步更新数据库,这一过程被称为刷出缓存(flush)。


在默认情况下,Session 在如下几种情况中会刷出缓存。


1)当应用程序调用 Transaction 的 commit() 方法时,该方法先刷出缓存(调用 session.flush() 方法),然后再向数据库提交事务(调用 commit() 方法)。


2)当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,会先刷出缓存,以保证查询结果能够反映持久化对象的最新状态。


3)调用 Session 的 flush() 方法。


以上就是 Hibernate 一级缓存的刷出情况。对于刚接触 Hibernate 框架的读者来说并不是很容易理解,为了帮助读者更好地理解 Session 的一级缓存,下面通过具体案例演示一级缓存的使用。


利用上面的程序,添加如下的测试方法,证明缓存的存在:


// 证明一级缓存的存在
@Test
public void test2() {
    Session session = HibernateUtils.getSession(); // 得到session对象
    session.beginTransaction();
    // 获取goods1对象时,由于一级缓存中没有数据,所以会发送SQL语句,查询数据库中的内容
    Goods goods1 = (Goods) session.get(Goods.class, 1);
    System.out.println(goods1);
    // 获取goods2对象时,不会发出SQL语句,会从Session缓存中获取数据
    Goods goods2 = (Goods) session.get(Goods.class, 1);
    System.out.println(goods2);
    session.getTransaction().commit();
    session.close();
}


第一次执行 get() 方法时,由于一级缓存中不存在 id 为 1 的 Goods 对象,所以 Hibernate 会发出 SQL 语句查询数据库中 id 为 1 的数据。查到后,会将该数据信息保存到一级缓存中。当再次调用 get() 方法获取该对象时,此时将不会发出 SQL 语句,这是因为该对象是在一级缓存中获取的。


打断点测试:


3c586b61dd8d41eca3782dda99ab0edd.png


执行完第一次get方法后,hibernate向mysql数据库发送了一天sql语句,说明此时的数据是从数据库中得来的。


b828203158424c3286d025e910309328.png


执行完第二个给语句后,没有输出。注意Goods[id=1,name=铅笔,price=0.5]是来自于之前的输出语句。也就是说,第二次调用get方法没有从数据库中获取数据而是从以及缓存中获取的。


hibernate的快照技术


为了确保一级缓存中的数据和数据库中的数据保持一致,在 Hibernate 框架中提供了快照技术。


Hibernate 向一级缓存中存入数据的同时,还会复制一份数据存入 Hibernate 快照中。当调用 commit() 方法时,会清理一级缓存中的数据操作,同时会检测一级缓存中的数据和快照区的数据是否相同。如果不同,则会执行 update() 方法,将一级缓存的数据同步到数据库中,并更新快照区;反之,则不会执行 update() 方法


// hibernate快照
@Test
public void test3() {
    Session session = HibernateUtils.getSession(); // 得到session对象
    session.beginTransaction();
    Goods goods = new Goods();
    goods.setName("钢笔");
    goods.setPrice(5.0);
    session.save(goods);    // 向一级缓存中存入session对象
    goods.setPrice(4.5);    // 提交价格
    session.getTransaction().commit();    //提交事务
    session.close();    //关闭资源
}


执行后截图:


8bb7833fb24d4b54af72ae55094d9fae.png


创建了一个 Goods 对象,并为该对象的属性进行赋值。在执行 save() 方法时,会将数据保存到一级缓存中。接着修改对象的 price 属性值,提交事务并关闭 session 对象。


从上面的运行结果中我们看到hibernate发送了两次sql语句。第一条sql语句当然是save语句执行发送的,那我们debug一下看一下这第二条语句发送的时机。


c9d407ea91f04d588aec973327af9f4b.png


发现第二条sql语句是执行commit语句发送的。


为什么会发出 update 语句呢?


这是因为在执行 session 的 save() 方法时,Hibernate 一级缓存中保存了 Goods 对象的数据且会复制一份数据放入到 Hibernate 快照中,在执行 commit() 方法时,Hibernate 会检测快照中的数据与一级缓存中的数据是否一致,由于在此之前 Goods 对象的 price 属性值发生了变化,导致快照中的数据与一级缓存中的数据已经不一致,因此发送了 update 语句,更新了 Hibernate 一级缓存中的数据。


我们从数据库中查看一下(执行该方法前清空了表中的数据):


923ab75ee8344e0e81d6bf5819e5b65f.png


从上面查询的结果可以看出,插入的数据是更新完之后打的数据。也就证实了上面所说的。


一级缓存的常用操作


分别为刷出、清除和刷新操作


刷出(flush)


一级缓存刷出功能是指调用 Session 的 flush() 方法时会执行刷出缓存的操作。


session flush在commit之前默认都会执行他。也可以手动执行它,他主要做了两件事:

1) 清理缓存。

2) 执行SQL。


session在什么情况下执行flush


  • 默认在事务提交时
  • 显示的调用flush
  • 在执行查询前,如:iterate

hibernate按照save(insert),update、delete顺序提交相关操作


测试:


// 刷出
@Test
public void test4() {
    Session session = HibernateUtils.getSession(); // 得到session对象
    session.beginTransaction();
    Goods goods = (Goods) session.get(Goods.class, 12);
    goods.setPrice(5.5);
    session.flush(); // 执行刷出操作,此时会发送update语句
    session.getTransaction().commit();
    session.close();
}


断点测试:

965f6c0fd1a942769e02a083c668d83d.png

a9e9ef76b2db437590c7e98112762328.png


发现当程序在断点处停下来后,输出了select 语句,并且此时的数据依然是之前的。接下来我们执行下一句。

740658ae364b459abdc17f178e93dbb8.png

f635b89629014c44856f3e5d7480eb2f.png


发现执行完session.flush();语句后输出了update语句, commit() 方法时,也同样发出了 update 语句,这说明在提交事务前,Hibernate 程序会默认先执行 flush() 方法。

提交完事务后数据库中的数据就改变了。


45988d0fc0684b028edf5686769deb7a.png


补充:


默认情况下 Session 在以下时间点刷新缓存:

1、显式调用 Session 的 flush() 方法

2、当应用程序调用 Transaction 的 commit()方法的时, 该方法先 flush ,然后在向数据库提交事务

3、当应用程序执行一些查询(HQL, Criteria)操作时,如果缓存中持久化对象的属性已经发生了变化,会先 flush 缓存,以保证查询结果能够反映持久化对象的最新状态


Session 的 save() 方法使一个临时对象转变为持久化对象

Session 的 save() 方法完成以下操作:

1、把 News 对象加入到 Session 缓存中, 使它进入持久化状态

2、选用映射文件指定的标识符生成器, 为持久化对象分配唯一的 OID. 在 使用代理主键的情况下, setId() 方法为 News 对象设置 OID 使无效的.

3、计划执行一条 insert 语句:在 flush 缓存的时候


清除(clear)


程序在调用 Session 的 clear() 方法时,可以执行清除缓存数据的操作。下面通过具体示例演示一级缓存中的清除功能。


测试代码:


    // 清除
    @Test
    public void test5() {
        Session session = HibernateUtils.getSession(); // 得到session对象
        session.beginTransaction();
        Goods goods = (Goods) session.get(Goods.class, 12);
        System.out.println(goods);
        goods.setPrice(6.5);
        session.clear(); // 清空一级缓存
        session.getTransaction().commit();
        session.close();
    }


执行完上面的操作后截图如下:

f205a16fd98e43d097d646a950853995.png

a2161b9c0370451c9be60e61cb1189af.png


发现只输出了一条select语句。并且数据库中的值没有被修改。下面我们把clear语句注释了。再执行一下。


1ef2988f4a78430fb33130bf4eb224ed.png

7ecc839be0ad482d9b65b0bc1fd1fb4e.png


会发现输出了一天select语句和一条update语句,并且数据库中的数据也修改了。


这是因为在执行 clear() 方法时,清空了一级缓存中的数据,所以 Goods 对象的修改操作并没有生效。


需要注意的是,如果将上述方法中的 session.clear() 方法更改为 session.evict(goods)方法,也可以实现同样的效果。这两个方法的区别是:clear() 方法是清空一级缓存中所有的数据,而 evict() 方法是清除一级缓存中的某一个对象


刷新(refresh)


程序在调用 Session 的 refresh() 方法时,会重新查询数据库,并更新 Hibernate 快照区和一级缓存中的数据。


// 刷新
@Test
public void test6() {
    Session session = HibernateUtils.getSession(); // 得到session对象
    session.beginTransaction();
    Goods goods = (Goods) session.get(Goods.class, 12);
    goods.setPrice(7.5);
    session.refresh(goods); // 查询数据库,恢复快照和一级缓存中的数据
    session.getTransaction().commit();
    session.close();
}


运行截图:


5414a4ab96c3450d8b59ff2da77b884e.png


执行完get方法后hibernate把数据库中查询到的值赋给了对象

56f6eb4180104046b75b52c37e0182f5.png

44a0a009e0764f45a4756d72c13e16af.png


setPrice方法执行,缓存中goods对象的price属性值被设置为7.5,且此时控制台中只输出了select语句。

c06d1db5f34149a39fa6a6caf31b1b84.png

d792b727a93d4e0e9466332bbc1e39bd.png


执行refresh方法后,price又别为了6.5,并且控制台中多了一条select语句。因为refresh方法的作用就是使快照和以及缓存中的数据与数据库中的数据保持一直。


说明:


其实当我们进行第一句查询时,hibernate把返回的Customer对象一式两份,一份保存到一级缓存,一份保存到快照当中,然后返回给我们的是缓存中的对象。


a8e469ca9f1e4d9481b24ab7194fad73.png

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
Oracle Java 关系型数据库
[学习笔记] 在Eclipse中使用Hibernate,并创建第一个Demo工程,数据库为Oracle XE
[学习笔记] 在Eclipse中使用Hibernate,并创建第一个Demo工程,数据库为Oracle XE
|
3月前
|
Java 关系型数据库 数据库连接
Hibernate学习笔记(一)快速入门
Hibernate学习笔记(一)快速入门
|
SQL Java 数据库连接
Hibernate_学习笔记
Hibernate_学习笔记
|
存储 SQL Java
hibernate学习笔记之二(映射关系与懒加载)
hibernate学习笔记之二(映射关系与懒加载)
hibernate学习笔记之二(映射关系与懒加载)
|
SQL XML 安全
hibernate学习笔记之一(上)
hibernate学习笔记之一(上)
hibernate学习笔记之一(上)
|
SQL JSON Java
SpringBoot 整合 JPA-Hibernate|学习笔记
快速学习 SpringBoot 整合 JPA-Hibernate
157 0
SpringBoot 整合 JPA-Hibernate|学习笔记
|
SQL Java 数据库连接
hibernate入门学习笔记
hibernate入门学习笔记
73 0
|
Java 数据库连接 开发工具
Hibernate 控制反转|学习笔记
快速学习 Hibernate 控制反转
|
缓存 Java 数据库连接
Hibernate Session 生命周期|学习笔记
快速学习 Hibernate Session 生命周期
176 0
|
Java 数据库连接 数据库
Hibernate学习笔记8,session管理,事务控制
Hibernate学习笔记8,session管理,事务控制 Hibernate的事务管理 事务(Transaction)是工作中的基本逻辑单位,可以用于确保数据库能够被正确修改,避免数据只修改了一部分而导致数据不完整,或者在修改时受到用户干扰。
1635 0