Java
Java Web框架

Hibernate(4)

简介:Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。

1. 对象状态

Hibernate规定三种状态:瞬时态、持久态、脱管态

  • 瞬时态:transient,session没有缓存对象,数据库也没有对应记录。OID没有值;
  • 持久态:persistent,session缓存对象,数据库最终会有记录(事务没有提交)。OID有值;
  • 脱管态:detached,session没有缓存对象,数据库有记录。OID特点有值。

对于以上三类对象的转换有以下的示意图:

1.对象状态的转换.png

三种状态的互相转换的测试代码如下:

  • @Test
  • // 演示三种状态
  • public void fun1() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • User u = new User(); // 瞬时状态
  • u.setName("tom"); // 瞬时状态
  • u.setPassword("1234"); // 瞬时状态
  • /**
  • 持久状态
  • 问题: 调用完save方法,数据库中有没有对应记录?
  • 没有对应记录,但是最终会被同步到数据库中,仍然是持久状态。
  • */
  • session.save(u);
  • // ------------------------------------------------
  • session.getTransaction().commit(); // 持久状态
  • session.close(); // 游离状态
  • }
  • 瞬时态转为持久态
  • @Test
  • public void fun2() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • User u = new User(); // 瞬时状态
  • u.setName("tom"); // 瞬时状态
  • u.setPassword("1234"); // 瞬时状态
  • /**
  • 持久状态。save方法会使用主键生成策略,为User指定id
  • 主键自增:打印insert语句
  • */
  • session.save(u);
  • // ------------------------------------------------
  • // 当User的id属性设置为increment时,会使用select max(id) ....语句查询最大的id值并进行自增;但当User的id属性设置为assigned,则需要手动指定主键,不指定将会报错。
  • session.getTransaction().commit(); // 持久状态
  • // 事务提交时,会把持久化状态对象同步到数据库中
  • session.close(); // 游离状态
  • }
  • 瞬时态转为游离态, (没有关联,没有id)转为(没有关联,有id,与数据库中对应的id)
  • @Test
  • public void fun3() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • User u = new User(); // 瞬时状态
  • u.setId(1); // 游离
  • // ----------------------------------------------------
  • session.getTransaction().commit(); // 持久状态
  • // 事务提交时,会把持久化状态对象同步到数据库中
  • session.close(); // 游离状态
  • }
  • 持久态瞬时态,(有关联,有id)转为(无关联,无id)
  • @Test
  • public void fun4() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • // 通过get方法,得到持久状态对象
  • User u = (User) session.get(User.class, 1); // 持久状态
  • // ----------------------------------------------------
  • session.getTransaction().commit(); // 持久状态
  • // 事务提交时,会把持久化状态对象同步到数据库中
  • session.close(); // 游离状态
  • u.setId(null);// 瞬时状态
  • }
  • 持久态转为瞬时态,(有关联,有id)转为(无关联,无id)
  • @Test
  • public void fun5() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • // 通过get方法,得到持久状态对象
  • User u = (User) session.get(User.class, 1); // 持久状态
  • session.evict(u);// 将User对象与session的关联移除
  • u.setId(null);// 瞬时状态
  • session.save(u);// 持久状态
  • // ----------------------------------------------------
  • session.getTransaction().commit(); // 持久状态
  • // 事务提交时,会把持久化状态对象同步到数据库中
  • session.close(); // 游离状态
  • }
  • 持久态转为游离态,(只需要将session的关联取消)
  • @Test
  • public void fun6() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • // 通过get方法,得到持久状态对象
  • User u = (User) session.get(User.class, 1); // 持久状态
  • session.evict(u);// 游离
  • // ----------------------------------------------------
  • session.getTransaction().commit(); // 游离状态
  • session.close(); // 游离状态
  • }
  • 游离态转为瞬时态,(移除ID)
  • @Test
  • public void fun7() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • // 通过get方法,得到持久状态对象
  • User u = (User) session.get(User.class, 1); // 持久状态
  • session.evict(u);// 游离
  • u.setId(null);// 瞬时
  • // ----------------------------------------------------
  • session.getTransaction().commit(); // 瞬时状态
  • session.close(); // 瞬时状态
  • }
  • 游离态转为持久态,(是否与Session关联)
  • @Test
  • public void fun8() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • // 通过get方法,得到持久状态对象
  • User u = (User) session.get(User.class, 1); // 持久状态
  • session.evict(u);// 游离
  • session.update(u);// 持久
  • // ----------------------------------------------------
  • session.getTransaction().commit(); // 持久状态,会打印update语句
  • session.close(); // 瞬时状态
  • }

三种状态有什么用?

  • 持久状态,我们使用Hibernate主要是为了持久化我们的数据;
  • 对于对象的状态,我们期望我们需要同步到数据库的数据,都被装换成持久状态;
  • 持久化状态特点:Hibernate会自动将持久化状态对象的变化同步到数据库中。

  • 游离态转为持久态,(是否与Session关联)

  • @Test
  • public void fun9() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • // 通过get方法,得到持久状态对象
  • User u = (User) session.get(User.class, 1); // 持久状态
  • // u.setName("jerry");// 持久状态
  • u.setId(3);// 与session建立关联的对象的ID,不允许修改。
  • session.update(u);// 多余,因为Hibernate会自动将持久化状态对象的变化同步到数据库中
  • // ----------------------------------------------------
  • session.getTransaction().commit(); // 持久状态,会打印update语句
  • session.close(); // 瞬时状态
  • }

2. 一级缓存

一级缓存:又称为session级别的缓存。当获得一次会话(session),Hibernate在session中创建多个集合(map),用于存放操作数据(PO对象),为程序优化服务,如果之后需要相应的数据,Hibernate优先从session缓存中获取,如果有就使用;如果没有再查询数据库。当session关闭时,一级缓存销毁。

2.1. 证明一级缓存

  • @Test
  • // 证明session缓存的存在
  • public void fun1() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • // 发送select语句,从数据库取出记录,并封装成对象
  • // 持久化状态对象=> 存到缓存中
  • User u1 = (User) session.get(User.class, 1);
  • // 再次查询时,会从缓存中查找,不会发送select
  • User u2 = (User) session.get(User.class, 1);
  • // 再次查询时,会从缓存中查找,不会发送select
  • User u3 = (User) session.get(User.class, 1);
  • System.out.println(u1 == u2);// true
  • System.out.println(u1 == u3);// true
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

移除缓存操作:

  • @Test
  • public void demo03(){
  • //清除缓存
  • Session session = factory.openSession();
  • session.beginTransaction();
  • User user = (User) session.get(User.class, 1); //--select
  • System.out.println(user);
  • //清除
  • //session.clear();
  • session.evict(user);
  • // 一级缓存没有缓存对象,从数据库直接查询
  • User user2 = (User) session.get(User.class, 1); //--select
  • System.out.println(user2);
  • session.getTransaction().commit();
  • session.close();
  • }

使用session.evict()可以将对象从缓存中移出。

2.2. 一级缓存和快照

快照:与一级缓存一样的存放位置,对一级缓存数据备份。保证数据库的数据与一级缓存的数据必须一致。如果一级缓存修改了,在执行commit提交时,将自动刷新一级缓存,执行update语句,将一级缓存的数据更新到数据库。

如以下代码:

  • @Test
  • // session缓存中的快照
  • public void fun2() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • User u1 = (User) session.get(User.class, 1);// 发送select语句,从数据库取出记录,并封装成对象
  • session.update(u1);
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

由于有快照的存在,session.update(u1)操作将不会打印任何信息,即不会访问数据库。但是,在如下代码中:

  • @Test
  • // session缓存中的快照
  • public void fun3() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • User u1 = new User();
  • u1.setId(1);
  • u1.setName("jerry");
  • u1.setPassword("1234");
  • session.update(u1);
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

由于事先并没有访问数据库,u1对象并不是从数据库中查询数据封装得到的,所以此时并没有缓存和快照数据,执行session.update(u1)将会访问数据库。

一级缓存是可以减少不必要的操作的,比如以下代码:

  • @Test
  • // 感受一级缓存效率的提高
  • public void fun4() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • User u1 = (User) session.get(User.class, 1);
  • u1.setName("tom");
  • session.update(u1);
  • u1.setName("jack");
  • session.update(u1);
  • u1.setName("rose");
  • session.update(u1);
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

如果使用传统的数据库操作模式,这个方法会访问数据库三次,但是有了Hibernate的一级缓存,只会访问数据库一次。

