Hibernate复习笔记(3)——Session缓存(Hibernate一级缓存)详解

Session 概述

通过 Session 操纵对象

•Session 接口是 Hibernate 向应用程序提供的操纵数据库的最主要的接口, 它提供了基本的保存, 更新, 删除和加载 Java 对象的方法.注意,这里只有加载,而没有查询。因为查询并不是由Session直接来操作的,我们需要用到Query query=session.createQuery来进行查询。

•Session 具有一个缓存, 位于缓存中的对象称为持久化对象, 它和数据库中的相关记录对应。Session的这个缓存,我们也称之为Hibernate的一级缓存 Session 能够在某些时间点, 按照缓存中对象的变化来执行相关的 SQL 语句, 来同步更新数据库, 这一过程被称为刷新缓存(flush)

•站在持久化的角度, Hibernate 把对象分为 4 种状态: 持久化状态临时状态游离状态删除状态. Session 的特定方法能使对象从一个状态转换到另一个状态.

首先,我们把环境搭建起来:

POM.xml:

<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>com.happyBKs.hibernate</groupId>
  <artifactId>hibernatePro2</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>hibernatePro2</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
    </dependency>

	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-core</artifactId>
		<version>5.2.5.Final</version>
	</dependency>

	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>6.0.5</version>
	</dependency>
	
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>6.0.5</version>
	</dependency>
  </dependencies>
</project>

(本文出自oscchina博主happyBKs的文章:https://my.oschina.net/happyBKs/blog/832102,版权归作者所有)

第一步,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.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&amp;characterEncoding=utf8&amp;useUnicode=true&amp;useSSL=false</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password"></property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        
        <!-- 配置hibernate的基本信息 -->
        <!-- hibernate所使用的的数据库方言  -->
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        
        <!--  执行操作是否在控制台打印SQL -->
        <property name="show_sql">true</property>
        
        <!-- 是否对SQL进行格式化 -->
        <property name="format_sql">true</property>
        
        <!-- 指定自动生成数据表的策略:在运行数据库的时候hibernate会为我们在数据库自动生成数据表的策略 -->
        <property name="hbm2ddl.auto">update</property>
        
        <!-- 指定关联的hbm.xml映射文件 -->
        <mapping resource="com/happybks/hibernate/hibernatePro2/beans/CuntomerBean.hbm.xml"/>
        
    </session-factory>
</hibernate-configuration>

第二步,建立bean实体类:

这里我们Date改用java.util.Date

package com.happyBKs.hibernate.hibernatePro2.beans;

import java.util.Date;
import java.sql.Timestamp;


public class CuntomerBean {

	private Integer cid;
	private String name;
	private Integer no;
	private Long score;
	private double money;
	private Date registerDate;
	private Timestamp loginTime;
	public Integer getCid() {
		return cid;
	}
	public void setCid(Integer cid) {
		this.cid = cid;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getNo() {
		return no;
	}
	public void setNo(Integer no) {
		this.no = no;
	}
	public Long getScore() {
		return score;
	}
	public void setScore(Long score) {
		this.score = score;
	}
	public double getMoney() {
		return money;
	}
	public void setMoney(double money) {
		this.money = money;
	}
	public Date getRegisterDate() {
		return registerDate;
	}
	public void setRegisterDate(Date registerDate) {
		this.registerDate = registerDate;
	}
	public Timestamp getLoginTime() {
		return loginTime;
	}
	public void setLoginTime(Timestamp loginTime) {
		this.loginTime = loginTime;
	}
	public CuntomerBean(String name, Integer no, Long score, double money, Date registerDate, Timestamp loginTime) {
		super();
		this.name = name;
		this.no = no;
		this.score = score;
		this.money = money;
		this.registerDate = registerDate;
		this.loginTime = loginTime;
	}
	public CuntomerBean() {

	}
	@Override
	public String toString() {
		return "CuntomerBean [cid=" + cid + ", name=" + name + ", no=" + no + ", score=" + score + ", money=" + money
				+ ", registerDate=" + registerDate + ", loginTime=" + loginTime + "]";
	}
	
}

第三步,建立hbm映射文件:CuntomerBean.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2017-1-14 15:06:13 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping>
    <class name="com.happyBKs.hibernate.hibernatePro2.beans.CuntomerBean" table="CUNTOMERBEAN">
        <id name="cid" type="java.lang.Integer">
            <column name="CID" />
            <generator class="native" />
        </id>
        <property name="name" type="java.lang.String">
            <column name="NAME" />
        </property>
        <property name="no" type="java.lang.Integer">
            <column name="NO" />
        </property>
        <property name="score" type="java.lang.Long">
            <column name="SCORE" />
        </property>
        <property name="money" type="double">
            <column name="MONEY" />
        </property>
        <property name="registerDate" type="java.util.Date">
            <column name="REGISTERDATE" />
        </property>
        <property name="loginTime" type="java.sql.Timestamp">
            <column name="LOGINTIME" />
        </property>
    </class>
</hibernate-mapping>

第四步,在测试类中调用hibernate的API:

初始化三步骤:SessionFactory(由Configure指定配置文件并build)、Session、Transaction。

结束三步骤:提交Transaction、关闭Session、关闭SessionFactory。

package com.happyBKs.hibernate.hibernatePro2;

import static org.junit.Assert.*;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class HibernateTest {

	private SessionFactory sessionFactory;
	private Session session;
	Transaction transaction;
	//在实际项目开发中session和transaction是不能作为成员变量的,因为会存在并发问题。
	//本例只是为了提供几个测试示例时方便所以这样写。
	
	@Before
	public void init(){
		System.out.println("init");
		Configuration configuration=new Configuration().configure();
		sessionFactory=configuration.buildSessionFactory();
		session=sessionFactory.openSession();
		transaction=session.beginTransaction();
	}
	
	@After
	public void destroy(){
		System.out.println("destroy");
		transaction.commit();
		session.close();
		sessionFactory.close();
	}
	
	
	@Test
	public void test() {
		System.out.println("test");
	}

}

运行输出日志:

init
一月 14, 2017 3:30:11 下午 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.2.5.Final}
一月 14, 2017 3:30:11 下午 org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
一月 14, 2017 3:30:12 下午 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
一月 14, 2017 3:30:12 下午 org.hibernate.boot.jaxb.internal.stax.LocalXmlResourceResolver resolveEntity
WARN: HHH90000012: Recognized obsolete hibernate namespace http://hibernate.sourceforge.net/hibernate-mapping. Use namespace http://www.hibernate.org/dtd/hibernate-mapping instead.  Support for obsolete DTD/XSD namespaces may be removed at any time.
一月 14, 2017 3:30:13 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
一月 14, 2017 3:30:13 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false]
一月 14, 2017 3:30:13 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
一月 14, 2017 3:30:13 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
一月 14, 2017 3:30:13 下午 org.hibernate.engine.jdbc.connections.internal.PooledConnections <init>
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
一月 14, 2017 3:30:13 下午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
一月 14, 2017 3:30:14 下午 org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@34abdee4] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
Hibernate: 
    
    create table CUNTOMERBEAN (
        CID integer not null auto_increment,
        NAME varchar(255),
        NO integer,
        SCORE bigint,
        MONEY double precision,
        REGISTERDATE datetime,
        LOGINTIME datetime,
        primary key (CID)
    )
