1、 注意session.clear()的动用,尤其在不断分页循环的时候
a) 在一个大集合中进行遍历,遍历msg,取出其中的含有敏感字样的对象
b) 另外一种形式的内存泄露 //面试是:Java有内存泄漏吗?
2、 1 + N问题 //典型的面试题
a) Lazy
b) BatchSize 设置在实体类的前面
c) joinfetch
3、 list 和 iterate不同之处
a) list取所有
b) Iterate先取ID,等用到的时候再根据ID来取对象
c) session中list第二次发出,仍会到数据库查询
d) iterate第二次,首先找session级缓存
一级缓存很短和session的生命周期一致,因此也叫session级缓存或事务级缓存
hibernate一级缓存
那些方法支持一级缓存:
* get()
* load()
* iterate(查询实体对象)
如何管理一级缓存:
* session.clear(),session.evict()
如何避免一次性大量的实体数据入库导致内存溢出
* 先flush,再clear
如果数据量特别大,考虑采用jdbc实现,如果jdbc也不能满足要求可以考虑采用数据本身的特定导入工具
Hibernate默认的二级缓存是开启的。
二级缓存也称为进程级的缓存,也可称为SessionFactory级的缓存(因为SessionFactory可以管理二级缓存),它与session级缓存不一样,一级缓存只要session关闭缓存就不存在了。而二级缓存则只要进程在二级缓存就可用。
二级缓存可以被所有的session共享
二级缓存的生命周期和SessionFactory的生命周期一样,SessionFactory可以管理二级缓存
二级缓存同session级缓存一样,只缓存实体对象,普通属性的查询不会缓存
二级缓存一般使用第三方的产品,如EHCache
1、 二级缓存的配置和使用:
配置二级缓存的配置文件:模板文件位于hibernate\etc目录下(如ehcache.xml),将模板存放在ClassPath目录中,一般放在根目录下(src目录下)
<ehcache>
<!--设置当缓存对象益出时,对象保存到磁盘时的保存路径。
如 d:\xxxx
The following properties aretranslated:
user.home - User's home directory
user.dir - User's current workingdirectory
java.io.tmpdir - windows的临时目录 -->
<diskStore path="java.io.tmpdir"/>
<!--默认配置/或对某一个类进行管理
maxInMemory - 缓存中可以存入的最多个对象数
eternal - true:表示永不失效,false:不是永久有效的。
timeToIdleSeconds - 空闲时间,当第一次访问后在空闲时间内没有访问,则对象失效,单位为秒
timeToLiveSeconds - 被缓存的对象有效的生命时间,单位为秒
overflowToDisk 当缓存中对象数超过核定数(益出时)时,对象是否保存到磁盘上。true:保存;false:不保存
如果保存,则保存路径在标签<diskStore>中属性path指定
-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
</ehcache>
2、 二级缓存的开启:
Hibernate中二级缓存默认就是开启的,也可以显示的开启
二级缓存是hibernate的配置文件设置如下:
<!--开启二级缓存,hibernate默认的二级缓存就是开启的 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
3、 指定二级缓存产品提供商:
修改hibernate的 配置文件,指定二级缓存提供商,如下:
<!--指定二级缓存提供商-->
<property name="hibernate.cache.provider_class">
org.hibernate.cache.EhCacheProvider
</property>
以下为常见缓存提供商:
Cache |
Provider class |
Type |
Cluster Safe |
Query Cache Supported |
Hashtable (not intended for production use) |
org.hibernate.cache.HashtableCacheProvider |
memory |
|
yes |
EHCache |
org.hibernate.cache.EhCacheProvider |
memory, disk |
|
yes |
OSCache |
org.hibernate.cache.OSCacheProvider |
memory, disk |
|
yes |
SwarmCache |
org.hibernate.cache.SwarmCacheProvider |
clustered (ip multicast) |
yes (clustered invalidation) |
|
JBoss TreeCache |
org.hibernate.cache.TreeCacheProvider |
clustered (ip multicast), transactional |
yes (replication) |
yes (clock sync req.) |
4、 使用二级缓存
a) xml方式:指定哪些实体类使用二级缓存:
方法一:在实体类映射文件中,使用<cache>来指定那个实体类使用二级缓存,如下:
<cache
usage="transactional|read-write|nonstrict-read-write|read-only" (1)
region="RegionName" (2)
include="all|non-lazy" (3)
/>
(1) usage(必须)说明了缓存的策略: transactional、 read-write、nonstrict-read-write或read-only。
(2) region (可选, 默认为类或者集合的名字(class orcollection role name)) 指定第二级缓存的区域名(name of the secondlevel cache region)
(3) include (可选,默认为 all) non-lazy 当属性级延迟抓取打开时, 标记为lazy="true"的实体的属性可能无法被缓存
另外(首选?), 你可以在hibernate.cfg.xml中指定<class-cache>和 <collection-cache> 元素。
这里的usage 属性指明了缓存并发策略(cache concurrency strategy)。
策略:只读缓存(Strategy:read only)
如果你的应用程序只需读取一个持久化类的实例,而无需对其修改, 那么就可以对其进行只读 缓存。这是最简单,也是实用性最好的方法。甚至在集群中,它也能完美地运作。
<class name="eg.Immutable" mutable="false">
<cache usage="read-only"/>
....
</class>
策略:读/写缓存(Strategy:read/write)
如果应用程序需要更新数据,那么使用读/写缓存 比较合适。 如果应用程序要求“序列化事务”的隔离级别(serializable transaction isolation level),那么就决不能使用这种缓存策略。 如果在JTA环境中使用缓存,你必须指定hibernate.transaction.manager_lookup_class属性的值, 通过它,Hibernate才能知道该应用程序中JTA的TransactionManager的具体策略。 在其它环境中,你必须保证在Session.close()、或Session.disconnect()调用前, 整个事务已经结束。 如果你想在集群环境中使用此策略,你必须保证底层的缓存实现支持锁定(locking)。Hibernate内置的缓存策略并不支持锁定功能。
<class name="eg.Cat" .... >
<cache usage="read-write"/>
....
<set name="kittens" ... >
<cache usage="read-write"/>
....
</set>
</class>
策略:非严格读/写缓存(Strategy: nonstrictread/write)
如果应用程序只偶尔需要更新数据(也就是说,两个事务同时更新同一记录的情况很不常见),也不需要十分严格的事务隔离,那么比较适合使用非严格读/写缓存策略。如果在JTA环境中使用该策略,你必须为其指定hibernate.transaction.manager_lookup_class属性的值, 在其它环境中,你必须保证在Session.close()、或Session.disconnect()调用前, 整个事务已经结束。
策略:事务缓存(transactional)
Hibernate的事务缓存策略提供了全事务的缓存支持, 例如对JBoss TreeCache的支持。这样的缓存只能用于JTA环境中,你必须指定为其hibernate.transaction.manager_lookup_class属性。
没有一种缓存提供商能够支持上列的所有缓存并发策略。下表中列出了各种提供器、及其各自适用的并发策略。
表 19.2. 各种缓存提供商对缓存并发策略的支持情况(Cache Concurrency Strategy Support)
Cache |
read-only |
nonstrict-read-write |
read-write |
transactional |
Hashtable (not intended for production use) |
yes |
yes |
yes |
|
EHCache |
yes |
yes |
yes |
|
OSCache |
yes |
yes |
yes |
|
SwarmCache |
yes |
yes |
|
|
JBoss TreeCache |
yes |
|
|
yes |
注:此方法要求:必须要标签<cache>放在<id>标签之前
<class name="com.wjt276.hibernate.Student"table="t_student">
<!-- 指定实体类使用二级缓存 -->
<cache usage="read-only"/>//***********
<id name="id"column="id">
<generator class="native"/>
</id>
<property name="name"column="name"/>
<!--
使用多对一标签映射 一对多双向,下列的column值必需与多的一端的key字段值一样。
-->
<many-to-one name="classes"column="classesid"/>
</class>
方法二:在hibernate配置文件(hibernate.cfg.xml)使用<class-cache>标签中指定
要求:<class-cache>标签必须放在<maping>标签之后。
<hibernate-configuration>
<session-factory>
…………
<mapping resource="com/wjt276/hibernate/Classes.hbm.xml"/>
<mapping resource="com/wjt276/hibernate/Student.hbm.xml"/>
<class-cache class="com.wjt276.hibernate.Student"usage="read-only"/>
</session-factory>
</hibernate-configuration>
一般推荐使用方法一。
b) annotation注解
为了优化数据库访问,你可以激活所谓的Hibernate二级缓存.该缓存是可以按每个实体和集合进行配置的.
@org.hibernate.annotations.Cache定义了缓存策略及给定的二级缓存的范围. 此注解适用于根实体(非子实体),还有集合.
@Entity
@Cache(usage= CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
publicclass Forest { ... }
@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
@Cache(usage =CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public SortedSet<Ticket> getTickets(){
return tickets;
}
@Cache(
CacheConcurrencyStrategy usage(); (1)
String region() default ""; (2)
String include() default"all"; (3)
)
(1) |
usage: 给定缓存的并发策略(NONE, READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL) |
(2) |
region (可选的):缓存范围(默认为类的全限定类名或是集合的全限定角色名) |
(3) |
include (可选的):值为all时包括了所有的属性(proterty), 为non-lazy时仅含非延迟属性(默认值为all) |
5、 应用范围
没有变化,近似于静态的数据。
6、 二级缓存的管理:
1、 清除指定实体类的所有数据
SessionFactory.evict(Student.class);
2、 清除指定实体类的指定对象
SessionFactory.evict(Student.class, 1);//第二个参数是指定对象的ID,就可以清除指定ID的对象
使用SessionFactory清除二级缓存
Sessionsession = null;
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Student student = (Student)session.load(Student.class, 1);
System.out.println("student.name=" +student.getName());
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
//管理二级缓存
SessionFactory factory = HibernateUtils.getSessionFactory();
//factory.evict(Student.class);
factory.evict(Student.class, 1);
try {
session = HibernateUtils.getSession();
session.beginTransaction();
//会发出查询sql,因为二级缓存中的数据被清除了
Student student = (Student)session.load(Student.class, 1);
System.out.println("student.name=" +student.getName());
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
7、 二级缓存的交互
Sessionsession = null;
try {
session = HibernateUtils.getSession();
session.beginTransaction();
//仅向二级缓存读数据,而不向二级缓存写数据
session.setCacheMode(CacheMode.GET);
Student student =(Student)session.load(Student.class, 1);
System.out.println("student.name=" +student.getName());
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
try {
session = HibernateUtils.getSession();
session.beginTransaction();
//发出sql语句,因为session设置了CacheMode为GET,所以二级缓存中没有数据
Student student =(Student)session.load(Student.class, 1);
System.out.println("student.name=" +student.getName());
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
try {
session = HibernateUtils.getSession();
session.beginTransaction();
//只向二级缓存写数据,而不从二级缓存读数据
session.setCacheMode(CacheMode.PUT);
//会发出查询sql,因为session将CacheMode设置成了PUT
Student student = (Student)session.load(Student.class, 1);
System.out.println("student.name=" +student.getName());
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
CacheMode参数用于控制具体的Session如何与二级缓存进行交互。
· CacheMode.NORMAL - 从二级缓存中读、写数据。
· CacheMode.GET - 从二级缓存中读取数据,仅在数据更新时对二级缓存写数据。
· CacheMode.PUT - 仅向二级缓存写数据,但不从二级缓存中读数据。
· CacheMode.REFRESH - 仅向二级缓存写数据,但不从二级缓存中读数据。通过hibernate.cache.use_minimal_puts的设置,强制二级缓存从数据库中读取数据,刷新缓存内容。
如若需要查看二级缓存或查询缓存区域的内容,你可以使用统计(Statistics) API。
Map cacheEntries = sessionFactory.getStatistics()
.getSecondLevelCacheStatistics(regionName)
.getEntries();
此时,你必须手工打开统计选项。可选的,你可以让Hibernate更人工可读的方式维护缓存内容。
hibernate.generate_statistics true
hibernate.cache.use_structured_entries true
8、 总结
load默认使用二级缓存,iterate默认使用二级缓存
list默认向二级缓存中加数据,但是查询时候不使用
查询缓存,是用于缓存普通属性查询的,当查询实体时缓存实体ID。
默认情况下关闭,需要打开。查询缓存,对list/iterator这样的操作会起作用。
可以使用<property name=”hibernate.cache.use_query_cache”>true</property>来打开查询缓存,默认为关闭。
所谓查询缓存:即让hibernate缓存list、iterator、createQuery等方法的查询结果集。如果没有打开查询缓存,hibernate将只缓存load方法获得的单个持久化对象。
在打开了查询缓存之后,需要注意,调用query.list()操作之前,必须显式调用query.setCachable(true)来标识某个查询使用缓存。
查询缓存的生命周期:当前关联的表发生修改,那么查询缓存生命周期结束
注意查询缓存依赖于二级缓存,因为使用查询缓存需要打开二级缓存
查询缓存的配置和使用:
* 在hibernate.cfg.xml文件中启用查询缓存,如:
<propertyname="hibernate.cache.use_query_cache">true</property>
* 在程序中必须手动启用查询缓存,如:
query.setCacheable(true);
例如:
session= HibernateUtils.getSession();
session.beginTransaction();
Query query = session.createQuery("selects.name from Student s");
//启用查询查询缓存
query.setCacheable(true);
List names = query.list();
for (Iteratoriter=names.iterator();iter.hasNext(); ) {
String name =(String)iter.next();
System.out.println(name);
}
System.out.println("-------------------------------------");
query = session.createQuery("selects.name from Student s");
//启用查询查询缓存
query.setCacheable(true);
//没有发出查询sql,因为启用了查询缓存
names = query.list();
for (Iteratoriter=names.iterator();iter.hasNext(); ) {
String name =(String)iter.next();
System.out.println(name);
}
session.getTransaction().commit();
Session session = sf.openSession();
session.beginTransaction();
List<Category>categories = (List<Category>)session.createQuery("from Category")
.setCacheable(true).list();
session.getTransaction().commit();
session.close();
Session session2 = sf.openSession();
session2.beginTransaction();
List<Category> categories2= (List<Category>)session2.createQuery("from Category")
.setCacheable(true).list();
session2.getTransaction().commit();
session2.close();
注:查询缓存的生命周期与session无关。
查询缓存只对query.list()起作用,query.iterate不起作用,也就是query.iterate不使用
1、 LRU、LFU、FIFO
———————————————————-----------------------------------------------------------------------------
解决1+N问题
缓存
在内存里开辟一块空间,把本来应该存储在硬盘上的东西,然后放在内存里,将来再读的时候直接从内存读,这部分内存就叫缓存.
could not instantiate RegionFactory [org.hibernate.cache.impl.bridge.RegionFactoryCacheProviderBridge] EhCache的jar包 ehcache-1.2.3.jar
二级缓存
查询缓存
查询缓存(2个语句一样)---重复查询才会使用缓存,2个查询不一样一定用不了,没有相应的算法能够实现
如何证明查询缓存起作用,发2条相同的list 如果不出现2条sql 语句即可证明
缓存算法: 内存的对象一旦满了之后,新来的对象要把原来的对象给替换掉,满了之后的对象先完蛋.
1、 ReadUncommited(未提交读):没有提交就可以读取到数据(发出了Insert,但没有commit就可以读取到。)很少用
2、 ReadCommited(提交读):只有提交后才可以读,常用,
3、 RepeatableRead(可重复读):mysql默认级别, 必需提交才能见到,读取数据时数据被锁住。
4、 Serialiazble(序列化读):最高隔离级别,串型的,你操作完了,我才可以操作,并发性特别不好,
隔离级别 |
是否存在脏读 |
是否存在不可重复读 |
是否存在幻读 |
Read Uncommitted(未提交读) |
Y |
Y |
Y |
Read Commited(提交读) |
N |
Y(可采用悲观锁解决) |
Y |
Repeatable Read(可重复读) |
N |
N |
Y |
Serialiazble(序列化读) |
|
|
|
脏读:没有提交就可以读取到数据称为脏读
不可重复读:再重复读一次,数据与你上读取的不一样。称不可重复读。
幻读:在查询某一条件的数据,开始查询的后,别人又加入或删除些数据,再读取时与原来的数据不一样了。
1、 Mysql查看数据库隔离级别:
方法:select@@tx_isolation;
2、 Mysql数据库修改隔离级别:
方法:set transactionisolation level 隔离级别名称;
例如:修改为未提交读:settransaction isolation level read uncommitted;
ACID即:事务的原子性、一致性、独立性及持久性
事务的原子性:是指一个事务要么全部执行,要么不执行.也就是说一个事务不可能只执行了一半就停止了.比如你从取款机取钱,这个事务可以分成两个步骤:1划卡,2出钱.不可能划了卡,而钱却没出来.这两步必须同时完成.要么就不完成.
事务的一致性:是指事务的运行并不改变数据库中数据的一致性.例如,完整性约束了a+b=10,一个事务改变了a,那么b也应该随之改变.
事务的独立性:是指两个以上的事务不会出现交错执行的状态.因为这样可能会导致数据不一致.
事务的持久性:是指事务运行成功以后,就系统的更新是永久的.不会无缘无故的回滚.
1、第一类丢失更新(Lost Update)
时间 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Hibernate谈到悲观锁、乐观锁,就要谈到数据库的并发问题,数据库的隔离级别越高它的并发性就越差
并发性:当前系统进行了序列化后,当前读取数据后,别人查询不了,看不了。称为并发性不好
数据库隔离级别:见前面章级
悲观锁:具有排他性(我锁住当前数据后,别人看到不此数据)
悲观锁一般由数据机制来做到的。
通常依赖于数据库机制,在整修过程中将数据锁定,其它任何用户都不能读取或修改(如:必需我修改完之后,别人才可以修改)
悲观锁一般适合短事务比较多(如某一数据取出后加1,立即释放)
长事务占有时间(如果占有1个小时,那么这个1小时别人就不可以使用这些数据),不常用。
用户1、用户2 同时读取到数据,但是用户2先 -200,这时数据库里的是800,现在用户1也开始-200,可是用户1刚才读取到的数据是1000,现在用户用刚刚一开始读取的数据1000-200为800,而用户1在更新时数据库里的是用房更新的数据800,按理说用户1应该是800-200=600,而现在是800,这样就造成的更新丢失。这种情况该如何处理呢,可采用两种方法:悲观锁、乐观锁。先看看悲观锁:用户1读取数据后,用锁将其读取的数据锁上,这时用户2是读取不到数据的,只有用户1释放锁后用户2才可以读取,同样用户2读取数据也锁上。这样就可以解决更新丢失的问题了。
实体类:
public class Inventory {
private int itemNo;
privateString itemName;
private int quantity;
public intgetItemNo() {
return itemNo;
}
public voidsetItemNo(intitemNo) {
this.itemNo =itemNo;
}
publicString getItemName() {
return itemName;
}
public voidsetItemName(String itemName) {
this.itemName =itemName;
}
public intgetQuantity() {
return quantity;
}
public voidsetQuantity(intquantity) {
this.quantity =quantity;
}
}
映射文件:
<hibernate-mapping>
<class name="com.wjt276.hibernate.Inventory"table="t_inventory">
<id name="itemNo">
<generator class="native"/>
</id>
<property name="itemName"/>
<property name="quantity"/>
</class>
</hibernate-mapping>
如果需要使用悲观锁,肯定在加载数据时就要锁住,通常采用数据库的for update语句。
Hibernate使用Load进行悲观锁加载。
Session.load(Classarg0, Serializable arg1, LockMode arg2) throws HibernateException
LockMode:悲观锁模式(一般使用LockMode.UPGRADE)
session= HibernateUtils.getSession();
tx = session.beginTransaction();
Inventory inv =(Inventory)session.load(Inventory.class, 1,LockMode.UPGRADE);
System.out.println(inv.getItemName());
inv.setQuantity(inv.getQuantity()-200);
session.update(inv);
tx.commit();
Hibernate:select inventory0_.itemNo as itemNo0_0_, inventory0_.itemName as itemName0_0_,inventory0_.quantity as quantity0_0_ from t_inventory inventory0_ whereinventory0_.itemNo=? for update //在select语句中加入for update进行使用悲观锁。
脑白金
Hibernate:update t_inventory set itemName=?, quantity=? where itemNo=?
注:只有用户释放锁后,别的用户才可以读取
注:如果使用悲观锁,那么lazy(悚加载无效)
乐观锁:不是锁,是一种冲突检测机制。
乐观锁的并发性较好,因为我改的时候,别人随边修改。
乐观锁的实现方式:常用的是版本的方式(每个数据表中有一个版本字段version,某一个用户更新数据后,版本号+1,另一个用户修改后再+1,当用户更新发现数据库当前版本号与读取数据时版本号不一致(等于小于数据库当前版本号),则更新不了。
Hibernate使用乐观锁需要在映射文件中配置项才可生效。
实体类:
public classInventory {
private int itemNo;
privateString itemName;
private int quantity;
private int version;//Hibernate用户实现版本方式乐观锁,但需要在映射文件中配置
public intgetItemNo() {
return itemNo;
}
public voidsetItemNo(intitemNo) {
this.itemNo =itemNo;
}
publicString getItemName() {
return itemName;
}
public voidsetItemName(String itemName) {
this.itemName =itemName;
}
public intgetQuantity() {
return quantity;
}
public voidsetQuantity(intquantity) {
this.quantity =quantity;
}
public intgetVersion() {
return version;
}
public voidsetVersion(intversion) {
this.version =version;
}
}
映射文件
<hibernate-mapping>
<!-- 映射实体类时,需要加入一个开启乐观锁的属性
optimistic-lock="version" 共有好几种方式:
- none -version - dirty - all
同时需要在主键映射后面映射版本号字段
-->
<class name="com.wjt276.hibernate.Inventory"table="t_inventory"optimistic-lock="version">
<id name="itemNo">
<generator class="native"/>
</id>
<version name="version"/><!—必需配置在主键映射后面 -->
<property name="itemName"/>
<property name="quantity"/>
</class>
</hibernate-mapping>
导出输出SQL语句:
createtable t_inventory (itemNo integer not null auto_increment, versioninteger not null, itemName varchar(255), quantity integer,primary key (itemNo))
注:添加的版本字段version,还是我们来维护的,是由hibernate来维护的。
乐观锁在存储数据时不用关心
_____________________________________________________________________
脏读:没有提交就可以读取到数据称为脏读
不可重复读:再重复读一次,数据与你上读取的不一样。称不可重复读。
幻读:在查询某一条件的数据,开始查询的后,别人又加入或删除些数据,再读取时与原来的数据不一样了。
e.g. 1, 2, 4, 8 (二进制效率)
悲观锁