脏数据检查:
什么是脏数据?脏数据并不是废弃和无用的数据,而是状态前后发生变化的数据。我们看下面的代码:
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>
……