hibernate缓存机制和事务隔离机制

一级缓存( Session缓存)

}         一级缓存的管理

◦          应用程序调用Session的save()、update()、saveOrUpdate()、get()或load(),以及调用查询接口的 list()、iterate() 时,如果在Session缓存中还不存在相应的对象,Hibernate就会把该对象加入到第一级缓存中。

◦          可以通过close/clear/evict清空缓存

}         作用

因为Session的生命期往往很短,存在于Session内部的第一级最快缓存的生命期当然也很短,所以第一级缓存的命中率是很低的。其对系统性能的改善也是很有限的。Session内部缓存的主要作用是保持Session内部数据状态同步。

二级缓存(SessionFactory缓存)

}         开启:

◦          <property name="hibernate.cache.use_second_level_cache">true</property>

◦          <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>

}         如何使用:

◦          类定义前面:@cache,指该类的对象都会放入二级缓存。
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)  //放入二级缓存中也可以被修改。一般用它。

}         什么内容时候放入二级缓存:

◦          经常被访问、改动不频繁、数量有限。

}         get/load会使用二级缓存。

}         iterate也会使用二级缓存。

}         list默认会往二级缓存中存放数据,即通过list查出的结果会放入二级缓存。但是list本身查询时不会使用二级缓存。

各种二级缓存插件

查询缓存

}         查询缓存只对query.list()起作用

}         查询缓存依赖于二级缓存,因此一定要打开二级缓存。

}         查询缓存实现机制:以查询语句为key,查到的对象的id为value

}         查询缓存的配置和使用:

◦          开启二级缓存

◦          <property name="hibernate.cache.use_query_cache">true</property> //默认是fasle

◦           在程序中必须手动启用查询缓存,如:query.setCacheable(true);

缓存算法问题

}         缓存满了后,将内存中哪个对象清掉。

◦          LRU

–          Least  Recently   Used  最近最少被使用的。每个缓存对象都记录一个最后使用时间。

◦          LFU

–          Least  Frequently  Used  最近使用频率最少。

◦          FIFO

–          First  In  First Out

1+N问题

}         在一对多/多对一中,经常出现1+N问题。

◦          在1方,查找得到了n个对象, 那么又需要将n个对象关联的集合取出,于是本来的一条sql查询变成了n+1条。

}         解决方案:

◦          懒加载(延时加载、延迟加载)

–          @OneToMany(mappedBy=“banji”,fetch=FetchType.LAZY) (默认即为此)

◦          二级缓存

–          在对象更新,删除,添加相对于查询要少得多时, 二级缓存的应用将不怕n+1问题,因为即使第一次查询很慢,之后直接缓存命中也是很快的,刚好又利用了n+1。

List和iterator区别

}         List仅仅会填充二级缓存,却不能利用二级缓存。

}         iterator可以读二级缓存,对于一条查询语句,它会先从数据库中找出所有符合条件的记录的ID,再通过ID去缓存找,对于缓存中没有的记录,再构造语句从数据库中查出。在缓存中没有命中的话,效率较低。

}         最好的办法就是:

◦          在应用启动时和数据被修改时使用list。平时则使用iterator。(只针对修改不频繁的数据!)

缓存机制的选用

}         一般开始开发并不使用缓存机制。

}         根据需求如果不能满足性能要求,才增加缓存。

◦          二级缓存:缓存数据内容变化频率不高的内容。

◦          查询缓存

}         很多系统经常在应用层增加缓存:

◦          OSCACHE在J2EE中的应用

数据批量处理

}         建议:

◦          大批量数据的处理不要使用hibernate,优先考虑JDBC的批量处理。(一般使用JDBC)

◦          如果对性能要求极高,可以考虑PL/SQL

事务隔离级别

}         事务基本概念

}         ACID即是atomicity(原子性),consistency(一致性),isolation(隔离性)和durability(执久性)的首字母的缩写

◦          原子性表示一个事务内的所有操作是一个整体,要 么全部成功,要么全失败;

◦          一致性表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前的状态;

◦          隔离性:事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。

◦          持久性事务完成之后,它对于系统的影响是永久性的。

}         事务隔离级别从低到高:

◦          读取未提交(Read Uncommitted)

◦          读取已提交(Read Committed)

◦          可重复读(Repeatable Read)

◦          序列化(serializable)

}         读取未提交(Read Uncommitted)

◦          这是最低的事务隔离级别,读事务不会阻塞读事务和写事务,写事务也不会阻塞读事务,但是会阻塞写事务。

◦          写事务不阻塞读事务,可以读取未提交的数据,容易造成脏读

◦          脏读现象:

◦          脏读解决方案:

–          如果在第一个事务提交前,任何其他事务不可读取其修改过的值,则可  以避免该问题。

◦          读取已提交(Read Committed)

◦          写事务就会阻塞读事务和写事务但是读事务不会阻塞读事务和写事务。读事务不阻塞写事务,但是有可能造成不可重复(在同一个事务中,再次读取数据时【就是你的select操作】,所读取的数据,和第1次读取的数据,不一样了。查询的结果将是不确定的)。

◦          不可重复读解决方案:

–          锁住已经查询出来的记录!不让其他事物进行写操作

◦          可重复读(Repeatable Read)

◦          读事务会阻塞写事务但是读事务不会阻塞读事务写事务会阻塞写事务和读事务。

◦          读事务不阻塞读事务(针对的是记录而不是表),可能会造成幻读问题

◦          幻读解决方案:

–          解决办法是锁表,不让产生幻读的记录插入或删除。

–          不过,一般不要考虑幻读问题。

◦          序列化(serializable)

◦          此种隔离级别是最严格的隔离级别,如果设置成这个级别,那么就不会出现以上所有的问题(脏读,不可重复读,幻影读)

◦          性能极低,一般不用!

}         我们一般采用读取已提交或者更低的事务隔离级别,配合各种并发访问控制策略来达到并发事务控制的目的。

}         如何使用:

<!-- 制定事务隔离级别 :1,2,4,8。二进制中:0001, 0010,0100,1000。这样直接采用位运算即可。权限控制中经常采用二进制位运算-->

<property name="hibernate.connection.isolation">2</property>

乐观锁和悲观锁

}         乐观锁Optimistic Locking

◦          顾名思义就是保持一种乐观的态度,我们认为系统中的事务并发更新不会很频繁,即使冲突了也没事,大不了重新再来一次。

◦          它的基本思想就是每次提交一个事务更新时,我们想看看要修改的东西从上次读取以后有没有被其它事务修改过,如果修改过,那么更新就会失败。

◦          常用实现方法:

–          在我们的实体中增加一个版本控制字段,每次事务更新后就将版本(Version)字段:版本字段的值加1.

–          在实体类中增加@Version, private  int  version;getset 即可。

}         悲观锁Pessimistic Locking

◦          基本思想就是每次一个事务读取某一条记录后,就会把这条记录锁住,这样其它的事务要想更新,必须等以前的事务提交或者回滚解除锁。

◦          悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)

乐观锁和悲观锁的比较:

}         乐观锁:

◦          优势:并发性好,性能较高。

◦          缺点:用户体验不好,录入了半天,提交时被告知已经修改!

}          

}         悲观锁:

◦          优势:会锁住记录,一个用户修改完成前,其他用户不能操作该记录。

◦          缺点:并发性不好,性能不高。

}         对于悲观锁是针对并发的可能性比较大,而一般在我们的应用中用乐观锁足以。

原文地址:https://www.cnblogs.com/quchengfeng/p/4111812.html