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。

  • 相关阅读:
    加分二叉树
    逃离牧场
    [Apio2012]dispatching
    靶形数独
    POJ 1459-Power Network(网络流-最大流-ISAP)C++
    题解 最优的挤奶方案(Optimal Milking)
    [HNOI2007]紧急疏散EVACUATE (湖南2007年省选)
    【LCA求最近公共祖先+vector构图】Distance Queries
    BZOJ1143: [CTSC2008]祭祀river
    BZOJ2140: 稳定婚姻
  • 原文地址:https://www.cnblogs.com/zhuyuewei/p/6067365.html
Copyright © 2011-2022 走看看