一、hibernate 中对象的状态
Hibernate把对象分为4中状态:
临时状态。
持久化状态。
游离状态。
删除状态。
+++ 临时状态(直接new出来的对象)
举例: User user = new User();
特点:
直接new出来的对象;
不处于session的缓存中,
数据库中没有对象的记录;
+++ 持久化状态(托管状态)(由session缓存托管的对象)
特点:
存储在sesison缓存中,被sesison缓存托管;
数据库中有对应的记录;
1.被session缓存托管的对象,就是持久化对象。
通过session的相关API操作对象,则该对象就会转化为持久化对象。
3.持久化对象可以更改相关属性,
但是只有事务被提交,持久化对象才会保存到数据库。
4.session缓存的生命周期与session的生命周期一致。
session创建,缓存就创建。
session关闭,缓存就关闭。
+++ 删除状态
我们可以把这一状态也规划化持久化状态中。
+++ 游离状态(session关闭后,由持久化对象转化而来)
特点:
不处于session缓存中;
数据库中有对应的记录
Session关闭后,由持久化对象转化而来;
new出的对象是临时状态。
该对象被sesson操作后转化为持久化状态。该对象被session托管。
session关闭后,持久化对象转为游离状态。
public static void main(String[] args) {
//临时状态,直接new出来的对象
ProjectEntity poj=new ProjectEntity();
//持久化状态。被session管理
session.beginTransaction();
ProjectEntity pro=(ProjectEntity) session.get(ProjectEntity.class, 1);
pro.setpDesc("tt"); //事务提交。修改数据会反映到数据库
session.getTransaction().commit();
pro.setpDesc("t11t");//事务未提交,修改数据不会反映到数据库
session.close();
//游离状态,脱离sesison管理
pro.setpDesc("t11t");
}
二、一级缓存(session级别的缓存)
2.1 缓存的分类
+++ Hibernate中缓存分类:
一级缓存
二级缓存
2.2 什么是一级缓存
在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存。
只要 Session 实例没有结束生命周期, 且没有清理缓存,
则存放在它缓存中的对象也不会结束生命周期。
Hibernate中Session对象是非线程安全的 ,不能定义成全局变量,
只能定义成局部变量。因此session作用时间短。
session的声明周期:
sf.openSession() session的创建
session.close() session的销毁
一级缓存是sesison级别的缓存,其生命周期和session的生命周期是一样的
随着session的创建而创建(sf.openSession())。
随着session的关闭而销毁(session.close())。
session一级缓存的生命周期(session的生命周期):
Session session=cf.openSession();
...
session.close();
2.3 一级缓存与持久化状态的对象的关系
1.一级缓存存储的对象就是持久化对象。持久化对象在缓存中一定有备份。
2.对象被sesison操作后,就会进入一级缓存,转化为持久化对象。
1.Session对象内部维护了一个缓存对象。它随着session对象的创建而创建,
随着session的关闭而销毁。
我们把这个一级缓存也称为session级别的缓存。
2.被session操作的对象都会进入session的缓存。
一级缓存中存储的对象,指的就是持久化对象。
3.持久化对象可以更改内部数据,但事务必须提交,才会同步到数据库,
2.4 一级缓存的作用
Session 缓存可减少 Hibernate 应用程序访问数据库的频率。
3.4.1 应用程序直接与内存进行交互,提高SQL执行效率,同时减少了与数据库交互次数
在传统JDBC中,用户操作sql语句会直接到数据库中查找。
在hibernate的JDBC中,用户操作sql语句首先会到session内部维护的一级缓存中查找,如果没有才回到数据库查找。
综上:应用程序从内存中读取持久化对象的速度显然比到数据库中查询数据的
速度快多了,
因此Session的缓存可以提高数据访问的性能。
3.4.2 可以将多条sql合为一条。 减少与数据库交互次数
当缓存中持久化对象的状态发生了变化,Session并不会立即执行相关的SQL语句,
这使得Session能够把几条相关的SQL语句合并为一条SQL语句,
以便减少访问数据库的次数,从而提高应用程序的性能。
案例1:
案例2:
public static void main(String[] args) {
session.beginTransaction();
UserEntity user = (UserEntity) session.get(UserEntity.class, 1);
//修改对象属性
user.setName("12");
user.setAge(12);
session.update(user);
//再次修改对象属性
user.setName("13");
session.getTransaction().commit();
}
三、一级缓存相关API
Session的缓存由hibernate维护, 用户不能操作缓存内容;
如果想操作缓存内容,必须通过hibernate提供的evit/clear方法操作。
特点:
只在(当前)session范围有效,作用时间短,效果不是特别明显!
在短时间内多次操作数据库,效果比较明显!
session.flush(); 生成sql语句并执行,让一级缓存与数据库同步
session.evict(arg0); 清除指定的缓存数据
session.clear(); 清除所有的缓存数据
session.close(); 清除所有的缓存数据.session关闭,缓存也就不存在了,session中的数据也就被清除了
1.持久化对象改变内部属性,事务必须提交才会与数据库同步。
2.在一个事务中,持久化对象可以多次更改内部属性,但只会生成一条sql语句。
在一个事务中,session.flush()会将缓存与数据库同步。
所以如果该方法前后都有持久化对象更改属性,
则会成成两条sql。
3.session.evit(obj)
session.clear() 会清除缓存内容。
3.1 session.flush()
3.1.1 session.flush() 的作用
+++ session.flush() 的作用
生成sql语句。将一级缓存与数据库同步。
>>>>> 只生成一条更新的sql语句
public static void main(String[] args) {
session.beginTransaction();
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity pro=(ProjectEntity) session.get(ProjectEntity.class, 11);
pro.setpDesc("1111");
pro.setpDesc("222");
session.flush();
}
>>>>> 生成两条更新的sql语句
session.flush()更新了持久化对象的状态。再次改变对象,
该对象会再次被打上修改标签。所以会生成两条sql。
public static void main(String[] args) {
session.beginTransaction();
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity pro=(ProjectEntity) session.get(ProjectEntity.class, 11);
pro.setpDesc("1111");
session.flush();//将缓存与数据库同步
pro.setpDesc("222");
session.flush();
}
3.1.2 什么时候才能将一级缓存与数据库同步
只有使用这两个方法才能生成sql,将一级缓存中的持久化对象与数据库同步。
1.session.flush()
2.session.getTracsaction().commit()
session.flush()
其目的就是生成sql语句并执行。将一级缓存与数据库同步
但不提交事务;
session.getTracsaction().commit() 。
其目的就是生成sql语句并执行。将一级缓存与数据库同步。
同时提交事务。
原理:
该方法内部隐式调用了session.flush()方法。
其作用是将一级缓存与数据库同步,同时提交事务。
commit() 和 flush() 方法的区别
flush 操作可能会执行一系列 sql 语句,但不提交事务;
commit 方法先调用flush() 方法,然后提交事务。提交事务意味着对数据库操作永久保存下来。
3.1.3 什么时候才能生成sql
1.对于查询操作,无论事务是否提交,只要查询就会直接生成sql。(懒加载除外)
2.对于更新操作(增、删、改),
只有使用session.flush() 或session.getTracsaction().commit()才会生成sql。
若不使用这两个方法,则不会生成sql。
原因:通过session做更新操作只是与一级缓存进行交互。
>>>>> 对于查询操作,无论事务是否提交,直接生成sql。
public static void main(String[] args) {
UserEntity user = (UserEntity) session.get(UserEntity.class, 1);
}
>>>>> 对于更新操作,只有使用session.flush() 或session.getTransaction().commit()方法,才会生成sql
对于更新操作,只有使用这两个方法才会生成sql。
session.flush()
session.getTransaction().commit()
/**
* 对于更新方法,不会生成sql
*/
private static void test() {
//开启事务
session.beginTransaction();
UserEntity user = (UserEntity) session.get(UserEntity.class, 1);
user.setName("12113");
//更新
session.update(user);
}
/**
* 对于更新方法,会生成sql
*/
private static void test2() {
//开启事务
session.beginTransaction();
UserEntity user = (UserEntity) session.get(UserEntity.class, 1);
user.setName("12113");
//更新
session.update(user);
session.getTransaction().commit();
}
>>>>> 对于更新操作,若不使用session.flush() 或session.getTransaction().commit()方法,不会生成sql的原因
虽然使用了sesison.update()方法。但是该方法只是与一级缓存进行交互。
它会将一级缓存中的持久化对象标志转为修改状态。
所以不会生成sql执行。
/**
* 对于更新方法,不会生成sql
*/
private static void test() {
//开启事务
session.beginTransaction();
UserEntity user = (UserEntity) session.get(UserEntity.class, 1);
user.setName("12113");
//更新
session.update(user);
}
3.1.4 session.flush 与session.getTracsaction().commit() 的区别
session.flush()
生成sql并执行,将一级缓存与数据库同步
session.getTracsaction().commit()
生成sql并执行,将一级缓存与数据库同步
同时提交事务。
不同点:
如果只是调用session.flush(),
即使将一级缓存与数据库同步,
但由于事务没有提交,也会回滚。
如果只是调用session.getTracsaction().commit()
由于他内部调用了session.flush(),
所以它不仅能实现将一级缓存与数据库同步,
同时也实现了提交事务。
3.2 session.evict(arg0)
将对象从一级缓存中清除。
>>>>> 只生成一条查询的sql语句
public static void main(String[] args) {
session.beginTransaction();
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity pro=(ProjectEntity) session.get(ProjectEntity.class, 11);
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity pro2=(ProjectEntity) session.get(ProjectEntity.class, 11);
System.out.println(pro==pro2);//true
session.getTransaction().commit();
}
>>>>> 生成两条查询的sql语句
public static void main(String[] args) {
session.beginTransaction();
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity pro=(ProjectEntity) session.get(ProjectEntity.class, 11);
//将对象从缓存中清除
session.evict(pro);
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity pro2=(ProjectEntity) session.get(ProjectEntity.class, 11);
System.out.println(pro==pro2);//false
session.getTransaction().commit();
}
}
3.3 session.clear()
清空一级缓存
>>>>> 只生成一条查询的sql语句
public static void main(String[] args) {
session.beginTransaction();
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity pro=(ProjectEntity) session.get(ProjectEntity.class, 11);
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity pro2=(ProjectEntity) session.get(ProjectEntity.class, 11);
System.out.println(pro==pro2);//true
session.getTransaction().commit();
}
>>>>> 生成两条查询的sql语句
public static void main(String[] args) {
session.beginTransaction();
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity pro=(ProjectEntity) session.get(ProjectEntity.class, 11);
session.clear();
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity pro2=(ProjectEntity) session.get(ProjectEntity.class, 11);
System.out.println(pro==pro2);//false
session.getTransaction().commit();
}
}
3.4 获取session一级缓存中的对象个数
可以利用session.getStatistics()方法统计出放入到session缓存中的对象的个数
public static void main(String[] args) {
session.beginTransaction();
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity pro=(ProjectEntity) session.get(ProjectEntity.class, 11);
session.getTransaction().commit();
//统计缓存中的对象个数
System.out.println(session.getStatistics().getEntityCount());
session.close();
}
3.5 一级缓存相关的API使用场景
如果查询的数据相当多,则这些数据会存储到缓存中。容易造成内存溢出。
所以这时候可以使用缓存相关的API来操作缓存。
Session.flush(); // 先与数据库同步
Session.clear(); // 再清空一级缓存内容
四、一级缓存的数据存储机制
4.1 一级缓存的数据存储结构
public static void main(String[] args) {
session.beginTransaction();
//查询所有的数据,然后放到缓存中。生成sql。
Query qr = session.createQuery("from ProjectEntity");
List<ProjectEntity> list = qr.list();
//先从缓存中查找是否有主键为7的数据,如果没有再查询数据库
ProjectEntity pro=(ProjectEntity) session.get(ProjectEntity.class, 7);
System.out.println(pro==list.get(0));
session.getTransaction().commit();
session.close();
}
+++ 一级缓存结构:
翻过无数资料,始终没有查找到session缓存的存储结构。
经过上述测试,我们可以假设session的存储结构是
Map<id,包装对象> map-new HashMap<id,包装对象>();
4.2 如何将数据存放到缓存中
1.当调用session的save/saveOrUpdate/get/load/list等所有API方法的时候,
就把对象放入session的缓存中。即这些对象就会被session托管。
对象被sesison操作后,就会进入一级缓存,转化为持久化对象。
2.session相关的所有API方法,都会将操作的对象转化为持久化对象,存放到缓存中。
get方法
session.get方法可以把一个对象放入到session的缓存中,以主键标识该对象,所以我们把这样的主键也叫oid
save方法
session.save方法可以把对象放入到缓存中
update方法
session.update方法把对象放入到了session缓存中
...
案例:通过session查询数据时,首先会在缓存中查找数据。
通过session查询数据时,首先会在缓存中查询数据。
查询不到,才会查询数据库,同时将查询的结果存方到缓存中。
+++ 只生成一条sql语句.且两次查询的对象相同
public static void main(String[] args) {
session.beginTransaction();
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity pro=(ProjectEntity) session.get(ProjectEntity.class, 9);
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity pro2=(ProjectEntity) session.get(ProjectEntity.class, 9);
System.out.println(pro==pro2);//true
session.getTransaction().commit();
}
案例: 持久化对象做更新操作时,只有事务被提交,才会保存到数据库
持久化对象做更新操作时,只有事务被提交,才会保存到数据库
+++ 持久化对象虽然更改了,但是由于事务未提交,所以不会更新到数据库
public static void main(String[] args) {
session.beginTransaction();
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity pro=(ProjectEntity) session.get(ProjectEntity.class, 11);
pro.setpDesc("111");
// session.getTransaction().commit();
session.close();
}
+++ 持久化对象更改操作,事务提交,保存到数据库
public static void main(String[] args) {
session.beginTransaction();
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity pro=(ProjectEntity) session.get(ProjectEntity.class, 11);
pro.setpDesc("111");
session.getTransaction().commit();
session.close();
}
4.2 如何从缓存中取数据
1.通过session操作的对象,都会从临时状态转化为持久化状态。
并由session缓存托管。
2.用户操作持久化对象,做增删改操作,都是对持久化对象的操作,并不会直接反应到数据库,
只有当调用session.flush()或者session.getTransaction().commit()时才会生成sql,持久化到数据库,
3.用户操作持久化对象,会对持久化对象打上相应的增删改标签,然后当与数据库同步时,hibernate会根据这些标签,生成sql。
4.只有当用户做主键查询或简单批量查询时,才会从缓存中取数据。
主键查询: get() -- 从缓存中取数据
load() -- 从缓存中取数据
批量查询:
list() -- 不会从缓存中取数据。
原理:生成一条sql查询语句
iterator() -- 会从缓存中取数据
原理:会生成N+1条sql。
iterator(),生成一条sql,查询符合条件的数据主键。
next()。根据主键ID查询数据。
5.list()不从缓存取数据,iterator()从缓存取数据。原因?
==》首先一级缓存的存储结构大致是Map<id,数据> 。
当用户做主键查询操作时,用户可以根据主键ID来map中查询数据。
但是如果用户是批量查询,则无法从map中筛选数据。
所以主键查询、iteraror()会从缓存中读取数据,
而复杂批量查询,则不会从缓存中取数据。
4.3 如何将缓存中的数据同步到数据库中
将缓存中数据同步到数据库:
a.session.flush()
b.session.getTransaction().commit()
1.session.flush方法会去检查session缓存中各个对象的状态。
如果该对象由临时状态转换过来的(没有主键值),则会让该对象生成一条insert语句。
如果该对象有主键值,则会对照副本,决定是否发出update语句。
2.当事务提交的时候,如果不显式的写session.flush的情况下,这个时候
hibernate内部会默认的执行session.flush。
4.4 session缓存是session级别的缓存,session之间不共享
+++ 不同的session是否会共享缓存数据?
不会。
User1 u1 = Session1.get(User.class,1); 把u1对象放入session1的缓存
Session2.update(u1); 把u1放入session2的缓存
U1.setName(‘new Name’);
如果生成2条update sql, 说明不同的session使用不同的缓存区,不能共享。
1)案例1
2)案例2
public static void main(String[] args) {
session.beginTransaction();
//先根据ID去缓存中查找是否有该对象,如果没有就去数据库中查找。找到后放入到缓存中
ProjectEntity entity=(ProjectEntity) session.get(ProjectEntity.class, 12);
ProjectEntity entity2=(ProjectEntity) session.get(ProjectEntity.class, 12);
System.out.println(entity==entity2); //true
session.getTransaction().commit();
session.close();
}