使用Hibernate持久层框架,方便操作数据库。在使用hibernate对数据库进行增删改查操作时,我们不能忽视Session缓存和快照在其中的作用影响,同时要熟悉对象三种状态的转换,下面就根据一些实例进行解析和进一步的理解。
各个案例没有特别的顺序,均为随机记录
1)session 获取
Session s = HiberHelper.getSession();
Session s0 = HiberHelper.getSession();
System.out.println(s==s0);//true
s.beginTransaction();//1,开始事务,为当前session分配连接资源
Student stu = new Student();
stu.setName("ZhaoSier");
stu.setAge(18);
stu.setSex('1');
stu.setHomeAddr("JYVTC");
s.save(stu);
s.getTransaction().commit();//session提交,自动关闭session
Session s1 = HiberHelper.openSession();//openSession,新创建连接
System.out.println(s1==s);//false
Session s2 = HiberHelper.openSession();//openSession,新创建连接
System.out.println(s2==s1);//false
s1.close();
s2.close();
1 通过OpenSession获取的连接为新创建的连接,每次获取均为一个新的对象,并且。
必须手动关闭
2 getCurrentSession获取的连接为当前线程的连接,多次获取返回的为同一个对象,并且在事务提交时自动关闭。
2)事务的开启与提交
Session session = HiberHelper.getSession();
System.out.println(session.isOpen());//true
session.beginTransaction();
System.out.println(session.isConnected());//true
System.out.println(session.isOpen());//true
Student stu = new Student();
stu.setName("ZhaoSier");
session.save(stu);
session.getTransaction().commit();
System.out.println(session.isConnected());//false
System.out.println(session.isOpen());//false
//已经关闭了的session,再次开启事物时,会报错,
//也就是说,session关闭了,那么当前session就不可以再次使用了
session.beginTransaction();//此处报错
session.getTransaction().commit();
从上面例子可知:
1 获取session时,并没有为该session分配数据库连接资源
2 开启事务时,会为该session绑定数据库连接
3 事务提交之后,连接资源会被收回,同时isOpen值为false
4 事务提交后,该session就变为了不可用的状态了,不能重新开启事务
5 如果需要重新操作数据库,就必须重新获取session
另外注意,连接的分配与释放,都是从连接池中获取,用完重新放回连接池中。
3)openSession
Session s1 = HiberHelper.openSession();
System.out.println(s1.isOpen());//true
s1.beginTransaction();
System.out.println(s1.isConnected());//true
s1.getTransaction().commit();
System.out.println(s1.isConnected());//true
System.out.println(s1.isOpen());//true
s1.beginTransaction();//不会报错
s1.close();
System.out.println(s1.isOpen());//false
System.out.println(s1.isConnected());//false
openSession获取的session与getCurrentSession获取的session明显有不同之处:
1 openSession得到的session可以重复使用,即可以多次开启/提交事务
2 即使事务提交,当前session的isConnected与isOpen也返回true
3 基于第二点,所以通过open获取到的session用完了一定不要忘了手动关闭,调用close方法,不然他占用的一些资源不会完全释放。
4 虽然session提交之后,isConnected返回true,但是通过调试可以发现,session对象中的Connection类型的对象值已经为null,说明真正的连接已经释放。
4)判断sql语句执行
Session se = HiberHelper.getSession();
se.beginTransaction();
Stut s = se.get(Stut.class, "1");//1
se.delete(s);//2
Stut s2 = new Stut();
s2.setId("1");
s2.setName("999");
se.save(s2);//3
se.save(s2);//4
se.save(s);//5
se.getTransaction().commit();//6
结果:
1 执行select查询语句,将对象s载入缓存
2 不执行语句,但是会将要执行的delete语句暂存起来,同时将session与快照中的对象s删除
3 执行delete语句,暂存insert语句但并不执行。对re-saved对象将之前暂存的delete执行,同时将s2加入缓存。
4 不执行语句,但也不会保存,因为操作的是同一个对象s2
5 报错,因为session中已经存在id为2的对象了,要将另一个相同id的对象加入session缓存
Exception in thread "main" org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [model.Stut#1]
6 由于第5步已经报错,第六步就不执行了
5)判断sql语句的执行
Session se = HiberHelper.getSession();
se.beginTransaction();
Stut s = se.get(Stut.class, "2");// 1
s.setClazz("Oralce669");
se.update(s);// 2
Stut s2 = new Stut();
s2.setId("2");
se.delete(s2);// 3
se.getTransaction().commit();// 4
日志打印结果:
1 执行select查询语句,将id为2的对象加入缓存和快照
2 不执行任何语句
3 执行select语句,同时紧接着报错:
Exception in thread "main" javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [model.Stut#2]
为什么会执行select语句呢?
s2对象在创建时,处于脱管状态,即数据库中存在该id对应的对象,但是session中不存在该对象(session中的s与s2不是同一个对象)。当删除脱管对象时,hibernate的执行流程是,先将该对象查询出来,添加进入缓存和快照,然后接着从缓存和快照中删除该对象,同时暂存一条delete语句(并不会立即执行,等事务提交时一起执行)。
所以当执行select查询对象后,将对象加入缓存时,发现缓存中已经有id为2的对象了,于是就报出了 “different object with the same identifier” 的error。
4 由于第3步报错,第四步不执行了。
Session se = HiberHelper.getSession();
se.beginTransaction();
Stut s = new Stut();
s.setId("2");
se.update(s);//1
System.out.println(s.getClazz());
Stut s1 = new Stut();
s1.setId("3");
se.update(s1);//2
se.getTransaction().commit();//3
结果分析:
1 无sql执行
2 无sql执行
3 分别针对两处update,打印两条update语句,但是对于第二条update执行结果,报错,事务回滚,数据库无更新。
错误如下:
Exception in thread "main" javax.persistence.OptimisticLockException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
意思就是,本期望能返回更改行的数量为1,但是第二条update语句返回更改行数量为0,所以报错。
从状态转换图也可以得知,s对象处于脱管状态,s1对象处于瞬时态,我们可以将脱管态的s通过update操作变为持久态;但是瞬时态的对象是无法进行update操作的,只能进行saveOrUpdate或save操作变为持久态,所以报错。
另外,程序运行到1节点时,并无任何sql语句的打印,就将该对象由脱管态变为了持久态,那hibernate背后进行了哪些操作呢?
我们可以有两种猜想方案:
1 hibernate将s对象放入session缓存的管理,同时为了保证事务的提交时,能够完整的修改该数据库对象,会在快照中放一个同id的空对象,这些在事务提交时,会比对缓存与快照,然后生成一个对所有字段修改的update语句。
或者也可以这样认为:
2 hibernate将s对象同时放入session缓存和快照中(当然快照中放入的是副本),并且暂存一条update语句,当事务提交时,再执行该update。
(偏向与第二条猜想)
7)判断sql的执行
Session se = HiberHelper.getSession();
se.beginTransaction();
Stut s = new Stut();
s.setId("2");
se.update(s);// 1
s.setName("666");
se.delete(s);// 2
se.getTransaction().commit();// 3
日志打印结果:
1 无sql执行打印
2 无sql执行打印
3 事务提交,先打印update语句,再打印delete语句,数据库中的数据被删除。
说明,在第1步和第2步,对session和快照进行了操作,缓存了sql语句,然后在最后提交时将缓存的sql执行。(所以第6部分,对update操作的第二种猜想更合理一些)
8)判断sql的执行
Session se = HiberHelper.getSession();
se.beginTransaction();
Stut s = new Stut();
s.setId("5");
s.setClazz("Oracle1666");
s.setName("Zhansan");
se.save(s);//1
s.setClazz("Oracle1603");
se.evict(s);//2
se.getTransaction().commit();//3
结果:
1 无sql执行
2 无sql执行
3 无sql执行
从执行结果可以得出,当evict移除对象时,对对象所做的增删改的操作都会被取消
9)hql中的集合操作
Session se = HiberHelper.getSession();
se.beginTransaction();
String hql ="select s from Stut s where id in (:ids) ";
Query q = se.createQuery(hql);
String[] ids = new String[]{"1","2"};
q.setParameterList("ids", ids);
List list = q.list();
System.out.println(list.size());
se.getTransaction().commit();
以上是在hibernate学习过程中,我们遇到的一些比较特殊的案例,通过查阅资料和思考,我们能够根据案例,进一步的理解hibernate的执行逻辑,加深知识的掌握。