Hibernate3.6 学习笔记

框架的诞生必然是为了解决某一问题,要对Hibernate的身世有所了解,知道它的优点、作用,能够干什么,不能够干什么,这然才算是掌握了Hibernate.

典型的B/S三层架构,这个都不陌生:

Hibernate3.6 学习笔记

为什么要把Dao单独作为一个层?这样设计肯定是有原因的.底层数据库的实现不同,Dao的实现也不尽相同,可能一个Dao接口下有很多个实现,比如MysqlDaoImpl或者OracleDaoImpl等,这样写很不方便,很麻烦,而且就算用dbUtils这样的框架简化开发,也有一些问题解决不了,比如要把一个域对象映射到一张表里面去,如果这个对象的属性名或者表的字段名发生了更改,就全部都要改,很不方便,而Hibernate就能解决这样的问题,它不用管底层数据库的实现是什么,只需要告诉它所说的方言并通过域对象生成SQL语句,通过配置文件映射到表,为业务逻辑层提供了面向对象的API,对数据访问进一步的封装.所以Hibernate的特点如下:

代码重用性高,可完成所有的数据访问操作 能够支持多种数据库平台 具有相对独立的性,当持久层变化时,不会影响上层实现

在实际应用中,对象的映射文件后缀名是.hbm.xml,Hibernate的主配置文件名为hibernate.cfg.xml.

映射文件描述对象与表之间的关系,映射文件并没有创建实质性的东西,只有当hibernate的Configuration类将配置文件读到流中,创建SessionFactory,SessionFactory才会检查表的结构,然后根据主配置文件里的hbm2ddl.auto的取值,作相应的反应.配置映射文件的时候,需要思考,需要什么信息,且只需要配置hibernate猜不到的信息,比如配置了对象的属性ID为主键,如果表中的主键列的列名为ID,则可以不用配置,不同的集合需要配置不同的属性:

    Set
        <set>    属性名、集合表、集合外键、存放元素的列
    List
        <list>    属性名、集合表、集合外键、存放元素的列、表示索引的列
    Bag
        <bag>    属性名、集合表、集合外键、存放元素的列
    Map
        <map>    属性名、集合表、集合外键、存放元素的列、表示key的列
    数组
        <array>    属性名、集合表、集合外键、存放元素的列、表示索引的列

集合有懒加载,是配置集合的元素中的lazy属性,可取的值有:true,false,extra.默认值为true,为懒加载,就是当你用的时候才加载.extra就是增强的"超级懒加载",此时大多数操作都不会初始化集合类,只有当程序第一次访问集合属性的iterator()方法时,才会导致集合类的初始化,或者是有时候只想看集合的长度或者是不是空的,不关心集合中存放着什么数据,如果这个时候把集合的全部数据都读取出来,就很没有必要,于是就有第二个作用:当程序第一次访问集合属性的 size(), contains() 和 isEmpty() 方法时, Hibernate 不会初始化集合类的实例, 仅通过特定的 select 语句查询必要的信息.需要注意的是只有集合的lazy属性才有extra的取值.普通的值映射如下:

 <!-- package属性,表示当前配置中所写的类名如果没有包名,则默认是这个包中的。 --> 
 <hibernate-mapping> 
     <!--class元素表明哪个实体对应哪张表,一个class元素代表一个实体的映射   
         name:指定实体(类的全限定名) 
         table:指定实体对应的表,可以不写,不写时代表表名和对象的简单名称一致 
         mutable:默认为true,表明该类的实例是可变的或者不可变的 
         dynamic-update:默认为 false,用于 UPDATE  SQL 将会在运行时动态生成,并且只更新那些改变过的字段
         dynamic-insert:默认为 false,用于 INSERT  SQL 将会在运行时动态生成,并且只包含那些非空值字段
     --> 
     <class name="..domain.Person" table="person"> 
         <!-- 主键映射 必须要有 --> 
         <id name="id" type="int"> 
             <!-- 主键的值生成器,有多种生成策略 --> 
             <generator class="identity"></generator> 
         </id> 
         <!-- 值映射 
             name:代表实体的属性名 
             type:值类型,在数据库中一个列可以存放的属性,例:int, varchar, date) 
             not-null:非空约束 
         --> 
         <property name="name" type="string" length="12"  not-null="true" /> 
          
         <property name="birthday" type="date" /> 
          
         <!-- 集合映射 
             bag map set list array对应不同的标签 
         --> 
         <list name="interest" table="interestList"> 
             <key column="interestID"></key> 
             <!-- List需要顺序 --> 
             <list-index column="index_"></list-index> 
             <!-- 元素列 --> 
             <element column="interest" type="string" length="22"></element> 
         </list> 
     </class> 
 </hibernate-mapping> 
 

主键值的生成策略虽然有很多,但是只有当<generator>元素的class属性的值为assigned策略时才需要自己指定主键值,其他的都由Hibernate指定,自己不需要指定,指定了也没用还需要注意一点的就是increment,它是由Hibernate来维护的自增长,有并发问题,除了这种自增长,还有几种方式,native会根据数据库的能力选择 identitysequence 或者hilo 中的一个,如果是mysql就会选择identity,由数据库维护,不会有并发问题.如果生成策略是hilo(高/低算法),需要额外配置几个参数.主键的类型分为两种:自然主键和代理主键.自然主键是在数据库表中把具有业务逻辑含义的字段作为主键,比如有客户的姓名,把客户的姓名当作主键,就是自然主键;代理主键就是采用一个与当前表中逻辑信息无关的字段作为其主键,比如在一张表中插入一列与业务数据毫无关系的数据.

主配置文件可以包含三类信息的配置:数据库连接信息、其他配置和声明映射文件.例如:

<hibernate-configuration> 
    <session-factory> 
        <!-- 数据库连接信息 --> 
        <property name="hibernate.connection.username">root</property> 
        <property name="hibernate.connection.password">root</property> 
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test</property> 
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> 
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> 
         
        <!-- 其他配置 --> 
        <property name="show_sql">true</property>
        <!-- 自动生成表结构,可选择的值有:
            create:每次启动时创建表结构,在建表前会先执行删除表的语句,以保证创建成功。
            update:每次启动时检测一下,如果表结构没有,则创建,不一样,则更新。
            create-drop: 每次启动时创建表结构,退出前删除表结构 
            validate: 每次启动时检测一下,如果表结构不一样,就报错

            但是hbm2ddl.auto属性的配置一般在新增时有效,更新时无效.需要注意一下.
        --> 
        <property name="hbm2ddl.auto">update</property> 
         
        <!-- 声明映射文件 --> 
        <mapping resource="../../domain/Person.hbm.xml" /> 
     
    </session-factory> 
</hibernate-configuration> 
主配置文件相对简单,以后数据库的连接信息也不用放在主配置文件中.主配置文件里面配置了一个sessionFactory,Hibernate把一次数据库的访问当作是会话来看待,我们就需要在程序中获得会话,就要利用到sessionFactory,sessionFactory由主配置文件来描述,我们就先要读取调查主配置文件,然后由Hibernate来产生sessionFactory:
private static SessionFactory sessionFactory = new Configuration()
                                .configure()
                                .buildSessionFactory();

读取主配置文件的方法有多种,都是Configuration类的configure()方法重载的版本,最简单的就是将主配置文件命名为hibernate.cfg.xml,原因如下:

    public Configuration configure() throws HibernateException {
        configure( "/hibernate.cfg.xml" );
        return this;
    }

这是源码.然后我们在主配置文件中利用<mapping>标签声明了映射文件,这个标签会找到指定的类,然后把相应的映射文件加载进来,我们还有另外一种方法加载映射文件:

    private static SessionFactory sessionFactory = new Configuration()
                                .configure()
                                .addClass(Person.class)
                                .buildSessionFactory();

只是比之前的代码多了一行,addClass()方法的返回值仍是Configuration对象,意味着如果你还有映射文件没有加载进来,仍可能继续接着写,那么它是如何找到映射文件的呢?其实很简单,我们一样可以写出来:

    public Configuration addClass(Class persistentClass) throws MappingException {
        String mappingResourceName = persistentClass.getName().replace( '.', '/' ) + ".hbm.xml";
        log.info( "Reading mappings from resource: " + mappingResourceName );
        return addResource( mappingResourceName, persistentClass.getClassLoader() );
    }

这是它的源码,先得到类的全限定名,再把点替换把/,就得到了路径,再连接后缀,就行了,方法本身很简单,我们也要掌握它的思想,也就是方法调用链.


对象的状态

hibernate的设计者为了更好的维护对象,以便生成恰当的SQL语句,引入了对象的状态这个概念.hibernate文档里面描述hibernate的状态分为三种:

瞬时(Transient),又称临时状态 持久(Persistent) 脱管(Detached)又称游离状态

Hibernate3.6 学习笔记

其实我也觉得如果按照Hibernate的关于对象的状态定义,状态应该分为四种才对:

Hibernate3.6 学习笔记

