Hibernate主键生成策略持久化对象的三种状态 一级缓存

    技术2022-07-12  87

    主键类型:

    自然主键:把具有业务含义的字段作为主键,称之为自然主键。例如如果在t_user表中把username字段作为主键,其前提条件必须是:每一个用户的姓名不允许为null,不允许用户重名并且不允许修改用户姓名。代理主键:把不具有业务含义的字段作为主键,称之为代理主键。

    Hibernate中的主键生成策略:

    increment(主键自增)用于long、short或int类型,由Hibernate自动以递增的方式生成唯一标识符,每次增量为1,只有当没有其他进程向同一张表中插入数据时才可以使用,不能在集群环境下使用。适用于代理主键identity(主键自增)用于long、short或int类型,主键采用底层数据库本身提供的主键生成标识符,条件是数据库支持自动增长的数据类型。它要求把主键定义成为自动增长类型。适用于代理主键sequence(序列)用于long、short或int类型,Hibernate根据底层数据库序列生成标识符。条件是数据库支持序列。适用于代理主键native根据底层数据库对自动生成标识符的能力来选择identity、sequence、hilo三种生成器中的一种,适用于代理主键uuidHibernate采用128位UUID算法来自动生成标识符,该算法能够在网络环境中生成唯一的字符串标识符,其UUID被编码为一个长度为32位的16进制字符串。适用于代理主键assigned    数据类型不限,由java程序负责生成标识符,Hibernate不管理主键,用户手动设置主键的值。如果不指定id元素的generator属性,则默认使用该主键生成策略。适用于自然主键

    对比一下:

          

     Hibernate持久化对象的三种状态:

    瞬时态瞬时态的实例是由new关键字创建、开辟内存空间的对象,不存在持久化标识OID(相当于主键值)尚未与Hibernate Session关联,在数据库中也没有记录。瞬时态的对象在内存中是孤立的,与数据库中的数据无任何关联,仅是一个信息携带的载体。持久态持久态对象存在持久化标识OID,加入到了Session缓存中,并且相关联的Session没有关闭,在数据库中有对应的记录,每条记录只对应唯一的持久化对象,需要注意的是,持久态对象是在事务还没有提交前变成持久态的。脱管态脱管态对象存在持久化标识OID,并且仍然与数据库中的数据存在关联,只是失去了与当前Session的关联,脱管态对象发生改变时Hibernate不能检测到。

    瞬时态到其他状态的转换

    瞬时态的对象由new关键字创建。

    瞬时态转换为持久态:执行Session的save()或saveOrUpdate()方法。瞬时态转换为脱管态:直接为瞬时态对象设置持久化标识OID。比如直接调用user.setId()方法。

    所以瞬时态和脱管态的本质区别就是看OID有没有值。(都没有被Session管理)

    持久态对象转换到其他状态

    持久化对象可以直接通过Hibernate中的Session的get()方法、load()方法,或者Query查询从数据库中获得。

    执行Session的delete()方法。需要注意的是被删除的持久化对象,不建议再次使用。执行Session的evict()、close()或clear()方法。

    evict()方法用于清除一级缓存中的某一个对象;

    close()方法用于关闭Session,清除一级缓存;

    clear()方法用于清除一级缓存的所有对象。

     

    脱管态对象转换到其他状态

    脱管态对象无法直接获得,是由其他状态对象转换而来的。

    脱管态转换为持久态:执行Session的update()、saveOrUpdate()或lock()方法脱管态转换为瞬时态:将脱管态对象的持久化标识OID设置为null。

    通过一段程序来看看它们之间的转换:

    public class HibernateDemo01 { public static void main(String[] args) { Configuration cfg = new Configuration().configure(); SessionFactory sessionFactory = cfg.buildSessionFactory(); Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); //持久态对象,此时没有持久化标识OID,且没有被session管理 User user = new User(); user.setUserName("杨达"); //执行完save方法后该对象被放入session的一级缓存中成为持久态对象。具有了持久化标识OID,被session管理。 session.save(user); tx.commit(); //session关闭之后user变成脱管态。(具有持久化标识OID,但是没有被session管理) session.close(); sessionFactory.close(); System.out.println(user); System.out.println(user.getId()); } }

    结果如下:

     持久态对象是可以自动更新数据库的

    public class Test { public static void main(String[] args) { Configuration cfg = new Configuration().configure(); SessionFactory sessionFactory = cfg.buildSessionFactory(); Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); User user = session.get(User.class, 1); user.setPassword("11111"); tx.commit(); session.close(); sessionFactory.close(); } }

    可以看看底层的sql语句如下:

    可以看到它在 提交事务时自动发送了一条sql语句。更新了数据库,而程序中并没有调用update方法。

     

    缓存:

    缓存是介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存。

    Hibernate的缓存分为一级缓存和二级缓存,Hibernate的这两级缓存都位于持久化层,存储的都是数据库数据的备份。其中第一级缓存为Hibernate的内置缓存,不能被卸载。

    Hibernate的一级缓存:

    Hibernate的一级缓存就是指Session缓存,Session缓存是一块内存空间,用来存放相互管理的java对象,在使用Hibernate查询对象的时候,首先会使用对象属性的OID值在Hibernate的一级缓存中进行查找,如果找到匹配OID值的对象,就直接将该对象从一级缓存中取出来使用,不会再查询数据库;如果没有找到,则会去数据库中查找相应数据。当从数据库中查询到所需数据时,该数据信息也会放在一级缓存中。Hibernate的一级缓存的作用就是减少对数据库的访问次数。只要Session实例没有被结束生命周期,存放在它缓存中的对象也不会结束生命周期。所以一级缓存也被称为Session基本的缓存。 

    Hibernate一级缓存的特点:

    当应用程序调用Session接口的save()、update()、saveOrUpdate()时,如果Session缓存中没有相应的对象,Hibernate就会自动的把从数据库中查询到的相应对象信息加入到一级缓存中去。

    当调用Session接口的load()、get()方法,以及Query接口的list()、iterator()方法时,会判断缓存中是否存在该对象,有则返回,不会查询数据库,如果缓存中没有要查询的对象,再去数据库中查询,并加到一级缓存中。

    验证一级缓存:

    //一级缓存验证 public class HibernateDemo03 { public static void main(String[] args) { SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); //根据id来查询记录,此时第一次执行查询一级缓存中还没有数据。会发送sql语句。 User user1 = session.get(User.class, 1); System.out.println(user1); //再次执行get可没有发送sql语句。 所以第二次查询到的对象是一级缓存中的对象 User user2 = session.get(User.class, 1); System.out.println(user2); //这里输出为true(比较的是对象引用的地址) System.out.println(user1 == user2); tx.commit(); session.close(); sessionFactory.close(); } }

    结果如下:

     

    上述谈到Hibernate的持久态对象能够自动去更新数据库,其实就是依赖了一级缓存,那么一级缓存为什么就可以去更新数据库了?  其实是因为一块特殊的区域:快照区。 

    Hibernate向一级缓存中存放数据时,同时复制一份数据放入到快照区中,当提交事务时同时会清理Session的一级缓存,这时会使用OID判断一级缓存中的对象和快照区的对象是否一致,如果两个对象中的属性发生变化,则执行update,将缓存中的内容同步到数据库中,并更新快照区;如果一致,则不执行update语句。Hibernate中快照区的作用就是确保一级缓存中的数据和数据库中的数据一致。

    测试原因,我将数据库中的password设置为null。所以此时该数据库表中的password字段值不再是上述的“11111”(“11111”效果也是一样的),而是null。

    程序分析如下:

     

    Processed: 0.012, SQL: 9