持久化对象的状态
•站在持久化的角度, Hibernate 把对象分为 4 种状态: 持久化状态, 临时状态, 游离状态, 删除状态. Session 的特定方法能使对象从一个状态转换到另一个状态.
•临时对象(Transient):
–在使用代理主键的情况下, OID 通常为 null
–不处于 Session 的缓存中
–在数据库中没有对应的记录
•持久化对象(也叫”托管”)(Persist):
–OID 不为 null
–位于 Session 缓存中
–若在数据库中已经有和其对应的记录, 持久化对象和数据库中的相关记录对应
–Session 在 flush 缓存时, 会根据持久化对象的属性变化, 来同步更新数据库
–在同一个 Session 实例的缓存中, 数据库表中的每条记录只对应唯一的持久化对象
•删除对象(Removed)
–在数据库中没有和其 OID 对应的记录
–不再处于 Session 缓存中
–一般情况下, 应用程序不该再使用被删除的对象
•游离对象(也叫”脱管”) (Detached):
–OID 不为 null
–不再处于 Session 缓存中
–一般情况需下, 游离对象是由持久化对象转变过来的, 因此在数据库中可能还存在与它对应的记录
四种状态的一组无比经典的比喻:
打个比方来说吧,对象好比一个学生。临时对象是一个考研复试刚通过,还为正式成为研究生的准学生,此时的他没有学号(OID为null),因为还未被报道入学所以还不算这个学校的学生因而不归这个学校管(不处于Session缓存中)。
持久化对象就好比一个已经入学报道、开始正常校园生活的学生,这时候领了学生证有了学号(OID不为NULL);在学校里接受学校的管理(位于Session中);此时他的个人档案也已经转到了学校档案室保存(如果数据库有对应的记录,持久化对象和数据库中的记录对应);在校期间,学校会将学生的信息变更、新的奖惩信息等变化都录入到他的档案中(Session 在 flush 缓存时, 会根据持久化对象的属性变化, 来同步更新数据库);并且一个学校只可能为其保存一份档案(–在同一个 Session 实例的缓存中, 数据库表中的每条记录只对应唯一的持久化对象)。
删除对象就好比一个学生被开除,学籍被注销了,当然学号也失效了,档案也被退掉了(–在数据库中没有和其 OID 对应的记录);从此你不在是这个学校的学生,不在收学校管理(不再处于 Session 缓存中);一般情况下,学校原则上也不再与你有任何交集往来(一般情况下, 应用程序不该再使用被删除的对象)。
游离对象就好比一个休学的学生,学校为他保留学籍,学号还在(OID 不为 null);但是他现在处于休学状态,可以离开学校不受学校管理(不再处于 Session 缓存中);休学的学生都是在校生因种种原因休学,他们的档案原本就在学校,休学继续保存(一般情况需下, 游离对象是由持久化对象转变过来的, 因此在数据库中可能还存在与它对应的记录)。
几个对象状态之间的状态切换也可以用这个比方继续演绎。
临时对象 -> 持久化对象:准学生正式录取报道入学。
持久化对象 -> 临时对象:学生休学。
临时对象 -> 持久化对象:学生复课返校。
持久化对象 -> 删除对象:在校生被学校开除。
临时对象 -> 删除对象:休学学生被劝退。
这里需要单独强调的是:持久化对象的两个特性:强调、强调、强调。。。
–持久化对象,Session 在 flush 缓存时, 会根据持久化对象的属性变化, 来同步更新数据库
–在同一个 Session 实例的缓存中, 数据库表中的每条记录只对应唯一的持久化对象
特别是第二条,数据库里的记录对应Session缓存对象时这个持久化对象只能有一个,不然无法建立flush和refresh这样的操作。
我们再次对照这个图来看,Hibernate的提供了可以实现增删改查等各种操作方法,但本质上或者说从对象层面应该是Session为我们提供了各种接口方法来完成对象的状态转移。
一个对象从无到有,到各种状态的转移,到最后的彻底销往,都是被Session的各个调用来控制的。(当然严谨地说,new和垃圾回收不算其调用,同时Query的部分接口方法也能控制对象状态的转移)。我大致归纳一下,但先不做细说:
从无到有分两种情况——新建和查询。
从无到有之查询:查询直接得到持久化对象,控制这种状态转移的方法分为两类:Session的get()和load方法();Query的list()、uniqueResult()、iterator()、scoll()方法。
从无到有之新建:通过new一个普通对象,这个对象在决定要存数据库之前和Hibernate没半毛钱关系,但是要存入数据库之前,Hibernate将其状态定义为临时状态。
临时状态 到 持久化状态,有几个“独一无二处使用”的标志性方法:Session的save()和persist(),并且临时对象 和 持久化对象之间是单向的,只能从临时对象到持久化对象。
持久化状态 和 游离状态之间是双向的:
从持久化状态到游离状态的Session方法有:close()、evicit()和clear()
从游离状态回到持久化状态的“独一无二处使用”的方法:update()
另外 从临时状态、游离状态 到 持久化状态有两个共同的方法:saveOrUpdate()和merge()。这两个方法会使任何对象转移到持久化状态,无论你是临时状态还是游离状态。
删除状态只进不出,来源只有两个:持久化状态或者游离状态,触发的方法只有delete()。
进入删除状态,并不意味这对象的完全销往,还需要等待垃圾回收。
进入对象的完全销往,可以从3个状态进行垃圾回收,注意,比进入删除状态多一个临时状态。
好,上面的这些有些像流水账,没有任何解释。我只是想说明Session的接口方法调用和对象状态转移的变化规律,不涉及具体的应用场景和实现原理。
具体使用场景、细节内容请往下看。
下面我们参照着持久化角度的4个状态对象转换图,介绍各个导致对象状态变化的方法:
Session 的 save()和persist() 方法
Session 的 save() 方法
(Save方法——将临时对象变为持久化对象,并将持久化对象保存到数据库)
•Session 的 save() 方法使一个临时对象转变为持久化对象
•Session 的 save() 方法完成以下操作:
–把 对象加入到 Session 缓存中, 使它进入持久化状态
–选用映射文件指定的标识符生成器, 为持久化对象分配唯一的 OID. 在 使用代理主键的情况下, setId() 方法为 对象设置 OID 使无效的.
–计划执行一条 insert 语句:在 flush 缓存的时候
•Hibernate 通过持久化对象的 OID 来维持它和数据库相关记录的对应关系. 当 News 对象处于持久化状态时, 不允许程序随意修改它的 ID
这个save()方法在之前的图中可以看出,它将临时对象变为持久化对象;同时将这个临时对象保存到数据库里面。
下面我们还是由例子来说明问题:
本文的基本代码,如bean的定义等与本系列上一篇博文中的一样,我就不重复了。
我这里只把hibernate配置文件给出:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 配置链接数据库的信息 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/happybksdb?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password"></property>
<!-- <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> -->
<!-- 配置hibernate的基本信息 -->
<!-- hibernate所使用的的数据库方言 -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 执行操作是否在控制台打印SQL -->
<property name="show_sql">true</property>
<!-- 是否对SQL进行格式化 -->
<property name="format_sql">true</property>
<!-- 指定自动生成数据表的策略:在运行数据库的时候hibernate会为我们在数据库自动生成数据表的策略 -->
<property name="hbm2ddl.auto">update</property>
<property name="connection.isolation">2</property>
<!-- 每一个隔离级别都对应一个整数:
–1. READ UNCOMMITED
–2. READ COMMITED
–4. REPEATABLE READ
–8. SERIALIZEABLE
-->
<!-- 指定关联的hbm.xml映射文件 -->
<mapping resource="com/happybks/hibernate/hibernatePro2/beans/CuntomerBean.hbm.xml"/>
</session-factory>
</hibernate-configuration>
CuntomerBean类的hbm文件:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2017-1-14 15:06:13 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping>
<class name="com.happyBKs.hibernate.hibernatePro2.beans.CuntomerBean" table="CUNTOMERBEAN">
<id name="cid" type="java.lang.Integer">
<column name="CID" />
<generator class="increment" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<property name="no" type="java.lang.Integer">
<column name="NO" />
</property>
<property name="score" type="java.lang.Long">
<column name="SCORE" />
</property>
<property name="money" type="double">
<column name="MONEY" />
</property>
<property name="registerDate" type="java.util.Date">
<column name="REGISTERDATE" />
</property>
<property name="loginTime" type="java.sql.Timestamp">
<column name="LOGINTIME" />
</property>
</class>
</hibernate-mapping>
我们编写一个测试类:这里我们看看testSave方法。
package com.happyBKs.hibernate.hibernatePro2;
import java.sql.Timestamp;
import java.util.Date;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.happyBKs.hibernate.hibernatePro2.beans.CuntomerBean;
public class HibernateTest {
private SessionFactory sessionFactory;
private Session session;
Transaction transaction;
//在实际项目开发中session和transaction是不能作为成员变量的,因为会存在并发问题。
//本例只是为了提供几个测试示例时方便所以这样写。
@Before
public void init(){
System.out.println("init");
Configuration configuration=new Configuration().configure();
sessionFactory=configuration.buildSessionFactory();
session=sessionFactory.openSession();
transaction=session.beginTransaction();
}
@After
public void destroy(){
System.out.println("destroy");
transaction.commit();
session.close();
sessionFactory.close();
}
@Test
public void test() {
System.out.println("test");
}
@Test
public void testSave(){
CuntomerBean bean=new CuntomerBean();
bean.setName("HappyBKs");
bean.setNo(314);
bean.setMoney(20688.68);
bean.setScore(98765432123456789L);
bean.setRegisterDate(new Date());
bean.setLoginTime(Timestamp.valueOf("2017-02-09 21:00:00"));
session.save(bean);
System.out.println(bean);
}
}
我们在session.save(bean);设置一个断点,看看前后控制台变化:
数据库我已经清空了:
在save执行之前:
执行之后:
此时,cid已经赋予了值1。注意,我们在映射文件中我们配置的主键生成方式是hibernate自增——increment,所以此时(save()之后)并没有向数据库发送insert操作。因为,我们在hbm映射文件指定主键ID的生成方式是 <generator class="increment" />,是通过查询数据表做大ID值,由Hibernate自增来生成ID的,所以在save的过程中,Hibernate只是发送了一个查询最大ID的select语句,并对对象ID属性设置了生成的ID,之后必须等到flush()方法被调用时才会发送insert语句(包含由hibernate根据查来的最大ID生成的自增ID),在提交事务时完成数据库执行即真正插入到数据表当中。
我们看到数据库中新插入的记录的ID就是持久化对象中生成的那个1。
这里提出一个问题——我们是否能够尝试修改持久化对象的ID。
这里我们做两种尝试:
(1)在save()方法调用之前,我们来设置ID。
@Test
public void testSave(){
CuntomerBean bean=new CuntomerBean();
bean.setName("HappyBKs");
bean.setNo(314);
bean.setMoney(20688.68);
bean.setScore(98765432123456789L);
bean.setRegisterDate(new Date());
bean.setLoginTime(Timestamp.valueOf("2017-02-09 21:00:00"));
bean.setCid(100);
session.save(bean);
System.out.println(bean);
}
然后我们单步执行到setCid之后,save方法调用之前。
我们可以看到这个对象,严格意义上说,此时对象还是new后的属性赋值的阶段,没有调用save方法,所以其状态仍然属于临时状态而非持久化状态。
当单步执行一步,执行了save方法之后,发现,对象的ID值变了,被hibernate赋值为了select最大ID+1后的值,2。
然后的故事可想而知,数据库里对应的记录ID也是持久化对象的ID,即2。
我们再做第二个尝试:
(2)在save()方法调用之后修改ID。
save之后,我们尝试修改ID为100,似乎也成功了没有什么问题。
但是当执行到flush()方法的过程中,报错了。
报错显示:
三月 06, 2017 11:20:39 下午 org.hibernate.internal.ExceptionMapperStandardImpl mapManagedFlushFailure
ERROR: HHH000346: Error during managed flush [org.hibernate.HibernateException: identifier of an instance of com.happyBKs.hibernate.hibernatePro2.beans.CuntomerBean was altered from 3 to 100]
Junit日志记录为:
javax.persistence.PersistenceException: org.hibernate.HibernateException: identifier of an instance of com.happyBKs.hibernate.hibernatePro2.beans.CuntomerBean was altered from 3 to 100
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:147)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:155)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:162)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1426)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:476)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3179)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2393)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:467)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:146)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:220)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:68)
at com.happyBKs.hibernate.hibernatePro2.HibernateTest.destroy(HibernateTest.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:36)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: org.hibernate.HibernateException: identifier of an instance of com.happyBKs.hibernate.hibernatePro2.beans.CuntomerBean was altered from 3 to 100
at org.hibernate.event.internal.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:64)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:175)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:135)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:216)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:85)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:38)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1420)
... 32 more
所以说,save()方法调用之后,那个对象就已经从临时状态变为了持久化状态,而持久化对象的ID是不能被修改的。
注意,注意,注意!!!持久化对象的ID是不能被修改的!
分析其原因应该也可以理解,持久化对象就是靠着这个ID与数据库中的某条记录一一对应的,如果这个ID可以任意修改还怎么对应,和谁对应呢?这还是持久化对象吗?所以持久化对象的ID是不能被修改的。
好吧,刚才抛了异常,所以第三条记录数据库也没有插进去。
最后对save()方法我们做个总结:
* 1.save()方法
* (1)使一个临时对象变为持久化对象。
* (2)为对象分配ID
* (3)在flush缓存时,会发送一条insert语句。
* (4)在save()方法之前设置ID是无效的。(ID是唯一用来标识对象与数据表之间关系的。)
* (5)持久化对象的ID是不能被修改的。
Session 的 persist() 方法
编写一个测试方法:
/**
* 和save()方法的区别:
* 在调用persist()方法之前,若对象已经有ID了,则不会执行insert,而抛出异常
*/
@Test
public void testPersist(){
CuntomerBean bean=new CuntomerBean();
bean.setName("Mr Ding");
bean.setNo(314);
bean.setMoney(20688.68);
bean.setScore(98765432123456789L);
bean.setRegisterDate(new Date());
bean.setLoginTime(Timestamp.valueOf("2017-03-07 21:00:00"));
bean.setCid(200);
session.persist(bean);
System.out.println(bean);
}
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.happyBKs.hibernate.hibernatePro2.beans.CuntomerBean
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:147)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:155)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:162)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:772)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:750)
at com.happyBKs.hibernate.hibernatePro2.HibernateTest.testPersist(HibernateTest.java:81)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:30)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.happyBKs.hibernate.hibernatePro2.beans.CuntomerBean
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:124)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:765)
... 27 more
org.hibernate.TransactionException: Transaction was marked for rollback only; cannot commit
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:217)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:68)
at com.happyBKs.hibernate.hibernatePro2.HibernateTest.destroy(HibernateTest.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:36)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
我们把那个设置ID的操作注释掉。单步观察persist方法的执行前后的各个细节。
执行完flush之前:
flush之后,发出insert语句,但数据库还没有对应记录。
commit之后,事务提交。数据库生成了对应于这个持久化对象的记录。
•persist() 和 save() 区别:
–当对一个 OID 不为 Null 的对象执行 save() 方法时, 会把该对象以一个新的 oid 保存到数据库中; 但执行 persist() 方法时会抛出一个异常.
Session 的 get() 和 load() 方法
•都可以根据给定的 OID 从数据库中加载一个持久化对象
•区别:
–当数据库中不存在与 OID 对应的记录时, load() 方法抛出 ObjectNotFoundException 异常, 而 get() 方法返回 null
–两者采用不同的延迟检索策略:load 方法支持延迟加载策略。而 get 不支持。
我们现在来说说get和load的在延迟加载这个问题上的不同之处:
/**
* get VS load:
* 1.执行get方法:会立即加载对象。
* 执行load方法,拖不使用该对象,则不会立即执行查询操作,而返回一个代理对象。
*
* get是立即检索,load是延迟检索(用的时候再检索)
*
* 如果我在数据库中没有对应的记录呢?
*
*/
@Test
public void testGet(){
CuntomerBean bean= session.get(CuntomerBean.class, 1);
System.out.println(bean.getClass().getName());
//System.out.println(bean);
}
@Test
public void testLoad(){
CuntomerBean bean= session.load(CuntomerBean.class, 1);
System.out.println(bean.getClass().getName());
//System.out.println(bean);
}
我们先运行testGet方法。get方法是非延迟加载的,我们在get调用的时候,Hibenate的Sesssion会向数据库发送一个查询SQL语句。
而load方法,我们运行testLoad方法可以看到,假如我们load之后不使用这个bean。那么,Hibernate一直都不会向数据库发送select语句,这正是说明了load方法是支持延迟加载的。那么,load方法调用之后,我们获得到的但是未使用的bean是空值吗?它如何做到在后续使用的时候自动加载呢?
我们通过答应bean的类型可以带到,load得到的bean起初只是一个代理对象:com.happyBKs.hibernate.hibernatePro2.beans.CuntomerBean_$$_jvst55f_0
只有当我们真正使用bean,如获取bean的属性值时,hibernate的Session才回去发送select请求。
我们将代码中的注释部分打开。
@Test
public void testLoad(){
CuntomerBean bean= session.load(CuntomerBean.class, 1);
System.out.println(bean.getClass().getName());
System.out.println(bean);
System.out.println(bean.getClass().getName());
}
我们可以看到,在从bean对象读取属性值之前,我们打印bean类型还是代理类型。
第二个问题:如果我们get或load的数据在数据库中没有会怎么样?
/**
* get VS load:
* 1.执行get方法:会立即加载对象。
* 执行load方法,拖不使用该对象,则不会立即执行查询操作,而返回一个代理对象。
*
* get是立即检索,load是延迟检索(用的时候再检索)
*
* 如果我在数据库中没有对应的记录呢?
*
* 2.若数据表中没有对应的记录,get返回null,load会抛出异常。
*
*
*/
@Test
public void testGet(){
CuntomerBean bean= session.get(CuntomerBean.class, 100);
//System.out.println(bean.getClass().getName());
System.out.println(bean);
}
@Test
public void testLoad(){
CuntomerBean bean= session.load(CuntomerBean.class, 100);
//System.out.println(bean.getClass().getName());
System.out.println(bean);
//System.out.println(bean.getClass().getName());
}
get方法:返回null
load方法:抛异常
为什么load会跑出异常呢?
想想便知道,既然load返回的是一个代理对象,然后被告知这个对象其实没有,代理对象“如何交代”?
就好比,我们现在拍个代表去办事,结果代表到了地点发现事情办不了,肯定崩溃了。
需要注意的是,如果异常是代理异常:
也就是说,如果我们不使用bean的属性,其实是不会跑出异常的。
第三个问题,如果我们在get和load之后,关闭session会发生什么?
/**
* get VS load:
* 1.执行get方法:会立即加载对象。
* 执行load方法,拖不使用该对象,则不会立即执行查询操作,而返回一个代理对象。
*
* get是立即检索,load是延迟检索(用的时候再检索)
*
* 如果我在数据库中没有对应的记录呢?
*
* 2.若数据表中没有对应的记录,get返回null,load会抛出异常。
*
* 3.load方法,在使用过程中有可能导致程序跑出懒加载异常LazyInitializationException:
* 在需要初始化代理对象之前已经关闭了Session,这个时候就可能会跑出这个异常。
*
*/
@Test
public void testGet(){
CuntomerBean bean= session.get(CuntomerBean.class, 1);
//System.out.println(bean.getClass().getName());
session.close();
System.out.println(bean);
}
@Test
public void testLoad(){
CuntomerBean bean= session.load(CuntomerBean.class, 1);
//System.out.println(bean.getClass().getName());
session.close();
System.out.println(bean);
//System.out.println(bean.getClass().getName());
}
我们将结束时的关闭session等操作去掉,放到get/Load方法与实际使用bean对象的语句之间:
@After
public void destroy(){
System.out.println("destroy");
//transaction.commit();
//session.close();
sessionFactory.close();
}
get方法调用之后,即便我们关闭了session,获取到的bean仍然可以使用。
而load方法之后,如果我们关闭session,那么使用对象就会爬出懒加载异常。
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:146)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:259)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:73)
at com.happyBKs.hibernate.hibernatePro2.beans.CuntomerBean_$$_jvst55f_0.toString(CuntomerBean_$$_jvst55f_0.java)
at java.lang.String.valueOf(Unknown Source)
at java.io.PrintStream.println(Unknown Source)
at com.happyBKs.hibernate.hibernatePro2.HibernateTest.testLoad(HibernateTest.java:116)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:30)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
总结一下:
get VS load:
* 1.执行get方法:会立即加载对象。
* 执行load方法,拖不使用该对象,则不会立即执行查询操作,而返回一个代理对象。
*
* get是立即检索,load是延迟检索(用的时候再检索)
*
* 如果我在数据库中没有对应的记录呢?
*
*
* 2.load方法,在使用过程中有可能导致程序跑出懒加载异常LazyInitializationException:
* 在需要初始化代理对象之前已经关闭了Session,这个时候就可能会跑出这个异常。
*
* 3.若数据表中没有对应的记录,且Session也没有被关闭,同时需要使用对象时:
* get返回null,
* load不使用该对象的任务属性,运行没问题;如果需要获取属性或初始化,会抛出异常。
其他几个方法请见下回。真诚脸:)