zoukankan      html  css  js  c++  java
  • hiernate-session

    一、概述

    Session 是 Hibernate 向应用程序提供操纵数据的主要接口,它提供了基本的保存、更新、删除和加载 Java 对象的方法。

    二、Session 缓存

    1.简介

    (1)Session 有一个缓存,称为 Hibernate 一级缓存。位于缓存中的对象称为持久化对象,每一个持久化对象与数据库中的一条记录对应。

    (2)站在持久化的角度,Hibernate 将对象分为 4 种状态:临时状态、持久化状态、游离状态、删除状态。

    2.测试 Session 缓存

    (1)准备

    ①hibernate.cfg.xml 文件请参看上一篇文章。

    ②SessionFactory、Session、Transaction

    复制代码
    private SessionFactory sessionFactory;
    private Session session;
    private Transaction transaction;
    
    @Before
    public void init() {
        Configuration configuration = new Configuration().configure();
        ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
        sessionFactory = configuration.buildSessionFactory(serviceRegistry);
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();
    }
    
    @After
    public void destroy() {
        transaction.commit();
        session.close();
        sessionFactory.close();
    }
    复制代码

    说明:使用单元测试类进行测试。因为是测试环境,不存在并发的情况,创建了一个 Session 对象。

    (2)测试

    复制代码
    @Test
    public void testSession() {
        News news = (News) session.get(News.class, 1);
        System.out.println(news);
    
        News news2 = (News) session.get(News.class, 1);
        System.out.println(news2);
    
        System.out.println(news.equals(news2));
    }
    复制代码

    测试结果:

    复制代码
    Hibernate: 
        select
            news0_.id as id1_0_0_,
            news0_.title as title2_0_0_,
            news0_.author as author3_0_0_,
            news0_.date as date4_0_0_ 
        from
            hibernate.news news0_ 
        where
            news0_.id=?
    News{id=1, title='Title', author='tom', date=2016-09-28}
    News{id=1, title='Title', author='tom', date=2016-09-28}
    true
    复制代码

    说明:

    第一次查询的时候,会将引用赋值给 news,同时向 Session 缓存中存入了一份。

    第二次查询的时候,并没有发送 select 语句,而是从 Session 缓存中直接获取的。

    3.操纵 Session 缓存

    (1)flush() :使数据表中的记录和 Session 缓存中的对象的状态保持一致。

    ① 在 Transaction 的 commit() 方法中,先调用 session 的 flush 方法,再提交事务。

    org.hibernate.engine.transaction.spi.AbstractTransactionImpl#commit

    复制代码
    @Override
    public void commit() throws HibernateException {
        if ( localStatus != LocalStatus.ACTIVE ) {
            throw new TransactionException( "Transaction not successfully started" );
        }
    
        LOG.debug( "committing" );
    
        beforeTransactionCommit();
    
        try {
            doCommit();
            localStatus = LocalStatus.COMMITTED;
            afterTransactionCompletion( Status.STATUS_COMMITTED );
        }
        catch ( Exception e ) {
            localStatus = LocalStatus.FAILED_COMMIT;
            afterTransactionCompletion( Status.STATUS_UNKNOWN );
            throw new TransactionException( "commit failed", e );
        }
        finally {
            invalidate();
            afterAfterCompletion();
        }
    }
    复制代码

    org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction#beforeTransactionCommit

    复制代码
    protected void beforeTransactionCommit() {
        this.transactionCoordinator().sendBeforeTransactionCompletionNotifications(this);
        if(this.isDriver && !this.transactionCoordinator().getTransactionContext().isFlushModeNever()) {
            this.transactionCoordinator().getTransactionContext().managedFlush();
        }
    
        if(this.isDriver) {
            this.transactionCoordinator().getTransactionContext().beforeTransactionCompletion(this);
        }
    
    }
    复制代码

    ② 可能会打印 SQL 语句,但是不会提交事务。

    ③ 在未提交事务或显式的调用 flush() 方法前,也可能会进行 flush() 操作。

    • 执行 HQL 或 QBC 查询,会先进行 flush() 操作,以得到数据表的最新记录。
    • 若记录的 ID 是由数据库使用的自增的方式生成的,则在调用 save() 方法时,就会立即发送 INSERT 语句,因为 save 方法后,必须保证对象的 ID 存在。

    (2)refresh():会强制发送 SELECT 语句,以使 Session 缓存中对象的状态和数据表中对应的记录保持一致。

    复制代码
    1 @Test
    2 public void testRefresh() {
    3     News news = (News) session.get(News.class, 1);
    4     System.out.println(news);
    5     session.refresh(news);
    6     System.out.println(news);
    7 }
    复制代码

    我在第5行断点,然后修改数据库中 News 的 `author` 字段,改为 jerry。执行。

    两次打印结果相同。

    Hibernate: 
        select
            news0_.id as id1_0_0_,
            news0_.title as title2_0_0_,
            news0_.author as author3_0_0_,
            news0_.date as date4_0_0_ 
        from
            hibernate.news news0_ 
        where
            news0_.id=?
    News{id=1, title='Title', author='tom', date=2016-09-28}
    Hibernate: 
        select
            news0_.id as id1_0_0_,
            news0_.title as title2_0_0_,
            news0_.author as author3_0_0_,
            news0_.date as date4_0_0_ 
        from
            hibernate.news news0_ 
        where
            news0_.id=?
    News{id=1, title='Title', author='tom', date=2016-09-28}
    View Code

    原因:数据库的隔离级别,Mysql 默认隔离级别为 REPEATABLE READ。

    在 Hibernate 的配置文件中可以显式的设置隔离级别. 每一个隔离级别都对应一个整数:

    1. READ UNCOMMITED

    2. READ COMMITED

    4. REPEATABLE READ

    8. SERIALIZEABLE

    Hibernate 通过为 Hibernate 映射文件指定 hibernate.connection.isolation 属性来设置事务的隔离级别。
    修改后的打印结果:

    两次打印结果不同。

    Hibernate: 
        select
            news0_.id as id1_0_0_,
            news0_.title as title2_0_0_,
            news0_.author as author3_0_0_,
            news0_.date as date4_0_0_ 
        from
            hibernate.news news0_ 
        where
            news0_.id=?
    News{id=1, title='Title', author='jerry', date=2016-09-28}
    Hibernate: 
        select
            news0_.id as id1_0_0_,
            news0_.title as title2_0_0_,
            news0_.author as author3_0_0_,
            news0_.date as date4_0_0_ 
        from
            hibernate.news news0_ 
        where
            news0_.id=?
    News{id=1, title='Title', author='tom', date=2016-09-28}
    View Code

    (3)clear():清理缓存。

    @Test
    public void testClear() {
        session.get(News.class, 1);
        session.clear();
        session.get(News.class, 1);
    }

    输出结果:

    Hibernate: 
        select
            news0_.id as id1_0_0_,
            news0_.title as title2_0_0_,
            news0_.author as author3_0_0_,
            news0_.date as date4_0_0_ 
        from
            hibernate.news news0_ 
        where
            news0_.id=?
    Hibernate: 
        select
            news0_.id as id1_0_0_,
            news0_.title as title2_0_0_,
            news0_.author as author3_0_0_,
            news0_.date as date4_0_0_ 
        from
            hibernate.news news0_ 
        where
            news0_.id=?
    View Code

    三、Session API

    1.四种状态的转换图

    (1)临时对象

    • 在使用代理主键的情况下,OID 通常为 null
    • 不处于 Session 的缓存中
    • 在数据库中没有对应的记录

    (2)持久化对象

    • OID 不为空
    • 位于 Session 缓存中
    • 在同一个 Session 实例的缓存中,数据库表中的每条记录只对应唯一的持久化对象

    (3)游离对象

    • OID 不为空
    • 不处于 Session 缓存中

    (4)删除对象

    • 在数据库中没有和其 OID 对应的记录
    • 不再处于 Session 缓存中

    2.save()

    (1)将一个临时对象转变为持久化对象

    (2)为对象分配 ID

    (3)在 flush 缓存的时候,计划执行一条 INSERT 语句

    (4)在 save() 方法前的 id 是无效的

    (5)持久化对象的 ID 是不能被更改的。因为 Hibernate 通过持久化对象的 OID 来维持它与数据库相关记录的对应关系。

    * persist() 和 save() 区别

    对一个 OID 不为 Null 的对象执行 save() 方法时,会把该对象以一个新的 OID 保存到数据库中,而 persist() 则会抛出一个异常。

    3.get()/load()

    (1)都可以根据 OID 从数据库中加载一个持久化对象。

    (2)执行 get() 时会立即加载对象。执行 load() ,若不使用该对象,则不会立即执行查询操作,而是返回一个代理对象。

    (3)get() 是立即检索,而 load() 是延迟检索。

    (4)若数据表中没有对应记录,Session 也没有被关闭。get() 返回 null,load() 使用返回对象时抛出异常。

    (5)load() 可能会抛出 LozyInitizationException 异常:在需要初始化代理对象之前已经关闭了 Session。

    @Test
    public void testLoad() {
        News news = (News) session.load(News.class, 1);
        session.close();
        System.out.println(news);
    }
    org.hibernate.LazyInitializationException: could not initialize proxy - no Session

    4.update()

    (1)将一个游离对象转变为持久化对象,并且计划执行一条 update 语句。

    (2)若更新一个持久化对象,不需要显式的调用 update() 方法。因为在调用 Transaction 的 commit() 方法时,会先执行 session 的 flush() 方法。

    (3)注意

    • 无论要更新的游离对象和数据表的记录是否一致,都会发送 UPADATE 语句。如何让只在不一致的情况下发送 UPDATE 语句?在 entity.hbm.xml 文件的 class 节点设置                             select-before-update=true(默认为 false)。通常不需要设置,与触发器协同工作时需要注意。
    • 若数据表中没有对应的记录,但还是调用了 update() 方法,会抛出异常。
    • 当 update() 方法关联一个游离对象时,如果在 Session 缓存中已经存在相同 OID 的持久化对象,会抛出异常。因为在 Session 缓存中不能有两个 OID 相同的对象。

    5.saveOrUpdate()

    (1)同时包含了 save() 和 update() 方法的功能。

    (2)判断是否是游离对象还是临时对象是根据 对象的 OID 来判定的。若为 null ,则执行 save() ,若不为 null,则判定为游离对象,执行 update() 。

    (3)若 OID 不为 null,但数据中还没有与之对应的记录,则会抛出一个异常。

    (4)了解:OID 值等于 id 的 unsaved-value 属性值的对象,也被认为是一个游离对象。

    6.delete()

    (1)既可以删除一个游离对象,也可以删除一个持久化对象。

    (2)只要 OID 和数据表中一条记录对应,就会准备执行 delete 操作,若 OID 在数据表中没有对应的记录,则抛出异常。

    (3)在执行 delete() 后,还是可以获取到对象的 OID,防止对该对象的其他持久化操作,可以通过设置 hibernate 配置文件的 hibernate.use_identifier_rollback 为 true,

    使删除对象后,把其 OID 值为 null。

    7.evict()

    把指定持久化对象从 session 缓存中移除。

    8.调用存储过程

    复制代码
    @Test
    public void testWork() {
        session.doWork(new Work() {
            @Override
            public void execute(Connection connection) throws SQLException {
                System.out.println(connection);
                // 调用存储过程
            }
        });
    }
    复制代码

    四、总结

    介绍了 Hibernate 的一级缓存,包括如何操纵 Session 的缓存,以及四种状态之间的转换,以及建立在 Session 缓存和四种状态基础上的 Session API。

  • 相关阅读:
    网络测量中基于Sketch方法的简单介绍
    Reading SBAR SDN flow-Based monitoring and Application Recognition
    Reading Meticulous Measurement of Control Packets in SDN
    Reading SketchVisor Robust Network Measurement for Sofeware Packet Processing
    ovs加dpdk在日志中查看更多运行细节的方法
    后缀数组
    (转载)LCA问题的Tarjan算法
    Codeforces Intel Code Challenge Final Round (Div. 1 + Div. 2, Combined) A. Checking the Calendar(水题)
    Vijos 1816统计数字(计数排序)
    卡特兰数
  • 原文地址:https://www.cnblogs.com/zhuyuewei/p/6067365.html
Copyright © 2011-2022 走看看