持久化对象的状态

站在持久化的角度,Hibernate 将持久化对象分为四种状态,分别是:临时状态、持久化状态、游离状态、删除状态。 Session 的特定方法能让对象从一个状态转换到另一个状态。

如果搞不懂 Hibernate 持久化对象的状态,那么就无法真正了解 Hibernate。

下面详细分析下这几种状态:

临时状态(Transient):相当于你还没来公司上班,公司没你这人 – OID 为 null – session 缓存中没有该对象 – 数据库中也没有该对象对应的记录 – 一般刚 new 出来的对象就处于这种状态。 持久化状态(Persist):相当于你来公司上班了,公司花名册上有你这个人 – OID 不为 null – 对象位于 session 缓存中 – 在数据库中有与之对应的记录,并且在 flush 时会根据对象属性变化同步更新数据库 – 在同一个 session 实例中,数据库表中的记录只对应唯一的持久化对象,即一对一。 游离对象(Detached):你请假了很久,你不知道公司把你开了还是没开 – OID 不为 null – session 缓存中没有该对象 – 数据库中可能有与之对应的记录,也可能没有,不一定。一般情况下,游离对象是由持久化对象转变过来的。比如清理了缓存,session 中没有,但是数据库表中有对应的记录,这个对象就是游离的。 删除对象(Removed):你被公司 Fire 了 – 在数据库中没有该对象 OID 对应的记录 – session 缓存中也存在该对象

上面的四种状态非常重要,一定要弄清楚。

持久化对象转换相关 API

上述四种状态的转换图如下:

save()

save 方法蛮多细节。 最基本的使用如下

@Test

public void testSave() {

// 刚创建,处于临时状态

News news = new News();

news.setTitle("C");

news.setAuthor("c_author");

news.setDate(new Date());

// save() 方法使得对象从临时状态变持久化状态

session.save(news);

}

此时成功插入一条数据到表中。 那么如果在 save() 前设置了 id,有用吗?

@Test

public void testSave() {

News news = new News();

// 手动设置 OID

news.setId(555);

news.setTitle("D");

news.setAuthor("d_author");

news.setDate(new Date());

session.save(news);

}

结果是依然插入成功,但是 ID 是使用我们在 hibernate.cfg.xml 中配置的自增策略生成,因此在 save() 方法前给对象设置 ID 是无效的。 那么如果在 save 后再手动修改对象的 ID 会怎样呢?

@Test

public void testSave() {

News news = new News();

news.setTitle("E");

news.setAuthor("E_author");

news.setDate(new Date());

session.save(news);

news.setId(666);

}

这样就会报错,说 id 从 6 变为 666。报错原因是 Hibernate 规定 save() 后不能再去修改对象的 ID。因为 session 中的对象就是靠 ID 和数据库表中的记录进行对应,如果该了,就找不到对应的记录了。

persist() 方法

persist() 方法如果正常使用和 save() 方法其实效果一样。

@Test

public void testPersist() {

News news = new News();

news.setTitle("F");

news.setAuthor("F_author");

news.setDate(new Date());

// persist 使得对象从临时状态变为持久状态

session.persist(news);

}

也是插入成功。 那么和 save() 方法不同的是,如果在 persist 之前设置了对象的 ID,则不会进行保存操作,而是抛出一个异常。

@Test

public void testPersist() {

News news = new News();

news.setId(777);

news.setTitle("G");

news.setAuthor("G_author");

news.setDate(new Date());

session.persist(news);

}

同样的,如果在 persist 之后修改了对象的 ID,也会抛出异常。

get() 方法 & load() 方法

get() 和 load() 方法在下面的用法是等价的。

@Test

public void testGet() {

News news = (News) session.get(News.class,1);

System.out.println(news);

}

@Test

public void testLoad() {

News news = (News) session.load(News.class,1);

System.out.println(news);

}

它们都会发送 select 语句。 再看下面的例子:

@Test

public void testGet() {

News news = (News) session.get(News.class,1);

//System.out.println(news);

}

@Test

public void testLoad() {

News news = (News) session.load(News.class,1);

//System.out.println(news);

}

此时 get 会发送 select 语句,而 load 不会发送了。 get 和 load 区别如下:

get 方法会立即加载对象,不管你后面用不用。而 load 方法如果后面不使用查出来的对象,则不会立即执行查询操作,而是返回一个代理对象。也就是说, get 是立即加载,而 load 是懒加载。当查询一个数据库表中不存在的记录时,get 方法会返回 null。但是 load 分为两种情况。 – 不使用 load 出来的对象的任何属性,则不会有问题。 – 使用 load 出来的对象的属性,则会抛出异常。@Test

public void testGet() {

// 查询不存在的记录,执行 SQL

News news = (News) session.get(News.class,99);

// 输出 null

System.out.println(news);

}

@Test

public void testLoad() {

// 查询不存在的记录,因为后面打印了 news 对象,调用 toString() 方法

// 因此会用到该对象的属性,也会发送 SQL。

News news = (News) session.load(News.class,99);

// 不会输出 null,而是抛出异常

System.out.println(news);

}

load 方法还可能会抛出懒加载异常。 LazyInitializationException。当我获取到代理对象时,还没初始化代理对象时,session 被关闭了,然后我要用查询出来的对象了,那么就会抛出 LazyInitializationException 异常。为什么会抛出异常呢,这是因为 load 返回的是代理对象,当我要查数据库用数据去填充代理对象时,连接被关闭了,那么就没法去加载了,所以可能会抛出异常。

update() 方法

