650 likes | 871 Views
对象 / 关系映射 Hibernate 作者:赵春晖. 要求:熟悉 Hibernate 的基本配置、操作。 目标:深入理解 Hibernate, 熟悉其存储方式和加载方式等。. 目录. POJO 与 PO 实体对象的管理 Hibernate 数据缓存 持久化的操作. POJO 和 PO 的概念.
E N D
对象/关系映射 Hibernate作者:赵春晖 要求:熟悉Hibernate的基本配置、操作。 目标:深入理解Hibernate,熟悉其存储方式和加载方式等。
目录 • POJO与PO • 实体对象的管理 • Hibernate数据缓存 • 持久化的操作
POJO和PO的概念 • POJO:Plain Ordinary java object 从字面上理解就是无格式的普通的JAVA对象,可以简单理解为不含逻辑代码的值对象(Value Object),在Hibernate 理解为数据库表所对应的Domain Object ,从数据层面来看,POJO作为数据实体的表现形式,也成为实体类(Entity bean),POJO和Hibernate映射文件构成了Hibernate 的基础代码 • PO:persistent Object 持久化对象 • 也就是说 在一些Objet/Relation Mapping工具中能够做到维护数据库表记录的PO全是符合JAVABEAN规范的纯 JAVA对象。如下:
POJO和PO的概念 public class TUser{ private Integer id; private String name; private Integer age; private Set address; public TUser(){ } ……… seter/geter }
POJO和PO的区别 • POJO是由new 创建 有GC回收而 PO 是由Insert数据库创建,由数据库delete删除,基本上PO的生命周期和数据库密切相关,另外PO只能存在数据库的一个Connection中 当Connection关闭 PO 则不存在 而POJO只要不被GC回收总是存在的。 • 由于诸多差别,PO和POJO在代码上肯定是不同的,起码PO会比POJO多一些用于管理数据库entity的属性和方法。而ORM追求的目标就是要在PO的使用上尽量要和POJO一致,对于程序员来讲他们可以把PO当作POJO来用而感觉不到PO的存在。 • 在Hibernate中是这样实现的
POJO和PO的区别 • 编写POJO。 • 编译POJO。 • 直接运行,在运行期 由Hibernate的CGLIB将POJO动态的转化为PO。 • 由此可以看出 Hibernate是在运行期将 POJO转化为PO的而CGLIB的效率也很高在运行期生成PO的字节码也很快,效率的损失几乎可以忽略不计。在运行期生成PO的好处是 对于程序员来说是无法接触到PO的可以说PO对他们来说是透明的,程序员可以更自由的以POJO的概念来操作PO。
POJO和PO的区别 • 当你在程序里面set/get的时候,Hibernate是从PO实例中取values的,所以即使Session关闭也一样可以set/get,可以进行跨Session的状态管理。 • 所以在分多层的应用中,由于持久层和业务层和web层都是分开的,此时Hibernate的PO完全可以当做一个POJO来用,也就是当做一个Value Object,在各层间自由传递,而不用去管Session是开还是关。(lazy loading除外)。
目录 • POJO与PO • 实体对象的管理 • Hibernate数据缓存 • 持久层的操作
实体对象的管理-对象的3种状态 • 在Hibernate中最核心的概念就是 对实体对象的状态管理(也就是上面提到的 POJO/Domain Object)。 • 实体对象的状态有三种,Transient(瞬时状态)、Persistent(持久状态)、Detached (脱管状态)
实体对象的管理-对象的3种状态 • Transient(瞬时状态) • 也就是未被持久化的VO(value object)是由JVM维护其生命周期。 • 如: methodA(){ TUser user=new TUser() User.setName(”ZCH”); }
实体对象的管理-对象的3种状态 • Persistent(持久状态) • 此时实体对象处于由hibernate管理的状态,并且与数据库表记录同步。 • 例如: TUser user=new TUser(); TUser antherUser=new TUser(); user.setName("aa1"); antherUser.setName("aa2"); //此时 user和antherUser都处于transient状态
实体对象的管理-对象的3种状态 Transaction tx=session.beginTransaction(); session.save(user); //此时 user处于 Persistent状态,由hibernate管理 //而 antherUser还处于transient状态 tx.commit(); //事务提交之后 数据库中插入name=“aa1“的记录 Transaction tx2=session.beginTransaction(); user.setName("aa1_1");// Persistent antherUser.setName("aa2_2");//transient tx2.commit(); //虽然没有显示调用save(),但此时user处于persistent //状态,hibenate会检查持久化对象的认识改动然后在当前操作单元 //结束后将对象数据与数据库同步
实体对象的管理-对象的3种状态 Detached(脱管状态) 也就是曾被持久化过,但现在和Session已经detached了,以VO的身份在运行 。 这种和Session已经detached(分离)的PO还能够进入另一个Session,继续进行PO状态管理,此时它就成为PO的第二种状态了。这种PO实际上是跨了Session进行了状态维护的。
实体对象的管理-对象的3种状态 例如: TUser user=new TUser(); user.setName("aa1"); //此时 user处于transient状态 Transaction tx=session.beginTransaction(); session.save(user); //此时 user处于 Persistent状态,由hibernate管理 tx.commit();//事务提交之后 数据库中插入name="aa1"记录 session.close(); //此时的 user已经处于 detached状态 因为与其关联的session已经关闭
实体对象的管理-对象的3种状态 • Transient(自由状态)和 Detached(游离状态) 的区别 • 区别在于Datached 对象还可以再次与某个session实例相关联而成为persistent对象 • 例如: Transaction tx=session.beginTransaction(); session.save(user); //此时 user处于 Persistent状态,由hibernate管理 tx.commit();//事务提交之后 数据库中插入name="aa1"记录 session.close(); //此时的 user已经处于 detached状态 因为与其关联的session已经关闭
实体对象的管理-对象的3种状态 Transaction tx2=session2.beginTransaction(); session2.update(user); //此时user对象借助session再次由hibernate纳入 //容器管理 //已经由 Detached状态改变为 Persistent; user.setName("aa1_1");// Persistent tx2.commit(); //由于属性变更将固化到数据库
实体对象的管理-对象的3种状态 在这里我们通过 update()方法将 detached对象 再次与hibernate持久层关联因此user在次转变 为 Persistent状态 。 那么自然会有这样一个问题,这个Detached的user对象 和 Transient的user对象到底有何不同 ? 不同就在于 transient的user对象并没有存在和数据库记录的对应 关系而 Detached的user是和数据库记录存在对应关系,不过 Detached的对象脱离了session的管理 其状态更新无法更新到 数据 库表中的对应记录 。
实体对象的管理-对象的3种状态 • 如果我新建一个对象,然后人为的让它属性的值和数据库中的一条记录对应, • 包括id的取值都一样。此时它能否说是处于游离状态呢?因为它和一条记录想对应呀。 • 实际上这些情况都是由于一些不和规范的操作而产生的。在Hibernate应用中, • 无论Java对象处于临时状态、持久化状态还是游离状态,应用程序都不应该修改它的ID。ID的值应该由Hibernate来维护和负责。
实体对象的管理-实体对象的识别 • 实体对象的识别 在java中,对象之间的比较主要通过以下2种方式 1、对象引用的比较(==) 引用的作用是判断两个变量是否引用了同一个对象 例如: TUser u1=new TUser(); TUser u2=new TUser(); System.out.println(u1==u2);//false
实体对象的管理-实体对象的识别 2、内容比较 例如: String str1=“aaa”; String str2=“bbb”; System.out.println(str1.equals(str2));//false 内容比较的目的是为了判断两个对象所包含的数据是否 相同。 而在hibernate中,面对持久层逻辑,我们必须面对新的难题; 如何判读两个实体对象是否相等。
实体对象的管理-实体对象的识别 假设出现这样的情况: TUser u1=session.load(TUser.class,new Integer(1)); u1.setAge(23); TUser u2=session.load(TUser.class,new Integer(1)); 上面的实例中 u1和u2这两个对象是否相等? 从java语言规范的角度而言,这两个对象无论从引用还是从具体 的内容都不相等。但是,站在持久层角度而言,这两个对象都代 表着数据库中同一条记录(t_user表中id=1的记录),这种关系有由 于持久层逻辑的出现而引入的,而这也引入了我们讨论的主题: 实体对象的身份识别。
实体对象的管理-实体对象的识别 如何确定一个实体对象的身份?站在数据库的角度,我们认为,一 个库表结构中,主键可以唯一确定一条记录,那么对于拥有同一主 键值的实体对象,则认为他们是相同的,如上面的例子 只要其主 键、相同,我们则认为他们相同。 对hibernate而言,这个规则也成立 org.hibernate.engine.EntityKey 封装了用于区分两个实体的识别信息。 在EntityKey中主要 维持了:实体ID,实体类名,ID类型 等属性, Hibernate通过实体ID 实体类名即可确定这个实体在数据库中对应 的记录,从而将其与其他对应不同记录的实体对象区分开。 另外EntityKey在hibernate缓存中也扮演着数据标识的角色, Hibernate将根据EntityKey 在缓存中寻找是否有对应的数据存在
实体对象的管理-实体对象的识别 • equals和hashCode方法: Java Collection将通过hashCode/equals方法判定两个对 象是否相等。 我们知道 Set集合类型不允许出现两个相同对象。如: Set set=new HashSet(); TUser u1=new TUser(); TUser u2=new TUser(); u1.setId(new Integer(1)),u2.setId(new Integer(1)); set.add(u1),set.add(u2); System.out.println(“set size=”+set.size());// set size=2 这里的TUser 对象没有覆盖Object.equals/hashCode方法,因此 Set 默认调用Object的equals/hashCode方法是否相等。
实体对象的管理-实体对象的识别 Object.equals()只是默认比较对象的引用是否相当,显然u1==u2是 不成立的,于是Collection判定这两个对象不相等,将其分别纳入 集合中。 现在我们修改下TUser类,使之覆盖Object.equals/hashCode方法 public TUser implements Serializable{ …………. public int hashCode(){ return this.getId.hasCode(); } public boolean equals(Object obj){ TUser u=(TUser)obj; return this.getId.equals(u.getId()); } }
实体对象的管理-实体对象的识别 在次运行测试代码,我们看到输出:set size=1; Set 认为u1和u2是2个相同对象则在容器内维护一个TUser对象 Collection在判断2个对象是否相等的时候会先调用hashCode方法 ,如果hashCode的值相等则判断equals方法,如果两次判断都成 功的话则认为2个对象相等。 对于我们上面改造过的TUser类而言,无论其实例之间其他属性值 有怎样的差异,只要id相等在Set中只会维持相同id的一个实例。 那么,这对hibernate又意味什么呢?
实体对象的管理-实体对象的识别 如果id相同则认为2个对象相同,这实际上也符合持久层的实体对象 身份识别原则。首先我们为TAddress添加equals/hashCode方法: publicint hashCode(){ int result=17; result=37*result+(this.id!=null?this.id.hashCode():0); return result; } publicboolean equals(Object other){ if(this==other)returntrue; if(!(other instanceof TAddress))returnfalse; final TAddress t=(TAddress)other; if(!(this.getId().equals(t.getId())))returnfalse; returntrue; }
实体对象的管理-实体对象的识别 然后再运行下列代码: TUser user=session.load(TUser.class,new Integer(1)); TAddress t=new TAddress (“dao li”); TAddress t1=new TAddress (“nan gang”); user. getAddressSet().add(t); user. getAddressSet().add(t1); System.out.println(“address size=”+user. getAddressSet().size()); 在这里 会报 java.lang.NullPointerException错误,这是因为在添加 地址TAddress的时候并未对id操作,此时id=null,而我们在覆盖的e quals方法里面并为对null进行判断,这时我们需要对id=null的情况 进行判断。对TAddress中的equals进行修改
实体对象的管理-实体对象的识别 那么 我们对id=null时判断应该是怎么样的呢? 如果认为是相等的话那么,set size 会等于1,这样我们在调用 session.save(user)的时候也只会向数据库发送一条地址数据, 当然这不是我们期待的结果因为我添加了两个地址对象,所以 对于 id=null的情况我们应该判断为 不等,这样就不会发生上面的 事情,所以修改的equals方法如下: publicboolean equals(Object other){ if(this==other)returntrue; if(!(other instanceof TAddress))returnfalse; final TAddress t=(TAddress)other; if(this.getId()==null&&t.getId()==null)returnfalse;//新添加 if(!(this.getId().equals(t.getId())))returnfalse; returntrue; }
实体对象的管理-实体对象的识别 当然,对于是否采用覆盖equals/hashCode方法和如果采 用覆盖方法,应该根据具体业务的需求进行判断决策
目录 • POJO与PO • 实体对象的管理 • Hibernate数据缓存 • 持久层的操作
Hibernate数据缓存 • Hibernate数据缓存分为两个层次,以hibernate语义加以区分可分为: 1、内部缓存(Session level,也称之为一级缓存) 2、二级缓存(SessionFactory level,也成为二级缓存)
Hibernate数据缓存-内部缓存 内部缓存在Hibernate中又称为一级缓存,属于应用事务 级缓存 . Session 在内部维护了一个Map数据类型,在Map中保持 了所有与当前Session相关联的数据对象.如果我们需要通 过Session加载摸个数据对象,Session会首先根据所需要 加载的数据类和id,在entitysByKey中寻找是否已有此数 据的缓存实例,如果存在且状态判定有效则将此数据实 例一结果返回. 内部缓存正常情况下由Hibernate 自动维护,如需手动干预,可以 通过以下方法完成: 1)session.evict 将某个特定的对象从缓存中清除。 2)Session.clear 清空内部缓存
Hibernate数据缓存-二级缓存 在Hibernate中,二级缓存涵盖了应用级缓存和分 布式缓存 . 二级缓存将由从属于本SessionFactory的所有 Session的实例共享,因此有时称SessionFactory Level Cache。 Hibernate在进行数据查询操作时,会首先在自身 内部的一级缓存中进行查找,如果在一级缓存中未 能命中,则将在二级缓存中进行查询,如果二级缓 存命中则以此数据作为结果返回。
Hibernate数据缓存-二级缓存 在应用二级缓存时,我们首先必须考虑以下问题: 1、数据库是否与其他应用共享 2、应用是否部署在集群环境下 对于第一种情况,往往也就意味着要放弃二级缓存 (如 表和其他应用共享) 对于第二种情况,就要考虑是否需要引入分布式缓 存机制,以及引入分布式缓存带来的性能变化 其次,哪些数据需要二级缓存?
Hibernate数据缓存-二级缓存 其次,哪些数据需要二级缓存? 显然对所有数据都进行二级缓存是最简单的方法, 但是如果数据量非常大的情况下反而会对性能产生 极大的影响。 因此,在考虑缓存机制应用策略的时候,我们你必 须对数据逻辑进行考察,如果满足下列条件,则可 将其纳入缓存管理
Hibernate数据缓存-二级缓存 1、数据不会被第三方应用修改 2、数据大小是否在可接受范围内 3、数据更新频率较低 4、同一数据可能会被系统频繁引用 在Hibernate中启用二级缓存,需要在hibernate. Cfg.xml中配置下,如: <property name="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider </property> 另外需要针对Cache实现本身进行配置,下面是一 个EhCache配置文件的例子
Hibernate数据缓存-二级缓存 <ehcache> <diskStore path="java.io.tmpdir"/> <defaultCache maxElementsInMemory="1000"//缓存中保存的数据对象数量 eternal="false"//缓存数据是否为常量 timeToIdleSeconds="1200"//缓存数据钝化时间 timeToLiveSeconds="1200"//缓存数据生存时间 overflowToDisk=“false” />//内存不足时是否启用磁盘缓存 </ehcache>
Hibernate数据缓存-二级缓存 之后需要我们的映射文件中指定各个映射实体,的 缓存同步策略: <class name=” com.ormdemo.domain.TUser”> <cache usage=”read-wirte”/> …. <set name=”address”> <cache usage=”read-write”> </set> </class>
Hibernate数据缓存-缓存同步策略 缓存同步策略可应用于实体类和集合属性 下面我们将围绕缓存同步策略进行讨论: 缓存同步策略决定了数据对象在缓存中的存取规 则。 为了使缓存调度遵循正确的应用级事务隔离机制我 们必须为每个实体类指定相应的缓存同步策略。 Hibernate提供以下4种缓存同步策略:
Hibernate数据缓存-缓存同步策略 1、read-only 只读。对于不会发生变化的数据,可 使用只读型缓存 2、nonstrict-read-wirte如果程序对并发访问下的 数据同步要求不是很严格,且数据更新操作频率较 低(几个小时或者更长时间更新一次),可以采用这 种方式。 3、read-write 严格的可读写缓存。基于时间戳判 定机制,实现了read-committed事务隔离级。可用 于对数据同步要求严格的情况,但不支持分布式缓 存,这也是应用中最常用到的同步策略。
Hibernate数据缓存-缓存同步策略 4、transactional 事务型缓存,必须运行在JTA事 务环境中。在事务缓存中,缓存的相关操作也被添 加到事务之中(此时的缓存类似于内存数据库),如 果由于某种原因导致事务失败,我们可以连同缓存 池中的数据一同回滚到事务开始之前的状态,事务 缓存实现了 Repeatable read 事务隔离级别,有效 保证了数据的合法性,适用于关键数据的缓存。 注:只有JbossCache支持事务性的Cache实现
目录 • POJO与PO • 实体对象的管理 • Hibernate数据缓存 • 持久层的操作
持久层的操作 下面我们就围绕Hibernate中常用的持久层实现原 理进行探讨。 首先是数据加载: Session.get/load Session.get/load均可以根据指定的实体类和id从 数据读取记录,并返回与之对应的实体对象.
持久层的操作-数据加载 那么get()和load方法有什么区别呢? hibernate中get方法和load方法的根本区别在于: 如果你使用load方法,hibernate认为该id对应的对 象(数据库记录)在数据库中是一定存在的,所以 它可以放心的使用,它可以放心的使用代理来延迟 加载该对象。在用到对象中的其他属性数据时 才 到二级缓存中查找,如果命中则返回,否则到数据 库中进行查找,一旦数据库不存在该记录就会报 org.hibernate.ObjectNotFoundException
持久层的操作-数据加载 我们看一个例子: TUser u1=impl.loadUser(new Integer(2)); System.out.println("----------------output1-----------------"); System.out.println("name="+u1.getName()); 这个是通过 session.load()的方法加载数据。下面是load 方法的执行过程。 (DefaultLoadEventListener.java:153) - loading entity: [com.ormdemo.domain.TUser#2] DEBUG [main] (DefaultLoadEventListener.java:230) - creating new proxy for entity …………..
持久层的操作-数据加载 首先是在session内部缓存中查找是否存在符合条件的实 题对象或代理,如果发现则返回,没有的话则创建实体代 理返回。 当我们调用System.out.println("name="+u1.getName()); 的时候,首先初始化代理程序(此时实体代理必须由sessio n管理,否则会报org.hibernate.LazyInitializationExceptio N错误)然后会在内部缓存和二级缓存中查找如果未能命中 则到数据库中查找。
持久层的操作-数据加载 而对于get()方法,hibernate会确认一下该id对应的数 据是否存在,首先在session缓存中查找,然后在二级缓 存中查找,还没有就查数据库,数据库中没有就返null。 虽然好多书中都这么说:“get()永远只返回实体类”,但实 际上这是不正确的,get方法如果在session缓存中找到了 该id对应的对象,如果刚好该对象前面是被代理过的,如 被load方法使用过,或者被其他关联对象延迟加载过,那 么返回的还是原先的代理对象,而不是实体类对象,如 果该代理对象还没有加载实体数据(就是id以外的 其他属性数据)
持久层的操作-数据加载 那么它会查询二级缓存或者数据库来加载数据,但是返回 的还是代理对象,只不过已经加载了实体数据 (lazy lo ading除外, hibernate3已经默认为lazy loading ).
持久层的操作-数据加载 Session.createQuery.list()和iterate() • List() 是执行Select SQL从数据库中获取所有符合的记录并构造实体对象,然后再实体对象纳入到缓存。 • Iterare() 首先执行一条Select SQL以获取符合查询条件的数据ID,随即iterate方法首先会在本地缓存中根据ID查找对应的实体对象是否存在,如果缓存中存在数据,则直接以此数据对象作为查询结果,如果没找到则在执行Select SQL语句获取对应的库表记录(也就是典型的N+1查询),并购在对象纳入缓存 • 关于实体加载请参考 get()/load()
持久层的操作-数据保存 数据保存 : Session.save()/pesist() Session.save(); Session.save()方法用于实体对象到数据库的持久化操。 也就是说session.save方法调用与实体对象所匹配的 Insert SQL,将数据插入库表。 例: TUser user=new TUser(); user.setName(“aa1”); Transaction tx=session.beginTransaction(); Session.save(user); Tx..commit();