test
destroy
一月 14, 2017 3:30:15 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false]

数据库自动生成了数据表(前提是hibernate.cfg.xml配置文件里 <property name="hbm2ddl.auto">update</property>)

 

Session缓存(一级缓存)

我们以一个例子作为开胃菜:

现在我在数据库里插入一条记录,主键CID为1。一会儿有用。

在刚才的测试类中添加一个测试方法:

	@Test
	public void testSelect(){
		CuntomerBean bean=session.get(CuntomerBean.class, 1);
		System.out.println(bean);
		CuntomerBean bean2=session.get(CuntomerBean.class, 1);
		System.out.println(bean2);
		if(bean==bean2){
			System.out.println("两个引用对应的是同一个对象");
		}
		else{
			System.out.println("两个引用对应的对象虽然属性一样,但是不是同一个对象");
		}
	}

这是两个查询操作,做的都是获取主键为1的那个记录。照理来说,这是两次查询,应该有两条SQL语句提交给数据库执行。但事实是怎么呢?

init
一月 15, 2017 10:47:36 下午 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.2.5.Final}
一月 15, 2017 10:47:36 下午 org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
一月 15, 2017 10:47:36 下午 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
一月 15, 2017 10:47:36 下午 org.hibernate.boot.jaxb.internal.stax.LocalXmlResourceResolver resolveEntity
WARN: HHH90000012: Recognized obsolete hibernate namespace http://hibernate.sourceforge.net/hibernate-mapping. Use namespace http://www.hibernate.org/dtd/hibernate-mapping instead.  Support for obsolete DTD/XSD namespaces may be removed at any time.
一月 15, 2017 10:47:36 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
一月 15, 2017 10:47:36 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false]
一月 15, 2017 10:47:36 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
一月 15, 2017 10:47:36 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
一月 15, 2017 10:47:36 下午 org.hibernate.engine.jdbc.connections.internal.PooledConnections <init>
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
一月 15, 2017 10:47:37 下午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
一月 15, 2017 10:47:37 下午 org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@1df98368] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
Hibernate: 
    select
        cuntomerbe0_.CID as CID1_0_0_,
        cuntomerbe0_.NAME as NAME2_0_0_,
        cuntomerbe0_.NO as NO3_0_0_,
        cuntomerbe0_.SCORE as SCORE4_0_0_,
        cuntomerbe0_.MONEY as MONEY5_0_0_,
        cuntomerbe0_.REGISTERDATE as REGISTER6_0_0_,
        cuntomerbe0_.LOGINTIME as LOGINTIM7_0_0_ 
    from
        CUNTOMERBEAN cuntomerbe0_ 
    where
        cuntomerbe0_.CID=?
CuntomerBean [cid=1, name=HappyBKs, no=314, score=98765432123456789, money=20688.68, registerDate=2017-01-15 22:30:01.0, loginTime=2017-01-15 12:00:00.0]
CuntomerBean [cid=1, name=HappyBKs, no=314, score=98765432123456789, money=20688.68, registerDate=2017-01-15 22:30:01.0, loginTime=2017-01-15 12:00:00.0]
两个引用对应的是同一个对象
destroy
一月 15, 2017 10:47:37 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false]

从控制台日志可以看到,由于我们在hibernate.cfg.xml中已经设置了发送SQL操作都要打印出来的设置。我们可以看到,这个查询两次的操作只提交了一个SQL的select查询。这是为什么呢?这就是Hibernate的Sesioin缓存。我们可以看到,将获取到的两个对象通过==判断发现,这两个引用居然引用的是同一个对象,而不是各自创建一个属性值相同的对象。

我们来阐述一下Session缓存的例子的过程:

第一次执行session.get的时候的确会去查询数据库,然后把Cuntomer对象得到,然后把这个对象给到了bean引用上;同时,还会把这个对象引用到Session缓存。也就是说,第一次执行session.get的时候从数据库查询执行得到的对象有两个引用,一个是我们代码中接收对象的bean,一个是Session缓存。

		CuntomerBean bean=session.get(CuntomerBean.class, 1);

当第二次session.get的时候,它先会看Session缓存里有没有,如果缓存里面已经有的话就不查询数据库了。而是把Session缓存中的引用的对象,给到来了第二个接收对象的引用bean2:

		CuntomerBean bean2=session.get(CuntomerBean.class, 1);

也就是说第二次session.get操作,没有向数据库发送SQL语句。

知识点归纳:

•在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存. 只要 Session 实例没有结束生命周期, 且没有清理缓存,则存放在它缓存中的对象也不会结束生命周期。也就是说在上面的例子中,就算bean和bean2两个引用以后都不引用这个对象了,这个对象也不会成为垃圾被回收,因为Session缓存里面还有引用在引用它。

•Session 缓存可减少 Hibernate 应用程序访问数据库的频率。因此,Session缓存使我们提高数据库运行效率的一种方式。

Session缓存也被称为Hibernate的一级缓存。后面我们还会提到Hibernate的二级缓存。

这里先只做一个简单区分:

一级缓存——Session级别的

二级缓存——SessionFactory级别的

 

上面我们知识点归纳的第一条中有一句——“没有清理缓存”。那么我们现在来看看对Session缓存的操作有哪些。

 

Hibernate Session缓存的3个操作:flush、refresh与clear

 

1. flush 缓存

从上面的图可以看到flush操作表明的箭头方向是从缓存对象指向数据库记录。即当调用Session的flush()方法的时候,它会强制将数据库中的记录与Session缓存中的对象保持同步,就是说在调用flush方法时,缓存里的对象属性会和数据库里的记录是否一致,不一致的话,如果不一致的话,会发送给数据库对应的SQL语句(改update、删delete、增insert)使数据库里的记录与Session缓存的对象保持一致。

•flush:Session 按照缓存中对象的属性变化来同步更新数据库

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

–显式调用 Session 的 flush() 方法

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

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

•flush 缓存的例外情况: 如果对象使用 native 生成器生成 OID, 那么当调用 Session 的 save() 方法保存对象时, 会立即执行向数据库插入该实体的 insert 语句.

•commit() 和 flush() 方法的区别:flush 执行一系列 sql 语句,但不提交事务;commit 方法先调用flush() 方法,然后提交事务. 提交事务意味着对数据库操作永久保存下来。

关于commit的时候做了什么,我们可以看看hibernate的源代码:

点击进入EntityTransaction,因为是接口,所以Ctrl+T看看继承关系下的类有哪些:

进入TransactionImpl

进入internalGetTransactionDriverControl().commit();

进入JdbcResourceLocalTransactionCoordinatorImpl

进入JdbcResourceLocalTransactionCoordinatorImpl.this.beforeCompletionCallback();

进入transactionCoordinatorOwner.beforeTransactionCompletion();

进入JdbcCoordinatorImpl

进入owner.beforeTransactionCompletion();

进入SessionImpl

看到了吧,一个调用的一个函数叫做flushBeforeTransactionCompletion();

进入看看:

再进

再进

好吧,就到这里,具体代码不解释了。但是你可以看到,在Hibernate提交事务的时候,的确先对Session中的持久化对象进行了flush相关操作。

 

好,我们用例子来看吧。

我们在上面的测试用例中增加一个测试方法:

	@Test
	public void testSessionFlush(){
		CuntomerBean bean=session.get(CuntomerBean.class, 1);
		bean.setMoney(40688.68);
	}

之前的两个方法,一头一尾,仍然在这个例子中起作用:

	private SessionFactory sessionFactory;
	private Session session;
	Transaction transaction;
	//在实际项目开发中session和transaction是不能作为成员变量的,因为会存在并发问题。
	//本例只是为了提供几个测试示例时方便所以这样写。
	
	@Before
	public void init(){
		System.out.println("init");
		Configuration configuration=new Configuration().configure();
		sessionFactory=configuration.buildSessionFactory();
		session=sessionFactory.openSession();
		transaction=session.beginTransaction();
	}
	
	@After
	public void destroy(){
		System.out.println("destroy");
		transaction.commit();
		session.close();
		sessionFactory.close();
	}

首先,我们Session从数据库中获取了一个对象,所以发送了一个select;然后修改了一个属性money。整个这个过程是在Session的生命周期里面,而且还开了事务、提交事务。修改属性的操作被Session感知到,Session感知到Session缓存中的对象被修改了、不一样了,因而在事务提交之前,Session会执行一个flush,这个flush会发送一个update语句。

我们运行以下看看:

init
二月 05, 2017 2:49:27 下午 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.2.5.Final}
二月 05, 2017 2:49:27 下午 org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
二月 05, 2017 2:49:27 下午 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
二月 05, 2017 2:49:27 下午 org.hibernate.boot.jaxb.internal.stax.LocalXmlResourceResolver resolveEntity
WARN: HHH90000012: Recognized obsolete hibernate namespace http://hibernate.sourceforge.net/hibernate-mapping. Use namespace http://www.hibernate.org/dtd/hibernate-mapping instead.  Support for obsolete DTD/XSD namespaces may be removed at any time.
二月 05, 2017 2:49:28 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
二月 05, 2017 2:49:28 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false]
二月 05, 2017 2:49:28 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
二月 05, 2017 2:49:28 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
二月 05, 2017 2:49:28 下午 org.hibernate.engine.jdbc.connections.internal.PooledConnections <init>
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
二月 05, 2017 2:49:28 下午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
二月 05, 2017 2:50:00 下午 org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@31c628e7] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
Hibernate: 
    select
        cuntomerbe0_.CID as CID1_0_0_,
        cuntomerbe0_.NAME as NAME2_0_0_,
        cuntomerbe0_.NO as NO3_0_0_,
        cuntomerbe0_.SCORE as SCORE4_0_0_,
        cuntomerbe0_.MONEY as MONEY5_0_0_,
        cuntomerbe0_.REGISTERDATE as REGISTER6_0_0_,
        cuntomerbe0_.LOGINTIME as LOGINTIM7_0_0_ 
    from
        CUNTOMERBEAN cuntomerbe0_ 
    where
        cuntomerbe0_.CID=?
destroy
Hibernate: 
    update
        CUNTOMERBEAN 
    set
        NAME=?,
        NO=?,
        SCORE=?,
        MONEY=?,
        REGISTERDATE=?,
        LOGINTIME=? 
    where
        CID=?
二月 05, 2017 2:52:04 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false]

