Hibernate 进阶

作者: 廖少少 | 来源:发表于2017-02-06 20:38 被阅读326次

    本文包括:

    1、Hibernate的持久化类

    2、Hibernate 持久化对象的三个状态(难点)

    3、Hibernate 的一级缓存

    4、Hibernate 中的事务与并发

    5、Hibernate 的查询方式(HQL:Hibernate Query Language)

    1、Hibernate的持久化类

    • 什么是持久化类?

      持久化类:就是一个 Java 类(咱们编写的 JavaBean),这个 Java 类与表建立了映射关系就可以成为是持久化类。

      简单记:持久化类 = JavaBean + xxx.hbm.xml

    • 持久化类的编写规则:

      1. 提供一个无参数 public访问控制符的构造器 —— 底层需要进行反射。

      2. 提供一个标识属性,映射数据表主键字段 —— 唯一标识 OID,数据库中通过主键,Java 对象通过地址确定对象,持久化类通过唯一标识 OID 确定记录。

      SQL 语句:cust_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)'

      JavaBean 代码:private Long cust_id;

      结论:JavaBean 中的 cust_id 即为唯一标识 OID。

      1. 所有属性提供 public 访问控制符的 set 和 get 方法。

      2. 标识属性应尽量使用基本数据类型的包装类型(默认值为 null)。

    • 自然主键和代理主键的区别:

      • 自然主键:该主键是对象本身的一个属性。例如:创建一个人员表,每个人都有一个身份证号(唯一的)。使用身份证号作为表的主键,称为自然主键。(开发中不会使用这种方式

      • 代理主键:该主键不是对象本身的一个属性。例如:创建一个人员表,为每个人员单独创建一个字段,用这个字段作为主键,称为代理主键。(开发中推荐使用这种方式

      简单记:创建表时,新增一个毫无关系的字段,用该字段作为主键。

    • 主键的生成策略:

      1. increment:适用于 short,int,long 作为主键,没有使用数据库的自动增长机制。

        • Hibernate 中提供的一种增长机制,具体步骤如下:

          • 先进行查询:select max(id) from user;

          • 再进行插入:获得最大值+1作为新的记录的主键。

        • 问题:不能在集群环境下或者有并发访问的情况下使用。

        分析:在查询时,有可能有两个用户几乎同时得到相同的 id,再插入时就有可能主键冲突!

      2. identity:适用于 short,int,long 作为主键。但是必须使用在有自动增长机制的数据库中,并且该数据库采用的是数据库底层的自动增长机制。

        • 底层使用的是数据库的自动增长(auto_increment),像 Oracle 数据库没有自动增长机制,而MySql、DB2 等数据库有自动增长的机制。
      3. sequence:适用于 short,int,long 作为主键,底层使用的是序列的增长方式。

        • Oracle 数据库底层没有自动增长,若想自动增长需要使用序列。
      4. uuid:适用于 char,varchar 类型的作为主键。

        • 使用随机的字符串作为主键.
      5. native:本地策略。根据底层的数据库不同,自动选择适用于该种数据库的生成策略(short,int,long)。

        • 如果底层使用的 MySQL 数据库:相当于 identity.

        • 如果底层使用 Oracle 数据库:相当于 sequence.

      6. assigned:主键的生成不用 Hibernate 管理了,必须手动设置主键。

      重点掌握:uuid(字符串)、native(数字)

    2、Hibernate 持久化对象的三个状态(难点)

    • 持久化对象的状态

      1. Hibernate 的持久化类(前文已写)

      2. Hibernate 的持久化类的状态

        • Hibernate为了管理持久化类:将持久化类分成了三个状态
          1. 瞬时态:Transient Object

            • 没有持久化标识 OID, 没有被纳入到 Session 对象的管理(即没有关系)
          2. 持久态:Persistent Object

            • 有持久化标识 OID,已经被纳入到 Session 对象的管理.
          3. 托管态:Detached Object

            • 有持久化标识 OID,没有被纳入到 Session 对象的管理.
    • Hibernate 持久化对象的状态的转换

      1. 瞬时态 -- 没有持久化标识 OID, 没有被纳入到 Session 对象的管理

        • 获得瞬时态的对象

            User user = new User();
          

          创建了持久化类的对象,该对象还没有 OID,也和 Session 对象无关,所以是瞬时态。

        • 瞬时态对象转换持久态

            session.save(user); 或者 session.saveOrUpdate(user);
          

          user对象进入缓存,且自动生成了 OID,故为持久态。

        • 瞬时态对象转换成托管态

            user.setId(1);
          

          手动设置了 OID,但没有和 Session 对象发生关系,故为托管态。

      2. 持久态 -- 有持久化标识OID,已经被纳入到Session对象的管理

        • 获得持久态的对象

            get()/load();
          
        • 持久态转换成瞬时态对象

            delete();  --- 比较有争议的,进入特殊的状态(删除态:Hibernate中不建议使用的)
          
        • 持久态对象转成脱管态对象

            session的close()/evict()/clear();
          

          Session 对象被销毁,所以持久化类的对象没有被 session 管理,所以为托管态。

      3. 脱管态 -- 有持久化标识OID,没有被纳入到Session对象的管理

        • 获得托管态对象:不建议直接获得脱管态的对象.

            User user = new User();
            user.setId(1);
          
        • 脱管态对象转换成持久态对象

            update();/saveOrUpdate()/lock();
          
        • 脱管态对象转换成瞬时态对象

            user.setId(null);
          
      4. 注意:持久态对象有自动更新数据库的能力!!!

        • 测试代码:

            /**
             * 持久态的对象有自动更新数据库的能力
             * session的一级缓存!!
             */
            @Test
            public void run1(){
                Session session = HibernateUtils.getSession();
                Transaction tr = session.beginTransaction();
                
                // 获取到持久态的对象
                User user = session.get(User.class,1);
                // user是持久态,有自动更新数据库的能力
                System.out.println(user.getName());
                
                // 重新设置新的名称
                user.setName("隔离老王");
                
                // 正常编写代码
                // session.update(user);
                
                tr.commit();
                session.close();
            }
          
        • 执行后,控制台输出:

            Hibernate: 
                select
                    user0_.id as id1_1_0_,
                    user0_.version as version2_1_0_,
                    user0_.name as name3_1_0_,
                    user0_.age as age4_1_0_ 
                from
                    t_user user0_ 
                where
                    user0_.id=?
            小风
            Hibernate: 
                update
                    t_user 
                set
                    version=?,
                    name=?,
                    age=? 
                where
                    id=? 
                    and version=?
          
        • 从中可以发现,在测试代码中我们把 session.update(user); 注释掉了,但是在控制台中发现仍有 update 语句,这就是持久态有自动更新数据库的能力,具体原因是 Session 对象的一级缓存(见下节)。

    1. 三个状态之间的转换图解:

    3、Hibernate 的一级缓存

    • Session 对象的一级缓存

      1. 什么是缓存?

        • 其实就是一块内存空间,将数据源(数据库或者文件)中的数据存放到缓存中。再次获取的时候,直接从缓存中获取,可以提升程序的性能!
      2. Hibernate 框架提供了两种缓存

        • 一级缓存 -- 自带的不可卸载的。一级缓存的生命周期与 session 一致,一级缓存称为 session 级别的缓存.
        • 二级缓存 -- 默认没有开启,需要手动配置才可以使用的。二级缓存可以在多个 session 中共享数据,二级缓存称为是 sessionFactory 级别的缓存.
      3. Session 对象的缓存概述

        • Session 接口中,有一系列的 java 的集合,这些 java 集合构成了 Session 级别的缓存(一级缓存)。将对象存入到一级缓存中,session 没有结束生命周期,那么对象在 session 中存放着。
        • 内存中包含 Session 实例 --> Session 的缓存(一些集合) --> 集合中包含的是缓存对象!
      4. 证明一级缓存的存在,编写查询的代码即可证明

        • 在同一个 Session 对象中两次查询,可以证明使用了缓存。
        • 测试代码:

            @Test
            public void run3(){
                Session session = HibernateUtils.getSession();
                Transaction tr = session.beginTransaction();
                
                // 最简单的证明,查询两次
                User user1 = session.get(User.class, 1);
                System.out.println(user1.getName());
                
                User user2 = session.get(User.class, 1);
                System.out.println(user2.getName());
                
                tr.commit();
                session.close();
            }
          
        • 控制台只输出一次如下信息:

            Hibernate: 
                insert 
                into
                    t_user
                    (version, name, age) 
                values
                    (?, ?, ?)
          
        • 进一步分析,若采用断点的方式 debug,发现在执行 User user2 = session.get(User.class, 1); 时,控制台不输出任何信息,所以证明了一级缓存的存在。

      5. Hibernate 框架是如何做到数据发生变化时进行同步操作的呢?

        • 实验步骤: 使用 get 方法查询 User 对象,然后设置 User 对象的一个属性,注意:没有做 update 操作。最后发现,数据库中的记录也改变了。
        • 原因:利用快照机制来完成的(SnapShot),且该特性正好和持久态拥有自动更新数据库能力相符合。
        • 快照机制:


    • 控制 Session 的一级缓存(了解)

      • 学习Session接口中与一级缓存相关的方法
        • Session.clear() -- 清空缓存。

          • 测试代码:

              /**
               * Session.clear()  -- 清空缓存。
               */
              @Test
              public void run5(){
                  Session session = HibernateUtils.getSession();
                  Transaction tr = session.beginTransaction();
                  
                  // 最简单的证明,查询两次
                  User user1 = session.get(User.class, 1);
                  System.out.println(user1.getName());
                  
                  // 清空缓存
                  session.clear();
                  
                  User user2 = session.get(User.class, 1);
                  System.out.println(user2.getName());
                  
                  tr.commit();
                  session.close();
              }
            
          • 控制台输出:

              Hibernate: 
                  select
                      user0_.id as id1_1_0_,
                      user0_.version as version2_1_0_,
                      user0_.name as name3_1_0_,
                      user0_.age as age4_1_0_ 
                  from
                      t_user user0_ 
                  where
                      user0_.id=?
              隔离老王
              Hibernate: 
                  select
                      user0_.id as id1_1_0_,
                      user0_.version as version2_1_0_,
                      user0_.name as name3_1_0_,
                      user0_.age as age4_1_0_ 
                  from
                      t_user user0_ 
                  where
                      user0_.id=?
              隔离老王
            
          • 由此可见,clear方法可以清除缓存。

        • Session.evict(Object entity) -- 从一级缓存中清除指定的实体对象。

          • 若把上面代码中的 session.clear(); 改为 session.evict(user1);,则控制台输出仍如上。
        • Session.flush() -- 刷出缓存

          • 在一般的快照机制中,是在事务提交时(tr.commit();),比较缓存与快照的不同,然后 Hibernate 框架自动执行 update SQL 语句,再更新数据库。

          • 而如果调用 flush 方法,则在执行该方法时就比较缓存与快照的不同,然后 Hibernate 框架自动执行 update SQL 语句,最后在事务提交时更新数据库。

          上面两点的区别本人经过断点调试一一验证,以保证其正确性。

    4、Hibernate 中的事务与并发

    • 事务相关的概念

      1. 什么是事务

        • 事务就是逻辑上的一组操作,组成事务的各个执行单元,操作要么全都成功,要么全都失败.
        • 转账的例子:冠希给美美转钱,扣钱,加钱。两个操作组成了一个事情!
      2. 事务的特性

        • 原子性 -- 事务不可分割.
        • 一致性 -- 事务执行的前后数据的完整性保持一致.
        • 隔离性 -- 一个事务执行的过程中,不应该受到其他的事务的干扰.
        • 持久性 -- 事务一旦提交,数据就永久保持到数据库中.
      3. 如果不考虑隔离性:引发一些读的问题

        • 脏读 -- 一个事务读到了另一个事务未提交的数据(数据库隔离中最重要的问题)
        • 不可重复读 -- 一个事务读到了另一个事务已经提交的 update 数据,导致多次查询结果不一致.
        • 虚读 -- 一个事务读到了另一个事务已经提交的 insert 数据,导致多次查询结构不一致.
      4. 通过设置数据库的隔离级别来解决上述读的问题

        • 未提交读:以上的读的问题都有可能发生.
        • 已提交读:避免脏读,但是不可重复读,虚读都有可能发生.
        • 可重复读:避免脏读,不可重复读.但是虚读是有可能发生.
        • 串行化:以上读的情况都可以避免.
      5. 如果想在Hibernate的框架中来设置隔离级别,需要在 hibernate.cfg.xml 的配置文件中通过标签来配置

        • 通过:hibernate.connection.isolation = 4 来配置
        • 取值:
          • 1—Read uncommitted isolation
          • 2—Read committed isolation
          • 4—Repeatable read isolation
          • 8—Serializable isolation
    • 丢失更新的问题

      1. 如果不考虑隔离性,也会产生写入数据的问题,这一类的问题叫丢失更新的问题。

      2. 例如:两个事务同时对某一条记录做修改,就会引发丢失更新的问题。

        • A 事务和 B 事务同时获取到一条数据,同时再做修改
        • 如果 A 事务修改完成后,提交了事务
        • B 事务修改完成后,不管是提交还是回滚,如果不做处理,都会对数据产生影响


      3. 解决方案有两种

        • 悲观锁

          • 采用的是数据库提供的一种锁机制,如果采用做了这种机制,在SQL语句的后面添加 for update 子句

            • 当A事务在操作该条记录时,会把该条记录锁起来,其他事务是不能操作这条记录的。

            • 只有当A事务提交后,锁释放了,其他事务才能操作该条记录

        • 乐观锁

          • 使用的不是数据库锁机制,而是采用版本号的机制来解决的。给表结构添加一个字段 version=0,默认值是0

            • 当 A 事务在操作完该条记录,提交事务时,会先检查版本号,如果发生版本号的值相同时,才可以提交事务。同时会更新版本号 version=1.

            • 当 B 事务操作完该条记录时,提交事务时,会先检查版本号,如果发现版本不同时,程序会出现错误。

      4. 使用 Hibernate 框架解决丢失更新的问题

        • 悲观锁(效率较低,不常见)

          • 使用 session.get(Customer.class, 1,LockMode.UPGRADE); 方法
        • 乐观锁

          • 在对应的 JavaBean 中添加一个属性,名称可以是任意的。例如:private Integer version; 提供 get 和 set 方法

          • 在映射的配置文件中,提供<version name="version"/>标签即可。

      附上本人以前学习的笔记,从 JDBC 角度分析事务、隔离级别、丢失更新问题:http://www.jianshu.com/p/aacde54542b5

    • 绑定本地的 Session

      1. 在上文所附的文章里,讲解了 JavaWEB 的事务,需要在业务层使用 Connection 来开启事务。

        • 一种是通过参数的方式,一层一层的传递下去
        • 另一种是把 Connection 绑定到 ThreadLocal 对象中,因为 ThreadLocal 对于内通过 map 存储了 key = 当前线程, value= Connection对象

        ThreadLocal 两个重要方法

        • get
          public T get() 返回此线程局部变量的当前线程副本中的值。如果变量没有用于当前线程的值,则先将其初始化为调用 initialValue() 方法返回的值。
          返回:
          此线程局部变量的当前线程的值
        • set
          public void set(T value) 将此线程局部变量的当前线程副本中的值设置为指定值。大部分子类不需要重写此方法,它们只依靠 initialValue() 方法来设置线程局部变量的值。
          参数:
          value - 存储在此线程局部变量的当前线程副本中的值。
      2. 在 Hibernate 框架中,使用 session 对象开启事务,而 session 对象存在于业务层中,所以需要把 session 对象传递到持久层,在持久层使用 session 对象操作数数据对象,框架提供了 ThreadLocal 的方式:

        • 首先需要在 hibernate.cfg.xml 的配置文件中提供配置

            <property name="hibernate.current_session_context_class">thread</property>
          
        • 然后重写 HibernateUtil 的工具类,使用 SessionFactory 的 getCurrentSession( )方法,获取当前的 Session 对象。并且该 Session 对象不用手动关闭,线程结束了,会自动关闭。

          • HibernateUtil 工具类:

              package com.itheima.utils;
              
              import org.hibernate.Session;
              import org.hibernate.SessionFactory;
              import org.hibernate.cfg.Configuration;
              
              /**
               * Hibernate框架的工具类
               * @author Administrator
               */
              public class HibernateUtils {
                  
                  // ctrl + shift + x
                  private static final Configuration CONFIG;
                  private static final SessionFactory FACTORY;
                  
                  // 编写静态代码块
                  static{
                      // 加载XML的配置文件
                      CONFIG = new Configuration().configure();
                      // 构造工厂
                      FACTORY = CONFIG.buildSessionFactory();
                  }
                  
                  /**
                   * 从工厂中获取Session对象
                   * @return
                   */
                  public static Session getSession(){
                      return FACTORY.openSession();
                  }
                  
                  /**
                   * // 从ThreadLocal类中获取到session的对象
                   * @return
                   */
                  public static Session getCurrentSession(){
                      return FACTORY.getCurrentSession();
                  }
                  
              }
            
          • UserService 业务层:

              package com.itheima.service;
              
              import org.hibernate.Session;
              import org.hibernate.Transaction;
              
              import com.itheima.dao.UserDao;
              import com.itheima.domain.User;
              import com.itheima.utils.HibernateUtils;
              
              public class UserService {
                  
                  public void save(User u1,User u2){
                      UserDao dao = new UserDao();
                      // 获取session
                      Session session = HibernateUtils.getCurrentSession();
                      // 开启事务
                      Transaction tr = session.beginTransaction();
                      try {
                          dao.save1(u1);
                          int a = 10/0;
                          dao.save2(u2);
                          // 提交事务
                          tr.commit();
                      } catch (Exception e) {
                          e.printStackTrace();
                          // 出现问题:回滚事务
                          tr.rollback();
                      }finally{
                          // 自己释放资源,现在,session不用关闭,线程结束了,自动关闭的!!
                      }
                  }
              }
            
          • UserDao 持久层:

              package com.itheima.dao;
              
              import org.hibernate.Session;
              
              import com.itheima.domain.User;
              import com.itheima.utils.HibernateUtils;
              
              public class UserDao {
                  
                  public void save1(User u1){
                      Session session = HibernateUtils.getCurrentSession();
                      session.save(u1);
                      //不用写 session.close; 
                  }
                  
                  public void save2(User u2){
                      Session session = HibernateUtils.getCurrentSession();
                      session.save(u2);
                  }
              
              }
            
        • 注意:想使用 getCurrentSession() 方法,必须要先配置才能使用。

    5、Hibernate 的查询方式(HQL:Hibernate Query Language)

    • Query 查询接口

      • 具体的查询代码如下:

          // 1.查询所有记录
          /*Query query = session.createQuery("from Customer");
          List<Customer> list = query.list();
          System.out.println(list);*/
          
          // 2.条件查询(? 从0开始)
          /*Query query = session.createQuery("from Customer where name = ?");
          query.setString(0, "李健");
          List<Customer> list = query.list();
          System.out.println(list);*/
          
          // 3.条件查询(设置别名)
          /*Query query = session.createQuery("from Customer where name = :aaa and age = :bbb");
          query.setString("aaa", "李健");
          query.setInteger("bbb", 38);
          List<Customer> list = query.list();
          System.out.println(list);*/
        
          // 4.模糊查询(注意 % 要写在 setString 里面)
          /*Query query = session.createQuery("from User where name like ?");
          query.setString(0, "%老%");
          List<Customer> list = query.list();
          System.out.println(list);*/
        

        在 JDBC 中,记录从1开始!
        在 HQL 中,记录从0开始!

    • Criteria 查询接口(做条件查询非常合适)

      • 完全面向对象,代码中基本不会体现 SQL 语言的特点

      • Criterion 是 Hibernate 提供的条件查询的对象,如果想传入条件,可以使用工具类 Restrictions ,在其内部有许多静态方法可以用来描述条件

      • 具体的查询代码如下

          // 1.查询所有记录
          /*Criteria criteria = session.createCriteria(Customer.class);
          List<Customer> list = criteria.list();
          System.out.println(list);*/
          
          // 2.条件查询(查询 name 字段为李健的记录)
          /*Criteria criteria = session.createCriteria(Customer.class);
          criteria.add(Restrictions.eq("name", "李健"));
          List<Customer> list = criteria.list();
          System.out.println(list);*/
          
          // 3.条件查询(查询 name 字段为李健、age 字段为38的记录)
          /*Criteria criteria = session.createCriteria(Customer.class);
          criteria.add(Restrictions.eq("name", "李健"));
          criteria.add(Restrictions.eq("age", 38));
          List<Customer> list = criteria.list();
          System.out.println(list);*/
        
          // Restrictions 提供了许多静态方法来描述条件
          criteria.add(Restrictions.gt("age", 18)); // gt:大于
          criteria.add(Restrictions.like("name", "%小%")); // like:模糊查询

    相关文章

      网友评论

        本文标题:Hibernate 进阶

        本文链接:https://www.haomeiwen.com/subject/agvuittx.html