天天看点

Hibernate的进一步了解(3)

一.Hibernate检索优化

检索就是查询

所谓检索优化,指的是对查询语句的执行时机进行把控,并不是代码中一出现查询语句,马上就在后台调用select语句,而是在代码真正需要时才执行select。即将select的执行进行最大可能的"延迟"。

直接通过get(),load()等查询语句加载的对象,成为主加载对象,而主加载对象所关联的对象,称为关联加载对象或从加载对象。

根据检索对象的不同,可以将检索优化分为两类:

(1)当前对象检索优化

(2)关联对象检索优化

对于不使用优化进行对象检索的过程,称为直接加载;否则称为延迟加载或者懒加载。

1.1 当前对象检索优化

1.get()和load()的区别:

共同点:

都是通过对象的ID进行查询

区别:

get():若加载的对象不存在,则返回null,采用直接加载

load():若加载的对象不存在,则抛出异常,采用延迟加载(代理模式)

注意:load()默认是延迟加载的。但可以通过修改.hbm,xml文件中class标签中的属性lazy(默认是true)为false,从而改变load()方法不是延迟加载的。

1.2关联对象检索优化

即对于从加载对象的检索。

映射文件中对于对象检索的优化配置属性有两个:lazy.fetch

1.多端加载优化

是指一方为主加载对象,而多方作为从加载对象

因此,fetch.lazy应设置在一方映射文件的关联属性中,即设置在set标签中。

lazy用于指定对象加载时机

lazy取值 意义
false 直接加载
true 延迟加载
extra

特别延迟加载,执行聚合函数

可以解决的,不进行详情查询

fetch用于指定对象加载方式

取值 意义
join 采用迫切左外连接查询
select 采用普通select查询
subselect 采用子查询

2.单端加载优化

是指多方为主加载对象,而一方作为从加载对象

因此,fetch.lazy设置在多方映射文件的关联属性中

lazy取值 意义
false 直接加载
proxy 使用字节码代理
no proxy 不研究
fetch取值 意义
join 采用迫切左外连接查询
select 采用普通select查询

二.Hibernate缓存机制

缓存就是数据交换的缓冲区(称作Cache),它先于内存与CPU交换数据。

根据缓存的范围,可以分为三类:

2.1 事务范围缓存

即一级缓存,是单Session缓存,只能被当前事务访问。伴随着事务的开启而开启,事务的关闭而关闭。

Session缓存,是Hibernate内置的,程序员是不能取消的。也就是说,只要使用了Hibernate了,就一定在使用Session缓存。当通过get()或者load()方法查询实体对象时,Hibernate会首先到缓存中查询,在找不到的情况下,才会发出SQL语句到DB中查询。

2.2 引用范围缓存

即二级缓存,是单SessionFactory级的缓存,其可以被应用程序内的所有事务共享访问。可以根据其功能和目的分为两种:

1.内置缓存

存放了映射元数据和预定义SQL语句。是只读的。

2.外置缓存

它是一个可配置的插件默认情况下,SessionFactory不会启用这个插件。SessionFactory的外置缓存也称为Hibernate的二级缓存。

常用的二级缓存第三方插件有:

Hibernate的进一步了解(3)

2.1 Ehcache环境搭建

(1)导入jar包

Hibernate的进一步了解(3)

(2)把ehcache.xml文件复制到当前目录的根路径下

(3)在主配置文件(hibernate.cfg.xml)中配置

 <!-- 开启二级缓存 -->
   <property name="hibernate.cache.use_second_level_cache">true</property>
  <!-- 注册二级缓存区域工厂 -->
  <property name="org.hibernate.cache.ehcache.EhCacheRegionFactory">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
           

指定类缓存:

<!-- 指定类缓存 -->
  <class-cache usage="read-only" class="com.sss.Customer"/>
  <class-cache usage="read-only" class="com.sss.Orders"/>
  
           

类缓存对象存放在专门的一个称为实体区域的缓存中,缓存的的内容为对象的详情。

指定集合缓存:

<!-- 指定集合缓存 -->
  <collection-cache usage="read-only" collection="com.sss.Customer.order"/>
           

集合缓存对象存放在专门的一个称为集合区域的缓存中,缓存的内容为集合中所包含对象的id.

Query缓存():

<!-- 开启Query缓存 -->
  <property name="hibernate.cache.use_query_cache">true</property>
           

Query查询会将结果放入缓存,但不会从中读取

Query缓存内容:从Query缓存中查找的依据不再是查询结果对象的id,而是Query查询语句,也就是说,Query查询结果,存放到Query缓存时,其key为Query的查询语句,value为查询结果。

修改时间戳

一般get查询,会先查找一级缓存,若没有,则会查找二级缓存,若还是没有,则到数据库中查找。

Query的executeUpdate()方法,会把修改结果直接跳过一级缓存,保存到数据库中。该方法会修改二级缓存对象中的一个属性:updateTimestamp(修改时间戳),一旦这个属性被修改,查询不会从二级缓存中读取数据,而是直接从DB中查询。

与二级缓存管理相关的方法,一般都定义在Cache接口,而Cache对象的获取,需要通过sessionFactory.getCache()方法。

2.3集群范围缓存

是多SessionFactory缓存,在集群环境中,缓存被一个机器或多个机器的进程共享。

2.4 快照

即副本。Hibernate中的快照,即数据库的副本。

Session缓存中的数据是可以修改的,但快照中的数据是不能修改的。快照中的数据始终是与数据库中的数据是一致的。

下面的例子中,详细的介绍了快照的作用:

虽然没有写update语句,但是数据库的数据已经被修改。