这个update实际上就是flush操作。

你可能会问,代码里面哪里有flush,我只能看到get了一个对象,然后set对象的属性,之后便是commit事务了,我们并没有显示调用Session的flush()方法。其实,除了显示调用flush()方法来刷新缓存之外,在commit()方法提交事务之前,会隐含一个flush缓存的操作。上面的一大组截图,已经展示了Hibernate源代码中的实现,SessionImpl类就是Session的一个具体实现,记录了在commit事务之前所做的flush操作。

如果你还是觉得难以清除理清楚发送的SQL语句、flush缓存、事务提交等等一系列的执行关系,那么我们设置几个断点来看一下:

首先我们将代码中的money属性额值再set一下成别的值。然后设置几个断点:我们自己调用transaction的commit()方法前加一个:(我们叫做A处)

SessionImpl的flush操作前加一个:(我们叫做B处)

JdbcResourceLocalTransactionCoordinatorImpl中commit之前加一个:(我们叫做C处)

这样,我们在每一个断点处,分别看看Session执行了什么操作,与数据库如何交互:

好,我们在一开始获取对象的地方也设置一个起始断点,然后单步执行:

我们看到,get对象之后,Session立即向数据库发送了select语句,查询到了数据库中相应的记录。(当然,如果以后再次获取这个对象,且没有其他变化,那么可能 直接从Session缓存中读取而不必每次都发送select语句查询数据库)

init
二月 05, 2017 3:18:53 下午 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.2.5.Final}
二月 05, 2017 3:18:53 下午 org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
二月 05, 2017 3:18:53 下午 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
二月 05, 2017 3:18:53 下午 org.hibernate.boot.jaxb.internal.stax.LocalXmlResourceResolver resolveEntity
WARN: HHH90000012: Recognized obsolete hibernate namespace http://hibernate.sourceforge.net/hibernate-mapping. Use namespace http://www.hibernate.org/dtd/hibernate-mapping instead.  Support for obsolete DTD/XSD namespaces may be removed at any time.
二月 05, 2017 3:18:54 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
二月 05, 2017 3:18:54 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false]
二月 05, 2017 3:18:54 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
二月 05, 2017 3:18:54 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
二月 05, 2017 3:18:54 下午 org.hibernate.engine.jdbc.connections.internal.PooledConnections <init>
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
二月 05, 2017 3:18:54 下午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
二月 05, 2017 3:18:55 下午 org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@31c628e7] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
Hibernate: 
    select
        cuntomerbe0_.CID as CID1_0_0_,
        cuntomerbe0_.NAME as NAME2_0_0_,
        cuntomerbe0_.NO as NO3_0_0_,
        cuntomerbe0_.SCORE as SCORE4_0_0_,
        cuntomerbe0_.MONEY as MONEY5_0_0_,
        cuntomerbe0_.REGISTERDATE as REGISTER6_0_0_,
        cuntomerbe0_.LOGINTIME as LOGINTIM7_0_0_ 
    from
        CUNTOMERBEAN cuntomerbe0_ 
    where
        cuntomerbe0_.CID=?

之后,我们继续单步,执行到A处时。commit()方法执行之前,输出的日志仍然没有变化,即还没有发送update:

之后运行到SessionImpl类的flushBeforeTransactionCompletion();一句:

此时update语句还是没有发送给数据库。

之后走一步,flushBeforeTransactionCompletion();之后

update语句在控制台打印出来了。这说明Session在flush操作刷新缓存时向数据库发送了update语句。

但是需要注意的是,这时候数据库的值仍然还是原先的值:

执行到JdbcResourceLocalTransactionCoordinatorImpl的commit操作之前:

数据库内的记录依然没有变化:

执行完jdbcResourceTransaction.commit();一步之后:

我们发现:数据库的记录的字段属性变了,更新了,这说明事务提交了,update语句执行完成数据库记录更新了。

总结一下,我们在调用transaction.commit();时,它内部会首先flush刷新缓存、然后再提交事务。

 flush 使数据表中的记录和Seesion缓存中的对象保持一致。为了保持一致,则可能发送对应的SQL语句。
(所谓“可能”,意思是如果调用flush方法时,Session缓存中的对象和数据库中的一模一样,那就不发送SQL语句了)
 1. 在Transaction的commit()方法中:先调session的flush方法,在提交事务
 2. flush()方法可能会发送SQL语句,但不会提交事务。

 

注意:
     在未提交事务或显示调用seesion.flush()方法之前,也有几种情况可能会进行flush操作
      1)执行HQL或QBC查询,(查询必须保证查的数据是最新的,所以必须flush,才能保证数据库里内容为最新)会先进行flush()操作,以得到数据表的最新记录。

    2)若记录的ID是由底层数据库使用自增方式生成的,则在调用save()方法后,就会立即发送INSERT语句
     *因为save方法后必须保证对象的ID是存在的!

我们分别看一个例子:

第一种情况:执行HQL或QBC查询

	@Test
	public void testSessionFlush_1(){
		CuntomerBean bean=session.get(CuntomerBean.class, 1);
		bean.setMoney(60688.68);
		
		CuntomerBean bean2=(CuntomerBean)session.createCriteria(CuntomerBean.class).uniqueResult();
		System.out.println(bean2);
	}

做的操作就是,获取一个对象,修改一个属性值;之后通过QBC查询这个对象(因为数据表里就一条记录现在,所以用uiqueResult这个查询了),然后打印出bean2,看看属性有没有是最新的。

之后和刚才一样,是事务提交的方法调用:

	@After
	public void destroy(){
		System.out.println("destroy");
		transaction.commit();
		session.close();
		sessionFactory.close();
	}

之后,单步来看看之日输出和数据库:

获取对象,发送了select语句。

设置对象属性为新值,修改了Session缓存对象,但没有发送SQL语句

执行一个QBC查询,这时候发现控制台发送了一个update操作。因为要保证在这个事务范围内,QBC查询的数据库对象必须是最新的,所以先将上面的修改操作通过update语句提交给数据库。

再执行一句,打印bean2:

init
二月 05, 2017 3:52:52 下午 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.2.5.Final}
二月 05, 2017 3:52:52 下午 org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
二月 05, 2017 3:52:53 下午 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
二月 05, 2017 3:52:53 下午 org.hibernate.boot.jaxb.internal.stax.LocalXmlResourceResolver resolveEntity
WARN: HHH90000012: Recognized obsolete hibernate namespace http://hibernate.sourceforge.net/hibernate-mapping. Use namespace http://www.hibernate.org/dtd/hibernate-mapping instead.  Support for obsolete DTD/XSD namespaces may be removed at any time.
二月 05, 2017 3:52:54 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
二月 05, 2017 3:52:54 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false]
二月 05, 2017 3:52:54 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
二月 05, 2017 3:52:54 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
二月 05, 2017 3:52:54 下午 org.hibernate.engine.jdbc.connections.internal.PooledConnections <init>
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
二月 05, 2017 3:52:54 下午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
二月 05, 2017 3:52:55 下午 org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@3dd31157] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
Hibernate: 
    select
        cuntomerbe0_.CID as CID1_0_0_,
        cuntomerbe0_.NAME as NAME2_0_0_,
        cuntomerbe0_.NO as NO3_0_0_,
        cuntomerbe0_.SCORE as SCORE4_0_0_,
        cuntomerbe0_.MONEY as MONEY5_0_0_,
        cuntomerbe0_.REGISTERDATE as REGISTER6_0_0_,
        cuntomerbe0_.LOGINTIME as LOGINTIM7_0_0_ 
    from
        CUNTOMERBEAN cuntomerbe0_ 
    where
        cuntomerbe0_.CID=?
二月 05, 2017 3:54:38 下午 org.hibernate.internal.SessionImpl createCriteria
WARN: HHH90000022: Hibernate's legacy org.hibernate.Criteria API is deprecated; use the JPA javax.persistence.criteria.CriteriaQuery instead
Hibernate: 
    update
        CUNTOMERBEAN 
    set
        NAME=?,
        NO=?,
        SCORE=?,
        MONEY=?,
        REGISTERDATE=?,
        LOGINTIME=? 
    where
        CID=?
Hibernate: 
    select
        this_.CID as CID1_0_0_,
        this_.NAME as NAME2_0_0_,
        this_.NO as NO3_0_0_,
        this_.SCORE as SCORE4_0_0_,
        this_.MONEY as MONEY5_0_0_,
        this_.REGISTERDATE as REGISTER6_0_0_,
        this_.LOGINTIME as LOGINTIM7_0_0_ 
    from
        CUNTOMERBEAN this_
CuntomerBean [cid=1, name=HappyBKs, no=314, score=98765432123456789, money=60688.68, registerDate=2017-01-15 22:30:01.0, loginTime=2017-01-15 12:00:00.0]

 

我们可以看到,打印的bean2的money值已经是修改后的60688.68了,但是此时数据库的值仍然没有变化。

从上面可以看出,执行QBC查询操作时,需要保证在同一个事务范围中查询到的数据库数据是最新的,所以Session自行执行flush刷新缓存,在查询(select操作)之前已经先提交了update操作。之后QBC获取数据库对象的数据已经是修改后的新数据,但需要注意的是在事务提交之前,数据库本身内的记录数据是不会更新的。

之后我们可以看到之后transaction.commit()方法中先flush操作,此时已经不会打印update语句了,因为刚才在执行QBC查询时已经发送过了update语句。并且和之前说的一样,在正式commit事务操作之前,数据库一直没有变化。

控制台也没有输出任何SQL语句,数据库也没有更新,因为事务还未提交:

最后,提交完事务之后:数据库变了。

 

第二种情况:记录的ID是由底层数据库使用自增方式生成的(native)

2)若记录的ID是由底层数据库使用自增方式生成的,则在调用save()方法后,就会立即发送INSERT语句
     * 因为save方法后必须保证对象的ID是存在的!

 

init
二月 05, 2017 6:56:36 下午 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.2.5.Final}
二月 05, 2017 6:56:36 下午 org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
二月 05, 2017 6:56:37 下午 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
二月 05, 2017 6:56:37 下午 org.hibernate.boot.jaxb.internal.stax.LocalXmlResourceResolver resolveEntity
WARN: HHH90000012: Recognized obsolete hibernate namespace http://hibernate.sourceforge.net/hibernate-mapping. Use namespace http://www.hibernate.org/dtd/hibernate-mapping instead.  Support for obsolete DTD/XSD namespaces may be removed at any time.
二月 05, 2017 6:56:37 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
二月 05, 2017 6:56:37 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false]
二月 05, 2017 6:56:37 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
二月 05, 2017 6:56:37 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
二月 05, 2017 6:56:37 下午 org.hibernate.engine.jdbc.connections.internal.PooledConnections <init>
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
二月 05, 2017 6:56:38 下午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
二月 05, 2017 6:56:38 下午 org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@3dd31157] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.

执行完session.save()方法之后,发现控制台打印了一个insert语句,说明Session向数据库发送提交了一个插入insert语句,插入数据库。

这就是在未提交事务或显示调用seesion.flush()方法之前,第2种情况可能会进行flush操作的情况:session对象的ID对应的数据库字段是自增的情况,这时候ID的生成需要依赖数据库本身。所以,在transcation.commit()方法的flush操作之前——Session调用save()时,就需要将insert语句提交给数据库,让数据库为之生成自增ID,被返回set到Session对应的cid字段上。

但是此时数据库中的数据表没有新增这条记录:

我们在继续一步:

我们可以看到控制台输出日志,对象的cid字段已经被赋予了值3。这个值必须由数据库自增给定,所以上一步session调用save方法时,必须提交数据库insert语句,这也是情理之中打的事情。

