java对象在JVM中的存活条件
在java中,我们使用User user = new User();
来创建一个java对象时,JVM会为其分配一块内存空间,此时,这个对象被变量“user”引用,那么它就会一直存在于内存中,而如果我们我们的“引用者user”升级了,User user = new VipUser()
。那么原来new User()
不再被任何变量引用,它就会结束自己的生命周期,然后会被JVM的智能垃圾回收期回收处理,以免再占用内存。
从以上分析,我们知道了java对象存活的条件就是:被(至少一个)变量引用
hibernate的对象存活条件
同样的,假设在我们使用hibernate访问数据库获取了一个小A对象,这个小A一样有它的存活条件,但与一般java对象不同,即使我们没有创建任何变量来引用小A,我们的小A还是能够活得好好的,这是因为小A被hibernate的Session缓存下来了。
理解Session的缓存机制
1. 缓存的实现机制:
在Session接口的实现类中,我们定义了一系列的java集合来存放从数据库中获取的数据,只要我们的Session实例没有结束声明周期,那么存放其中的对象就不会结束其生命周期。
2. Session缓存的作用
1. 减少对数据库的访问次数,优化性能。
比如我们来看下面的例子
Long time1 = System.currentTimeMillis();//记录时间
User user1_1 = session.get(User.class,1);
Long time2 = System.currentTimeMillis();//记录结束时间1
System.out.println("user1_1获取完毕,耗时:"+(time2 - time1)+"毫秒,准备开始获取user1_2");
User user1_2 = session.get(User.class,1);//与上面id相同
System.out.println("user1_2获取完毕,耗时:"+(System.currentTimeMillis() - time2 )+"毫秒,准备开始获取user2");
User user2 = session.get(User.class,2);
System.out.println(user1_1 == user1_2);
System.out.println(user1_2== user2);
运行程序,观察我们的打印信息:
Hibernate: select user0_.id as id1_0_0_, user0_.name as name2_0_0_ from t_user2 user0_ where user0_.id=?
user1_1获取完毕,耗时:26毫秒,准备开始获取user1_2
user1_2获取完毕,耗时:0毫秒,准备开始获取user2
Hibernate: select user0_.id as id1_0_0_, user0_.name as name2_0_0_ from t_user2 user0_ where user0_.id=?
true
false
在我们获取user1_2时,并没有查询数据库而且获取时间几乎为0,说明是直接从缓存中读取的,而在比较对象属性中,user1_1和user1_2相等,说明它们的引用地址也相同,而且必定与session缓存中的引用地址一致
从上面我们还能看到,Session标识缓存的不同对象,是通过对象类型和对象标识符id共同判别的,一旦两者一致,session即判别为同一对象,同时,我们也可归纳出利用session查询数据库的过程:比如我们要查询id为1的User,则查询过程如下时序图所示:
Created with Rapha?l 2.1.0SessionSessionSession缓存Session缓存数据库数据库:1. 查找缓存是否有对象类型为User且id为12. 找到并返回2. 没找到则查询数据库3. 返回数据结果并缓存4. 返回数据到引用变量
2. 保证数据库中的记录和缓存中的相应对象内容一致
在session清理缓存(flush)时,会进行脏检查,如果发现缓存中的最新数据与数据库记录不一致,会将最新数据更新到数据库中。
那么,session是如何进行脏检查的呢?难道每次清理前,针对所有的缓存数据访问数据库来进行匹对?这样效率太低了。实际上,在上述时序图的第3步到第4步之间,Session会将获得的数据结果先copy一份(这份copy还未经任何处理,肯定是和数据库记录一致的),再返回给引用变量。这样我们将所有最新的数据与最初copy的校对一下,一旦出现差异,就将最新数据更新到数据库。
3. Session缓存的清理
在我们每次针对引用变量修改对象属性后,对应的Session缓存中的数据也会被修改,这是显然的,因为它们的所指向的内存地址是一致的。但修改后,hibernate并不会马上执行相应的数据库操作,只有在特定条件下,如session被清理或特定的方法被调用才会访问数据库。这里谈谈session被清理的三个时间点:
1. 在完成事务提交之前,session会被清理一次。这样的好处是一方面可以减少在事务作用过程中,大量执行的数据库记录修改操作。另一方面还可以尽可能缩短当前事务对相关资源的锁定时间
2. 在执行一些复杂的查询操作时,需要清理缓存,更新数据库,确保查询得到的数据是最新的。
3. 显示地调用Session.flush()方法
如果我们不希望在上述的某些时刻清理,我们可以通过Session的setFlushMode()方法来定制,它提供了3种模式共我们选择:
模式 | 复杂查询方法被执行 | 事务提交时 | 显式调用flush() | 使用场景 |
---|---|---|---|---|
FlushMode.AUTO(默认模式) | 清理 | 清理 | 清理 | 正常应用场景 |
FlushMode.COMMIT | 不清理 | 清理 | 清理 | 需要避免过多查询操作清理缓存以提高性能的场景 |
FlushMode.NEVER | 不清理 | 不清理 | 清理 | 需要长时间运行的复杂事务场景 |
tips:从上面我们还能看出,我们要修改用户信息,完全用显式地执行
session.update(user)
语句,只需直接修改Session缓存对象属性即可,如下所示
user.setName("newName");
session.flush();
我们数据库中相应的User记录name属性也被修改了!
此外,Session在清理缓存时,按照以下顺序执行sql语句。
1。按照应用程序调用save()方法的先后顺序,执行所有的对实体进行插入的insert语句。
2。所有对实体进行更新的update语句。
3。所有对实体进行删除的delete语句。
4。所有对集合元素进行删除、更新或插入的sql语句。
5。执行所有对集合进行插入的insert语句。
6。按照应用程序调用delete()方法的先后执行,执行所有对实体进行删除的delete语句。