欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

后端开发面试题(二)Hibernate篇

程序员文章站 2022-03-14 09:48:59
...

文章目录

1、Hibernate 中对象的三种状态及如何切换

1.1 三种状态介绍

  • 1、瞬时态(临时态、*态)
      比如通过Java的new关键字来生成一个实体对象时,这时这个实体对象就处于*状态:
Person person = new Person("zhangsan");

  这时该Person对象就处于*状态,因为,此时person只是通过JVM获得了一块内存空间,还并没有通过Session对象的save()方法保存进数据库,因此也就还没有纳入Hibernate的缓存管理中,也就是说customer对象现在还*的游荡于Hibernate缓存管理之外。所以我们可以看出*对象最大的特点就是,在数据库中不存在一条与它对应的记录。
  瞬时对象特点:
    (1) 不和 Session 实例关联
    (2) 在数据库中没有和瞬时对象关联的记录

  • 2、持久态
      持久化对象就是已经被保存进数据库的实体对象,并且这个实体对象现在还处于Hibernate的缓存管理之中。这是对该实体对象的任何修改,都会在清理缓存时同步到数据库中,示例:
Person person = new Person("zhangsan");		//new一个Person对象
tx=session.beginTransaction();		//开启事务
session.save(person);			//将customer持久化到数据库中
person=(Person)session.load(person.class,1);	//根据id查出Person zhangsan
person.setAge(28);		//修改Person的年龄为28
tx.commit();		//提交事务

  我们并没有显示调用session.update()方法来保存更新,但是对实体对象的修改还是会同步更新到数据库中,因为此时customer对象通过save方法保存进数据库后,已经是持久化对象了,然后通过load方法再次加载它,它仍然是持久化对象,所以它还处于Hibernate缓存的管理之中,这时当执行tx.commit()方法时,Hibernate会自动清理缓存,并且自动将持久化对象的属性变化同步到到数据库中。
  持久的实例在数据库中有对应的记录,并拥有一个持久化标识 (identifier)。
持久对象总是与 Session 和 Transaction 相关联,在一个 Session 中,对持久对象的改变不会马上对数据库进行变更,而必须在 Transaction 终止,也就是执行 commit() 之后,才在数据库中真正运行 SQL 进行变更,持久对象的状态才会与数据库进行同步。
  瞬时对象转为持久对象:
   (1) 通过 Session 的 save() 和 saveOrUpdate() 方法把一个瞬时对象与数据库相关联,这个瞬时对象就成为持久化对象。
   (2) 使用 fine(),get(),load() 和 iterater() 等方法查询到的数据对象,将成为持久化对象。
  持久化对象的特点:
   (1) 和 Session 实例关联
   (2) 在数据库中有和持久对象关联的记录

  • 3、脱管态(离线态、游离态)
      当一个持久化对象,脱离开Hibernate的缓存管理后,它就处于游离状态,游离对象和*对象的最大区别在于,游离对象在数据库中可能还存在一条与它对应的记录,只是现在这个游离对象脱离了Hibernate的缓存管理,而*对象不会在数据库中出现与它对应的数据记录。示例:
Person person = new Person("zhangsan");		
tx=session.beginTransaction();		
session.save(person);			
person=(Person)session.load(person.class,1);	
person.setAge(28);		
tx.commit();		
session.close();		//关闭session

  当session关闭后,customer对象就不处于Hibernate的缓存管理之中了,但是此时在数据库中还存在一条与customer对象对应的数据记录,所以此时customer对象处于游离状态。
  与持久对象关联的 Session 被关闭后,对象就变为脱管对象。对脱管对象的引用依然有效,对象可继续被修改。
  脱管对象特点:
   (1) 本质上和瞬时对象相同
   (2) 只是比爱瞬时对象多了一个数据库记录标识值 id.(存在过数据库的证明)

1.2 三种状态的切换

  直接new出来的对象就是临时/瞬时状态的,此时该对象还没有被持久化(没有保存在数据库中),不受Session的管理。
  当保存在数据库中的对象就是持久化状态,比如调用调用session的save /saveOrUpdate /get /load /list等方法时。
  当Session关闭了以后,持久化的对象就变成了游离状态。
  三种状态的转换图示:
后端开发面试题(二)Hibernate篇
  简而言之,new出来的对象是瞬时状态->保存到数据库中(受Session管理)就是持久化状态->将session close掉就是游离状态

2、Hibernate 的缓存机制

