站在持久化的角度,Hibernate 将持久化对象分为四种状态,分别是:临时状态、持久化状态、游离状态、删除状态。 Session 的特定方法能让对象从一个状态转换到另一个状态。
如果搞不懂 Hibernate 持久化对象的状态,那么就无法真正了解 Hibernate。
下面详细分析下这几种状态:
临时状态(Transient):相当于你还没来公司上班,公司没你这人
– OID 为 null
– session 缓存中没有该对象
– 数据库中也没有该对象对应的记录
– 一般刚 new 出来的对象就处于这种状态。
持久化状态(Persist):相当于你来公司上班了,公司花名册上有你这个人
– OID 不为 null
– 对象位于 session 缓存中
– 在数据库中有与之对应的记录,并且在 flush 时会根据对象属性变化同步更新数据库
– 在同一个 session 实例中,数据库表中的记录只对应唯一的持久化对象,即一对一。
游离对象(Detached):你请假了很久,你不知道公司把你开了还是没开
– OID 不为 null
– session 缓存中没有该对象
– 数据库中可能有与之对应的记录,也可能没有,不一定。一般情况下,游离对象是由持久化对象转变过来的。比如清理了缓存,session 中没有,但是数据库表中有对应的记录,这个对象就是游离的。
删除对象(Removed):你被公司 Fire 了
– 在数据库中没有该对象 OID 对应的记录
– session 缓存中也存在该对象
上面的四种状态非常重要,一定要弄清楚。
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() 方法如果正常使用和 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() 方法在下面的用法是等价的。
@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);
}
@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 区别如下:
@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);
}
虽然没有主动调用过 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 的持久化对象时,就会报错,这是不允许的。
Session 的 saveOrUpdate() 方法同时包含了 save() 与 update() 方法的功能。
临时对象是 id 为 null 的对象,也就是刚 new 出来的对象。
判定对象为临时对象的标准
saveOrUpdate() 就是如果 ID null,则插入,不为 null,则更新。但是注意,如果更新的 id 不存在时,会报错。
测试下 unsaved-value。
<id name="id" type="java.lang.Integer" unsaved-value="10">
@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 不用去管。
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, 使它们变为临时对象。
<property name="hibernate.use_identifier_rollback">trueproperty>
设置后执行如下代码:
@Test
public void testDelete() {
News news = (News) session.get(News.class, 3);
session.delete(news);
System.out.println(news);
}
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 无法直接调用存储过程,需要通过 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);
}