多了一个删除状态,因为游离状态本身也是有对象引用,只是没有被session管理起来,不会被gc回收.四种对象状态的完整定义:

临时:一般是新new的对象 持久:有OID,对对象的操作都会同步到数据库中 游离:对象有OID,并且与数据中的某条记录对应,修改对象不会同步到数据库 删除:session调用了delete()方法把对象从数据库中删除后状态

对象状态虽然不多,但是在使用中要格外小心,只有持久状态的对象才能同步到数据库中.管理对象状态的常用方法:

save():使临时对象持久对象(把对象交给Session管理) update():把游离对象变为持久对象(把对象交给Session管理) saveOrUpdate():把临时对象或游离对象变为持久对象,通过OID判断对象为什么对象 delete():把持久或游离状态变为删除状态(只要是数据库中有记录,就能删除,没有就报错) get():立即加载,如果指定ID的数据不存在,则返回null load():延迟加载,但实体类不能是final的,否则延迟加载失效,如果指定id的数据不存在,则抛异常

上面这些方法中需要特意说下的是load()方法.load()方法是基于类的懒加载,也就是class标签的lazy属性,默认值为true.在为true的情况下调用load()方法会返回一个代理类,且是一个没有初始化的代理类,只有当使用这个类的某个方法时才会去真正的加载记录;如果没有在数据库中找到对应的记录,则抛出异常.如果类的修饰符是fianl,由于hibernate是使用的继承创建的代理类,所以在这种情况下就不能创建代理类,将立即加载.

Hibernate中有二级缓存的概念,既然有二级缓存,那也自然就有"一级缓存"了,实际上,一级缓存就是session的缓存,Session 接口是Hibernate向应用程序提供的操纵对数据库的最主要的接口,它提供了基本的保存,更新,删除和加载Java对象的方法.Session接口的实现中包含了一系列的集合,这些集合构成了Session缓存,只要Session实例的生命周期没有结束,存放在它缓存里的对象也不会结束生命周期,因为还有这些集合在引用这些对象.所以如果有对象已经不需要了,就要即时的清除.把对象变为游离状态或者删除状态.

Session提供了一系列对缓存进行操作的方法:

clear():清空Session(把Session的缓存中所有的对象全部清除出去) evict(Object):把指定的对象从Session缓存中驱逐出去 flush():把Session缓存中的状态马上同步到数据库.默认情况下,生成的update语句、delete语句都是在flush()执行的时候执行,
          在事务提交的时候会自动的先flush()一下.但是insert语句在什么时候执行,是跟主键生成策略有关的.如果是由数据库生成主
          键,则遇到insert()语句会马上执行,因为需要得到主要值,以便之后使用;如果生成策略是assigned,则会在flush()执行的时候
          执行,因为主键值是由你提供的,所以Hibernate不用问数据库也知道.但是调用flush()方法可以让缓存里面的语句马上执行,以同步状态. refresh(Object):让缓存中的对象与数据库中的数据状态一致.假如我要把一个对象变成持久状态,我先把它从数据库从拿出来(从
           数据库中取出就是持久状态),然后把它的数据进行更新,存储到某个地方,如果在更新---存储的过程之间,数据被另一个人修
           改了,我存储的时候也无法获取最新的数据,因为Hibernate已经查询过一次,已经有了这个对象的持久状态,它便不会再进行
           查询,这个时候如果想要获取最新的数据,就要使用refresh(Object)方法,把想要更新的对象传入进去,Hibernate就会进行再次查询.



事务管理

Hibernate的事务(Transaction)使用的很简单,和Spring整合后也就用不着了,简单的记下几个方法.

//取得session
Session session = sessionFactory.openSession();
//打开事务
Transaction tx = session.beginTransaction();
//取得OID为7的持久对象
Person person = (Person) session.load(Person.class, 7);
//回滚事务
tx.rollback();
//设置事务超时时间
tx.setTimeout(30000);
//判断事务是不是已经提交
tx.wasCommitted();
//判断事务是不是已经回滚
tx.wasRolledBack();
//提交事务
tx.commit();
//关闭session(关闭连接)
session.close();

事务的隔离级别,可以配置的值(隔离级别,对应的整数表示):


  • READ UNCOMMITED     1
    READ COMMITED         2
    REPEATABLE READ      4
    SERIALIZEABLE           8

可以在主配置文件中增加<property>标签的hibernate.connection.isolation属性来修改隔离级别.隔离级别标准有4种,但具体应看数据库支持几种,Oracle 只支持2种事务隔离级别:READ COMMITED, SERIALIZABLE.Oracle 默认的事务隔离级别为 READ COMMITED;Mysql 支持4中事务隔离级别,Mysql 默认的事务隔离级别为: REPEATABLE READ,可以修改,Mysql中对事务进行操作:

查看当前连接隔离级别:SELECT @@tx_isolation;

查看全局隔离级别:SELECT @@global.tx_isolation;

设置当前连接连接的隔离级别:set transaction isolation level read committed;

设置全局连接的隔离级别:set global transaction isolation level read committed;

设置是否自动提交(0为false):set autocommit=0;

查看当前的自动提交状态:select @@autocommit;


关联关系映射

关系总体分为三种:一对多,多对多,一对一.在配置映射文件时, 需要选择与保存对象相应的集合标签.

在一对多的关系中,往往只需要一方维护两者的关系,因为在关系型数据库中,只需要有一方存储关系就行了,称为外键,外键列保存在多的一方即可,对于对象来说,双方都设置关联才比较好,这样双方才都知道与对方关联,但是对于数据库来说,只需要一方设置有关联就可以了,不管哪一方,只进行设置,默认就会生成一条update语句来更新外键的值,以保存关联关系(维护关联关系),如果关联一个没有保存的对象(有效的关联),会抛出一个TransientObjectException异常.不管在哪一方都能找到另一方,称为双向关联,单项关联就是指仅仅从某一方才能找到另一方,而另一方不能找到某一方,因为在另一方的数据库中没有维护关系,自然就找不到.关联关系中重要的属性:

  • inverse:是否只由对方维护关联关系,默认值为false.对于一对多,维护关联关系是指更新外键列的值,对于多对多,维护关联关系是指在中间表中插入或删除记录,这个属性只在表示关联关系的集合映射中用(一对多、多对多)
  • cascade:级联操作,即对从对象也做相应的操作,级联风格有:save-update, delete, none, all, delete-orphan, all-delete-orphan, ...,默认值为none.只要是关联关系,都可以配这个属性(一对多、多对一、多对多、一对一)
  • order-by:指定排序子句(order by子句,是指定的sql语句,所以要写列名,这个属性可以在所有的无序集合映射中使用(<set>中可以用,<list>中不可以用),模拟一的一方:

    <!-- 
        不用指定表名,因为根据类,可以自动找到映射文件,映射文件里有描述表的结构
        cascade="all":将一个关联关系(无论是对值对象的关联,或者对一个集合的关联)
        标记为父/子关系的关联。 
        这样对父对象进行 save/update/delete 操作就会导致子对象也进行 save/update/delete 操作
        lazy:默认值为true,开启懒加载.属性interests是set集合的引用,这个集合里面的对象存在数据库中,是person对象的子对象,
        如果开启懒加载,且没有在session关闭之前访问interests的成员,则关闭session之后就获取不到了,访问将抛LazyInitializationException异常.
     -->
     <set name="interests" cascade="all">
          <key column="personID"></key>
          <one-to-many class="..domain.Interest"/>
     </set>

多的一方:

<!--  
    很贴切的标签
    name:指定对应的属性
    column:指明外键列的列名
-->
<many-to-one class="..domain.Person" name="person" column="personID"></many-to-one>
    

假设我有一个员工表,和一个部门表,我有15个员工和5个部门,它们的关系在数据库中表示为员工表的外键列,在session开启之后获取到部门的持久对象,也将获取和它关联的员工,在session关闭之前所做的任何修改都将直接同步到数据库中.如果要解除两者的关联关系:

// 解除关联关系
@Test
public void testRemoveRelation() {
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    // --------------------------------------------------------------
 
    // Employee employee = (Employee) session.get(Employee.class, 15);
    // employee.setDepartment(null);
 
    Department department = (Department) session.get(Department.class, 5);
    // department.getEmployees().remove(obj); // 与指定的员工解除关联关系
    // department.getEmployees().clear(); // 与关联的所有员工解除关联关系
    // department.setEmployees(null); // 与关联的所有员工解除关联关系
    department.setEmployees(new HashSet<Employee>()); // 与关联的所有员工解除关联关系
 
    // --------------------------------------------------------------
    tx.commit();
    session.close();
}

删除部门时,如果有和它关联的员工,则可能会有约束异常,必须先把与员工的关系解除或者连员工一起删除,需要设置cascade属性,然后才能进行:

@Test
public void testDelete() {
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    // --------------------------------------------------------------
 
    /** 删除部门时,如果有员工引用这个部门的id,则:
            a,如果inverse=true,即不能维护关联关系,就会 抛异常: Cannot delete or update a parent row: a foreign key constraint fails
             b,如果inverse=false,即可以维护关联关系,则Hibernate会先取消与所有员工的关联,再删除部门,不会有异常。
       
        先获取持久对象,用get()方法或者load()方法,或者自己模拟一下:
        Department department = new Department();
        department.setId(4);
        只要OID对应即可.
    */
    Department department = (Department) session.get(Department.class, 4);
    session.delete(department);
    //department.getEmployees().clear(); // 解除与所有关联员工的关联关系
 
    // --------------------------------------------------------------
    tx.commit();
    session.close();
}


多对多的关联关系比多对一的关联关系更好理解一些,通常在数据库中有张中间表来描述:

Hibernate3.6 学习笔记

在Hibernate中默认双方都能维护关联关系,但双方都设置关联关系会有异常:java.sql.BatchUpdateException.因为中间表通常以两个外键作为联合主键,在程序中,由于是多对多的关系,双方维护的对象都是一样的,比如:

Hibernate3.6 学习笔记

从任何一方都能关联,任何一方都清楚的展示了对方的属性,任何一方的对象里面都维护着另一方的对象(双向关联),这样一来,在持久化对象的时候,就会在中间表中出现重复记录,当然是不允许的.所以一般做法为:只关联一方或者在某一方设置inverse=”true”即可.多对多是关联关系里面用的最多的:

<!-- 多对多映射(employees : Set<Employee> 属性)
        <key column="..."/>是配置集合外键,即引用当前对象表的主键的那个外键
           
        inverse="true"表示放弃维护关联关系(由对方维护)。在多对多中,维护关联关系是指的在中间表中插入一条数据
 -->
<set name="employees" table="emmployee_role" inverse="true">
    <key column="roleId"></key>
    <many-to-many class="Employee" column="employeeId"></many-to-many>
</set>


一对一.一对一虽然不难,理解起来比多对多稍微麻烦一点,假设一个部门只能有一个员工,一个员工也只能有一个部门.一对一有两种映射方式:

基于主键(推荐)

Hibernate3.6 学习笔记

这是一种万能方式,因为employee表中的主键是id,属于代理主键,如果开发部被删除了,zhang将不会被删除,因为department_id可能设置允许为null.

有外键的表的映射文件(employee):

<!--
    这里用到了多对一的标签,但是这里并不是指的多对一,从unique属性就能看出来这一列的记录不能重复,那
    为什么要用这个标签而不是one-to-one呢?因为这张表需要一个保存其他表数据的外键列,而one-to-one
    标签不会生成一个外键列,所以需要用到many-to-one标签,并且给它设置unique属性让它唯一,因而也可
    以认为一对一只是多对多的一种特例.给它加上cascade属性是为了更方便的管理对象,如果子对象没有持久
    化,也自动给它持久化
-->
<many-to-one    cascade="all" 
                unique="true" name="department" 
                class="..domain.Department" 
                column="departmentID">
</many-to-one>

无外键表的映射文件(department):

<!--  
     表中不需要维护两个相同的记录,需要获取时一并获取即可,无外键方,使用<one-to-one>
     property-ref:默认与被关联的实体的主键相关联,有了property-ref属性,
                  就可以通过它与被指定的实体主键以外的字段相关联
 -->
  <one-to-one name="employee" property-ref="department" class="..domain.Employee"></one-to-one>

基于外键

Hibernate3.6 学习笔记

在这种方式下,employee表中的id既当主键又当外键,如果开发部被删除了,zhang也要被删除,因为主键不能为null,与其他对象有多个一对一关系时,如果外键设计在当前表中,则最多只能有一个基于主键的一对一映射.有外键方的对象需要独立存在而不与对方的某一条数据关联时,不能使用基于主键的一对一映射.除非保证employee表中的id始终有值对应.除了这些以外,还需要在两者之间寻找主从关系,比如人是必须存在的,身份证需要依赖人,人就是主,身份证就是从.
主:

<!-- 
       只需要描述自己的idCard属性所对应的对象即可
       Hibernate会自动找到对应的映射文件
--> 
<one-to-one name="idCard" class="..domain.IdCard"></one-to-one> 
 

从:

<!--
      因为自己的主键也是外键,一张表中可以存在多个外键,
      所以必须说明自己的主键是参照哪个外键
-->
<id name="id">
    <generator class="foreign">
        <param name="property">person</param>
    </generator>
</id>

. . .
<!--
      constrained:是否加上外键约束,默认值为false
-->
<one-to-one name="person" class="..domain.Person" constrained="true">
</one-to-one>

不管关联关系是什么,不是谁都能维护关联关系,关于谁才能维护关联关系:

一对多:
            默认双方都能维护
            在一方可以通过设置inverse="true"来放弃维护关联关
            在多方(有外键方)始终能维护关联关系,因为外键在自己表中
多对多:
            默认双方都能维护
            在任何一方都可以通过设置inverse="true"来放弃维护关联关系。注意只需设置一边就可以
一对一:
            不管是采基于外键的还是基于主键的一对一映射,都是只有有外键方可以维护关联关系,没有外键方不可以维护。而且不可以配置
            当采用基于主键的一对一映射时,双方都不能解除关联关系,因为主键值为能为null

单向关联与上述的一致,能维护关系的一方才可以做单向关联到对方.

 

继承映射

映射继承结构的时候,一般是整个继承结构使用一个映射文件,名称前缀为超类的名,继承映射有三种方式,全部以论坛的帖子为例:

Hibernate3.6 学习笔记

方式一:

一个继承结构只有一张表,同时只有一个映射文件,那它的子类怎么办?用<subclass>标签配置它的子类.另外分析下,因为是一张表,表中要有所有的字段,但是主题的帖子肯定不需要floor(楼层),只有回复表才需要标注自己是几楼,同理,主题的extra(如精华、置顶...)也是主题表特有的.那么如何判断帖子是什么类型呢?判断帖子有extra,则是主题,有floor,则是回复等,所有可能情况如下:

Hibernate3.6 学习笔记

前两条记录还好说,第三条怎么算呢?可能是一个没有extra的主贴,也有可能是一个没有标注楼层的回复,总之,从设计表的结构上就要把这种可能性排除,所以采用中表中加入一个type来指定具体的类型.那么,如果把类型在添加记录的时候就加入进来呢?不同类型的对象添加的时候自然就是不一样的值,比如主题就是type=topic,回复就是type=reply.如下:

<hibernate-mapping package="..mapping">
    <!-- 一个继承结构一张表的映射方式:
        需要有一个额外的列用于保存类型标志。
        每个类都需要指定discriminator-value属性,表示代表当前类型的标志。如果没有指定,默认为当前类的全限定名。
        父类与子类只需要映射自己的属性就可以了。
     -->
    <class name="Article" table="Article" discriminator-value="Article">
        <id name="id">
            <generator class="identity"></generator>
        </id>
        <!-- 指定用于辨别是什么类型标志列 必须放在property标签之前-->
        <discriminator column="type_type="string"></discriminator>
 
        <property name="title"></property>
        <property name="content"></property>
 
        <!-- 子类:Topic -->
        <subclass name="Topic" discriminator-value="Topic">
            <property name="extra"></property>
        </subclass>
 
        <!-- 子类:Reply -->
        <subclass name="Reply" >
            <property name="floor"></property>
        </subclass>
    </class>
</hibernate-mapping>


方式二:

每个类一张表,抽象类也有表(<joined-subclass>),子类表与父类表是一对一的关系,在映射时,每个类都只映射自己特有的属性,公共的属性值存放在父类表中,父类表的主键和子类表的主键相关联,不相关的子类表,不添加记录:

<hibernate-mapping package="..mapping">
    <!-- 每个类一张表
        子类表与父类表是一对一的关系
        在映射时,每个类都只映射自己特有的属性
     -->
    <class name="Article" table="Article">
        <id name="id">
            <generator class="identity"></generator>
        </id>
        <property name="title"></property>
        <property name="content"></property>
 
        <!-- 子类:Topic 
            需要指定表名。
            需要指定<key column="id"></key>,表示自己的主键,且是外键引用父类表的主键(一对一的关系)
        -->
        <joined-subclass name="Topic" table="Topic">
            <key column="id"></key>
            <property name="type"></property>
        </joined-subclass>
 
        <!-- 子类:Reply -->
        <joined-subclass name="Reply" table="Reply">
            <key column="id"></key>
            <property name="floor"></property>
        </joined-subclass>
    </class>
</hibernate-mapping>
 
 
方式三:
 
每个非抽象类一张表(<union-subclass>),只要不是抽象类,就对应一张表.每个表中都有全部属性的列,包括从父类继承过来的属性.如果父类是抽像的,也会给父类建张表,但是永远是空表,所以要在<class>中加上属性abstract="true",这样就不会为父类建表了.主键生成策略不能使用identity,不然每个表主键都从1开始递增,获取持久对象的时候,指定的OID不唯一,因为在一个继承结构中不能出现有相同主键值的两个不同类型的数据,否则在使用父类查询时就会查出多条记录,这样是不可以的,只要有可能造成这个问题的主键生成策略都不可以用.
 
<hibernate-mapping package="..mapping">
    <class name="Article" table="itcast_Article">
        <id name="id">
            <generator class="hilo">
              <param name="table">hi_value</param>
              <param name="column">next_value</param>
              <param name="max_lo">100</param>
            </generator>
        </id>
        <property name="title"></property>
        <property name="content"></property>
 
        <!-- 子类:Topic 
            需要指定表名。
        -->
        <union-subclass name="Topic" table="topic">
            <property name="type"></property>
        </union-subclass>
 
        <!-- 子类:Reply -->
        <union-subclass name="Reply" table="reply">
            <property name="floor"></property>
        </union-subclass>
    </class>
</hibernate-mapping>
 

关于Hibernate的查询,有多种方式:

  1. 根据OID查询(get()与load())
  2. 导航对象图检索方式:  根据已经加载的对象导航到其他对象
  3. HQL(Hibernate Query Language)
  4. QBC(Query By Criteria)

HQL

 

命名查询

Query query = session.getNamedQuery("queryAllEmployees");
List list = query.setParameter(0, 5).list(); 

对应的配置文件:

<!-- 定义命句查询语句 -->
<query name="queryAllEmployees">
    <!--[CDATA[FROM Employee WHERE id>?]]-->
</query> 

如果执行一条查询,查询出了多个结果,则会抛NonUniqueResultException异常.

 

Criteria(面向对象的查询)

// 1,简单查询(查询所有的Employee)
// Criteria criteria = session.createCriteria(Employee.class);
// criteria.add(Restrictions.gt("id", 5)); // id大于5
// criteria.add(Restrictions.le("id", 10)); // id小于等于10
// criteria.addOrder(Order.desc("id")); // 按id降序排列
Criteria criteria = session.createCriteria(Employee.class)//
        .add(Restrictions.gt("id", 5)) // id大于5
        .add(Restrictions.le("id", 10)) // id小于等于10
        .addOrder(Order.asc("name")) // 第1顺序,按name升序排列
        .addOrder(Order.desc("id")); // 第2顺序按id降序排列
// 执行查询,得到结果集合
List list = criteria.list();
// 显示结果
for (Object obj : list) {
    if (obj.getClass().isArray()) { // 如果是数组
        String result = Arrays.toString((Object[]) obj);
        System.out.println(result);
    } else { // 如果不是数组,则使用toString()的结果
        System.out.println(obj);
    }
}
// 执行查询,得到唯一的结果。如果查询出了多个结果,则会抛异常。
// Object result = criteria.uniqueResult();
 

 


配置数据库连接池(c3p0)

使用Hibernate包中自带的c3p0.只需在主配置文件中配置几个关键属性即可:

<!-- 使用c3p0连接池  配置连接池提供的供应商-->
<property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<!--在连接池中可用的数据库连接的最少数目 -->
<property name="c3p0.min_size">5</property>
<!--在连接池中所有数据库连接的最大数目  -->
<property name="c3p0.max_size">20</property>
<!--设定数据库连接的过期时间,以秒为单位,
如果连接池中的某个数据库连接处于空闲状态的时间超过了timeout时间,就会从连接池中清除 -->
<property name="c3p0.timeout">120</property>
<!--每3000秒检查所有连接池中的空闲连接 以秒为单位-->
<property name="c3p0.idle_test_period">3000</property> 

 


 

二级缓存

Hibernate中提供了两个级别的缓存:

一级缓存是session的缓存,它属于session的生命周期,session关闭后缓存也将清除.这就说明用session来提升性能的能力有限,而二级缓存是sessionFactory级别的,可以做更多的事情,但是默认是不开启的,我们想要使用,就必须先开启.在主配置文件中添加以下代码来开启二级缓存:

<property name="cache.use_second_level_cache">true</property> 

光开启还不够,还要提供二级缓存的实现,需要给定缓存的提供商,有多种提供商可供选择,EhCacheProvider是一个专业的缓存提供商,在这里就使用它为hibernate的二级缓存服务.给出配置文件的相关配置:

<ehcache>
    <!-- 在内存中存不下的时候,存放的临时目录 -->
    <diskStore path="c:/ehcache"/>
    <!-- 
        maxElementsInMemory:可存10000个对象,超出后存入临时目录
        eternal:设置对象是否为永久的,true表示永不过期,此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false 
        timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。
                    如果此值为0,表示对象可以无限期地处于空闲状态。 
        timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。
                    如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于 timeToIdleSeconds 属性值 
        overflowToDisk:设置基于内在的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
    -->
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />
</ehcache> 

到此才算是完全开启,但是,光开启还不够!需要指定哪些类需要缓存,不需要的就不指定.如果有哪个类更新频率很快,比如新闻、首页之类的,就不需要缓存.把类缓存起来称为类缓存.

<class-cache usage="read-write" class="..senondcache.Department"/>
<class-cache usage="read-write" class="..senondcache.Employee"/> 
二级缓存中的类缓存,只适用于使用id查询的方式,比如get()或load(),对于使用HQL的方式不可以缓存,比如"FROM Department d WHERE d.id=1"就不会缓存,比如以下就可以缓存:

 

@Test
public void testSecondCache() {
    // ==========================================================
    // 第一个Session
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    // --------------------------------------------------------------
    Department department = (Department) session.get(Department.class, 1);
    System.out.println(department);
    System.out.println(department.getEmployees());
    // --------------------------------------------------------------
    tx.commit();
    session.close();
    System.out.println("/n----------/n");
    // ==========================================================
    // 第二个Session
    session = sessionFactory.openSession();
    tx = session.beginTransaction();
    // --------------------------------------------------------------
    Department department2 = (Department) session.get(Department.class, 1);
    System.out.println(department2);
    System.out.println(department2.getEmployees());
    // --------------------------------------------------------------
    tx.commit();
    session.close();
} 

如果想使用查询缓存,还要在主配置文件中打开"开关":

<property name="cache.use_query_cache">true</property> 

但是这样还不够,没有缓存哪条语句,在程序中指定:

@Test
    public void testQueryCache() {
        // ==========================================================
        // 第一个Session
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();
        // --------------------------------------------------------------
        Department department = (Department) session.createQuery(//
                "FROM Department d WHERE d.id=?")//
                .setParameter(0, 1)//
                .setCacheable(true)// 把查询语句缓存起来,以后使用就是使用缓存了
                .uniqueResult();
        System.out.println(department);
        // --------------------------------------------------------------
        tx.commit();
        session.close();
        System.out.println("/n----------/n");
        // ==========================================================
        // 第二个Session
        session = sessionFactory.openSession();
        tx = session.beginTransaction();
        // --------------------------------------------------------------
        Department department2 = (Department) session.createQuery(//
                "FROM Department d WHERE d.id=?")//
                .setParameter(0, 1)//
                .setCacheable(true)// 使用查询缓存
                .uniqueResult();
        System.out.println(department2);
        // --------------------------------------------------------------
        tx.commit();
        session.close();
    } 

 

如果类中有集合,则集合不会缓存进来,除非设置集合缓存:

<collection-cache usage="read-write" collection="..senondcache.Department.employees"/> 

 

还有另外一种缓存,叫时间戳缓存.

时间戳缓存就是指定Hibernate的二级缓存会自动的检测,如果使用了Update或Delete语句,则把一些数据清出缓存,但是它只会清出二级缓存,如果要更新一级缓存,必须使用refresh()方法.

最后还有一个和缓存相关的,使用Query对象.

Query.list()不会使用缓存,除非调用了.setCacheable(true)才可以,但是HQL一变或参数变了,就不会再用缓存的结果了.

但是Query.iterate()会使用类缓存,原理是:

  • 先执行一个查询,查询所有的符合条件的id
  • 再使用每一个对象时,先根据id找缓存,如果找不着,就会生成一个select .. where id=? 的查询
  • 就是说会有n+1次查询的问题(n是指定符合条件的数据量,1是指定查询id的SQL语句)
@Test
public void testQueryIterate() {
    // ==========================================================
    // 第一个Session
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    // --------------------------------------------------------------
    List list = session.createQuery("FROM Department d WHERE d.id<=2").list();
    for(Object obj : list){
        System.out.println(obj);
    }
    // --------------------------------------------------------------
    tx.commit();
    session.close();
    System.out.println("/n----------/n");
    // ==========================================================
    // 第二个Session
    session = sessionFactory.openSession();
    tx = session.beginTransaction();
    // --------------------------------------------------------------
    Iterator iter = session.createQuery("FROM Department d WHERE d.id<=3").iterate();
    while(iter.hasNext()){
        System.out.println(iter.next());
    }
    // --------------------------------------------------------------
    tx.commit();
    session.close();
}
 

 


如果本文有什么问题,不严谨的地方,请及时指出来.不胜感激.

上一篇:Object-C--->Swift之(六)函数类型


下一篇:Weblogic命令行更换控制台密码