2.1 Hibernate缓存原理

  对于Hibernate这类ORM而言,缓存显的尤为重要,它是持久层性能提升的关键.简单来讲Hibernate就是对JDBC进行封装,以实现内部状态的管理,OR关系的映射等,但随之带来的就是数据访问效率的降低,和性能的下降,而缓存就是弥补这一缺点的重要方法。
  缓存就是数据库数据在内存中的临时容器,包括数据库数据在内存中的临时拷贝,它位于数据库与数据库访问层中间。ORM在查询数据时首先会根据自身的缓存管理策略,在缓存中查找相关数据,如发现所需的数据,则直接将此数据作为结果加以利用,从而避免了数据库调用性能的开销.而相对内存操作而言,数据库调用是一个代价高昂的过程。
  一般来讲ORM中的缓存分为以下几类:

  • 1、事务级缓存
      即在当前事务范围内的数据缓存。就Hibernate来讲,事务级缓存是基于Session的生命周期实现的,每个Session内部会存在一个数据缓存,它随着 Session的创建而存在,随着Session的销毁而灭亡,因此也称为Session Level Cache。
  • 2、应用级缓存
      即在某个应用中或应用中某个独立数据库访问子集中的共享缓存,此缓存可由多个事务共享(数据库事务或应用事务),事务之间的缓存共享策略与应用的事务隔离机制密切相关。在Hibernate中,应用级缓存由SessionFactory实现,所有由一个SessionFactory创建的 Session实例共享此缓存,因此也称为SessionFactory Level Cache。
  • 3、分布式缓存
      即在多个应用实例,多个JVM间共享的缓存策略。分布式缓存由多个应用级缓存实例组成,通过某种远程机制(RMI,JMS)实现各个缓存实例间的数据同步,任何一个实例的数据修改,将导致整个集群间的数据状态同步。

2.2 Hibernate的一、二级缓存策略

  Hibernate中提供了两级Cache,第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate管理的,一般情况下无需进行干预;第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围或群集范围的缓存。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载,属于多事务级别,要防止事务并发性。
  缓存是以map的形式进行存储的(key-id,value-object)。

  • 1、一级缓存(Session)
      事务范围,每个事务(Session)都有单独的第一级缓存。
      一级缓存的管理:当应用程序调用Session的save()、update()、saveOrUpdate()、get()或load(),以及调用查询接口的 list()、iterate()–(用的是n+1次查询,先查id)或filter()方法时,如果在Session缓存中还不存在相应的对象,Hibernate就会把该对象加入到第一级缓存中。当清理缓存时,Hibernate会根据缓存中对象的状态变化来同步更新数据库。 Session为应用程序提供了两个管理缓存的方法: evict(Object obj):从缓存中清除参数指定的持久化对象。 clear():清空缓存中所有持久化对象,flush():使缓存与数据库同步。
      当查询相应的字段如(name),而不是对象时,不支持缓存。
  • 2、二级缓存(SessionFactory)
      Hibernate的二级缓存策略的一般过程如下:
       1>条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL句查询数据库,一次获得所有的数据对象(这个问题要考虑,如果你查询十万条数据时,内存不是被占用)。
       2>把获得的所有数据对象根据ID放入到第二级缓存中。
       3>当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
       4>删除、更新、增加数据的时候,同时更新缓存。
      Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query Cache。

2.3 二级缓存的使用

  • 1、什么样的数据适合存放到第二级缓存中?
      很少被修改的数据
      不是很重要的数据,允许出现偶尔并发的数据
      不会被并发访问的数据
      参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。
  • 2、不适合存放到第二级缓存的数据?
      经常被修改的数据
      财务数据,绝对不允许出现并发
      与其他应用共享的数据。

2.4 常用的二级缓存插件

  常用的缓存插件 Hibernater 的二级缓存是一个插件,下面是几种常用的缓存插件:
   1>EhCache
    可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,对Hibernate的查询缓存提供了支持。
   2>OSCache
    可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持。
   3>SwarmCache
    可作为群集范围内的缓存,但不支持Hibernate的查询缓存。
   4>JBossCache
    可作为群集范围内的缓存,支持事务型并发访问策略,对Hibernate的查询缓存提供了支持。

3、Hibernate 的查询方式有哪些

  Hibernate 的查询方式常见的主要分为三种: HQL(Hibernate Query Language), QBC(Query By Criteria,命名查询), 以及使用原生 SQL 查询(Sql Query)。

3.1 HQL

  HQL是一种偏向于面向对象的查询方式,示例:

        String name = "iphone";
        Query q =s.createQuery("from Product p where p.name like ?");
        q.setString(0, "%"+name+"%");
        List<Product> ps= q.list();

3.2 QBC

  QBC是一种完全面向对象的查询方式,示例:

        String name = "iphone";
        Criteria c= s.createCriteria(Product.class);
        c.add(Restrictions.like("name", "%"+name+"%"));
        List<Product> ps = c.list();

3.3 原生SQL

  使用原生SQL语句查询方式的示例:

        String name = "iphone";
        String sql = "select * from product_ p where p.name like '%"+name+"%'";
        Query q= s.createSQLQuery(sql);
        List<Object[]> list= q.list();

4、Hibernate和 Mybatis 的区别

  Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,建立对象与数据库表的映射。是一个全自动的、完全面向对象的持久层框架。
  Mybatis是一个开源对象关系映射框架,是一个半自动化的持久层框架。
  Hibernate和MyBatis都支持JDBC和JTA事务处理
  两者都是ORM框架,所以在了解两者的差别之前,我们有必要先简单认识一下ORM。

4.1 ORM 的工作原理

  1. 以一定的映射方式,把实体模型和数据库关系的映射
  2. ORM框架启动时加载这些映射和数据库配置文件
  3. ORM通过对原生jdbc的封装提供更加便利的操作
  4. Dao通过ORM提供便捷API对象的方式操作数据库关系

4.2 两者的差别

  • 1、两者最大的区别
      针对简单逻辑,Hibernate与MyBatis都有相应的代码生成工具,可以生成简单基本的DAO层方法。
      针对高级查询,MyBatis需要手动编写SQL语句,以及ResultMap,而Hibernate有良好的映射机制,开发者无需关心SQL的生成与结果映射,可以更专注于流程。
  • 2、开发难度对比
      Hibernate的开发难度大于MyBatis,主要由于Hibernate比较复杂,庞大,学习周期比较长。
      MyBatis则相对简单,并且MyBatis主要依赖于SQL的书写,让开发者刚进更熟悉。
  • 3、sql书写比较
      Hibernate也可以自己写sql来指定需要查询的字段,但这样就破坏了Hibernate开发的简洁性,不过Hibernate具有自己的日志统计。
      MyBatis的sql是手动编写的,所以可以按照要求指定查询的字段,不过没有自己的日志统计,所以要借助Log4j来记录日志。
  • 4、数据库扩展性计较
      Hibernate与数据库具体的关联在XML中,所以HQL对具体是用什么数据库并不是很关心
      MyBatis由于所有sql都是依赖数据库书写的,所以扩展性、迁移性比较差。
  • 5、对象管理比对
      Hibernate 是完整的对象-关系映射的框架,开发工程中,无需过多关注底层实现,只要去管理对象即可;
      Mybatis 需要自行管理 映射关系;
  • 6、sql优化方面
      Hibernate 自动生成sql,有些语句较为繁琐,会多消耗一些性能;
      Mybatis 手动编写sql,可以避免不需要的查询,提高系统性能;
  • 7、缓存机制比较
      Hibernate的二级缓存配置在SessionFactory生成配置文件中进行详细配置,然后再在具体的表对象映射中配置那种缓存。
      MyBatis的二级缓存配置都是在每个具体的表对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓冲机制,并且MyBatis可以在命名空间*享相同的缓存配置和实例,通过Cache-ref来实现。
      两者比较,因为Hibernate对查询对象有着良好的管理机制,用户无需关心SQL,所以在使用二级缓存时如果出现脏数据,系统会报出错误提示。 而MyBatis在这一方面使用二级缓存时需要特别小心,如果不能完全去掉数据更新操作的波及范围,避免cache的盲目使用,否则,脏数据的出现会给系统的正常运行带来很大的隐患。

4.3 两者的优势

  • 1、Mybatis优势
      MyBatis可以进行更为细致的SQL优化,可以减少查询字段。
      MyBatis容易掌握,而Hibernate门槛较高。
  • 2、Hibernate优势
      Hibernate的DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射。
      Hibernate对对象的维护和缓存要比MyBatis好,对增删改查的对象的维护要方便。
      Hibernate数据库移植性很好,MyBatis的数据库移植性不好,不同的数据库需要写不同SQL。
      Hibernate有更好的二级缓存机制,可以使用第三方缓存。MyBatis本身提供的缓存机制不佳。

5、Hibernate 和 JDBC 优缺点对比

5.1 JDBC的优缺点

  • 1、JDBC的优点
      因为是底层操作所以效率高
  • 2、JDBC的缺点
      1)代码繁琐
      2)不是面向对象的数据库操作
      3)资源关闭的代码也很繁琐
      4)没有做到数据库缓存
      5)移植性比较差(MySQL无法移植到Oracle)

5.2 Hibernate的优缺点

  • 1、Hibernate的优点
      1)代码比较精简
      2)是面向对象的数据库操作
      3)只需要关闭一个对象就可以了(关闭Session)
      4)数据缓存 (一级缓存、二级缓存、查询缓存)
      5)移植性比较好
  • 2、Hibernate的缺点:
      1)程序员无法控制SQL语句的生成 (HQL语句可以手写SQL语句)
      2)一个项目对SQL语句优化特别高,HIbernate则不适合
      3)如果一张表的数据量特别大,则不适合HIbernate

5.3 两者的差异

  两者相同点:
   1>两者都是 java 数据库操作的中间件。
   2>两者对数据库进行直接操作的对象都是线程不安全的,都需要及时关闭。
   3>两者都可对数据库的更新操作进行显式的事务处理。
  两者不同点:
   1>JDBC 是 SUN 公司提供一套操作数据库的规范,使用 java 代码操作数据库。Hibernate 是一个基于 jdbc 的主流持久化框架,对 JDBC 访问数据库的代码做了封装。
   2>使用的 SQL 语言不同:JDBC 使用的是基于关系型数据库的标准 SQL 语言,Hibernate 使用的是 HQL(Hibernatequery language)语言。
   3>操作的对象不同:JDBC 操作的是数据,将数据通过 SQL 语句直接传送到数据库中执行,Hibernate 操作的是持久化对象,由底层持久化对象的数据更新到数据库中。
   4>数据状态不同:JDBC 操作的数据是“瞬时”的,变量的值无法与数据库中的值保持一致,而 Hibernate 操作的数据是可持久的,即持久化对象的数据属性的值是可以跟数据库中的值保持一致的。

6、JDBC、hibernate 和 ibatis 的区别

  • 1、jdbc(手动)
      手动写sql;
      delete、insert、update要将对象的值一个一个取出传到sql中,不能直接传入一个对象;
       select:返回的是一个Resultset,要从ResultSet中一行一行、一个字段一个字段的取出,然后封装到一个对象中,不直接返回一个对象。
  • 2、ibatis(Mybatis的前身,半自动化)
      sql要手动写;
    ;  delete、insert、update:直接传入一个对象;
      select:直接返回一个对象。
  • 3、hibernate:(全自动)
      不写sql,自动封装;
      delete、insert、update:直接传入一个对象;
      select:直接返回一个对象。

7、讲下Hibernate 的 orm 思想

  ORM 指的是对象关系型映射(Object RelationShip Mapping ),指的就是我们通过创建实体类对象和数据库中的表关系进行一一对应,来实现通过操作实体类对象来更改数据库里边的数据信息。起到关键作用的是通过Hibernate 的映射文件+Hibernate 的核心配置文件,接下来以一个例子来看下这两个文件。
  假如有一个实体类Product:

public class Product {
    int id;
    String name;
    float price;
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() {  return name; }
    public void setName(String name) { this.name = name; }
    public float getPrice() { return price; }
    public void setPrice(float price) {  this.price = price; }  
}

  我们就可以在该文件对应的package目录下创建一个 实体类名.hbm.xml文件(在该例子中即Product.hbm.xml),用来映射数据库中的表和实体类之间的关系,该文件的简单语法是:

 <class name="实体类名" table="实体类对应的数据库中表名">
    <id name="id" column="表主键字段名" type="java.lang.Integer">
       <generator class="设置主键生成策略类型"/>
    </id>
    <property></property>
</class>

  Product.hbm.xml文件的内容示例:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
 
<hibernate-mapping package="com.how2java.pojo">
    <class name="Product" table="product_">
        <id name="id" column="id">
            <generator class="native">
            </generator>
        </id>
        <property name="name" />
        <property name="price" />
    </class>
</hibernate-mapping>

  上面内容的意思是:Product类对应数据库中的product_表。在配置文件中,类Product对应表product_,id映射表里的字段id,< generator class=“native” > 意味着id的自增长方式采用数据库的本地方式。
  在创建了Product.hbm.xml文件后,还需要将该文件添加到Hibernate的核心配置文件hibernate.cfg.xml中,需要添加的内容示例:

<mapping resource="com/how2java/pojo/Product.hbm.xml" />

8、get 和 load 的区别

  Hibernate中根据Id单条查询获取对象的方式有两种,分别是get()和load()。两者的不同主要有以下四点:

  • 1、get 是立即加载,load 是延时加载。
  • 2、get 会先查一级缓存,再查二级缓存,然后查数据库;load 会先查一级缓存,如果没有找到,就创建代理对象,等需要的时候去查询二级缓存和数据库。(这里就体现 load 的延迟加载的特性。)
  • 3、get 如果没有找到会返回 null,load 如果没有找到会抛出异常。
  • 4、当我们使用 session.load()方法来加载一个对象时,此时并不会发出 sql 语句,当前得到的这个对象其实是一个代理对象,这个代理对象只保存了实体对象的 id 值,只有当我们要使用这个对象,得到其它属性时,这个时候才会发出 sql 语句,从数据库中去查询我们的对象;相对于 load 的延迟加载方式,get 就直接的多,当我们使用session.get()方法来得到一个对象时,不管我们使不使用这个对象,此时都会发出 sql 语句去从数据库中查询出来。

