Hibernate技术

不懂技术的人或者技术新手往往容易被 框架 二字所唬住,所谓框架是前人对相关问题处理方案的总结,将对某类问题最有价值的解决方式汇集在一起,形成框架。其它人使用时,仅仅只需要按照框架缔结者设定的规则以及调用的 API ,来完成对框架的使用。
学习 hibernate ,学习的主要是 hibernate 的使用规则,理解这个框架的思想。
1 Configuration SessionFactory Session 三个类一个都不能少,通通都需要了解。所谓了解,其实是夸大了,不少人仅仅只是使用这三个类最简单的创建过程代码,但这已经足够应付绝大多数场景了。无论你使用的时 hibernate.properties ,还是 hibernate.cfg.xml ,抑或者你自定义了一个配置文件,确保自己配置的正确性。
2 、必须学习 hbm.xml 文件的编写规则。新手可以依靠一些自动生成工具来完成对 hbm.xml 以及 java 文件的生成,但上手便这样,不利于学习。工具虽然方便,但是掩盖了生成时所应该知道的基本原理。
3 hibernate 的三种查询方式: HQL Criteria Native SQL 。每种技术有其应用的优势场景,技术不分优劣,只有最适合当前场景使用的。 HQL 是使用最频繁的, Criteria 是完全 OO 的,当你需要使用特定数据库的特性时, Native SQL 是首选。
4 、关联关系。数据库中的多表关联本是平常事,但这个问题又恰恰是最容易让人晕头转向的地方。 hibernate 处理关联关系的精髓内容在 hbm.xml 中,学习时( 1 )注意关联行为的主被动方( 2 )弄清关联的对象所依据的字段。
5 、优化。 Hibernate 在封装现有 JDBC 操作的同时,对数据库操作进行了默认的一些优化。而通过延迟加载与批量操作等相关参数设置,我们可以进一步对数据库操作的性能进行优化。
Hibernate 学习总结
1 Hibernate 概述
Hibernate
是一个持久层框架,用来负责实现对象和关系型数据库的转换。 2003 Hibernate 2 发布, 2005 Hibernate 3 发布。现在已经成为最为流行的 ORM Object/Relational Mapper )框架。
2 Hibernate
内容
2.1 Hibernate
的三种状态
 A. Transient
(临时对象)
 new
出来的对象,此状态下的对象还没有与数据库的记录对应上。或者通过 session.delete(obj) 操作的 obj 对象将从 Detached 状态改变为 Transient 状态
 B. Persistent
(持久对象)
 
-- Transient 状态的对象使用 Session save() 方法保存到数据库后,对象成为 persistent 状态,例:
DomesticCat fritz = new DomesticCat();
fritz.setColor(Color.GINGER);
fritz.setSex('M');
fritz.setName("Fritz");
Long generatedId = (Long) session.save(fritz);
--使用 Hibernate 从数据库得到数据并封装为对象(例如使用 get() load() ),则该对象为 Persistent 状态;
>>get()
User user = (User) session.get(User.class, new Long(userID));
>>load()
User user = (User) session.load(User.class, new Long(userID));
get()
load() 的区别在于:当要查找的对象数据不存在时, load() 方法就是直接抛出异常,而 get() 方法则返回 null
-- Detached 状态对象重新和 session 关联后(通过 update lock 方法)变成 Persistent 状态,例:
>>update()
//user
session1 关闭后留下的未关联对象
user.setPassword("secret");
Session session2 = sessionFactory.openSession();
Transaction tx = session2.beginTransaction();
session2.update(user); //
此时 user Detached 状态转为 Persistent 状态( session user 关联)
user.setUsername("jonny");
tx.commit();
session2.close();
这种方式,关联前后做修改都不打紧,关联前后做的修改都会被更新到数据库;
比如关联前你修改了 password ,关联后修改了 username ,事务提交时执行的 update 语句会把 password username 都更新
>>lock()
Session session2 = sessions.openSession();
Transaction tx = session2 .beginTransaction();
session2 .lock(user, LockMode.NONE);
user.setPassword("secret");
user.setLoginName("jonny");
tx.commit();
session2 .close();
这种方式,关联前后是否做修改很重要,关联前做的修改不会被更新到数据库,
比如关联前你修改了 password ,关联后修改了 loginname ,事务提交时执行的 update 语句只会把 loginname 更新到数据库
所以,确信未关联对象没有做过更改才能使用 lock()
Ps: 如果将 Session 实例关闭,则 Persistent 状态的对象会成为 Detached 状态。
 C. Detached
(未关联对象)
Detached
状态的对象,与数据库中的具体数据对应,但脱离 Session 实例的管理,例如:
在使用 load() get() 方法查询到数据并封装为对象之后,将 Session 实例关闭,则对象由 Persistent 状态变为 Detached 状态。
Detached
状态的对象之任何属性变动,不会对数据库中的数据造成任何的影响。
这种状态的对象相当于 cache 数据,因为他不和 session 关联,谁都可以用,任何 session 都可以用它,用完后再放到 cache 中。
2.2 VO
PO
 