但是需要注意的是,虽然session中的缓存对象已经有了cid值,这个值从数据库插入时自增而来,但是实际上,这个时候事务还没有提交,所以数据表中还没有这条记录:

然后还是看看后续几个断点处的执行情况:

直到事务正式提交的前一步,(即使transaction.commit()中的一开始也会flush),但是数据库中依然没有insert这条记录。

之后,正式提交一步执行之后:数据表中终于插入了这条记录

好,这个例子要说明的是:如果对象中的某个属性对应的数据库字段是数据库本身自增生成的,那么在save()的时候就会直接发送insert语句给数据库,Session中对应的额属性随之立即更新为数据库给定的自增值,但数据库此时不会生成新插入的记录,直到事务正式被提交之后才会插入记录。这是不flush一级缓存仍然提交SQL的第二个特例。

这个例子我们需要注意,它有一个前提条件,那就是这个属性值来自于数据库本身的自增值。但是,这个自增值的获取方式不一定非要数据库生成,hibernate自己也有一种利用高低算法的生成自增id方式。我们只需要在映射文件的主键中配置生成方式即可。

如果我们在配置hibernate时,在映射关系配置文件中指定的生成方式是native,那么这个字段或者属性的值将由数据库自增生成;如果我们将其配置为hilo,hibernate会自己根据高低算法生成id,这样在save时就不需要数据库提供自增值,也就不需要再先于flush()调用之前——save()执行时提交insert语句给数据库了。

好,我们试试看,首先修改native为hilo。但是,我们发现直接报错了。我搜索了网上的答案,有人说是hibernate版本原因,应该是hibernate5开始不支持hilo了。请用seqhilo。但是seqhilo要求数据库必须是支持sequence的数据库,如Oracle。

(seqhilo与hilo类似,通过hi/lo算法实现的主键生成机制,只是将hilo中的数据表换成了序列sequence,需要数据库中先创建sequence,适用于支持sequence的数据库,如Oracle。)

我这里由于使用的mysql就不在提供seqhilo的示例了。但我想说明的是,hilo等这一类利用hibernate高低算法生成主键的方式的情况下,提交insert的语句的时间点就没必要是session.save的时候了,而是在提交事务时,调用flush操作时再提交insert语句给数据库。save的时候只是hibernate生成自增值给Session对象属性,提交完整insert语句放在了事务提交前的flush操作时做。

Hibernate 主键生成策略

或者看看这篇介绍吧。请参考这篇文章吧:http://www.cnblogs.com/hoobey/p/5508992.html

算了,我们还是用increment来说明上面说的问题吧。

数据库目前如下(刚才手贱,把表里东西删了,临时咋查两条,cid为3和4)

映射文件主键改好:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2017-1-14 15:06:13 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping>
    <class name="com.happyBKs.hibernate.hibernatePro2.beans.CuntomerBean" table="CUNTOMERBEAN">
        <id name="cid" type="java.lang.Integer">
            <column name="CID" />
            <generator class="increment" />
        </id>
        <property name="name" type="java.lang.String">
            <column name="NAME" />
        </property>
        <property name="no" type="java.lang.Integer">
            <column name="NO" />
        </property>
        <property name="score" type="java.lang.Long">
            <column name="SCORE" />
        </property>
        <property name="money" type="double">
            <column name="MONEY" />
        </property>
        <property name="registerDate" type="java.util.Date">
            <column name="REGISTERDATE" />
        </property>
        <property name="loginTime" type="java.sql.Timestamp">
            <column name="LOGINTIME" />
        </property>
    </class>
</hibernate-mapping>

还是单步调试着看:

然后,我们执行save方法,发现控制台打印了一个SQL语句,不过不是insert,而是一个查询最大cid值得select语句。这说明,当hibernate映射文件配置主键生成方式为increment时,hibernate在save时会发送一个查询现有数据表中最大id值得select语句,并按照获取的最大id值,hibernate算出一个新增的id值。

在执行一步,打印bean,我们已经能够看到,hibernate按照查询来的最大cid值,自行生成了一个新的额id值5:

然后,继续执行:

在flush之前,还是没有发送insert

执行完flush这一步之后:insert语句发送给了数据库!

但是此时数据库还是没有插入这条cid为5的新记录:

然后继续执行:

执行commit这一步之后:数据库插入了这条新记录

但是我心中有个疑问:

这种increment方式虽然是hibernate自己生成自增主键,但是却是依赖于查询得到的当时数据表中的最大id值。如果这时候,有另一个插入操作并发执行是否会两个save方法得到同样的最大id值,从而生成同样的新增id值,进而在插入insert时报出主键冲突的错误呢?

不会,因为事务的隔离性。这个我们后面再说。

 

flush还为我们提供了多种运行方式,设置刷新缓存的时间点:

设定刷新缓存的时间点

•若希望改变 flush 的默认时间点, 可以通过 Session 的 setFlushMode() 方法显式设定 flush 的时间点

 

2. refresh操作

Session与数据库之间的第2种操作。它的操作与flush是数据方向相反的操作。flush做的是将Session缓存中的属性变化通过update、insert、delete语句同步给数据库。而refresh是Session通过select查询数据库中的最新记录,把数据库的最新数据同步到Session缓存。

我们还是像详解flush那样,用例子,看源码,看输出、各个操作的时间节点和操作节点是哪里。

增加下面这个测试方法

	/**
	 * refresh()会强制发送select语句,以使Session缓存总的对象的状态和数据表中的记录保持一致!
	 * 这里无论Session缓存对象与数据库里的一样或不一样都会发送select,因为hibernate也不知道两者是否相同,所以必须发select查询一下
	 */
	@Test
	public void testSessionRefresh(){
		CuntomerBean bean=session.get(CuntomerBean.class, 5);
		System.out.println(bean);
		
		System.out.println(bean);
	}