9、如何进行 Hibernate 的优化

9.1 数据库设计

  1>建索引
  2>减少表之间的关联
  3>简化查询字段,没用的字段不要,已经对返回结果的控制,尽量返回少量数据
  4>适当的冗余数据,不过分最求高范式

9.2 HQL优化

  1>实体查询:可以使用sql语句查询
  2>实体的更新和删除:hibernate3中直接提供更加灵活更加效率的解决方法
  3>属性查询:动态构造实例对象,对结果集进行封装
  4>分组与排序:
   Order by子句
    Group by子句与统计查询
    优化统计查询:内连接,外连接
  5>参数绑定:和jdbc一样,对hibernate的参数绑定提供了丰富的支持。

9.3 缓存

  1>数据库级缓存
    这级缓存是最高效和安全的,但不同的数据库可管理的层次并不一样,比如,在ORACLE中,可以在建表时指定将整个表置于缓存当中。
  2>SESSION缓存
    在一个HIBERNATE SESSION有效,这级缓存的可干预性不强,大多于HIBERNATE自动管理,但它提供清除缓存的方法,这在大批量增加/更新操作是有效的。比如,同时增加十万条记录,按常规方式进行,很可能会发现OutofMemeroy的异常,这时可能需要手动清除这一级缓存:Session.evict以及Session.clear。
  3>应用缓存
    在一个SESSIONFACTORY中有效,因此也是优化的重中之重,因此,各类策略也考虑的较多,在将数据放入这一级缓存之前,需要考虑一些前提条件:
    1)数据不会被第三方修改(比如,是否有另一个应用也在修改这些数据?)
    2)数据不会太大
    3)数据不会频繁更新(否则使用CACHE可能适得其反)
    4)数据会被频繁查询
    5)数据不是关键数据(如涉及钱,安全等方面的问题)。
   缓存有几种形式,可以在映射文件中配置:read-only(只读,适用于很少变更的静态数据/历史数据),nonstrict-read-write,read-write(比较普遍的形式,效率一般),transactional(JTA中,且支持的缓存产品较少)。
  4>分布式缓存
    同3的配置一样,只是缓存产品的选用不同,在目前的HIBERNATE中可供选择的不多,oscache, jboss cache,目前的大多数项目,对它们的用于集群的使用(特别是关键交易系统)都持保守态度。在集群环境中,只利用数据库级的缓存是最安全的。

9.4 结果集的使用

  1>查询方式
    list只能利用查询缓存(但在交易系统中查询缓存作用不大),无法利用二级缓存中的单个实体,但是list查出的对象会写入二级缓存,但它一般只生成较少的sql语句,很多情况就是一条。
    iterator则利用二级缓存,对于一条查询语句,它会先从数据库中找到所有符合条件的记录的ID,在通过ID去缓存找,对于缓存中没有的记录,在构造语句从数据库查出,第一次的执行会产生N+1条SQL语句。
  2>产生结果
   用list可能会溢出
   通过Iterator,配合缓存管理API,在海量数据查询中可以很好的解决内存问题。
  3>综合考虑
   一般List会填充二级缓存,却不能利用二级缓存,而Iterator可以读二级缓存,然而无法命中的话,效率很低效。一般处理方法,就是第一次查询使用list,随后使用iterator查询。

9.5 主配置

  1>查询缓存,同下面讲的缓存不太一样,它是针对HQL语句的缓存,即完全一样的语句再次执行时可以利用缓存数据。但是,查询缓存在一个交易系统(数据变更频繁,查询条件相同的机率并不大)中可能会起反作用:它会白白耗费大量的系统资源但却难以派上用场。
 &emsp2> fetch_size,同JDBC的相关参数作用类似,参数并不是越大越好,而应根据业务特征去设置
  3> batch_size同上。
  4> 生产系统中,切记要关掉SQL语句打印。

9.6 延迟加载

  1>实体延迟加载:通过使用动态代理实现
  2>集合延迟加载:通过实现自有的SET/LIST,HIBERNATE提供了这方面的支持
  3>属性延迟加载。

9.7 事务控制

