您的位置:首页 > 其它

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

2017-02-06 11:17 435 查看

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</sco
20000
pe>
</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&characterEncoding=utf8&useUnicode=true&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&characterEncoding=utf8&useUnicode=true&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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: