首先了解下hibernate的三种状态:
Hibernate的对象有3种状态,分别为:瞬时态(Transient)、 持久态(Persistent)、脱管态(Detached)。处于持久态的对象也称为PO(Persistence Object),瞬时对象和脱管对象也称为VO(Value Object)。
瞬时态
由new命令开辟内存空间的java对象,eg. Person person = new Person("xxx", "xx");
如果没有变量对该对象进行引用,它将被java虚拟机回收。
瞬时对象在内存孤立存在,它是携带信息的载体,不和数据库的数据有任何关联关系,在Hibernate中,可通过session的save()或saveOrUpdate()方法将瞬时对象与数据库相关联,并将数据对应的插入数据库中,此时该瞬时对象转变成持久化对象。
持久态
处于该状态的对象在数据库中具有对应的记录,并拥有一个持久化标识。如果是用hibernate的delete()方法,对应的持久对象就变成瞬时对象,因数据库中的对应数据已被删除,该对象不再与数据库的记录关联。
当一个session执行close()或clear()、evict()之后,持久对象变成脱管对象,此时持久对象会变成脱管对象,此时该对象虽然具有数据库识别值,但它已不在HIbernate持久层的管理之下。
持久对象具有如下特点:
1. 和session实例关联;
2. 在数据库中有与之关联的记录。
脱管态
当与某持久对象关联的session被关闭后,该持久对象转变为脱管对象。当脱管对象被重新关联到session上时,并再次转变成持久对象。
脱管对象拥有数据库的识别值,可通过update()、saveOrUpdate()等方法,转变成持久对象。
脱管对象具有如下特点:
1. 本质上与瞬时对象相同,在没有任何变量引用它时,JVM会在适当的时候将它回收;
2. 比瞬时对象多了一个数据库记录标识值。
——————————————————————————————————————
1、hibernate拦截器、监听器
拦截器
Interceptor 接口提供了从会话 (session) 回调 (callback) 应用程序 (application) 的机制, 这种回调机制可以允许应用程序在持久化对象被保存、更新、删除或是加载之前,检查并(或)修改其 属性。
你可以直接实现 Interceptor 接口,也可以(最好)继承自 EmptyInterceptor 。
拦截器可以有两种 :Session 范围内的,和 SessionFactory 范围内的:
当使用某个重载的 SessionFactory.openSession() 使用 Interceptor 作为参数调用打开一个 session 的时候,就指定了 Session 范围内的拦截器。
Session session = sf.openSession( new AuditInterceptor() );
SessionFactory 范围内的拦截器要通过 Configuration 中注册,而这必须在创建 SessionFactory 之前。在这种情况下,给出的拦截器会被这个 SessionFactory 所打开的所有 session 使用了,除非 session 打开时明确指明了使用的拦截器。 SessionFactory 范围内的拦截器,必须是线程安全的,因为多个 session 可能并发使用这个拦截器,要因此小心不要保存与 session 相关的状态。
new Configuration().setInterceptor( new AuditInterceptor() );
监听器
监听器在运行时被实例化为单例 (singleton) 对象,也就是说,所有同类型的事件的处理共享同一个监听器实例,因此监听器不应该保存任何与请求相关的状态。
用户定制的监听器需要实现相关的事件监听器接口,或者从一个合适的基类继承(甚至是从 Hibernate 自带的默认事件监听器类继承,作者已经通过这些类的 non-final 定义给予我们这样的暗示和权利了)。
用户定制的监听器可以通过编程使用 Configuration 对象 来注册,也可以在 Hibernate 的 XML 格式的配置文件中进行声明(不支持在 Properties 格式的配置文件声明监听器)。 下面是一个用户定制的加载事件 (load event) 的监听器:
public class MyLoadListener implements LoadEventListener {
// this is the single method defined by the LoadEventListener interface
public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType) throws HibernateException {
if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) )
{ throw MySecurityException("Unauthorized access"); }
}
}
除此之外你还需要修改一处配置,来告诉 Hibernate ,除了默认的监听器,还要附加选定的监听器。
<hibernate-configuration>
<session-factory> ... <event type="load">
<listener class="com.eg.MyLoadListener"/>
<listener class="org.hibernate.event.def.DefaultLoadEventListener"/>
</event> </session-factory>
</hibernate-configuration>
看看用另一种方式,通过编程的方式来注册它。
Configuration cfg = new Configuration(); LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() }; cfg.EventListeners().setLoadEventListeners(stack);
2、save()和persist()的区别
persist():只接受临时状态的对象,即主键没有值的对象,如果传递一个非临时状态的对象给它,则会抛出异常。
(1)persist把一个瞬态的实例持久化,但是并"不保证"标识符被立刻填入到持久化实例中,标识符的填入可能被推迟到flush的时间。
(2)persist"保证",当它在一个transaction外部被调用的时候并不触发一个Sql Insert,这个功能是很有用的,当我们通过继承Session/persistence context来封装一个长会话流程的时候,一个persist这样的函数是需要的。
(3)save"不保证"第2条,它要返回标识符,所以它会立即执行Sql insert,不管是不是在transaction内部。
3、update和merge的区别
merge的作用:
如果session中存在相同持久化标识(identifier)的实例,用用户给出的对象的状态覆盖旧有的持久实例
如果session没有相应的持久实例,则尝试从数据库中加载,或创建新的持久化实例,最后返回该持久实例
用户给出的这个对象没有被关联到session上,它依旧是脱管的。
重点是最后一句:
当我们使用update的时候,执行完成后,我们提供的对象A的状态变成持久化状态,但当我们使用merge的时候,执行完成,我们提供的对象A还是脱管状态,hibernate或者new了一个B,或者检索到一个持久对象B,并把我们提供的对象A的所有的值拷贝到这个B,执行完成后B是持久状态,而我们提供的A还是托管状态。
4、 所有的持久化对象都应具备一个无参的默认构造方法 ,使得hibernate可以通过constructor.newIntance()够着实体对象。
5、fecth只支持 inner join 和left join 联合查询的数据加载方式有效。
6、瞬时态和脱管态的区别 :数据库有无对应的信息。瞬时态无,脱管态有。
7、po 和 vo 的区别
VO,值对象(Value Object),PO,持久对象(Persisent Object),它们是由一组属性和属性的get和set方法组成。从结构上看,它们并没有什么不同的地方。但从其意义和本质上来看是完全不同的。
(1)VO是用new关键字创建,由GC回收的。
PO则是向数据库中添加新数据时创建,删除数据库中数据时削除的。并且它只能存活在一个数据库连接中,断开连接即被销毁。
(2)VO是值对象,精确点讲它是业务对象,是存活在业务层的,是业务逻辑使用的,它存活的目的就是为数据提供一个生存的地方。
PO则是有状态的,每个属性代表其当前的状态。它是物理数据的对象表示。使用它,可以使我们的程序与物理数据解耦,并且可以简化对象数据与物理数据之间的转换。
(3)VO的属性是根据当前业务的不同而不同的,也就是说,它的每一个属性都一一对应当前业务逻辑所需要的数据的名称。
PO的属性是跟数据库表的字段一一对应的。
(4)PO对象需要实现序列化接口。
8、脏数据检查策略
脏数据并不是废弃和无用的数据,而是状态前后发生变化的数据。我们看下面的代码:
Transaction tx=session.beginTransaction(); User user=(User)session.load(User.class,”1”);//从数据库中加载符合条件的数据 user.setName(“zx”);//改变了user对象的姓名属性,此时user对象成为了所谓的“脏数据” tx.commit();
当事务提交时,Hibernate会对session中的PO(持久化对象)进行检测,判断持久化对象的状态是否发生了改变,如果发生了改变就会将改变更新到数据库中。这里就存在一个问题,Hibernate如何来判断一个实体对象的状态前后是否发生了变化。也就是说Hibernate是如何检查出一个数据已经变脏了。
通常脏数据的检查有如下两种办法:
A、数据对象监控:
数据对象监控是通过拦截器对数据对象的setter方法进行监控来实现的,这类似于数据库中的触发器的概念,当某一个对象的属性调用了setter方法而发生了改变,这时拦截器会捕获这个动作,并且将改属性标志为已经改变,在之后的数据库操作时将其更新到数据库中。这个方法的优点是提高了数据更新的同步性,但是这也是它的缺点,如果一个实体对象有很多属性发生了改变,势必造成大量拦截器回调方法的调用,这些拦截器都是通过Dynamic Proxy或者CGLIB实现的,在执行时都会付出一定的执行代价,所以有可能造成更新操作的较大延时。
B、数据版本比对:
这种方法是在持久化框架中保存数据对象的最近读取版本,当提交数据时将提交的数据与这个保存的版本进行比对,如果发现发生了变化则将其同步跟新到数据库中。这种方法降低了同步更新的实时性,但是当一个数据对象的很多属性发生改变时,由于持久层框架缓存的存在,比对版本时可以充分利用缓存,这反而减少了更新数据的延迟。
在Hibernate中是采用数据版本比对的方法来进行脏数据检查的,我们结合下面的代码来讲解 Hibernate的具体实现策略。
Transaction tx=session.beginTransaction(); User user=(User)session.load(User.class,”1”); user.setName(“zx”); tx.commit();
当调用tx.commit();时好戏就此开场,commit()方法会调用session.flush()方法,在调用flush()方法时,会首先调用flushEverything()来进行一些预处理(如调用intercepter,完成级联操作等),然后调用flushEntities()方法,这个方法是进行脏数据检查的关键。
在继续讲解之前,我要先来介绍一个内部数据结构EntityEntry,EntityEntry是从属于SessionImpl(Session接口的实现类)的内部类,每一个EntityEntry保存了最近一次与数据库同步的实体原始状态信息(如:实体的版本信息,实体的加锁模式,实体的属性信息等)。除了EntityEntry结构之外,还存在一个结构,这个结构称为EntityEntries,它也是SessionImpl的内部类,而且是一个Map类型,它以”key-value”的形式保存了所有与当前session实例相关联的实体对象和原始状态信息,其中key是实体对象,value是EntityEntry。而flushEntities()的工作就是遍历entityEntities,并将其中的实体对象与原始版本进行对比,判断实体对象是否发生来了改变。flushEntities()首先会判断实体的ID是否发生了改变,如果发生了改变则认为发生了异常,因为当前实体与EntityEntry的对应关系非法。如果没有发生异常,而且经过版本比对判断确实实体属性发生了改变,则向当前的更新任务队列中加入一个新的更新任务,此任务将在将在session.flush()方法中的execute()方法的调用中,转化为相应的SQL语句交由数据库去执行。最后Transaction将会调用当前session对应的JDBC Connection的commit()方法将当前事务提交。
脏数据检查是发生在显示保存实体对象时,所谓显示保存是指在代码中明确使用session调用save,update,saveOrupdate方法对实体对象进行保存,如:session.save(user);但是有时候由于级联操作的存在,会产生一个问题,比如当保存一个user对象时,会根据user对象的状态来对他所关联的address对象进行保存,但是此时并没有根据级联对象的显示保存语句。此时需要Hibernate能根据当前对象的状态来判断是否要将级联对象保存到数据库中。此时,Hibernate会根据unsaved-value进行判断。Hibernate将首先取出目标对象的ID,然后将ID与unsaved-value值进行比较,如果相等,则认为实体对象尚未保存,进而马上将进行保存,否则,则认为实体对象已经保存,而无须再次进行保存。比如,当向一个user对象新加入一个它所关联的address对象后,当进行session.save(user)时,Hibernate会根据unsaved-value的值判断出哪个address对象需要保存,对于新加入的address对象它的id尚未赋值,以此为null,与unsaved-value值相等,因此Hibernate会将其视为未保存对象,生成insert语句加以保存。如果想使用unsaved-value必须如下配置address对象的id属性:
…… <id name=”id” type=”java.lang.Integer” unsaved-value=”null”> <generator class=”increment”/> </id> ……
9、在集合缓存策略中read-only只会缓存索引值(ID)不会缓存实体。
10、集合映射中set 对象为String 的时候可能会出现问题,由于set不重复性,和java中String的特性导致。
11、一级缓存只缓存实体,不缓存属性。
12、clear()和evict()区别
当调用save方法的时候,hibernate把持久化对象添加到缓存,又把该对象添加到一个要进行插入的集合inserts里,而调用evict方法只是把该对象从缓存中清空了,当执行flush操作或者事务提交之后,inserts执行完毕后要更新缓存中该对象的持久化标志位为已经持久化,但是没更新到,就出现上述错误,而调用clear会清空掉缓存和inserts集合、updates集合和deletes集合,所以调用clear之后不会出错,数据库中也不会有记录出现。
如果hibernate的autocommit配置为true的话,可以在evict之前调用flush
如果hibernate的autocommit配置为false的话,就不能那么用了,去掉evict
13、Session的不同操作对对象状态的影响
Session的save()方法
save()方法将一个瞬时对象转变为持久对象。
Session的update()方法
update()方法将一个分离对象转变为持久对象。
Session的lock()方法
调用lock()方法将对象同Session相关联而不强制更新。
Session的merge()方法
拷贝指定对象的状态到具有相同对象标识符的持久对象。
Session的saveOrUpdate()方法
saveOrUpdate()方法对于瞬时对象,执行save()方法,对于分离对象,执行update()方法。
Session的load()和get()方法
1、如果找不到符合条件的纪录,get()方法将返回null.而load()将会报出ObjectNotFoundEcception.
2、load()方法可以返回实体的代理类实例,而get()永远只返回实体类.
(另外,很多地方都注明get()方法不会查询二级缓存,经测试,证明get()和load()方法都会查询二级缓存)
Session的delete()方法
delete()方法用于从数据库中删除与持久化对象对应的记录。如果传入的是一个持久化对象,Session就执行一条delete语句。如果传入的参数是分离对象,先使分离对象与Session关联,使它变为持久化对象,然后才计划执行一个delete语句。
Session的evict()方法
evict()方法从Session的缓存中删除一个持久对象。
delete()方法用于从数据库中删除与持久化对象对应的记录。如果传入的是一个持久化对象,Session就执行一条delete语句。如果传入的参数是分离对象,先使分离对象与Session关联,使它变为持久化对象,然后才计划执行一个delete语句。
Session的evict()方法
evict()方法从Session的缓存中删除一个持久对象。
14、getCurrentSession()与openSession()的区别?
* 采用getCurrentSession()创建的session会绑定到当前线程中,而采用openSession()创建的session则不会.
* 采用getCurrentSession()创建的session在commit或rollback时会自动关闭,而采用openSession()创建的session必须手动关闭.
使用getCurrentSession()需要在hibernate.cfg.xml文件中加入如下配置:
* 如果使用的是本地事务(jdbc事务)
<property name="hibernate.current_session_context_class">thread</property>
* 如果使用的是全局事务(jta事务)
<property name="hibernate.current_session_context_class">jta</property>
分享到:
相关推荐
**Hibernate 框架概述** Hibernate 是一个开源的对象关系映射(ORM)框架,它为 Java 开发者提供了一种在 Java 应用程序中管理关系...通过实践和深入学习,你可以更好地利用 Hibernate 实现高效、可靠的数据库应用。
### J2EE系统之-hibernate学习总结 #### 对象持久化理论 - **对象持久化定义**:将对象中的数据转换存储至外部持久性存储设备的过程,如数据库、磁盘等。 - **对象持久化的原因**: - 内存易失性:断电后数据丢失...
**hibernate学习总结** Hibernate 是一款开源的对象关系映射(ORM)框架,它极大地简化了Java应用程序与数据库之间的交互。在Java世界中,Hibernate 提供了一种在对象模型和关系数据库之间进行转换的机制,使得开发...
【Struts+Spring+Hibernate 学习总结】 SSH(Struts、Spring、Hibernate)是Java Web开发中的一个经典企业级应用架构,它将MVC设计模式应用到Web应用的各个层次,实现了良好的分层和解耦。这篇学习总结旨在帮助读者...
### Hibernate学习总结 #### 一、Hibernate简介 Hibernate是一个开源的对象关系映射(ORM)框架,用于Java应用程序与数据库进行交互。它允许开发人员以面向对象的方式来操作数据库,从而简化了数据访问层的开发工作...
**Hibernate学习笔记与总结** Hibernate 是一款开源的对象关系映射(ORM)框架,它为Java开发者提供了一种在关系数据库上操作对象数据的便捷方式。本文将深入探讨Hibernate的核心概念、配置、实体类、映射文件、...