2007-07-23
Hibernate持久化实现
一、实体对象的生命周期
1,实体对象的三种状态(生命周期中的三种状态) (1),Transient:在内存中的自由存在,与数据库中的记录无关。 public void methodA{ TUser user = new TUser(); user.setName("Emma"); ---user与数据库中的记录没有关联。 } (2),Persistent:处于由Hibernate框架所管理的状态。实体对象的引用被纳入Hibernate实体容器中加以管理。 TUser user = new TUser(); TUser anotherUser = new TUser(); user.setName("Emma"); anotherUser.setName("Kevin"); //此时user和anotherUser都处于Transient状态 Transaction tx = session.beginTransaction(); session.save(user); //此时的user对象已经由Hibernate纳入实体管理容器,处于Persistent状态 //而anotherUser仍然处于Transient状态。 tx.commit(); //事务提交之后,库表中已经插入一条用户"Emma"的记录 //对于anotherUser则无任何操作 Transaction tx2 = session.beginTransaction(); user.setName("Emma_1"); //Persistent anotherUser.setName("Kevin_1"); //Transient tx2.commit(); //虽然这个事务中我们没有显式调用Session.save()方法保存user对象,但是由于处于 //Persistent状态的对象将自动被固化到数据库中,因此user对象的变化也将被同步到 //数据库中,也就是说数据库中"Emma"的用户记录已经被更新为"Emma_1" //此时anotherUser仍然是个普通Java对象,处于Transient状态,它不受Hibernate //框架管理,因此其属性的更改也不会对数据库产生任何影响. Transient------Session.save()------>Persistent Object---------Session.load()------>Persistent (Object------Session(有效期内)-----Persistent) //由Hibernate返回的Persistent对象 TUser user = (TUser)session.load(TUser.class,new Integer(1)); //Session.load方法中,在返回对象之前,Hibernate就已经将其对象纳入 //其实体容器中 Persistent对象<------一一对应------>数据库中的一条记录 (3),Detached: Oberct(Persistent)-----对应的Session实例关闭----->Object(Detached) TUser user = new TUser(); user.setName("Emma");//user in Transistent Transaction tx = session.beginTransaction(); session.save(user);//user in Persistent tx.commit(); session.close();//user in Detached Detached 与 Transient的区别: Detached对象可以再次与某个Session实例相关联而成为Persistent对象(Transient也可以,但其内部不一 样)---Transient状态的user与库表中的数据缺乏对应关系,而Deatached状态的user却在库表中存在相应的记录(由主键唯一确 定)。 人工制造一个Detached对象: TUser user = new TUser(); user.setName("Emma"); //硬编码为其指定主键值(假设库表中存在id=1的记录) user.setId(new Integer(1)); //此时user成为一个"人造detached对象" Transaction tx = session.beginTransaction(); session.update(user);//Session根据其主键值,将其变为Persistent user.setAge(new Integer(20)); tx.commit(); session.flush(); session.close(); Object(Persistent)---Session.delete()--->Object(Transient) Transient:从库表中找不到对应的记录。 Detached:从库表中能找到对应的记录(只是没有与session进行关联)。 一般而言,应该避免直接将PO传递到系统中的其他层面,一种解决办法是,通过构造一个新的VO,通过属性父指示器具备与PO相同的属性值,并以其 为传输媒质(实际上,这个VO被用作Data Transfer Object,即DTO),将此VO传递给其他层面以实现必须的数据传送。 属性复制:AJC Beanutils组件.例: TUser user = new TUser(); TUser anotherUser = new TUser(); user.setName("Emma"); user.setAge(new Integer(1)); try{ BeanUtils.copyProperties(anotherUser,user); }catch(){ } 2,实体对象识别 (1),实体身份识别(Data Identity) 如何判定两个实体对象是否相等? 站在数据库的角度,我们认为在一个库表结构中,主键可以唯一确定一条记录,那么对于拥有同样主键值的实体对象,则认为他们等同。 在持久层之外,对象是否相等也遵循着特定领域中的逻辑规则。---这样的逻辑规则如何体现在我们的实体对象之间?--------覆盖Object.equals() public boolean equals(Object object){ TUser user = (TUser)object; return this.getId().equals(user.getId()); } public int hashCode(){ return this.getId().intValue(); } 问题: TUser user = (TUser)session.load(TUser.class,new Integer); TAddress addr1 = new TAddress(); addr1.setAddress("Shanghai"); TAddress addr2 = new TAddress(); addr2.setAddress("Guangdong"); user.getAddresses().add(addr1);//addr1.id=null; user.getAddresses().add(addr2);//addr2.id=null; System.out.println("Items in set : " + user.getAddresses().size()); --主键值生成机制,id只有在Session.save()方法执行之后才会被设置。--解决方法: 1,不覆盖equals/hashCode方法的情况下将面临:实体对象的跨Session识别。包含了两个针对同一库表记录的实体,当 Session.save时,将得到一个NonUniqueObjectException异常。---只是用一个session实例可避免。 2,实现值比对。 Elipse中免费插件:a) Commonclipse b) Commons4E. 注意:只需针对实体类的属性进行处理,而不要设计实体类所关联的集合类的比对,否则在多对多关系中很容易引发一些其它的问题。 3,业务关键信息判定---值比对的一个子集。 (2),脏数据检查 ---并非废弃或者无用的数据,而是指一个数据对象所携带的信息发生了改变之后的状态。 Transaction tx = session.beginTransaction(); TUser user = (TUser)session.load(TUser.class,new Integer(1)); //此时user对象处于由数据库读出的原始状态 user.setAge(30);//此时user对象所携带的信息发生了变化,成为所谓的“脏数据” tx.commit(); 事务提交时,Hibernate会对session中的PO进行检测,判断哪些发生了变化,并将方生变化的数据更新到数据库中。 Hibernate如何进行脏数据识别? (1),数据对象监控---通过拦截器对数据对象的设值方法(setter)进行拦截,一旦setter方法被调用,则将其标志为“待更新”状态。 (2),数据版本对比---在持久层框架中维持数据对象的最近读取版本,将提交数据与此进行对比。-------Hibernate采用这种策略。 tx.commit(); --public void commit() throws HibernateException{ ...... if(session.getFlushMode()!=FlushMode.NEVER){ session.flush(); } ...... } ----public void flush() throws HibernateException{ ...... flushEverything();//刷新所有数据,首先完成一些预处理工作,之后即调用flushEntities方法对当前Session中的实体对象进行刷新。--判定脏数据 execute();//执行数据库SQL完成持久化动作 } (3),unsaved-value ---数据(VO)保存(Insert)时(显式保存 or 根据级联关系对联接类进行保存),Hibernate将根据这个值来判断对象是否需要保存。 3,数据缓存 ---持久层性能提升的关键。 缓存:是数据库数据在内存中的临时容器,它包含了库表数据在内存中的临时拷贝,位于数据库与数据访问层之间。 ORM数据读取:首选缓存,查到则返回---避免了数据库调用的性能开销。 对于企业级应用,数据库与应用服务器位于不同的物理服务器,也就是每次数据库访问都是一次远程调用--Socket的创建于销毁,数据的打拆包,数据库执行查询指令,网络传输的延时等。---本地内存中数据缓存的价值。 (1)数据缓存策略 ORM的数据缓存应包含:1,事务级缓存(事务范围内):基于Session生命周期。 2,应用级/进程级缓存(在SessionFactory层实现),所有由此SessionFactory创建的Session实例共享此缓存。---多 实例并发运行会产生问题:A,B共享同一数据库,各自维持其缓存,A对数据库进行了更新,B缓存数据仍为更新前。--> 3,分布式缓存(在所个应用实例,多个JVM之间共享的缓存模式),由多个应用级缓存实例组成集群。---解决了多实例并发运行过程中的数据同步问题。 注意:如果当前应用于其它应用共享数据库,采取一些保守策略(避免缓存机制的使用)可能更加稳妥。 (2),Hibernate数据缓存 (1),内部缓存(Session Level) (2),二级缓存(SessionFactory Level) Hibernate缓存发挥作用的情况: (1),通过id(主键)加载数据时 ---Session.load(),Session.iterate() (2),延迟加载 内部缓存:应用事务级缓存,由Hibernate自动维护,可通过以下方法手动干预。 Session.evict---将某个特定对象从内存缓存中清除。 Session.clear---清空内部缓存。 二级缓存:涵盖了应用级缓存和分布式缓存。 Session在进行数据查询操作时,会首先在自身内部的一级缓存中进行查找,如果一级缓存未能命中,则在二级缓存中查询,如果二级缓存命中,则以此数据作为结果返回。 引入二级缓存需要考虑的问题: (1),数据库是否与其它应用共享 (2),应用是否需要部署在集群环境中 满足以下条件,则可将其纳入缓存管理 (1),数据不会被第三方应用修改 (2),数据大小载客接受的范围之内 (3),数据更新频率较低 (4),同一数据可能会被系统频繁使用 (5),非关键数据(关键数据,如金融账户数据) (3),第三方缓存实现 (1),JCS--某些情况下可能导致内存泄漏以及死锁。 (2),EHCache--默认---无法做到分布式缓存。 (3),OSCache (4),JBoss Cache--提供分布式缓存(Repplication方式) (5),SwarmCache--提供分布式缓存(invalidation方式) Hibernate中启用二级缓存,需配置hibernate.cfg.xml如下: <hibernate-configutarion> <session-factory> ...... <property name="hibernate.cache.provider_class"> net.sf.ehcache.hibernate.Provider <property> ...... </session-factory> <hibernate-configuration> 还需要配置ehcache.xml。 之后,需要在我们的映射文件中指定各个映射实体(以及collection)的缓存同步策略: <class name="TUser"> <cache usage="read-write"/> ... <set name="addresses" ...> <cache usage="read-write"/> ... </set> </class> (4),缓存同步策略 --为了使得缓存调度遵循正确的应用级事务隔离机制,必须为每个实体类指定相应的缓存同步策略。 4种内置的缓存同步策略:read-only,nonstrict-read-write,read-write,transactional(JTA,此时的缓存类似一个内存数据库) 5,事务管理(ACID) (1),数据库事务管理隔离等级 事务隔离:通过某种机制,在并行的多个事务之间进行分隔,使每个事务在其执行过程中保持独立(如同当前只有此事务单独运行)。 Hibernate中的事务隔离依赖于底层数据库提供的事务隔离机制。 数据操作过程中可能出现的3种不确定情况: 脏读取:一个事务读取了另一个并行事务未提交的数据。 不可重复读取:一个事务再次读取之前曾读取过的数据时,发现该数据已经被另一个已提交的事务修改。 虚读:一个事务重新执行一个查询,返回一套符合查询条件的纪录,但这些记录中包含了因为其它最近提交的事务而产生的新纪录。 4个事务隔离等级: Read Uncommitted,Read Committed,Repeatable Read,Serializable (2),Hibernate事务管理 ---Hibernate是JDBC的轻量级封装,本身并不具备事务管理能力。在事务管理层,Hibernate将其委托给底层的JDBC或JTA,以实现事务的管理和调度。 (3),基于JDBC的事务管理---如同JDBC (4),基于JTA的事务管理---提供了跨Session的事务管理能力。JTA事务管理由JTA容器实现,JTA容器对当前加入事务的众多 Connection进行调度,实现其事务性要求。参与JTA事务的Connection需避免对事务管理进行干涉。也就是说如果采用JTA Transaction,就不应该再调用Hibernate的Transaction功能。 6,锁 ---给我们选定的目标数据上锁,使其无法被其它程序修改。 (1),悲观锁---依靠数据库层提供的锁机制。 String hqlStr = "from TUser as user where user.name=‘Erica‘"; Query query = session.createQuery(hqlStr); query.setLockMode("user",LockMode.UPGRADE);//加锁(for update) List userList = query.list();//执行查询,获取数据 query.setLockMode对查询语句中,特定别名(user)所对应的记录进行加锁。 Hibernate的加锁模式(Hibernate内部使用)有: LockMode.NONE:无锁机制 LockMode.WRITE:Hibernate在Insert和Update记录的时候会自动获取。 LockMode.READ:Hibernate在读取记录的时候会自动获取。 依赖数据库的悲观锁机制(应用层): LockMode.UPGRADE:利用数据库的for update子句加锁。 LockMode.UPGRADE_NOWAIT:Oracle的特定实现,利用Oracle的for update nowait子句实现加锁。 注意:应该查询开始之前设定加锁。 (2),乐观锁 ---大多是基于数据版本记录机制实现。 数据版本:即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。 乐观锁策略:提交版本必须大于记录当前版本才能执行更新。 添加一个Version属性描述符 <hibernate-mapping> <class name="TUser" table="T_USER" dynamic-update="true" dynamic-insert="true" optimistic-lock="version"> <id></id> <version column="version" name="version" type="java.lang.Integer"/> </class> </hibernate-mapping> 注意:version节点必须出现在ID节点之后。 违反乐观锁策略时:tx.commit()处抛出StaleObjectStateException异常,并指出版本检查失败,当前事务正在试图提交一个过期数据。通过捕捉这个异常,我们就可以在乐观锁校验失败时进行相应处理。 二、持久层操作 |
|
来自: zybingliu > 《Hibernate》