相关前后操作和之前一样:

	private SessionFactory sessionFactory;
	private Session session;
	Transaction transaction;
	//在实际项目开发中session和transaction是不能作为成员变量的,因为会存在并发问题。
	//本例只是为了提供几个测试示例时方便所以这样写。
	
	@Before
	public void init(){
		System.out.println("init");
		Configuration configuration=new Configuration().configure();
		sessionFactory=configuration.buildSessionFactory();
		session=sessionFactory.openSession();
		transaction=session.beginTransaction();
	}
	
	@After
	public void destroy(){
		System.out.println("destroy");
		transaction.commit();
		session.close();
		sessionFactory.close();
	}

单步执行

第一次打印的结果如上。这时候,就在程序停在这个断点的时候,我们尝试到msyql中手动更改这条记录中的字段值:

然后,我们继续单步程序,执行第二个打印输出:

我们发现Session中的对象属性值没有发生变化:

所以呢?你懂的,我将数据库中对应的记录改回去,然后再在代码中加入refresh操作试试:

	/**
	 * refresh()会强制发送select语句,以使Session缓存总的对象的状态和数据表中的记录保持一致!
	 * 这里无论Session缓存对象与数据库里的一样或不一样都会发送select,因为hibernate也不知道两者是否相同,所以必须发select查询一下
	 */
	@Test
	public void testSessionRefresh(){
		CuntomerBean bean=session.get(CuntomerBean.class, 5);
		System.out.println(bean);
		session.refresh(bean);
		System.out.println(bean);
	}

单步执行:

在第一次打印了bean之后,我们还是手动去修改一下mysql数据库中的对应记录:

然后单步执行session.refresh(bean);后,控制台输出了一个select语句,没错,hibernate正是需要在查看数据库的记录,并与Session缓存中的对象作比较,所以发送了这个select语句给数据库。

refresh的操作就是查询数据库对应记录的最新值,然后如果发现有变化,那么把数据库的值同步过来。

我们再次打印,往下一步,看看是否Session中的对象的属性值是否已经随着数据库中记录的变化而变化了:

我们惊讶的发现,虽然我们已经refresh了,但是再次打印Session中的对象时,那个score属性的值还是666,并没有随着数据库中的记录一起变为888!

你可能会大骂:“Hibernate是烂,refresh有bug!”

其实,出现这种情况的原因并不是refresh操作有问题,而是数据库的隔离级别设置的隔离级别造成的。

 

数据库的隔离级别

•对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:

–脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.

–不可重复读: 对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.

–幻读: 对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.

•数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题.

•一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱

•数据库提供的 4 种事务隔离级别:

•Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE. Oracle 默认的事务隔离级别为: READ COMMITED

•Mysql 支持 4 中事务隔离级别. Mysql 默认的事务隔离级别为: REPEATABLE READ

 

在 MySql 中设置隔离级别

•每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别. MySQL 默认的隔离级别为 Repeatable Read

•查看当前的隔离级别: SELECT @@tx_isolation;

•设置当前 mySQL 连接的隔离级别: 

–set transaction isolation level read committed;

•设置数据库系统的全局的隔离级别:

– set global transaction isolation level read committed;

 

在 Hibernate 中设置隔离级别

•JDBC 数据库连接使用数据库系统默认的隔离级别. 在 Hibernate 的配置文件中可以显式的设置隔离级别. 每一个隔离级别都对应一个整数:

–1. READ UNCOMMITED

–2. READ COMMITED

–4. REPEATABLE READ

–8. SERIALIZEABLE

•Hibernate 通过为 Hibernate 映射文件指定 hibernate.connection.isolation 属性来设置事务的隔离级别

 

回到我们刚才的例子,我们之所以refresh之后,Session缓存对象没有更新属性,是因为Mysql数据库与默认的隔离级别是“可重复读”。

我们需要将Mysql的数据库的隔离级别做修改,或者通过Hibernate配置在Hibernate中设置隔离级别。

这里我们选择后者方式:修改/hibernatePro2/src/main/java/hibernate.cfg.xml

我们设置隔离级别值为2,即“读已提交”

<?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.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&amp;characterEncoding=utf8&amp;useUnicode=true&amp;useSSL=false</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password"></property>
<!--         <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> -->
        
        <!-- 配置hibernate的基本信息 -->
        <!-- hibernate所使用的的数据库方言  -->
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        
        <!--  执行操作是否在控制台打印SQL -->
        <property name="show_sql">true</property>
        
        <!-- 是否对SQL进行格式化 -->
        <property name="format_sql">true</property>
        
        <!-- 指定自动生成数据表的策略:在运行数据库的时候hibernate会为我们在数据库自动生成数据表的策略 -->
        <property name="hbm2ddl.auto">update</property>
        
        <property name="connection.isolation">2</property>
		<!-- 每一个隔离级别都对应一个整数:
			–1. READ UNCOMMITED
			–2. READ COMMITED
			–4. REPEATABLE READ
			–8. SERIALIZEABLE
		-->
        
        <!-- 指定关联的hbm.xml映射文件 -->
        <mapping resource="com/happybks/hibernate/hibernatePro2/beans/CuntomerBean.hbm.xml"/>
        
    </session-factory>
</hibernate-configuration>

然后我们将数据库对应记录的字段改回原先的666。

再单步试试:

再修改数据库:

继续单步:先refresh——发送select语句;再执行第二次打印:

我们发现在将数据库的隔离级别由默认的“可重复读”改为“读已提交”之后,我们refresh操作之后,Session中的对象的属性值已经能够及时从数据库中同步过来了,为888!

 

3. clear操作

清理缓存,是Session缓存与数据库之间的第三种操作。它做的就是将Session缓存中引用的对象给清楚掉。言下之意,清除缓存之后,再次查询的时候,已经无法获取这个对象了,必须再查数据库。

还是用例子说吧:

先写个方法:查询两次cid为5的记录。

	/**
	 * 清理缓存clear(){
	 */
	@Test
	public void testSessionClear(){
		CuntomerBean bean=session.get(CuntomerBean.class, 5);
		System.out.println(bean);
		
		CuntomerBean bean2=session.get(CuntomerBean.class, 5);
		System.out.println(bean2);
	}

