Update , SavaOrUpdate ,unsaved-null

来源:互联网 发布:如何删除kingroot软件 编辑:程序博客网 时间:2024/06/07 03:32

 

先来点概念:

在Hibernate中,最核心的概念就是对PO的状态管理。一个PO有三种状态:

1、未被持久化的VO
此时就是一个内存对象VO,由JVM管理生命周期

2、已被持久化的PO,并且在Session生命周期内
此时映射数据库数据,由数据库管理生命周期

3、曾被持久化过,但现在和Session已经detached了,以VO的身份在运行
这种和Session已经detached的PO还能够进入另一个Session,继续进行PO状态管理,此时它就成为PO的第二种状态了。这种PO实际上是跨了Session进行了状态维护的。

在传统的JDO1.x中,PO只有前面两种状态,一个PO一旦脱离PM,就丧失了状态了,不再和数据库数据关联,成为一个纯粹的内存VO,它即使进入一个新的PM,也不能恢复它的状态了。

Hibernate强的地方就在于,一个PO脱离Session之后,还能保持状态,再进入一个新的Session之后,就恢复状态管理的能力,但此时状态管理需要使用session.update或者session.saveOrUpdate,这就是Hibernate Reference中提到的“requires a slightly different programming model ”

现在正式进入本话题:

简单的来说,update和saveOrUpdate是用来对跨Session的PO进行状态管理的。

假设你的PO不需要跨Session的话,那么就不需要用到,例如你打开一个Session,对PO进行操作,然后关闭,之后这个PO你也不会再用到了,那么就不需要用update。

因此,我们来看看上例:
java代码: 

 1 Foo foo=sess.load(Foo.class,id);
 2 foo.setXXX(xxx);
 3 sess.flush();
 4 sess.commit();



PO对象foo的操作都在一个Session生命周期内完成,因此不需要显式的进行sess.update(foo)这样的操作。Hibernate会自动监测到foo对象已经被修改过,因此就向数据库发送一个update的sql。当然如果你非要加上sess.update(foo)也不会错,只不过这样做没有任何必要。

而跨Session的意思就是说这个PO对象在Session关闭之后,你还把它当做一个VO来用,后来你在Session外面又修改了它的属性,然后你又想打开一个Session,把VO的属性修改保存到数据库里面,那么你就需要用update了。

java代码: 

  // in the first session
  Cat cat = (Cat) firstSession.load(Cat.class, catId);
  Cat potentialMate = new Cat();
  firstSession.save(potentialMate);
  
  // in a higher tier of the application
  cat.setMate(potentialMate);
  
  // later, in a new session
 10 secondSession.update(cat)// update cat
 11 secondSession.update(mate); // update mate
 12 



cat和mate对象是在第一个session中取得的,在第一个session关闭之后,他们就成了PO的第三种状态,和Session已经detached的PO,此时他们的状态信息仍然被保留下来了。当他们进入第二个session之后,立刻就可以进行状态的更新。但是由于对cat的修改操作:cat.setMate(potentialMate); 是在Session外面进行的,Hibernate不可能知道cat对象已经被改过了,第二个Session并不知道这种修改,因此一定要显式的调用secondSession.update(cat); 通知Hibernate,cat对象已经修改了,你必须发送update的sql了。

所以update的作用就在于此,它只会被用于当一个PO对象跨Session进行状态同步的时候才需要写。而一个PO对象当它不需要跨Session进行状态管理的时候,是不需要写update的。

再谈谈saveOrUpdate的用场:

saveOrUpdate和update的区别就在于在跨Session的PO状态管理中,Hibernate对PO采取何种策略。

例如当你写一个DAOImpl的时候,让cat对象增加一个mate,如下定义:
java代码: 

1 public void addMate(Cat cat, Mate mate) {
 2     Session session = ...;
 3     Transacton tx = ...;
 4     session.update(cat);
 5     cat.addMate(mate);
 6     tx.commit();
 7     session.close();
 8 };



显然你是需要把Hibernate的操作封装在DAO里面的,让业务层的程序员和Web层的程序员不需要了解Hibernate,直接对DAO进行调用。