Transient 状态和 Detached 状态的对象统称为 VO Value Object )对象,而将 Persistent 状态的对象称为 PO Persistence Object )对象。
 
我认为,可以简单的理解为当前和 session 关联的对象就是 PO 对象,否则为 VO 对象
 
Ps:
应该尽量避免将 PO 对象传入 / 传出除持久层外的其他层面(如在 view 层修改 PO )。解决办法是构造一个新的 VO ,使其具备和 PO 相同的属性,在系统其他层面使用 VO 进行数据传输。(但在实际应用中这种方法增加了系统复杂性并且影响性能,所以很多系统设计还是倾向于可以在 view 层操纵 PO 的)
2.3 SessionFactory
Session
SessionFactory
用来创建 session 对象,由于 SessionFactory 本身是单例实现,并且是线程安全的,所以在一个应用中只能创建一个 SessionFactory 实例。例:
Configuration config = new Configuration.configure();
SessionFactory sessionFactory = config.buildSessionFactory();
Session 对象用 SessionFactory 创建,创建后可以调用其 save,get,delete,find 方法对持久层数据进行操作。(其中, find() 方法在 Hibernate3 中被取消,用 Query Criteria 代替)最后,通过调用 session.close() 方法关闭 session
2.4
缓存
 
缓存种类:分为事务级缓存,应用级缓存和分布式缓存。但由于分布式缓存在同步过程中会占用大量带宽,严重影响系统性能,所以不建议使用。
2.4.1
一级缓存
 
一级缓存是 Hibernate 的内部缓存,属于事务级缓存。
 
它在 SessionImpl 中,以 Map 的形式实现。当要从 Session 中取得某个 PO 时,会判断此 PO id ClassName Map 中是否已经存在,如果已经存在,则直接从缓存中取出。
 
有两种手动干预内部缓存的方法:
a. Session.evict
将某个特定对象从内部缓存中清楚
b. Session.clear
清空内部缓存
  当批量插入数据时,会引发内存溢出,这就是由于内部缓存造成的。例如:
 For(int i=0; i<1000000; i++){
  For(int j=0; j<1000000; j++){
   User user = new User();
   user.setUserName(“gaosong”);
   user.setPassword(“123”);
   session.save(user);  
}
}
在每次循环时,都会有一个新的对象被纳入内部缓存中,所以大批量的插入数据会导致内存溢出。解决办法有两种: a 定量清除内部缓存 b 使用 JDBC 进行批量导入,绕过缓存机制。
a 定量清除内部缓存
For(int i=0; i<1000000; i++){
  For(int j=0; j<1000000; j++){
   User user = new User();
   user.setUserName(“gaosong”);
   user.setPassword(“123”);
   session.save(user);  
   if(j%50 == 0){
    session.flush();
    session.clear(); //
清除内部缓存的全部数据,及时释放出占用的内存
}
}
}
b 使用 JDBC 进行批量导入,绕过缓存机制
Transaction tx=session.beginTransaction(); //
使用 Hibernate 事务处理边界 Connection conn=session.connection();
PrepareStatement stmt=conn.prepareStatement(“insert into T_STUDENT(name) values(?)”);
for(int j=0;j++;j<200){
for(int i=0;i++;j<50) {
stmt.setString(1,”feifei”);
}
}
stmt.executeUpdate();
tx.commit(); //
使用 Hibernate 事务处理边界
ps :注意,这里使用 Hibernate 的事务处理机制处理 Connection
 2.4.2
二级缓存
2.4.2.1
概述
 Hibernate
的二级缓存涵盖了应用级缓存和分布式缓存领域。 Hibernate 默认使用 EHCache 作为二级缓存的实现。(二级缓存由第三方实现)
 
缓存后,可能出现问题主要有:
1. 
脏读:一个事务读取了另一个并行事务未提交的数据。
2. 
不可重复读:一个事务再次读取之前的数据时,得到的数据不一致,被另一个已提交的事务修改。
3. 
幻想读:一个事务重新执行一个查询,返回的记录中包含了因为其他最近提交的事务而产生的新记录。
对应以上问题, Hibernate 提供了四种内置的缓存同步策略:
1. read-only
:只读。对于不会发生改变的数据,可使用只读型缓存。
2. nonstrict-read-write
:如果程序对并发访问下的数据同步要求不是非常严格,且数据更新操作频率较低,可以采用本选项,获得较好的性能。
3. read-write
:严格可读写缓存。基于时间戳判定机制,实现了 “read committed” 事务隔离等级。可用于对数据同步要求严格的情况,但不支持分布式缓存。这也是实际应用中使用最多的同步策略。
4. transactional
:事务型缓存,发生异常的时候,缓存也能够回滚,必须运行在 JTA 事务环境中。实现了 “Repeatable read” 事务隔离等级。
Ps: 读写缓存和不严格读写缓存在实现上的区别在于,读写缓存更新缓存的时候会把缓存里面的数据换成一个锁,其他事务如果去取相应的缓存数据,发现被锁住了,然后就直接取数据库查询。
hibernate2.1 ehcache 实现中,如果锁住部分缓存的事务发生了异常,那么缓存会一直被锁住,直到 60 秒后超时。
不严格读写缓存不锁定缓存中的数据。
2.4.2.2

 
为了避免脏数据的出现, Hibernate 默认实现了乐观锁机制。原理是为表加一个 version 字段来控制。例如: version 初始值为 1 A B 两个事务同时修改表, A B 都读入了 version=1 A 修改完成后将 version 1 version=2 B 修改完后也将 version 1 ,当试图提交时发现:当前 version 已经为 2 ,而提交版本也为 2 ,违反了 提交版本必须大于记录版本 的乐观锁策略,事务被回滚。因此避免了脏数据的出现的可能。
2.4.2.3 Class
缓存
 Class