//	session.get()都做了什么工作
//	     1)将数据从DB中读取出来
//	     2)将数据转换为对象,存放到堆内存中
//	     3)将对象的id放入Session缓存map的key中
//	     4)将对象的引用放入Session缓存map的value中
//	     5)将对象的详情放入到“快照”中
	     Student student=session.get(Student.class, 2);
//       下面的修改语句修改的是堆内存中的对象数据
	     student.setName("张三");
	     
//	     事务提交时做的工作
//	     1)将堆内存中的数据与"快照"的数据进行对比,若不同,则执行upate;若相同,则不执行Update
	     session.getTransaction().commit();
           

1.Session的刷新与同步

Session的刷新是指:Session缓存中数据的更新

Session的同步是指:将Session缓存中的数据同步更新到DB中;执行同步的时间点只有一个:事务的提交

    当代码中执行了对Session中现有数据的修改操作时,即Update和Delete语句后,Session缓存并不会马上刷新,而是在某个时间点到来时,才会刷新缓存:刷新时间点主要有三个:

(1)执行Query查询

(2)执行session.flush()

(3)执行事务的提交

例1:删除操作

Student student=session.get(Student.class, 2);
//	   执行到Session.delete()后,并不会直接执行delete的SQL语句
//	     而是在到达刷新点之后,才会执行
//	     这就是对session缓存数据的刷新,实际上是对堆内存中数据的更新
	     session.delete(student);//此时,只把session缓存中Map集合中key所对应的value删除
//	     刷新点
	     session.createQuery("from Student").list();
	     
           

注意:此时并没有进行事务的提交,所以数据库中的数据并没有删除

例2:修改操作

Student student=session.get(Student.class, 2);
//       当执行修改操作时,即使到达刷新点,
//	  是否执行update的SQL语句还要取决于修改的数据与快照中数据的对比结果:
//	  相同,则不执行;否则执行
	     student.setName("李四");
	     session.update(student);
       //	     刷新点
	     session.createQuery("from Student").list();
           

例3 :插入操作

Student student=new Student("王五",25);
//	插入操作无需到达刷新点
	 session.save(student);
//	 刷新点
	 session.creatQuery("from Student").list();
           

通过Session的setFlushMode()方法 ,可以设置缓存刷新模式。

三.事务处理

一.事务四大特性(简称ACID)

(1)原子性(Atomicity)

事务中的全部操作在数据库中是不可分割的,要么全部完成,要么均不执行。

(2)一致性(Consistency)

几个并发执行的事务,期执行结果必须与按某一顺序串行执行的结果相一致。

(3)隔离性(Isolation)

事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。

(4)持久性(Durability)

对于任意已提交事务,系统必须保证该事物对数据库的改变不被丢失,即使数据库出现故障。

二.事务的并发问题

(1)脏读

A事务读取了B事务未提交的数据。

(2)不可重复读

读取了已提交的事务。事务A读取数据后,事务执行更新(修改,删除,添加)操作,使A无法再现前一次的读取结果。

(3)丢失修改

两个事务A与B,读入同一数据并修改,B提交的结果破坏了A提交的结果,导致A的修改丢失。

(4)幻读

也称为虚读。

在同一事务中,虽然多次执行相同的查询,查询结果是相同的,但后面的查询结果已经与DB中真正的数据不一致。

三.事务的隔离级别

读取未提交

读取已提交

可重复读

串行化

四.封锁机制

事务的隔离级别,是DBMS隐式的为数据添加了锁。

串行化级别是为表添加的表级锁,其他隔离级别所添加的锁均为行锁。

锁分为两类:

1.乐观锁

每次访问数据时,都会乐观的认为其他事务此时肯定不会同时修改该数据。乐观锁是加在代码中的。

一般充当乐观锁的数据有两类:版本号和时间戳。

2.悲观锁

每次访问数据时,都会悲观的认为其他的事务一定会同时修改该数据。悲观锁施加在数据库中的。

悲观锁是行锁,若要为表中的记录添加行锁,则需要通过查询语句来为符合条件的记录添加指定的锁。

悲观锁分为两种:

(1)排它锁:也称X锁,写锁;

        在查询语句后添加 for update,则会为每一条符合条件的记录添加写锁,不能在为这些数据添加其他类型的锁。

        若事务T对数据对象A加上写锁,则只允许T读取和修改A,其他任何事务都不能在对A添加任何类型的锁,知道T释放A上的写锁,这就保证了其他事务T在释放A上的锁之前不能再修改A。

(2)共享锁:也称S锁,读锁;

       在查询语句添加lock in share mode,则会为每一条符合条件的记录添加读锁,不能在为这些数据添加写锁,防止修改数据。

      若事务T对数据对象A加上读锁,则事务T可以读取A但不能修改A,其他事务只能再对A加读锁,而不能加写锁,知道A上的读锁被全部释放。

3.隔离级别的设置

<!-- 设置事务的隔离级别 -->
   <property name="hibernate.connection.isolation">4</property>
           

4.乐观锁的实现

(1)实体类Bean中要增加一个用于记录版本的属性,要求类型为int,其值有系统自动维护,初始值为0,每次自动增1.

private int sversion;
  
public int getSversion() {
	return sversion;
}
public void setSversion(int sversion) {
	this.sversion = sversion;
}
           

(2)映射文件中增加<version/>标签,其name属性用于指定作为版本的属性名称,类型要求为int;其还有一个子标签<timestamp/>用于指定时间戳的属性名称,要求其类型为java.sql.timestamp。要求写在<id/>与<property/>之间。

<version name="sversion" column="S_VERSION"></version>
           

5.悲观锁的实现

四.Hibernate注解式开发

代替的是.hbm.xml映射文件。

继续阅读