分享

Hibernate持久化实现 - TO BE THE BEST MAN---tenn - JavaEye技术网站

 zybingliu 2008-11-05
一、实体对象的生命周期

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异常,并指出版本检查失败,当前事务正在试图提交一个过期数据。通过捕捉这个异常,我们就可以在乐观锁校验失败时进行相应处理。

二、持久层操作

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多