一.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的二级缓存。
常用的二级缓存第三方插件有:
2.1 Ehcache环境搭建
(1)导入jar包
(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映射文件。