运行的结果如下:我们可以看到,和之前说的一样,查询两次这个记录,第一次会发送select语句,并在Session缓存中引用获取的对象,第二次查询的时候就不再发送select语句了。

init
二月 05, 2017 10:34:03 下午 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.2.5.Final}
二月 05, 2017 10:34:03 下午 org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
二月 05, 2017 10:34:03 下午 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
二月 05, 2017 10:34:03 下午 org.hibernate.boot.jaxb.internal.stax.LocalXmlResourceResolver resolveEntity
WARN: HHH90000012: Recognized obsolete hibernate namespace http://hibernate.sourceforge.net/hibernate-mapping. Use namespace http://www.hibernate.org/dtd/hibernate-mapping instead.  Support for obsolete DTD/XSD namespaces may be removed at any time.
二月 05, 2017 10:34:04 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
二月 05, 2017 10:34:04 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false]
二月 05, 2017 10:34:04 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
二月 05, 2017 10:34:04 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
二月 05, 2017 10:34:04 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001007: JDBC isolation level: READ_COMMITTED
二月 05, 2017 10:34:04 下午 org.hibernate.engine.jdbc.connections.internal.PooledConnections <init>
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
二月 05, 2017 10:34:04 下午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
二月 05, 2017 10:34:05 下午 org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@27eb3298] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
Hibernate: 
    select
        cuntomerbe0_.CID as CID1_0_0_,
        cuntomerbe0_.NAME as NAME2_0_0_,
        cuntomerbe0_.NO as NO3_0_0_,
        cuntomerbe0_.SCORE as SCORE4_0_0_,
        cuntomerbe0_.MONEY as MONEY5_0_0_,
        cuntomerbe0_.REGISTERDATE as REGISTER6_0_0_,
        cuntomerbe0_.LOGINTIME as LOGINTIM7_0_0_ 
    from
        CUNTOMERBEAN cuntomerbe0_ 
    where
        cuntomerbe0_.CID=?
CuntomerBean [cid=5, name=ningning, no=908, score=888, money=20000.0, registerDate=2017-02-05 20:53:36.0, loginTime=2017-02-05 12:00:00.0]
CuntomerBean [cid=5, name=ningning, no=908, score=888, money=20000.0, registerDate=2017-02-05 20:53:36.0, loginTime=2017-02-05 12:00:00.0]
destroy
二月 05, 2017 10:34:05 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false]

 

现在我们在这个方法中,两个查询之间加入一个session.clear();

	/**
	 * 清理缓存clear(){
	 */
	@Test
	public void testSessionClear(){
		CuntomerBean bean=session.get(CuntomerBean.class, 5);
		System.out.println(bean);
		session.clear();
		CuntomerBean bean2=session.get(CuntomerBean.class, 5);
		System.out.println(bean2);
	}

打印日志,发现现在两次查询个发送了一个select语句给数据库。

init
二月 05, 2017 10:35:12 下午 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.2.5.Final}
二月 05, 2017 10:35:12 下午 org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
二月 05, 2017 10:35:12 下午 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
二月 05, 2017 10:35:12 下午 org.hibernate.boot.jaxb.internal.stax.LocalXmlResourceResolver resolveEntity
WARN: HHH90000012: Recognized obsolete hibernate namespace http://hibernate.sourceforge.net/hibernate-mapping. Use namespace http://www.hibernate.org/dtd/hibernate-mapping instead.  Support for obsolete DTD/XSD namespaces may be removed at any time.
二月 05, 2017 10:35:13 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
二月 05, 2017 10:35:13 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false]
二月 05, 2017 10:35:13 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
二月 05, 2017 10:35:13 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
二月 05, 2017 10:35:13 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001007: JDBC isolation level: READ_COMMITTED
二月 05, 2017 10:35:13 下午 org.hibernate.engine.jdbc.connections.internal.PooledConnections <init>
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
二月 05, 2017 10:35:13 下午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
二月 05, 2017 10:35:14 下午 org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@27eb3298] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
Hibernate: 
    select
        cuntomerbe0_.CID as CID1_0_0_,
        cuntomerbe0_.NAME as NAME2_0_0_,
        cuntomerbe0_.NO as NO3_0_0_,
        cuntomerbe0_.SCORE as SCORE4_0_0_,
        cuntomerbe0_.MONEY as MONEY5_0_0_,
        cuntomerbe0_.REGISTERDATE as REGISTER6_0_0_,
        cuntomerbe0_.LOGINTIME as LOGINTIM7_0_0_ 
    from
        CUNTOMERBEAN cuntomerbe0_ 
    where
        cuntomerbe0_.CID=?
CuntomerBean [cid=5, name=ningning, no=908, score=888, money=20000.0, registerDate=2017-02-05 20:53:36.0, loginTime=2017-02-05 12:00:00.0]
Hibernate: 
    select
        cuntomerbe0_.CID as CID1_0_0_,
        cuntomerbe0_.NAME as NAME2_0_0_,
        cuntomerbe0_.NO as NO3_0_0_,
        cuntomerbe0_.SCORE as SCORE4_0_0_,
        cuntomerbe0_.MONEY as MONEY5_0_0_,
        cuntomerbe0_.REGISTERDATE as REGISTER6_0_0_,
        cuntomerbe0_.LOGINTIME as LOGINTIM7_0_0_ 
    from
        CUNTOMERBEAN cuntomerbe0_ 
    where
        cuntomerbe0_.CID=?
CuntomerBean [cid=5, name=ningning, no=908, score=888, money=20000.0, registerDate=2017-02-05 20:53:36.0, loginTime=2017-02-05 12:00:00.0]
destroy
二月 05, 2017 10:35:14 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&characterEncoding

猜你喜欢

转载自blog.csdn.net/u012833739/article/details/54891039