此时问题就来了:上面的代码运行正确有一个必要的前提,那就是方法调用参数cat对象必须是一个已经被持久化过的PO,也就是来说,它应该首先从数据库查询出来,然后才能这样用。但是业务层的程序员显然不知道这种内部的玄妙,如果他的业务是现在增加一个cat,然后再增加它的mate,他显然会这样调用,new一个cat对象出来,然后就addMate:

java代码: 

 1 Cat cat = new Cat();
 2 cat.setXXX();
 3 daoimpl.addMate(cat,mate);



但是请注意看,这个cat对象只是一个VO,它没有被持久化过,它还不是PO,它没有资格调用addMate方法,因此调用addMate方法不会真正往数据库里面发送update的sql,这个cat对象必须先被save到数据库,在真正成为一个PO之后,才具备addMate的资格。

你必须这样来操作:

java代码: 

 1 Cat cat = new Cat();
 2 cat.setXXX();
 3 daoimpl.addCat(cat);
 4 daoimpl.addMate(cat, mate);



先持久化cat,然后才能对cat进行其他的持久化操作。因此要求业务层的程序员必须清楚cat对象处于何种状态,到底是第一种,还是第三种。如果是第一种,就要先save,再addMate;如果是第三种,就直接addMate。

但是最致命的是,如果整个软件分层很多,业务层的程序员他拿到这个cat对象也可能是上层Web应用层传递过来的cat,他自己也不知道这个cat究竟是VO,没有被持久化过,还是已经被持久化过,那么他根本就没有办法写程序了。

所以这样的DAOImpl显然是有问题的,它会对业务层的程序员造成很多编程上的陷阱,业务层的程序员必须深刻的了解他调用的每个DAO对PO对象进行了何种状态管理,必须深刻的了解他的PO对象在任何时候处于什么确切的状态,才能保证编程的正确性,显然这是做不到的,但是有了saveOrUpdate,这些问题就迎刃而解了。

现在你需要修改addMate方法:

java代码: 

1 public void addMate(Cat cat, Mate mate) {
 2     Session session = ...;
 3     Transacton tx = ...;
 4     session.saveOrUpdate(cat);
 5     cat.addMate(mate);
 6     tx.commit();
 7     session.close();
 8 };



如上,如果业务层的程序员传进来的是一个已经持久化过的PO对象,那么Hibernate会更新cat对象(假设业务层的程序员在Session外面修改过cat的属性),如果传进来的是一个新new出来的对象,那么向数据库save这个PO对象。

BTW: Hibernate此时究竟采取更新cat对象,还是save cat对象,取决于unsave-value的设定。

这样,业务层的程序员就不必再操心PO的状态问题了,对于他们来说,不管cat是new出来的对象,只是一个VO也好;还是从数据库查询出来的的PO对象也好,全部都是直接addMate就OK了:
 
 
----------------------------------------------------------------------------------------
 

关于unsaved-value

当你显式的使用session.save()或者session.update()操作一个对象的时候,实际上是用不到unsaved-value的。某些情况下(父子表关联保存),当你在程序中并没有显式的使用save或者update一个持久对象,那么Hibernate需要判断被操作的对象究竟是一个已经持久化过的持久对象,是一个尚未被持久化过的内存临时对象。例如:

代码:
Session session = ...;
Transaction tx = ...;

Parent parent = (Parent) session.load(Parent.class, id);

Child child = new Child();
child.setParent(parent);
child.setName("sun");

parent.addChild(child);
s.update(parent);

s.flush();
tx.commit();
s.close();


在上例中,程序并没有显式的session.save(child); 那么Hibernate需要知道child究竟是一个临时对象,还是已经在数据库中有的持久对象。如果child是一个新创建的临时对象(本例中就是这种情况),那么Hibernate应该自动产生session.save(child)这样的操作,如果child是已经在数据库中有的持久对象,那么Hibernate应该自动产生session.update(child)这样的操作。

