hibernate常见面试题详解

整理hibernate常见面试题

  1. 简述hibernate运行原理或者工作原理
  2. 简述hibernate的get和load方法区别
  3. 简述hibernate数据三种状态
  4. 简述hibernate的缓存机制
  5. 简述hibernate中getCurrentSession和openSession区别
  6. 简述hibernate的乐观锁和悲观锁
  7. 简述hibernate的懒加载机制
  8. 简述hibernate的事务机制

1、hibernate运行原理:
hibernate里面提供了3个核心接口
Configuration、SessionFactory、Session
1、hibernate启动的时候利用Configuration读取xml配置文件
2、通过配置文件创建SessionFactory对象,初始化hibernate基本信息
3、获取session然后调用CRUD方法进行数据操作,hibernate会把我们的数据进行三种状态的划分,然后根据状态进行管理我们的数据,对应的发送SQL进行数据操作
4、关闭session,如果有事务的情况下,需要手动获取事务并开启,然后事务结束后提交事务。
5、在提交事务的时候,去验证我们的快照里面的数据和缓存数据是否一致,如果不一致,发送SQL进行修改,

2、hibernate的get方法和load方法的区别
1、get和load都是利用主键策略查询数据,
2、get默认不使用懒加载机制,load默认要使用懒加载机制,所谓的懒加载就是我们这个数据如果不使用,hibernate就不发送SQL到数据库查询数据。
3、当查询数据库不存在的数据的时候,get方法返回null,load方法抛出空指针异常,
原因是因为,load方法采用的动态代理的方式实现的,我们使用load方法的时候,hibernate会创建一个该实体的代理对象,该代理只保存了该对象的ID,当我们访问该实体对象其他属性,hibernate就发送SQL查询数据封装到代理对象,然后在利用代理对象返回给我们实际的数据,

3、hibernate的数据三种状态
hibernate把他所管理的数据划分为三种状态
瞬时的(刚new出来的数据–内存有,数据库没有)
持久的 (从数据查询的,或者刚保存到数据库,session没关闭的, 数据库有,内存也有)
游离的 、脱管的(数据库有,内存没有)
这里写图片描述

实际上hibernate对数据划分三种状态,主要是为了管理我们持久的数据,在事务提交的时候,hibernate去对比处于持久状态的数据是否发生改变,(快照区、一级缓存区),当我们会话结束前,对持久状态数据进行了修改的话,快照区的数据会跟着改变。当session提交事务的时候,如果发现快照区和一级缓存的数据不一致,就会发送SQL进行修改。

4. 简述hibernate的缓存机制
hibernate分为2级缓存
一级缓存又叫session缓存,又叫事务级缓存,生命周期从事务开始到事务结束,一级缓存是hibernate自带的,暴力使用,当我们一创建session就已有这个缓存了。数据库就会自动往缓存存放,
二级缓存是hibernate提供的一组开放的接口方式实现的,都是通过整合第三方的缓存框架来实现的,二级缓存又叫sessionFactory的缓存,可以跨session访问。常用的EHcache、OScache,这个需要一些配置。

当我们每次 查询数据的时候,首先是到一级缓存查看是否存在该对象,如果有直接返回,如果没有就去二级缓存进行查看,如果有直接返回,如果没有在发送SQL到数据库查询数据,
当SQL发送查询回该数据的时候,hibernate会把该对象以主键为标记的形式存储到二级缓存和一级缓存,如果返回的是集合,会把集合打散然后以主键的形式存储到缓存。一级缓存和二级缓存只针对以ID查询的方式生效,get、load方法。

5. 简述hibernate中getCurrentSession和openSession区别
getCurrentSession和openSession都是通过H的工厂去获取数据库的会话对象,
1、getCurrentSession会绑定当前线程,而openSession不会,因为我们把hibernate交给我们的spring来管理之后,我们是有事务配置,这个有事务的线程就会绑定当前的工厂里面的每一个session,而openSession是创建一个新session。
2、getCurrentSession事务是有spring来控制的,而openSession需要我们手动开启和手动提交事务,
3、getCurrentSession是不需要我们手动关闭的,因为工厂会自己管理,而openSession需要我们手动关闭。
4、而getCurrentSession需要我们手动设置 绑定事务的机制,有三种设置方式,jdbc本地的Thread、JTA、第三种是spring提供的事务管理机制org.springframework.orm.hibernate4.SpringSessionContext,而且srping默认使用该种事务管理机制,

6. 简述hibernate的乐观锁和悲观锁

   hibernate在管理我们数据的时候的,永远无法避免一个问题,那就是并发,