缓存是一个 Map ,其中 key ClassName Value 是一个 id 序列(其中的 id 就对应 DB 中的一条记录,也就是对应一个 PO )。当调用 session.iterate(…) 方法时,将通过一条查询语句返回一个 id 序列,只要序列中的 id Map 中存在,则取出 Map 中此 id 对应的 POJO
 session.iterate(…)
方法和 session.find(…) 方法的区别: session.find(…) 方法并不读取 ClassCache ,它通过查询语句直接查询出结果数据,并将结果数据 put classCache session.iterate(…) 方法返回 id 序列,根据 id 读取 ClassCache ,如果没有命中在去 DB 中查询出对应数据。
 
当使用 session.find(…) 方法时,如果返回大量数据,会将很多 POJO 放入内存中,导致内存溢出,此时可以用 limit 方式限定返回数量。
 Ps:
由于 session.iterate(…) 方法不能规避 N+1 的问题,所以基本不用此方法。
2.4.2.4
查询缓存
 Hibernate
查询缓存根据 HQL 生成的 SQL 作为 key ,对应一个查询出的 id 序列,如果两次查询的 SQL 相同,将命中查询缓存,根据 id 序列再到 ClassCache load 出对应的 POJO 。但是,如果在两次查询中数据库表有改动( Update/Insert/Delete ),则会将查询缓存中的数据清空。所以查询缓存只能应用于两次查询中 DB 表没有改变,并且是同样的 SQL 查询的情况。
 
这里有个问题,就是查询缓存机制可能会遇到 N+1 的问题。因为当从查询缓存中读出 id 序列后,将去 ClassCache 中找,如果此时 ClassCache 中没有 id 对应的 POJO ,则将向数据库中发送查询语句。所以一定要确保 ClassCache 中数据的生命周期要比 QueryCache 的长,(比如 ClassCache 的超时时间一定不能短于 QueryCache 设置的超时时间)
 2.4.2.5
批量操作( Update/Delete
 
Hibernate3.0.5 以前的版本和以后的有很大差异,这里分两部分祥解:
1. Hibernate2.x
Hibernate2.x 中,批量操作时是不推荐用 Hibernate 自己的 update/delete 方法的,这是因为 Hibernate 在批量更新 / 删除时必须维护 ClassCache ,这就导致出现 N+1 的情况影响性能,所以不可取。解决方法是使用 JDBC 的相应方法,绕开缓存。
2. Hibernate3.x
Hibernate3.x 中,加入了 “bulk update/delete” 的操作,使得可以使用 Hibernate 本身的批量更新功能。
“bulk update”
只是简单的发送一条 update 语句,并不维护 ClassCache 。此时就会有脏数据出现的可能。(需要确认)
另一方面, “bulk delete” 发送一条 delete 语句,并且到 ClassCache 中将 delete 对应的 Class 设置为无效,实际是清空了表对应的 ClassCache
2.4.3 session.save
session.save
方法的执行顺序:
1. 
在一级缓存中寻找 POJO ,如果命中则认为此对象执行过 Insert 方法,直接返回。
2. 
如果实现了 lifecycle 接口,则调用 onSave 方法
3. 
如果实现了 Validatable 接口,则调用 validate 方法
4. 
调用拦截机的 Interceptor.onSave 方法
5. 
构造 Insert Sql ,并且执行
6. 
返回新插入记录的 id POJO
7. 
POJO 放入一级缓存中( session.save 不会将 POJO 放入二级缓存,是因为通过 save 保存的 POJO 在事务的剩余部分被修改的可能往往很高,频繁更改二级缓存得不偿失)
8. 
处理级连关系
3 Hibernate3.2
新特性
1. Hibernate Annotation
 2. bulk update/delete
(批量更新 / 删除)
 3.
lucene 集成
 4.
取消 Hibernate2 中的 session.find 方法,改为 session.createQuery.list 方法
 5.
修改包名  
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值