2.2.1. 一级缓存细节问题

  • @Test
  • // 保存对象时使用 save方法
  • // 保存对象时使用 persist方法
  • // 这二者在使用起来区别? 没有区别
  • // persist方法来自于JPA 接口
  • // save方法来自于Hibernate
  • public void fun1() {
  • Session session = HibernateUtils.openSession();
  • // session.beginTransaction();
  • // ------------------------------------------------
  • User u = new User();
  • u.setName("张三");
  • // session.save(u); // insert语句被打印,是为了获得id
  • session.persist(u);
  • // ------------------------------------------------
  • // session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

HQL查询不会使用一级缓存:

  • @Test
  • public void fun2() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • List<User> list1 = session.createQuery("from User").list();
  • List<User> list2 = session.createQuery("from User").list();
  • List<User> list3 = session.createQuery("from User").list();
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

但HQL语句批量查询时查询结果会放入缓存中:

  • @Test
  • public void fun3() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • List<User> list1 = session.createQuery("from User").list();
  • User u = (User) session.get(User.class, 1);
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

SQL查询结果会不会放入一级缓存中? 如果把查询结果封装到对象中,对象会放入一级缓存。

  • @Test
  • public void fun4() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • List<User> list1 = session.createSQLQuery("select * from t_user")
  • .addEntity(User.class).list();
  • User u = (User) session.get(User.class, 1);
  • System.out.println(u);
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

SQL查询结果会不会放入一级缓存中?没有把查询结果封装到对象中,对象不会放入一级缓存。

  • @Test
  • public void fun5() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • List list1 = session.createSQLQuery("select * from t_user").list();
  • User u = (User) session.get(User.class, 1);
  • System.out.println(u);
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

2.2.2. 一些常见问题

关于save()persist()方法的具体区别。

Persist提供的理念是将对象完整的持久化,持久化也包括对象的ID。如果数据库配置的主键策略为自增,当我们手动指定id并进行Persist操作时,会产生矛盾而直接报错:

  • @Test
  • public void fun1() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • User u = new User();
  • u.setId(99);
  • // 在保存之前设置了id,那么就会将设置的id进行insert,但是主键策略是由数据库来维护,所以产生矛盾而抛出异常
  • session.persist(u);
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

而使用save()方法的时候,保存的对象如果手动指定了id,该id也会被认为无效而忽略:

  • @Test
  • public void fun2() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • User u = new User();
  • u.setId(99);
  • // save方法,如果保存的对象在保存之前设置了id,那么该id也被认为是无效的
  • session.save(u);
  • System.out.println(u.getId());
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

关于HQL查询使用一级缓存的问题。HQL查询结果会放入Session一级缓存中,但是每次调用HQL查询都会生成SQL语句,并不代表HQL没有使用一级缓存.。

  • @Test
  • public void fun1() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • List<User> list1 = session.createQuery("from User").list(); // 会发送sql
  • List<User> list2 = session.createQuery("from User").list();// 会发送sql
  • // 该处打印true,表示两个对象是相同的对象,用到了一级缓存
  • System.out.println(list1.get(0) == list2.get(0));
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

缓存中的数据如果与数据库中的不同步会怎么样?会优先使用缓存中的。在这种情况下需要直接使用JDBC。但是在一级缓存中出现该问题的几率比较小。因为一级缓存的生命周期比较短,一级缓存生命周期会在openSession()开始,在session.close()销毁。

  • @Test
  • public void fun2() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • User u1 = (User) session.get(User.class, 1);
  • // 此处使用SQL工具手动改变数据库中的id为1的数据
  • // 但是此时的u2取出来的数据还是缓存中的数据
  • User u2 = (User) session.get(User.class, 1);
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

2.3. 常用的API

  • evict()clear()
  • @Test
  • // 1. evict 将缓存中的对象移除
  • // 2. clear 清空一级缓存
  • public void fun1() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • User u1 = (User) session.get(User.class, 1);
  • session.clear();
  • User u2 = (User) session.get(User.class, 1);
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }
  • refresh(),强制刷新缓存中的对象:

可以用来解决缓存与数据库数据不同步的问题。

  • @Test
  • public void fun2() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • User u1 = (User) session.get(User.class, 1);
  • session.refresh(u1); // 将缓存中的对象立刻与数据库同步,会再发送一个sql语句
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }
  • flush()对比快照,并提交缓存对象
  • @Test
  • public void fun3() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • User u1 = (User) session.get(User.class, 1);
  • // u1.setName("zhangsan");
  • session.flush();// 立刻提交session缓存中的对象到数据库
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }
  • saveOrUpdate()方法
  1. 当数据库主键是代理主键时(native),saveOrUpdate()可以同时完成保存或更新操作,主键为空时执行save(),主键有值执行update()
  • @Test
  • //
  • //
  • public void fun4() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • User u = new User();
  • u.setId(99);
  • u.setName("jack");
  • u.setPassword("1234");
  • session.saveOrUpdate(u);
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }
  1. 当数据库主键是自然主键时(assigned),saveOrUpdate()也可以同时完成保存或更新操作,但当主键为空时会报错,因为无论是save()还是update()都必须指定id,主键有值时先会根据主键查询数据库,数据库中存在时执行update(),数据库中不存在时执行insert()
  • @Test
  • public void fun5() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • User u = new User();
  • u.setId(88);
  • u.setName("jack01");
  • u.setPassword("1234");
  • session.saveOrUpdate(u);
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }

在我们使用Hibernate时候,注意要避免出现,两个相同的ID对象放入一级缓存的情况,会报错。

  • @Test
  • public void fun6() {
  • Session session = HibernateUtils.openSession();
  • session.beginTransaction();
  • // ------------------------------------------------
  • User u = (User) session.get(User.class, 1);// 持久化,缓存中存在
  • session.evict(u); // 游离态,缓存中不存在
  • User u2 = (User) session.get(User.class, 1);// 持久化,缓存中存在
  • session.update(u); // 将U重新变为持久化状态,缓存中存在
  • // ------------------------------------------------
  • session.getTransaction().commit();
  • session.close(); // 游离状态
  • }