Hibernate技术

来源:互联网 发布:无闻 golang 编辑:程序博客网 时间:2024/05/16 23:52

Hibernate技术

不懂技术的人或者技术新手往往容易被框架二字所唬住,所谓框架是前人对相关问题处理方案的总结,将对某类问题最有价值的解决方式汇集在一起,形成框架。其它人使用时,仅仅只需要按照框架缔结者设定的规则以及调用的API,来完成对框架的使用。

学习hibernate,学习的主要是hibernate的使用规则,理解这个框架的思想。

1ConfigurationSessionFactorySession三个类一个都不能少,通通都需要了解。所谓了解,其实是夸大了,不少人仅仅只是使用这三个类最简单的创建过程代码,但这已经足够应付绝大多数场景了。无论你使用的时hibernate.properties,还是hibernate.cfg.xml,抑或者你自定义了一个配置文件,确保自己配置的正确性。

2、必须学习hbm.xml文件的编写规则。新手可以依靠一些自动生成工具来完成对hbm.xml以及java文件的生成,但上手便这样,不利于学习。工具虽然方便,但是掩盖了生成时所应该知道的基本原理。

3hibernate的三种查询方式:HQLCriteriaNative SQL。每种技术有其应用的优势场景,技术不分优劣,只有最适合当前场景使用的。HQL是使用最频繁的,Criteria是完全OO的,当你需要使用特定数据库的特性时,Native SQL是首选。

4、关联关系。数据库中的多表关联本是平常事,但这个问题又恰恰是最容易让人晕头转向的地方。hibernate处理关联关系的精髓内容在hbm.xml中,学习时(1)注意关联行为的主被动方(2)弄清关联的对象所依据的字段。

5、优化。Hibernate在封装现有JDBC操作的同时,对数据库操作进行了默认的一些优化。而通过延迟加载与批量操作等相关参数设置,我们可以进一步对数据库操作的性能进行优化。

Hibernate学习总结

1 Hibernate概述
Hibernate
是一个持久层框架,用来负责实现对象和关系型数据库的转换。2003Hibernate 2发布,2005Hibernate 3发布。现在已经成为最为流行的ORMObject/Relational Mapper)框架。
2 Hibernate
内容
2.1 Hibernate
的三种状态
 A. Transient
(临时对象)
 new
出来的对象,此状态下的对象还没有与数据库的记录对应上。或者通过session.delete(obj)操作的obj对象将从Detached状态改变为Transient状态
 B. Persistent
(持久对象)
 
--Transient状态的对象使用Sessionsave()方法保存到数据库后,对象成为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关联后(通过updatelock方法)变成Persistent 状态,例:
>>update()
//user
session1关闭后留下的未关联对象
user.setPassword("secret");
Session session2 = sessionFactory.openSession();
Transaction tx = session2.beginTransaction();
session2.update(user); //
此时userDetached状态转为Persistent状态(sessionuser关联)
user.setUsername("jonny");
tx.commit();
session2.close();
这种方式,关联前后做修改都不打紧,关联前后做的修改都会被更新到数据库;
比如关联前你修改了password,关联后修改了username,事务提交时执行的update语句会把passwordusername都更新
>>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状态的对象统称为VOValue Object)对象,而将Persistent状态的对象称为POPersistence 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中被取消,用QueryCriteria代替)最后,通过调用session.close()方法关闭session
2.4
缓存
 
缓存种类:分为事务级缓存,应用级缓存和分布式缓存。但由于分布式缓存在同步过程中会占用大量带宽,严重影响系统性能,所以不建议使用。
2.4.1
一级缓存
 
一级缓存是Hibernate的内部缓存,属于事务级缓存。
 
它在SessionImpl中,以Map的形式实现。当要从Session中取得某个PO时,会判断此POidClassNameMap中是否已经存在,如果已经存在,则直接从缓存中取出。
 
有两种手动干预内部缓存的方法:
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.1ehcache实现中,如果锁住部分缓存的事务发生了异常,那么缓存会一直被锁住,直到60秒后超时。
不严格读写缓存不锁定缓存中的数据。
2.4.2.2

 
为了避免脏数据的出现,Hibernate默认实现了乐观锁机制。原理是为表加一个version字段来控制。例如:version初始值为1AB两个事务同时修改表,AB都读入了version=1A修改完成后将version1version=2B修改完后也将version1,当试图提交时发现:当前version已经为2,而提交版本也为2,违反了提交版本必须大于记录版本的乐观锁策略,事务被回滚。因此避免了脏数据的出现的可能。
2.4.2.3 Class
缓存
 Class
缓存是一个Map,其中keyClassNameValue是一个id序列(其中的id就对应DB中的一条记录,也就是对应一个PO)。当调用session.iterate(…)方法时,将通过一条查询语句返回一个id序列,只要序列中的idMap中存在,则取出Map中此id对应的POJO
 session.iterate(…)
方法和session.find(…)方法的区别:session.find(…)方法并不读取ClassCache,它通过查询语句直接查询出结果数据,并将结果数据putclassCachesession.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序列再到ClassCacheload出对应的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. 
返回新插入记录的idPOJO
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.
修改包名 

原创粉丝点击