因此我们需要暗示一下Hibernate,究竟child对象应该对它自动save还是update。在上例中,显然我们应该暗示Hibernate对child自动save,而不是自动update。那么Hibernate如何判断究竟对child是save还是update呢?它会取一下child的主键属性 child.getId() ,这里假设id是 java.lang.Integer类型的。如果取到的Id值和hbm映射文件中指定的unsave-value相等,那么Hibernate认为child是新的内存临时对象,发送save,如果不相等,那么Hibernate认为child是已经持久过的对象,发送update。

unsaved-value="null" (默认情况,适用于大多数对象类型主键 Integer/Long/String/...)

当Hibernate取一下child的Id,取出来的是null(在上例中肯定取出来的是null),和unsaved-value设定值相等,发送save(child)

当Hibernate取一下child的id,取出来的不是null,那么和unsaved-value设定值不相等,发送update(child)

例如下面的情况:

代码:
Session session = ...;
Transaction tx = ...;

Parent parent = (Parent) session.load(Parent.class, id);
Child child = (Child) session.load(Child.class, childId);

child.setParent(parent);
child.setName("sun");

parent.addChild(child);
s.update(parent);

s.flush();
tx.commit();
s.close();


child已经在数据库中有了,是一个持久化的对象,不是新创建的,因此我们希望Hibernate发送update(child),在该例中,Hibernate取一下child.getId(),和unsave-value指定的null比对一下,发现不相等,那么发送update(child)。

BTW: parent对象不需要操心,因为程序显式的对parent有load操作和update的操作,不需要Hibernate自己来判断究竟是save还是update了。我们要注意的只是child对象的操作。另外unsaved-value是定义在Child类的主键属性中的。

代码:
<class name="Child" table="child">
<id column="id" name="id" type="integer" unsaved-value="null">
  <generator class="identity"/>
</id>
...
</class>


如果主键属性不是对象型,而是基本类型,如int/long/double/...,那么你需要指定一个数值型的unsaved-value,例如:

代码:
unsaved-null="0"



在此提醒大家,很多人以为对主键属性定义为int/long,比定义为Integer/Long运行效率来得高,认为基本类型不需要进行对象的封装和解构操作,因此喜欢把主键定义为int/long的。但实际上,Hibernate内部总是把主键转换为对象型进行操作的,就算你定义为int/long型的,Hibernate内部也要进行一次对象构造操作,返回给你的时候,还要进行解构操作,效率可能反而低也说不定。因此大家一定要扭转一个观点,在Hibernate中,主键属性定义为基本类型,并不能够比定义为对象型效率来的高,而且也多了很多麻烦,因此建议大家使用对象型的Integer/Long定义主键。

unsaved-value="none"和
unsaved-value="any"

主主要用在主键属性不是通过Hibernate生成,而是程序自己setId()的时候。

在这里多说一句,强烈建议使用Hibernate的id generator,或者你可以自己扩展Hibernate的id generator,特别注意不要使用有实际含义的字段当做主键来用!例如用户类User,很多人喜欢用用户登陆名称做为主键,这是一个很不好的习惯,当用户类和其他实体类有关联关系的时候,万一你需要修改用户登陆名称,一改就需要改好几张表中的数据。偶合性太高,而如果你使用无业务意义的id generator,那么修改用户名称,就只修改user表就行了。

由这个问题引申出来,如果你严格按照这个原则来设计数据库,那么你基本上是用不到手工来setId()的,你用Hibernate的id generator就OK了。因此你也不需要了解当

unsaved-value="none"和
unsaved-value="any"

究竟有什么含义了。如果你非要用assigned不可,那么继续解释一下:

unsaved-value="none" 的时候,由于不论主键属性为任何值,都不可能为none,因此Hibernate总是对child对象发送update(child)

unsaved-value="any" 的时候,由于不论主键属性为任何值,都肯定为any,因此Hibernate总是对child对象发送save(child)

大多数情况下,你可以避免使用assigned,只有当你使用复合主键的时候不得不手工setId(),这时候需要你自己考虑究竟怎么设置unsaved-value了,根据你自己的需要来定。

BTW: Gavin King强烈不建议使用composite-id,强烈建议使用UserType。

因此,如果你在系统设计的时候,遵循如下原则:

1、使用Hibernate的id generator来生成无业务意义的主键,不使用有业务含义的字段做主键,不使用assigned。