虽然没有主动调用过 UPDATE 方法,但是在前面在控制台也看到过 UPDATE 语句,这是因为在 commit 时,执行了 flush() 方法,当数据库表中的记录和缓存中的对象不一致时,就会发送 UPDATE 语句。因此,在下面的这种情况,掉不掉用 update() 方法结果都是一样的。

@Test

public void testUpdate() {

News news = (News) session.load(News.class,1);

news.setAuthor("SUN");

// session.update(news); 可写可不写

}

但是,如果要去更新一个游离对象,则必须手动调用 update() 方法。

@Test

public void testUpdate2() {

News news = (News) session.get(News.class,1);

// 清空缓存,则 news 变为游离对象,缓存中没有,但是数据库表中存在

session.clear();

news.setAuthor("ORACLE");

// 必须手动调用 update 方法,更新游离对象

session.update(news);

// update 将游离对象变为持久化对象,加载到缓存中,因此下面的 select 语句不会发送

News news2 = (News) session.get(News.class,1);

System.out.println(news2);

}

update() 方法也有几个需要注意的地方: 一是当缓存中没有,总会发送 update 语句去更新数据库,和触发器一起工作会出问题。 二是当游离的对象在数据库中没有记录,还去更新,就会报错。 三是update语句会将游离对象转换成持久化对象,当 session 中有两个相同的 OID 的持久化对象时,就会报错,这是不允许的。

saveOrUpdate() 方法

Session 的 saveOrUpdate() 方法同时包含了 save() 与 update() 方法的功能。

临时对象是 id 为 null 的对象,也就是刚 new 出来的对象。

判定对象为临时对象的标准

Java 对象的 OID 为 null映射文件中为 设置了 unsaved-value 属性, 并且 Java 对象的 OID 取值与这个 unsaved-value 属性值匹配 一般根据 OID 是否为 null 就可以判断,比较极端才会出现第二种情况。

saveOrUpdate() 就是如果 ID null,则插入,不为 null,则更新。但是注意,如果更新的 id 不存在时,会报错。 测试下 unsaved-value。

@Test

public void testSaveOrUpdate() {

News news = new News();

news.setAuthor("bbbb");

news.setTitle("BBBB");

news.setDate(new Date());

// 将 OID 的值设置的和 unsaved-value 值一样

news.setId(10);

session.saveOrUpdate(news);

}

此时执行,会执行插入操作。前两条 SQL 是 hibernate 生成 id 算法获取 id 的 SQL 不用去管。

delete() 方法

Session 的 delete() 方法既可以删除一个游离对象, 也可以删除一个持久化对象。 只要 OID 对应的上,就能删除!

删除游离对象(session 缓存中不存在,但数据库表中存在)

@Test

public void testDelete() {

News news = new News();

news.setId(262144);

session.delete(news);

}

删除持久化对象(session 缓存中有,数据库表中也有):

@Test

public void testDelete() {

News news = (News) session.get(News.class, 262144);

session.delete(news);

}

注:如果删除一个不存在的 id,则会抛出异常。

这里的 delete 并不是直接删除,而是计划删除,真正的删除是在 flush 缓存的时候删除的。看如下代码:

@Test

public void testDelete() {

News news = (News) session.get(News.class, 229376);

session.delete(news);

System.out.println(news);

}

打印日志中并没有执行 delete 语句。 我们打印该对象,发现该对象的 id 还是存在的,那么我们甚至可以拿到对象的 id 干坏事,比如删了这个对象,然后再更新这个对象,或者删了这个对象,再保存这个对象。

@Test

public void testDelete() {

News news = (News) session.get(News.class, 294912);

session.delete(news);

System.out.println(news);

news.setTitle("FF");

session.update(news); // 报错 deleted instance passed to update()

session.saveOrUpdate(news); // 新增,没问题

}

但是理论上来讲,一个被删除的对象,怎么还能对它进行修改或新增,或者说,被删除的对象,就不应该还有 id。 所以说,hibernate 也想到了。Hibernate 的 cfg.xml 配置文件中有一个 hibernate.use_identifier_rollback 属性, 其默认值为 false, 若把它设为 true, 将改变 delete() 方法的运行行为: delete() 方法会把持久化对象或游离对象的 OID 设置为 null, 使它们变为临时对象。

true

设置后执行如下代码:

@Test

public void testDelete() {

News news = (News) session.get(News.class, 3);

session.delete(news);

System.out.println(news);

}

此时 id 变为 null,干不了坏事儿了。

evict() 方法

evict 的作用是从 session 缓存中删除指定的持久化对象。

@Test

public void testEvict() {

News news1 = (News) session.get(News.class, 2);

News news2 = (News) session.get(News.class, 3);

news1.setTitle("AA");

news2.setTitle("BB");

session.evict(news2);

}

上述代码如果不加 evict,则会在 commit 时 flush,修改数据库表中的记录。而让 news evict 之后,news2 变成游离状态了,所以 title 是不会修改的,必须调用 update() 方法才能修改。

Hibernate 调用存储过程

Hibernate 无法直接调用存储过程,需要通过 Work 接口,得到 Connection 对象,再通过原生 JDBC 调用存储过程。Work 接口: 直接通过 JDBC API 来访问数据库的操作。

Session 的 doWork(Work) 方法用于执行 Work 对象指定的操作, 即调用 Work 对象的 execute() 方法. Session 会把当前使用的数据库连接传递给 execute() 方法。 这里我就简单的打印下 connection 就行。

@Test

public void testWork() {

Work work = new Work() {

@Override

public void execute(Connection connection) throws SQLException {

System.out.println(connection);

}

};

session.doWork(work);

}

对于批量操作,还是原生的 API 效率会更高。

好文推荐

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: