不懂技术的人或者技术新手往往容易被
“
框架
”
二字所唬住,所谓框架是前人对相关问题处理方案的总结,将对某类问题最有价值的解决方式汇集在一起,形成框架。其它人使用时,仅仅只需要按照框架缔结者设定的规则以及调用的
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
和
SessionSessionFactory
用来创建
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(jP == 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.savesession.save
方法的执行顺序:
1.
在一级缓存中寻找
POJO
,如果命中则认为此对象执行过
Insert
方法,直接返回。
2.
如果实现了
lifecycle
接口,则调用
onSave
方法
3.
如果实现了
Validatable
接口,则调用
validate
方法
4.
调用拦截机的
Interceptor.onSave
方法
5.
构造
Insert Sql
,并且执行
6.
返回新插入记录的
id
给
POJO7.
将
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.
修改包名
转载请注明原文地址: https://ibbs.8miu.com/read-19403.html