事务方面对性能有影响的主要包括:事务方式的选用,事务隔离级别以及锁的选用。
  1> 事务方式选用
   如果不涉及多个事务管理器事务的话,不需要使用JTA,只有JDBC的事务控制就可以。
  2>事务隔离级别
   参见标准的SQL事务隔离级别。
  3>锁的选用
   悲观锁(一般由具体的事务管理器实现),对于长事务效率低,但安全。乐观锁(一般在应用级别实现),如在HIBERNATE中可以定义VERSION字段,显然,如果有多个应用操作数据,且这些应用不是用同一种乐观锁机制,则乐观锁会失效。因此,针对不同的数据应有不同的策略,同前面许多情况一样,很多时候我们是在效率与安全/准确性上找一个平衡点,无论如何,优化都不是一个纯技术的问题,你应该对你的应用和业务特征有足够的了解。

10、什么是 Hibernate 延迟加载

  延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。
  延迟加载的过程:通过代理(Proxy)机制来实现延迟加载。Hibernate 从数据库获取某一个对象数据时、获取某一个对象的集合属性值时,或获取某一个对象所关联的另一个对象时,由于没有使用该对象的数据(除标识符外),Hibernate 并不从数据库加载真正的数据,而只是为该对象创建一个代理对象来代表这个对象,这个对象上的所有属性都为默认值;只有在真正需要使用该对象的数据时才创建这个真正的对象,真正从数据库中加载它的数据。
  Hibernate中默认采用延迟加载的情况主要有以下几种
   1、当调用session上的load()加载一个实体时,会采用延迟加载。
   2、当session加载某个实体时,会对这个实体中的集合属性值采用延迟加载。
   3、当session加载某个实体时,会对这个实体所有单端关联的另一个实体对象采用延迟加载。

11、比较 Hibernate 三种检索策略的优缺点

  1、立即检索(lazy=false)
   优点:对应用程序完全透明,不管对象处于持久化状态,还是游离状态,应用程序都可以方便的从一个对象导航到与它关联的对象;
   缺点:1.select 语句太多;2.可能会加载应用程序不需要访问的对象白白浪费许多内存空间;
  2、延迟检索(lazy=true)
   优点:由应用程序决定需要加载哪些对象,可以避免可执行多余的 select 语句,以及避免加载应用程序不需要访问的对象。因此能提高检索性能,并且能节省内存空间;
   缺点:应用程序如果希望访问游离状态代理类实例,必须保证他在持久化状态时已经被初始化;
  3、 迫切左外连接检索( fetch=“join”)
   优点:1、对应用程序完全透明,不管对象处于持久化状态,还是游离状态,应用程序都可以方便地冲一个对象导航到与它关联的对象。2、使用了外连接,select 语句数目少;
   缺点:1、可能会加载应用程序不需要访问的对象,白白浪费许多内存空间;2、复杂的数据库表连接也会影响检索性能;

12、简述一下 hibernate 的开发流程

  总体的流程如下:
   1>导入相关jar包;
   2>编写数据库中表数据对应实体类;
   3>配置实体类对应的映射文件***.hbm.xml;
   4>配置核心配置文件hibernate.cfg.xml;
   5>写具体的hibernate使用代码。
  一个简单的hibernate代码过程书写示例:
   1>加载 hibernate 的配置文件,读取配置文件的参数(jdbc 连接参数,数据 库方言,hbm 表与对象关系映射文件)
   2>创建 SessionFactory 会话工厂(内部有连接池)
   3>打开 session 获取连接,构造 session 对象(一次会话维持一个数据连接, 也是一级缓存)
   4>开启事务
   5>进行操作
   6>第六步:提交事务
   7>关闭 session(会话)将连接释放
   8>关闭连接池

13、Hibernate工作原理及为什么要用

13.1 原理

  1>读取并解析配置文件
  2>读取并解析映射信息,创建SessionFactory
  3>打开Sesssion
  4>创建事务Transation
  5>持久化操作
  6>提交事务
  7>关闭Session
  8>关闭SesstionFactory

13.2 为什么要用

  1. 对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。
  2. Hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM实现。他很大程度的简化DAO层的编码工作
  3. hibernate使用Java反射机制,而不是字节码增强程序来实现透明性。
  4. hibernate的性能非常好,因为它是个轻量级框架。映射的灵活性很出色。它支持各种关系数据库,从一对一到多对多的各种复杂关系。