并发指的是多个线程同时访问同一个资源,而并发会存在很多问题,
同步指的是多个线程访问同一资源的时候,当一个线程对该资源的操作完成了以后,才交给下一个线程进行操作,有点像排队
异步指的是多个线程访问同一资源的时候,所有的线程一起访问这个资源,不需要等待其他线程访问完成,也可以进行访问和操作。有点像挤公交车
而我们hiberante在并发的时候会存在如下问题:
1、丢失数据更新
这里写图片描述
如上图我们可以看出,线程1和线程2都对同一条数据进行更新,
在T8时间点线程2已经完成了数据的更新,在T9时间点如果线程1回滚age=20,那线程2的更新age=25就丢失,
另外一种,如果T9时间点 我们线程1提交,那我们线程2的age=25也丢失了
2、数据脏读
这里写图片描述
所谓的数据脏读,就是当并发的时候,一个线程进行修改,还未提交事务的时候另一个线程就读取了更新后的数据,但是后面第一个线程回滚,更新无效,那第二个线程读取到的数据就是脏数据
3、数据虚读或者幻读
这里写图片描述
此处的线程1修改完age之后,线程2立马进来修改了name,并提交事务,线程1在读取我们的数据,发现被修改了2个字段,这就是虚读或者幻读

4、不可重复读
这里写图片描述
此处线程1先查询age=20,线程2把age修改Wie30,线程1再次查询的时候发现age=30,所以重复读取两次数据不一致,所以重复读取出错,

以上问题在并发中都可能存在,所以我们hiberante一定要处理线程并发的情况,hibernate就需要进行线程同步,提供的机制是利用锁来完成,

悲观锁
所谓的悲观锁,就是hibernate心态不好,认为每一次操作都会出现并发,所以当我们查询数据的时候就直接把这一条数据锁住,不让别人操作。底层是利用的数据库的for update来实现的,就是查询数据的时候利用数据库把当前查询的数据给锁住,不让其他线程操作,实现如下:

    Admin admin1 = (Admin) session.get(Admin.class, 1, LockMode.UPGRADE);
    Admin admin2 = (Admin) session.get(Admin.class, 1, LockOptions.UPGRADE);

H4以后就不推荐使用LockMode,而使用LockOptions来实现悲观锁

//      Admin admin = (Admin) session.get(Admin.class, adminId);

        Admin admin1 = (Admin) session.get(Admin.class, 1, LockMode.UPGRADE);
//      Admin admin2 = (Admin) session.get(Admin.class, 1, LockOptions.UPGRADE);

//      SQLQuery q = session.createSQLQuery("select * from xx_plat_admin");
//      q.setLockOptions(LockOptions.UPGRADE);
//      
//      Query q1 = session.createQuery("from Admin admin");
//      q1.setLockMode("admin", LockMode.UPGRADE);
//      q1.setLockOptions(LockOptions.UPGRADE);

发送SQL如下:

Hibernate: select admin0_.admin_id as admin_id1_1_0_, admin0_.admin_pwd as admin_pw2_1_0_, admin0_.admin_role as admin_ro4_1_0_, admin0_.admin_username as admin_us3_1_0_ from xx_plat_admin admin0_ where admin0_.admin_id=? for update

我们发现 后面多一个关键字for update,实际上是利用的数据库的锁机制

所以hibernate的 悲观锁是利用数据库的for update来实现的, 在hibernate查询数据的时候,利用LockOptions.UPGRADE来进行设置
但是悲观锁是在查询的时候每一条数据都会被锁住,只有当是session关闭的时候,锁才会释放,这样效率非常的低下,所以一般不使用。

乐观锁
所谓的乐观锁就是比较乐观的,在hibernate当中提供了两种机制来进行实现
1、version 版本号来验证
2、timestamp 时间戳来验证

<timestamp name=""></timestamp>
<version name=""></version>

xml的配置 很简单,就值为实体增加改属性的配置即可

public void selectAccount() {
        Session session1 = this.sessionFactory.openSession();
        Session session2 = this.sessionFactory.openSession();
        Account a1 = (Account) session1.get(Account.class, 1);
        Account a2 = (Account) session2.get(Account.class, 1);

        System.out.println(a1);
        System.out.println(a2);
        a2.setMoney(111);
        session2.flush();
        session2.close();

        a1.setMoney(222);
        session1.flush();
        session1.close();
        System.out.println(a1);
        System.out.println(a2);

    }

此处我们进行修改数据的操作,打印如下:

Hibernate: select account0_.account_id as account_1_0_2_, account0_.admin_id as admin_id4_0_2_, account0_.account_money as account_2_0_2_, account0_.account_version as account_3_0_2_, admin1_.admin_id as admin_id1_1_0_, admin1_.admin_pwd as admin_pw2_1_0_, admin1_.admin_role as admin_ro4_1_0_, admin1_.admin_username as admin_us3_1_0_, role2_.role_id as role_id1_3_1_, role2_.createdate as createda2_3_1_, role2_.role_name as role_nam3_3_1_, role2_.role_status as role_sta4_3_1_ from xx_plat_acocunt account0_ left outer join xx_plat_admin admin1_ on account0_.admin_id=admin1_.admin_id left outer join xx_plat_role role2_ on admin1_.admin_role=role2_.role_id where account0_.account_id=?
Hibernate: select account0_.account_id as account_1_0_2_, account0_.admin_id as admin_id4_0_2_, account0_.account_money as account_2_0_2_, account0_.account_version as account_3_0_2_, admin1_.admin_id as admin_id1_1_0_, admin1_.admin_pwd as admin_pw2_1_0_, admin1_.admin_role as admin_ro4_1_0_, admin1_.admin_username as admin_us3_1_0_, role2_.role_id as role_id1_3_1_, role2_.createdate as createda2_3_1_, role2_.role_name as role_nam3_3_1_, role2_.role_status as role_sta4_3_1_ from xx_plat_acocunt account0_ left outer join xx_plat_admin admin1_ on account0_.admin_id=admin1_.admin_id left outer join xx_plat_role role2_ on admin1_.admin_role=role2_.role_id where account0_.admin_id=?
Hibernate: select account0_.account_id as account_1_0_2_, account0_.admin_id as admin_id4_0_2_, account0_.account_money as account_2_0_2_, account0_.account_version as account_3_0_2_, admin1_.admin_id as admin_id1_1_0_, admin1_.admin_pwd as admin_pw2_1_0_, admin1_.admin_role as admin_ro4_1_0_, admin1_.admin_username as admin_us3_1_0_, role2_.role_id as role_id1_3_1_, role2_.createdate as createda2_3_1_, role2_.role_name as role_nam3_3_1_, role2_.role_status as role_sta4_3_1_ from xx_plat_acocunt account0_ left outer join xx_plat_admin admin1_ on account0_.admin_id=admin1_.admin_id left outer join xx_plat_role role2_ on admin1_.admin_role=role2_.role_id where account0_.account_id=?
Hibernate: select account0_.account_id as account_1_0_2_, account0_.admin_id as admin_id4_0_2_, account0_.account_money as account_2_0_2_, account0_.account_version as account_3_0_2_, admin1_.admin_id as admin_id1_1_0_, admin1_.admin_pwd as admin_pw2_1_0_, admin1_.admin_role as admin_ro4_1_0_, admin1_.admin_username as admin_us3_1_0_, role2_.role_id as role_id1_3_1_, role2_.createdate as createda2_3_1_, role2_.role_name as role_nam3_3_1_, role2_.role_status as role_sta4_3_1_ from xx_plat_acocunt account0_ left outer join xx_plat_admin admin1_ on account0_.admin_id=admin1_.admin_id left outer join xx_plat_role role2_ on admin1_.admin_role=role2_.role_id where account0_.admin_id=?
Account [id=1, money=113, version=7]
Account [id=1, money=113, version=7]
Hibernate: update xx_plat_acocunt set admin_id=?, account_money=?, account_version=? where account_id=? and account_version=?
Hibernate: update xx_plat_acocunt set admin_id=?, account_money=?, account_version=? where account_id=? and account_version=?
Exception in thread "main" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.xingxue.entity.Account#1]
    at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2521)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3240)
    at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3138)
    at 

我们发现第二修改后提交事务的时候抛出了异常:
1、当我们两个session去查询数据的时候都得到了当前数据的版本号为7,
2、线程2发送修改SQL的时候会先检查当前数据的版本和前面查询的版本是否一致,如果一致,则修改,修改成功之后版本号更改为8
3、线程1发送修改SQL的时候也会先检查版本,结果发现先查询的为7,而数据库的版本为8,所以抛出异常,及乐观锁实现了
同理我们也可以退timestamp实现原理也应该是如此

7. 简述hibernate的懒加载机制、延迟加载
在查询数据的时候,由于有时数据量过大,一级缓存显然是不够用的,所以hibernate就提出了懒加载机制,所谓的懒加载就是延迟加载,及不使用该数据的时候不发送SQL到数据库查询,当需要使用该数据的时候才发送SQL进行查询数据并返回,
H中的懒加载是利用代理对象来完成的,也就是使用懒加载的时候,返回的是一个改实体的代理对象,这个代理对象保存默认值,当我们需要使用时,发送SQL查询数据,然后将代理对象得到的数据返回给我们的对象,使用懒加载的前提是session没有关闭,如果session关闭,懒加载会抛出异常。
H的load方法默认是懒加载机制,
同时在我们的关系映射中,也可以通过fetch=FetchType.LAZY来指定是否使用懒加载,一对多是默认懒加载,

8. 简述hibernate的事务机制
事务4大特性 ACID
原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)
原子性:将事务中所做的操作捆绑成一个原子单元,即对于事务所进行的数据修改等操作,要么全部执行,要么全部不执行。
一致性:事务在完成时,必须使所有的数据都保持一致状态,而且在相关数据中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构都应该是正确的。
隔离性:由并发事务所做的修改必须与任何其他事务所做的修改相隔离
持久性:事务完成之后,它对系统的影响是永久的,即使出现系统故障也是如此。

Hibernate对JDBC进行了轻量级的封装,它本身在设计时并不具备事务处理功能。Hibernate将底层的JDBCTransaction或JTATransaction进行了封装,再在外面套上Transaction和Session的外壳,其实是通过委托底层的JDBC或JTA来实现事务的处理功能的。hibernate默认使用jdbc的事务控制,如果被spring管理,就会使用spring的事务管理机制

猜你喜欢

转载自blog.csdn.net/sky274548769/article/details/78732741