2、使用对象类型(String/Integer/Long/...)来做主键,而不使用基础类型(int/long/...)做主键

3、不使用composite-id来处理复合主键的情况,而使用UserType来处理该种情况。


那么你永远用的是unsaved-value="null" ,不可能用到any/none/..了

 

------------------------------------------------------------------------------

 

非显示数据保存时,Hibernate将根据unsaved-value这个值来判断对象是否需要保存。
所谓显式保存,是指代码中明确调用session 的save、update、saveOrupdate方
法对对象进行持久化。如:

Java代码
  1. session.save(user);  


而在某些情况下,如映射关系中,Hibernate 根据级联(Cascade)关系对联接类进行保存。此时代码中没有针对级联对象的显示保存语句,需要Hibernate 根据对象当前状
态判断是否需要保存到数据库。此时,Hibernate即将根据unsaved-value进行判定。
首先Hibernate会取出目标对象的id。
之后,将此值与unsaved-value进行比对,如果相等,则认为目标对象尚未保存,否
则,认为对象已经保存,无需再进行保存操作。
如:user对象是之前由hibernate从数据库中获取,同时,此user对象的若干个关
联对象address 也被加载,此时我们向user 对象新增一个address 对象,此时调用
session.save(user),hibernate会根据unsaved-value判断user对象的数个address
关联对象中,哪些需要执行save操作,而哪些不需要。
对于我们新加入的address 对象而言,由于其id(Integer 型)尚未赋值,因此为
null,与我们设定的unsaved-value(null)相同,因此hibernate将其视为一个未保存
对象,将为其生成insert语句并执行。
这里可能会产生一个疑问,如果“原有”关联对象发生变动(如user的某个“原有”
的address对象的属性发生了变化,所谓“原有”即此address对象已经与user相关联,
而不是我们在此过程中为之新增的),此时id值是从数据库中读出,并没有发生改变,自然
unsaved-value(null)也不一样,那么Hibernate是不是就不保存了?
上面关于PO、VO 的讨论中曾经涉及到数据保存的问题,实际上,这里的“保存”,
实际上是“insert”的概念,只是针对新关联对象的加入,而非数据库中原有关联对象的
“update”。所谓新关联对象,一般情况下可以理解为未与Session 发生关联的VO。而
“原有”关联对象,则是PO。如上面关于PO、VO的讨论中所述:
对于save操作而言,如果对象已经与Session相关联(即已经被加入Session的实体容器中),则无需进行具体的操作。因为之后的Session.flush过程中,Hibernate
会对此实体容器中的对象进行遍历,查找出发生变化的实体,生成并执行相应的update
语句。

 

---------------------------------------------------------

 

1.save()导致的最终结果是执行insert语句,先看下面的代码:
 
  1. Session session2=HibernateUtil.getSession();  Customer c2=(Customer)session2.load(Customer.classnew Long(5));  
  2. HibernateUtil.closeSession(session2);  
  3. c2.setName("ghsea5");  
  4. session.save(c2);  
  5. tx.commit();  
       这里查询出的是ID为5的一个对象,执行session.save(c2)后数据库里会插入一条ID为6的数据,如果只是想修改这个ID为5的对象的name属性,而不是新插入一条数据,可以执行session.flush(),使session与数据库同步。不要错误地用save()去同步session缓存和数据库。它只是用来保存一个记录的,是用来“Persist the given transient instance, first assigning a generated identifier”。

update和saveOrUpdate是用来对跨Session(即脱 管的)的PO进行状态管理的。
2.“update的作用就在于此,它只会被用于当一个PO对象跨Session进行状态同步的时候才需要写。而一个PO对象当它不需要跨Session进行状态管理的时候,是不需要写update的。”
  1. tx=session.beginTransaction();  
  2. Session session2=HibernateUtil.getSession();  
  3. Customer c2=(Customer)session2.load(Customer.classnew Long(5));  
  4. HibernateUtil.closeSession(session2);  
  5. c2.setName("ghsea5");  
  6. session.update(c2);  
  7. tx.commit();  
      执行第4行后,
c2变成脱管对象,第6行,用session对以c2进行更新,这里c2是跨Session的。
原创粉丝点击