14、Hibernate是如何延迟加载(懒加载)

  首先介绍一下延迟加载的几种情况:

  • 当调用Session上的load()方法加载实体时,就会采用延迟加载;
  • 当Session加载某个实体时,会对这个实体中的集合属性值采用延迟加载;
  • 当Session加载某个实体时,会对这个实体所单端关联(one-to-one,many-to-one)的另一个实体对象采用延迟加载。

  延迟加载的过程:Hibernate从数据库获取某一个对象数据、获取某一个对象的集合属性时,获取某一个对象所关联的另一个对象时,由于没有使用该对象的数据,hibernate并不从数据库加载真正的数据。而是为该对象创建一个代理对象来代表这个对象,这个对象上的所有属性都为默认值,只有在真正的需要该对象的数据时才创建这个真实的对象,真正的从数据库中加载数据。

  • 1、要实现某个实体的延迟加载,用load方法即可,示例:
        Product p = (Product)s.load(Product.class, 1);
        System.out.println("log1");
        System.out.println(p.getName());
        System.out.println("log2");
  • 2、要实现关系的延迟加载,可以在 实体类.hbm.xml 的class类标签或者是set集合的标签中设置lazy=false,表示立即加载,而true表示懒加载。示例:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
 
<hibernate-mapping package="com.how2java.pojo">
    <class name="Category" table="category_">
        <id name="id" column="id">
            <generator class="native">
            </generator>
        </id>
        <property name="name" />
 
        <set name="products" lazy="true">
            <key column="cid" not-null="false" />
            <one-to-many class="Product" />
        </set>
                 
    </class>
     
</hibernate-mapping>

15、Hibernate中怎样实现类之间的关系

  类与类之间的关系主要体现在表与表之间的关系进行操作,它们都是对对象进行操作,我们程序中把所有的表与类都映射在一起,它们通过配置文件中的many-to-one、one-to-many、many-to-many,来实现类之间的关系。

16、Hibernate里面的sorted collection 和ordered collection有什么区别

  sorted collection是在内存中通过Java的Comparator进行排序的;ordered collection是在数据库中通过order by进行排序的。对于比较大的数据集,为了避免在内存中对它们进行排序而出现 Java中的OutOfMemoryError,最好使用ordered collection。

17、在数据库中条件查询速度很慢的时候,如何优化

  1、建索引
  2、减少表之间的关联
  3、优化sql,尽量让sql很快定位数据,不要让sql做全表查询,应该走索引,把数据量大的表排在前面
  4、简化查询字段,没用的字段不要,已经对返回结果的控制,尽量返回少量数据

18、SessionFactory是线程安全么

  SessionFactory就是一个用于创建Hibernate的Session对象的工厂。SessionFactory通常是在应用启动时创建好的,应用程序中的代码用它来获得Session对象。作为一个单个的数据存储,它也是 线程安全的,所以多个线程可同时使用同一个SessionFactory。Java JEE应用一般只有一个SessionFactory,服务于客户请求的各线程都通过这个工厂来获得Hibernate的Session实例,这也是为什么SessionFactory接口的实现必须是线程安全的原因。还有,SessionFactory的内部状态包含着同对象关系影射有关的所有元数据,它是 不可变的,一旦创建好后就不能对其进行修改了。

19、persist和save的区别

  • 1、persist不保证立即执行,可能要等到flush;save会立即执行Sql的insert语句;
  • 2、persist不更新缓存;save更新。
  • 3、使用 save保存持久化对象时,该方法返回该持久化对象的标识属性值(即对应记录的主键值);使用 persist来保存持久化对象时,该方法没有任何返回值。
  • 4、在事务里执行到save,会向数据库插一条数据,如果事务里异常,会回滚,删除数据库中插入的数据;在事务里执行到persist,不会向数据库插数据,事务commit了才会插入数据。

20、常见的主键生成策略有哪些

  Hibernate提供相应的API能自动生成数据库表,说到表,一定会设计到的一个概念就是主键,主键是由程序自动生成的,不应该由用户自己输入。Hibernat提供了主键生成策略,下面主要讲一下常用的几种,配置形式如下:

<id name="id">
    <generator class="主键生成策略名称" />      
</id>
  • 1、uuid
      Universally Unique Identifier的缩写,通用唯一识别码,UUID是由一组32位数的16进制数字所构成是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的; Hibernate在保存对象时,生成一个uuid字符串作为主键,保证了唯一性,但其无任何业务逻辑意义; 适用与所有的数据库,但是生成的主键占用的存储空间较大
  • 2、identity
       identity是由底层数据库自己生成的标识符,但这个主键必须设置为自增长,由数据库生成,使用identity的前提条件是底层数据库支持自动增长字段类型,如SQL Server、MySQL等,Oracle这类没有自增字段的则不支持。如果使用MySQL数据库,则主键字段必须设置成auto_increment(此句上网查得的),但是自己试验得出,Hibernate在创建MySql中的表时会自动将该字段设置成auto_increment,该字段必须设置为是能自增的数据类型,否则会报错。
  • 3、hilo
      hilo(高低位方式high low)是hibernate中最常用的一种生成方式,需要一张额外的表保存hi的值,该表中只有一条数据,即记录hi的下一个值,适用于所有数据库,但生成的标志只能在一个数据库中保证唯一,配置如下:
<id name="id" column="id">
    <generator class="hilo">
        <!--指定保存hi值的表名,MySQL默认表名为hibernate_unique_key-->
        <param name="table">hibernate_hilo</param>
        <!--指定保存hi值的列名-->
        <param name="column">next_hi</param>
        <!--指定低位的最大值-->
        <param name="max_lo">100</param>
    </generator>
</id>

  如果使用默认的表名和列名,可以省略table和column:

<id name="id" column="id">
    <generator class="hilo">
        <param name="max_lo">100</param>
    </generator>
</id>

  hilo生成器生成主键的过程(以hibernate_unique_key表,next_hi列为例):

  1. 获得hi值:读取并记录数据库的hibernate_unique_key表中next_hi字段的值,数据库中此字段值加1保存。
  2. 获得lo值:从0到max_lo循环取值,差值为1,当值为max_lo值时,重新获取hi值,然后lo值继续从0到max_lo循环。
  3. 根据公式 hi * (max_lo + 1) + lo计算生成主键值。
      注意:当hi值是0的时候,那么第一个值不是0*(max_lo+1)+0=0,而是lo跳过0从1开始,然后再按上面的规律生成主键,即1,101,202。
      那max_lo配置多大合适呢?这要根据具体情况而定,如果系统一般不重启,而且需要用此表建立大量的主键,可以吧max_lo配置大一点,这样可以减少读取数据表的次数,提高效率;反之,如果服务器经常重启,可以吧max_lo配置小一点,可以避免每次重启主键之间的间隔太大,造成主键值主键不连贯。
  • 4、sequence
      只适用于支持sequence机制生成主键的数据库,主键值由数据库生成,如MySql就不支持,Oracle就支持
  • 5、native
      native由hibernate根据使用的数据库自行判断采用identity、hilo、sequence其中一种作为主键生成方式,灵活性很强,这样以来如果一个项目中使用多个数据库时,就可以使用这种方式
  • 6、foreign
    使用另外一个相关联的对象的主键作为该对象主键。主要用于一对一关系中。

  对数据库的依赖性:
    UUID,increment、Hilo、assigned:对数据库无依赖
    identity:依赖Mysql或sql server,主键值不由hibernate维护
    sequence:适合于oracle等支持序列的dbms,主键值不由hibernate维护,由序列产生
    native:根据底层数据库的具体特性选择适合的主键生成策略,如果是mysql或sqlserver,选择identity,如果是oracle,选择sequence
  主键生成策略的选择:一般来说推荐UUID,因为生成主键唯一,且对数据库无依赖,可移植性强。

21、简述hibernate中getCurrentSession和openSession区别

  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默认使用该种事务管理机制。

22、为什么在Hibernate的实体类中要提供一个无参数的构造器这一点非常重要

  每个Hibernate实体类必须包含一个 无参数的构造器, 这是因为Hibernate框架要使用Reflection API,通过调用Class.newInstance()来创建这些实体类的实例。如果在实体类中找不到无参数的构造器,这个方法就会抛出一个InstantiationException异常。

23、谈谈Hibernate中inverse的作用

  inverse属性默认是false,就是说关系的两端都来维护关系。 比如Student和Teacher是多对多关系,用一个中间表TeacherStudent维护。Gp)i 如果Student这边inverse=”true”, 那么关系由另一端Teacher维护,就是说当插入Student时,不会操作TeacherStudent表(中间表)。只有Teacher插入或删除时才会触发对中间表的操作。所以两边都inverse=”true”是不对的,会导致任何操作都不触发对中间表的影响;当两边都inverse=”false” 或默认时,会导致在中间表中插入两次关系。

24、Hibernate如何实现数据表映射的继承关系

  1、两个表,子类重复父类的属性。
   2、一个表,子类父类共用一个表
  3、两个表,子类引用父类的主键,享用公共的字段或属性。

25、在hibernate中进行多表查询,每个表中各取几个字段,也就是说查询出来的结果集并没有一个实体类与之对应,如何解决这个问题

  解决方案一,按照Object[ ]数据取出数据,然后自己组bean。
  解决方案二,对每个表的bean写构造函数,比如表一要查出field1,field2两个字段,那么有一个构造函数就是Bean(type1 filed1,type2 field2) ,然后在hql里面就可以直接生成这个bean了。

  本文内容均来自网上,只为方便学习使用,原文链接:
  Hibernate最全面试题
  JAVA三大框架面试题之hibernate(含答案)
  hibernate中的对象三种状态以及三种对象之间的转换