天天看点

hibernate系列(五)Session接口方法

session接口方法主要有save、persist、load、get、update、saveorupdat、merge、delete等,这里主要是对我看hibernate书籍的一个实践加总结。 

首先是save()方法: 

以之前的customer和order为例,看下类文件: 

<a href="http://my.oschina.net/pingpangkuangmo/blog/376315#">?</a>

1

2

3

4

5

6

7

8

9

<code>public</code> <code>class</code> <code>customer {</code>

<code>    </code><code>private</code> <code>long id;</code>

<code>    </code><code>private</code> <code>string name;</code>

<code>    </code><code>private</code> <code>string email;</code>

<code>    </code><code>private</code> <code>timestamp registeredtime;</code>

<code>    </code><code>private</code> <code>set&lt;order&gt; orders;</code>

<code>//略get、set方法</code>

<code>}</code>

<code>public</code> <code>class</code> <code>order {</code>

<code>    </code><code>private</code> <code>string ordernumber;</code>

<code>    </code><code>private</code> <code>customer customer;</code>

映射文件customer.hbm.xml如下: 

10

11

12

13

14

15

16

17

18

<code>&lt;?</code><code>xml</code> <code>version</code><code>=</code><code>"1.0"</code> <code>encoding</code><code>=</code><code>"utf-8"</code><code>?&gt;</code>

<code>&lt;!</code><code>doctype</code> <code>hibernate-mapping public</code>

<code>          </code><code>"-//hibernate/hibernate mapping dtd 3.0//en"</code>

<code>&lt;</code><code>hibernate-mapping</code><code>&gt;</code>

<code>    </code><code>&lt;</code><code>class</code> <code>name</code><code>=</code><code>"com.ligang.domain.customer"</code> <code>table</code><code>=</code><code>"customer"</code><code>&gt;</code>

<code>        </code><code>&lt;</code><code>id</code> <code>name</code><code>=</code><code>"id"</code> <code>column</code><code>=</code><code>"id"</code> <code>type</code><code>=</code><code>"long"</code><code>&gt;</code>

<code>            </code><code>&lt;</code><code>generator</code> <code>class</code><code>=</code><code>"identity"</code><code>/&gt;</code>

<code>        </code><code>&lt;/</code><code>id</code><code>&gt;</code>

<code>        </code><code>&lt;</code><code>property</code> <code>name</code><code>=</code><code>"name"</code> <code>column</code><code>=</code><code>"name"</code> <code>type</code><code>=</code><code>"string"</code><code>/&gt;</code>

<code>        </code><code>&lt;</code><code>property</code> <code>name</code><code>=</code><code>"email"</code> <code>column</code><code>=</code><code>"email"</code> <code>type</code><code>=</code><code>"string"</code><code>/&gt;</code>

<code>        </code><code>&lt;</code><code>property</code> <code>name</code><code>=</code><code>"registeredtime"</code> <code>column</code><code>=</code><code>"registeredtime"</code> <code>type</code><code>=</code><code>"timestamp"</code><code>/&gt;</code>

<code>        </code><code>&lt;</code><code>set</code> <code>name</code><code>=</code><code>"orders"</code> <code>cascade</code><code>=</code><code>"save-update"</code> <code>inverse</code><code>=</code><code>"true"</code><code>&gt;</code>

<code>            </code><code>&lt;</code><code>key</code> <code>column</code><code>=</code><code>"customer_id"</code><code>/&gt;</code>

<code>            </code><code>&lt;</code><code>one-to-many</code> <code>class</code><code>=</code><code>"com.ligang.domain.order"</code><code>/&gt;</code>

<code>        </code><code>&lt;/</code><code>set</code><code>&gt;</code>

<code>    </code><code>&lt;/</code><code>class</code><code>&gt;</code>

<code>&lt;/</code><code>hibernate-mapping</code><code>&gt;</code>

save方法如下所示: 

<code>@test</code>

<code>    </code><code>public</code> <code>void</code> <code>testsave(){</code>

<code>        </code><code>session session=hibernatedao.getsession();</code>

<code>        </code><code>transaction tx=session.begintransaction();</code>

<code>        </code> 

<code>        </code><code>customer customer=</code><code>new</code> <code>customer();</code>

<code>        </code><code>customer.setname(</code><code>"小明"</code><code>);</code>

<code>        </code><code>customer.setemail(</code><code>"[email protected]"</code><code>);</code>

<code>        </code><code>timestamp t=</code><code>new</code> <code>timestamp(system.currenttimemillis());</code>

<code>        </code><code>customer.setregisteredtime(t);</code>

<code>        </code><code>session.save(customer);</code>

<code>        </code><code>system.out.println(customer.getid());</code>

<code>        </code><code>customer.setemail(</code><code>"[email protected]"</code><code>);</code>

<code>        </code><code>tx.commit();</code>

<code>        </code><code>session.close();</code>

<code>    </code><code>}</code>

我们会看到如下的sql语句: 

<code>hibernate:</code><code>insert</code> <code>into</code> <code>hibernate.customer (</code><code>name</code><code>, email, registeredtime)</code><code>values</code> <code>(?, ?, ?)</code>

<code>73</code>

<code>hibernate:</code><code>update</code> <code>hibernate.customer</code><code>set</code> <code>name</code><code>=?, email=?, registeredtime=?</code><code>where</code> <code>id=?</code>

会有一个insert语句和一个update语句。这里的save方法执行时,并没有真正的去执行一条insert语句,而是仅仅从数据库中获取下一个id,并赋值给customer,获取当时customer信息的一个快照,计划执行一条insert语句,然后在事务提交时才会去真正执行该语句,在真正执行前,如果你向数据库中插入一条记录,该记录则会使用下一个id,即customer虽然未向数据库插入,但是已经占据一个id了。执行完该insert语句后会发现当前的customer和已经持久化的customer是不一致的,然后就需要执行一次update语句。 

再来看下persist方法 

该方法和save()方法的作用是一样的都是将一个临时对象转变为持久化对象。但是它和save的区别下面来介绍: 

19

<code>        </code><code>session.persist(customer);</code>

<code>        </code><code>customer.setname(</code><code>"萨菲您稍等"</code><code>);</code>

上述正常情况下和save是一样的,不同之处先来看下官方文档: 

hibernate系列(五)Session接口方法

这里说明了两点: 

第一:persist并不保证一定会给对象的id赋值,这一赋值可能在flush时才会去执行。而save则不同,persist返回void,而save方法是返回id的,即save方法必须从数据库中取出一个可用的id。 

第二:persist在事务之外是不会计划执行insert的,而save方法则会计划执行insert的,同时会从数据库中取出一个可用id。 

对于第一点,什么情况下persist会为对象的id赋值,我目前还不了解,不再说明。 

对于第二点,可做如下实验: 

此时并没有开启事务,打印的信息如下: 

<code>null</code>

没有insert语句,同时没有去获取id。 

而对于save()方法: 

也没有开启事务,打印信息如下: 

<code>hibernate: insert into hibernate.customer (name, email, registeredtime) values (?, ?, ?)</code>

<code>85</code>

save方法在没有事务的情况下,仍然计划执行一条insert语句,同时从数据库中获取一个可用id,虽然最终没有insert,但是此id已被占用。 

load和get方法 

这个比较好理解,get方法始终返回一个真正对象,而load方法则需要根据lazy属性的true和false采用不同的加载策略,当为lazy=true时采用延迟加载的策略,即返回一个代理对象,内部是由javassist来实现代理的。 

update方法: 

20

21

22

<code>    </code><code>public</code> <code>void</code> <code>testupdate(){</code>

<code>        </code><code>session session1=hibernatedao.getsession();</code>

<code>        </code><code>transaction tx=session1.begintransaction();</code>

<code>        </code><code>session1.save(customer);</code>

<code>        </code><code>session1.close();</code>

<code>        </code><code>session session2=hibernatedao.getsession();</code>

<code>        </code><code>transaction tx2=session2.begintransaction();</code>

<code>        </code><code>customer.setname(</code><code>"小红"</code><code>);</code>

<code>        </code><code>session2.update(customer);</code>

<code>        </code><code>customer.setemail(</code><code>"917312290小红@qq.com"</code><code>);</code>

<code>        </code><code>tx2.commit();</code>

<code>        </code><code>session2.close();</code>

首先使用session1将一个临时对象转化为持久化对象,关闭session1,则该持久化对象变为游离对象(含有主键),通过session2将该游离对象更新为持久化对象。我们可以看到如下sql: 

虽然我们有多次set来更新cunstomer的内容,但是最终只有一次update语句。update方法所做的内容是,计划执行一条update语句,但是此时的更新参数并没有确定,只是在事务提交时才会确认更新的参数。这里和save方法就不太一样,save方法计划执行一条insert语句,同时将此时的数据的参数也确定下来了,一旦后面再次更新参数就要执行update语句来更新(见上述save介绍)。 

对于update还有一个内容就是,当游离对象属性都没发生改变时,调用update语句仍然会执行一条update语句,如下: 

如果想在游离对象没有任何属性更新时就不进行update更新则需要设置select-before-update="true",即在更新前先执行一次查询,通过对比查询出来的数据和现在的数据是否发生变化来决定是否进行update操作。如下设置映射文件: 

<code>&lt;</code><code>class</code> <code>name</code><code>=</code><code>"com.ligang.domain.customer"</code> <code>table</code><code>=</code><code>"customer"</code> <code>lazy</code><code>=</code><code>"true"</code> <code>select-before-update</code><code>=</code><code>"false"</code><code>&gt;</code>

还是上述同样的程序,执行结果如下: 

<code>hibernate:</code><code>select</code> <code>customer_.id, customer_.</code><code>name</code> <code>as</code> <code>name2_0_, customer_.email</code><code>as</code> <code>email3_0_, customer_.registeredtime</code><code>as</code> <code>register4_0_</code><code>from</code> <code>hibernate.customer customer_</code><code>where</code> <code>customer_.id=?</code>

可以看到先执行一条select语句,然后发现数据并没有发生变化,所以就没有执行update语句。如果数据经常发生变化,则不需要设置select-before-update="true",因为会多于执行一条select语句。 

一个session不能拥有两个及以上id相同的对象,一旦拥有一个,然后想保存第二个时就会发生nonuniqueobjectexception异常,如下: 

23

<code>        </code><code>customer customer1=</code><code>new</code> <code>customer();</code>

<code>        </code><code>customer1.setname(</code><code>"小明"</code><code>);</code>

<code>        </code><code>customer1.setemail(</code><code>"[email protected]"</code><code>);</code>

<code>        </code><code>session1.save(customer1);</code>

<code>        </code><code>customer customer2=(customer)session2.get(customer.</code><code>class</code><code>,customer1.getid());</code>

<code>        </code><code>customer1.setname(</code><code>"小红"</code><code>);</code>

<code>        </code><code>session2.update(customer1);</code>

在session1关闭时,customer1对象就变为游离对象,然后session2加载了id为customer1对象id的持久化对象作为customer2,此时customer1和customer2的id是一样的,同时customer2已作为session2的持久化对象,此时更改customer1,然后去保存customer1,此时就会抛出nonuniqueobjectexception异常,如下: 

<code>org.hibernate.nonuniqueobjectexception: a different object with the same identifier value was already associated with the session : [com.ligang.domain.customer#</code><code>98</code><code>]</code>

<code>    </code><code>at org.hibernate.engine.internal.statefulpersistencecontext.checkuniqueness(statefulpersistencecontext.java:</code><code>617</code><code>)</code>

<code>    </code><code>at org.hibernate.event.internal.defaultsaveorupdateeventlistener.performupdate(defaultsaveorupdateeventlistener.java:</code><code>301</code><code>)</code>

<code>    </code><code>at org.hibernate.event.internal.defaultsaveorupdateeventlistener.entityisdetached(defaultsaveorupdateeventlistener.java:</code><code>244</code><code>)</code>

<code>    </code><code>at org.hibernate.event.internal.defaultupdateeventlistener.performsaveorupdate(defaultupdateeventlistener.java:</code><code>55</code><code>)</code>

<code>    </code><code>at org.hibernate.event.internal.defaultsaveorupdateeventlistener.onsaveorupdate(defaultsaveorupdateeventlistener.java:</code><code>90</code><code>)</code>

<code>    </code><code>at org.hibernate.internal.sessionimpl.fireupdate(sessionimpl.java:</code><code>739</code><code>)</code>

<code>    </code><code>at org.hibernate.internal.sessionimpl.update(sessionimpl.java:</code><code>731</code><code>)</code>

<code>    </code><code>at org.hibernate.internal.sessionimpl.update(sessionimpl.java:</code><code>726</code><code>)</code>

<code>    </code><code>at com.ligang.test.dao.customerdaotest.testupdate(customerdaotest.java:</code><code>47</code><code>)</code>

<code>    </code><code>at sun.reflect.nativemethodaccessorimpl.invoke0(native method)</code>

<code>    </code><code>at sun.reflect.nativemethodaccessorimpl.invoke(nativemethodaccessorimpl.java:</code><code>57</code><code>)</code>

<code>    </code><code>at sun.reflect.delegatingmethodaccessorimpl.invoke(delegatingmethodaccessorimpl.java:</code><code>43</code><code>)</code>

saveorupdate方法: 

此方法包含了save和update的功能,当前对象为临时对象时会调用save方法,当前对象是游离对象时调用update方法。它怎么判断当前对象是临时对象和游离对象呢?如果主键id为integer,则可以判断主键id是否为null来判断,id为null则为临时对象,否则为游离对象。如果主键id为int类型,则需要设置unsaved-value来进行区分,如下: 

<code>&lt;id name=</code><code>"id"</code> <code>column=</code><code>"id"</code> <code>type=</code><code>"long"</code> <code>unsaved-value=</code><code>"0"</code><code>&gt;</code>

<code>      </code><code>&lt;generator</code><code>class</code><code>=</code><code>"identity"</code><code>/&gt;</code>

<code>&lt;/id&gt;</code>

当对象的id等于unsaved-value值时就为临时对象,否则为游离对象。 

merge方法: 

对于上述update方法抛出nonuniqueobjectexception异常,如果我们想不抛出异常,并且去更新持久化对象,就要使用merge方法,但是merge方法并不局限于此。merger的处理流程如下: 

对于merge(customer1): 

(1)如果customer1为游离对象,则根据它的id到session缓存中取持久化对象,如果取到则计划执行一条update语句,测试如下: 

<code>    </code><code>public</code> <code>void</code> <code>testmerge1(){</code>

<code>        </code><code>session2.merge(customer1);</code>

打印的sql如下: 

<code>hibernate:</code><code>select</code> <code>customer0_.id</code><code>as</code> <code>id1_0_0_, customer0_.</code><code>name</code> <code>as</code> <code>name2_0_0_, customer0_.email</code><code>as</code> <code>email3_0_0_, customer0_.registeredtime</code><code>as</code> <code>register4_0_0_</code><code>from</code> <code>hibernate.customer customer0_</code><code>where</code> <code>customer0_.id=?</code>

(2)如果customer1为游离对象,则根据它的id到session缓存中取持久化对象,如果未取到则从数据库中查找出,如果查到则同样更新属性,计划执行一条update语句,此种情况比情况1多了一步向数据库中查询的操作,测试如下: 

<code>    </code><code>public</code> <code>void</code> <code>testmerge2(){</code>

情况一的查询是我们主动触发的,主要是将数据加载到session2的缓存中,与本情况的查询是不一样的,本情况的查询是因为在缓存中没有对象的持久化对象才触发向数据库中查找。 

(3)如果customer1为游离对象,则根据它的id到session缓存中取持久化对象,如果未取到则从数据库中查找出,如果从数据库中也未取到,则会新建一个customer对象,并把customer1的属性复制过去,然后保存该新建的对象,返回该对象的引用,测试如下: 

<code>    </code><code>public</code> <code>void</code> <code>testmerge3(){</code>

<code>        </code><code>customer customer1=(customer) session1.get(customer.</code><code>class</code><code>,100l);</code>

<code>        </code><code>session1.delete(customer1);</code>

<code>        </code><code>customer1.setname(</code><code>"小红的妈妈"</code><code>);</code>

<code>        </code><code>customer customer2=(customer) session2.merge(customer1);</code>

<code>        </code><code>system.out.println(customer1==customer2);</code>

<code>hibernate:</code><code>delete</code> <code>from</code> <code>hibernate.customer</code><code>where</code> <code>id=?</code>

<code>false</code>

首先是session1从数据库中加载id为100l的数据,然后删除它,关闭session1,则此时customer1则变为游离对象,更改此游离对象,调用merge方法,session2首先从缓存中找id为100l的对象,没有则从数据库中找id为100l的数据,所以会有一条select语句,然后从数据库中也没找到,则session2创建一个新对象customer2,把customer1的属性值复制给customer2,然后insert customer2,所以会有一条insert语句,由于此时customer2是新建的,并不是直接使用的customer1,所以customer1==customer2是false。 

(4)如果customer1是临时对象,则同情况三后半部分一样,直接新建一个新的customer对象,然后复制插入,返回新对象的引用,测试如下: 

<code>    </code><code>public</code> <code>void</code> <code>testmerge4(){</code>

<code>        </code><code>system.out.println(customer1.getid());</code>

delete方法: 

(1)首先是删除一个持久化对象,测试如下: 

<code>    </code><code>public</code> <code>void</code> <code>testdelete1(){</code>

<code>        </code><code>customer customer1=(customer)session2.get(customer.</code><code>class</code><code>,102l);</code>

<code>        </code><code>session2.delete(customer1);</code>

删除了持久化对象customer1,则从数据库中将其删除,同时将其从session持久化缓存中移除,但是没有删除其id值,真正的删除是发生在session清理缓存的时候,打印的sql如下: 

<code>hibernate: select customer0_.id as id1_0_0_, customer0_.name as name2_0_0_, customer0_.email as email3_0_0_, customer0_.registeredtime as register4_0_0_ from hibernate.customer customer0_ where customer0_.id=?</code>

<code>102</code>

<code>hibernate: delete from hibernate.customer where id=?</code>

若想在删除一个对象时同时删除其id属性的值,则需要设置hibernate的hibernate.cfg.xml配置文件的hibernate.use_identifier_rollback属性,将其设置为true。同样的测试例子打印的sql如下: 

<code>0</code>

这里有个疑问就是,为什么不把id置为null,而是赋值为0(customer的id类型为long类型)。 

(2)删除一个游离对象,测试如下: 

<code>    </code><code>public</code> <code>void</code> <code>testdelete2(){</code>

<code>        </code><code>customer customer1=(customer) session1.get(customer.</code><code>class</code><code>,99l);</code>

<code>99</code>

customer1在session1关闭后成为一个游离对象,具有id值,因此和删除持久化对象的区别并不大,同理设置hibernate.use_identifier_rollback属性将id删除,不再实验。 

上一篇: 优先级队列
下一篇: 链式队列