Hibernate学习笔记

参考http://blog.csdn.net/RainyTooo/archive/2006/08/27/1125696.aspx

参考《Hibernate参考文档》,挑选整理出的学习笔记

 

一、Hibernate相关知识

 

1.1 持久类

类似:

package vo;

 

import java.util.Date;

 

public class Event {

    private Long id;

    private String title;

    private Date date;

 

    public Event() {

 

    }

 

    public Long getId() {

       return id;

    }

 

    public void setId(Long id) {

       this.id = id;

    }

 

    public String getTitle() {

       return title;

    }

 

    public void setTitle(String title) {

       this.title = title;

    }

 

    public Date getDate() {

       return date;

    }

 

    public void setDate(Date date) {

       this.date = date;

    }

 

}

标准的JavaBean形式。

所有的持久类(persistent classes)都要求有无参的构造器(no-argument constructor); 因为Hibernate必须要使用Java反射机制(Reflection)来实例化对象。构造器(constructor)的访问控制可以是私有的(private), 然而当生成运行时代理(runtime proxy)的时候将要求使用至少是package级别的访问控制,这样在没有字节码编入 bytecode instrumentation)的情况下,从持久化类里获取数据会更有效率一些。

 

1.2 映射文件(xxx.hbm.xml

表与持久类对应的映射文件

类似:

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<!--

    Mapping file autogenerated by MyEclipse Persistence Tools

-->

<hibernate-mapping>

    <class name="vo.Event" table="EVENTS">

       <id name="id" type="java.lang.Long" column="EVENT_ID">

           <generator class="increment"></generator>

       </id>

       <property name="title" type="java.lang.String"></property>

       <property name="date" type="timestamp" column="EVENT_DATE"></property>

    </class>

</hibernate-mapping>

 

当没有设定column参数的时候,Hibernate缺省使用属性名作为字段(column)名。

<property name="title" type="java.lang.String"></property>

<property name="date" type="timestamp" column="EVENT_DATE"></property>

 

当属性类型唯一时,Type属性也是可以省略的,跟vo类类型对应,但比如,对于date类型,要区分是SQL datetimestamptime,所以要指明。

 

1.3 配置文件

类似于

<?xml version='1.0' encoding='UTF-8'?>

<!DOCTYPE hibernate-configuration PUBLIC

          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

          "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<!-- Generated by MyEclipse Hibernate Tools.                   -->

<hibernate-configuration>

    <session-factory>

        <!-- JDBC connection pool (use the built-in) -->

        <property name="connection.pool_size">1</property>

       <property name="connection.url">

           jdbc:hsqldb:data/tutorial

       </property>

       <!-- SQL dialect -->

       <!-- SQL方言 Hibernate产生针对特定数据库语法的SQL语句 -->

        <property name="dialect">org.hibernate.dialect.HSQLDialect</property>

       <property name="myeclipse.connection.profile">sa</property>

       <property name="connection.password"></property>

       <property name="connection.driver_class">

           org.hsqldb.jdbcDriver

       </property>

       <!-- Echo all executed SQL to stdout -->

        <property name="show_sql">true</property>

       

       <mapping resource="vo/Event.hbm.xml" />

    </session-factory>

</hibernate-configuration>

配置HibernateSessionFactory 一个关联于特定数据库全局性的工厂(factory)。如果你要使用多个数据库,通常应该在多个配置文件中使用多个<session-factory> 进行配置。

 

 

1.4 启动Hibernate

这个启动过程包括创建一个全局性的SessoinFactory并把它储存在一个应用程序容易访问的地方。 SessionFactory可以创建并打开新的Session 一个Session代表一个单线程的单元操作,SessionFactory则是一个线程安全的全局对象,只需要创建一次。

我们将创建一个HibernateUtil帮助类(helper class)来负责启动Hibernate并使 操作Session变得容易。这个帮助类将使用被称为ThreadLocal Session 的模式来保证当前的单元操作和当前线程相关联。

代码如下:

package com;

 

import org.hibernate.HibernateException;

import org.hibernate.Session;

import org.hibernate.SessionFactory;

import org.hibernate.cfg.Configuration;

 

public class HibernateUtil {

    public static final SessionFactory sessionFactory;

   

    static{

       try {

           // Create the SessionFactory from hibernate.cfg.xml

           sessionFactory = new Configuration().configure().buildSessionFactory();

          

       } catch (Throwable ex) {

            // Make sure you log the exception, as it might be swallowed

            System.err.println("Initial SessionFactory creation failed." + ex);

            throw new ExceptionInInitializerError(ex);

       }

    }

   

    public static final ThreadLocal session = new ThreadLocal();

   

    public static Session currentSession()throws HibernateException{

       Session s = (Session)session.get();

       // Open a new Session, if this thread has none yet

       if(s==null){

           s = sessionFactory.openSession();

           session.set(s);

       }

       return s;

    }

   

    public static void closeSession() throws HibernateException{

       Session s = (Session)session.get();

       if(s!=null){

           s.close();

       }

       session.set(null);

    }

   

}

 

这个类不仅仅在它的静态初始化过程(仅当加载这个类的时候被JVM执行一次)中产生全局SessionFactory 同时也有一个ThreadLocal变量来为当前线程保存Session。不论你何时 调用HibernateUtil.currentSession(),它总是返回同一个线程中的同一个Hibernate单元操作。 而一个HibernateUtil.closeSession()调用将终止当前线程相联系的那个单元操作。

 

加载并存储对象

编写一个带有main()方法 EventManager

package dao;

 

import java.util.Date;

import java.util.List;

 

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.hibernate.Session;

import org.hibernate.Transaction;

 

import vo.Event;

 

import com.HibernateUtil;

 

public class EventManager {

 

    private static Log log=LogFactory.getLog(EventManager.class);

   

    public static void main(String[] args) {

       EventManager mgr = new EventManager();

       if(args[0].equals("store")){

           mgr.createAndStoreEvent("My Event", new Date());

       }else if(args[0].equals("list")){

           List events = mgr.listEvents();

           for (int i = 0; i < events.size(); i++)  {

              Event event = (Event)events.get(i);

              System.out.println("Event:"+event.getTitle()+",Time:"+event.getDate());

           }

       }

       HibernateUtil.sessionFactory.close();

       log.info("ok");

    }

   

    public List listEvents() {

       Session session = HibernateUtil.currentSession();

       Transaction tx = session.beginTransaction();

      

       List result = session.createQuery("from Event").list();

      

       tx.commit();

        session.close();

       return result;

    }

 

    public void createAndStoreEvent(String title , Date date){

       Session session = HibernateUtil.currentSession();

       Transaction tx = session.beginTransaction();

      

       Event event = new Event();

       event.setDate(date);

       event.setTitle(title);

      

       session.save(event);

      

       tx.commit();

       HibernateUtil.closeSession();

    }

}

我们创建一个新的Event对象并把它传递给HibernateHibernate现在负责创建SQL并把 INSERT命令传给数据库。

 

运行,带参数“store”,输出结果如下:

 

 

 

 

 

整个项目图如下:

 

 

Log4j.xml代码如下:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

 

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false">

   

    <appender name="FILE" class="org.apache.log4j.DailyRollingFileAppender">

       <param name="File" value="process.log" />

       <param name="Append" value="true" />

       <param name="DatePattern" value="'.'yyyy-MM-dd" />

       <layout class="org.apache.log4j.PatternLayout">

           <param name="ConversionPattern" value="%d %-5p [%c{1}] - %m%n" />

       </layout>

    </appender>  

   

    <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">

       <param name="Target" value="System.out" />

       <param name="Threshold" value="DEBUG" />

 

       <layout class="org.apache.log4j.PatternLayout">

           <!-- The default pattern: Date Priority [Category] Message/n -->

           <param name="ConversionPattern" value="%d %-5p [%c{1}] - %m%n" />

       </layout>

    </appender>

   

    <category name="org.hibernate">

       <priority value="WARN"/>

       <appender-ref ref="CONSOLE" />

    </category>

   

    <category name="dao.EventManager">

       <priority value="INFO"/>

       <appender-ref ref="CONSOLE" />

    </category>

 

    <!-- <root>

       <appender-ref ref="FILE"/>

       <appender-ref ref="CONSOLE"/>

    </root> -->

   

    <root>

       <appender-ref ref="FILE"/>

    </root>

 

</log4j:configuration>

 

 

二、Hibernate体系结构

2.1 概况

2.1.1 SessionFactory (org.hibernate.SessionFactory)

针对单个数据库映射关系经过编译后的内存镜像,它也是线程安全的(不可变)。 它是生成Session的工厂,本身要用到ConnectionProvider 该对象可以在进程或集群的级别上,为那些事务之间可以重用的数据提供可选的二级缓存。

 

2.1.2 Session (org.hibernate.Session)

表示应用程序与持久储存层之间交互操作的一个单线程对象,此对象生存期很短。 其隐藏了JDBC连接,也是Transaction的工厂。 其会持有一个针对持久化对象的必选(第一级)缓存,在遍历对象图或者根据持久化标识查找对象时会用到。

 

2.1.3 持久的对象及其集合

带有持久化状态的、具有业务功能的单线程对象,此对象生存期很短。 这些对象可以是普通的JavaBeans/POJO,唯一特殊的是他们正与(仅仅一个)Session相关联。 这个Session被关闭的同时,这些对象也会脱离持久化状态,可以被应用程序的任何层自由使用。 (例如,用作跟表示层打交道的数据传输对象data transfer object。)

 

2.1.4 瞬态(transient)以及脱管(detached)的对象及其集合

持久类的没有与Session相关联的实例。 他们可能是在被应用程序实例化后,尚未进行持久化的对象。 也可能是因为实例化他们的Session已经被关闭而脱离持久化的对象。

 

2.1.5 事务Transaction (org.hibernate.Transaction)

(可选的)应用程序用来指定原子操作单元范围的对象,它是单线程的,生存期很短。 它通过抽象将应用从底层具体的JDBCJTA以及CORBA事务隔离开。 某些情况下,一个Session之内可能包含多个Transaction对象。 尽管是否使用该对象是可选的,但是事务边界的开启与关闭(无论是使用底层的API还是使用Transaction对象)是必不可少的。

 

2.1.6 ConnectionProvider (org.hibernate.connection.ConnectionProvider)

(可选的)生成JDBC连接的工厂(同时也起到连接池的作用)。 它通过抽象将应用从底层的DatasourceDriverManager隔离开。 仅供开发者扩展/实现用,并不暴露给应用程序使用。

 

2.1.7 TransactionFactory (org.hibernate.TransactionFactory)

(可选的)生成Transaction对象实例的工厂。 仅供开发者扩展/实现用,并不暴露给应用程序使用。

 

 

2.1.8 扩展接口

Hibernate提供了很多可选的扩展接口,你可以通过实现它们来定制你的持久层的行为。 具体请参考API文档。

 

在一个“轻型”的体系结构中,应用程序可能绕过 Transaction/TransactionFactory 以及 ConnectionProvider API直接跟JTAJDBC打交道。

 

 

 

2.2 实例状态

一个持久化类的实例可能处于三种不同状态中的某一种。 这三种状态的定义则与所谓的持久化上下文(persistence context)有关。 HibernateSession对象就是这个所谓的持久化上下文:

2.2.1 瞬态(transient

该实例从未与任何持久化上下文关联过。它没有持久化标识(相当于主键)。

 

2.2.2 持久(persistent)

实例目前与某个持久化上下文有关联。 它拥有持久化标识(相当于主键),并且可能在数据库中有一个对应的行。 对于某一个特定的持久化上下文,Hibernate保证持久化标识与Java标识(其值代表对象在内存中的位置)等价。

 

2.2.3 脱管(detached)

实例曾经与某个持久化上下文发生过关联,不过那个上下文被关闭了, 或者这个实例是被序列化(serialize)到这个进程来的。 它拥有持久化标识,并且在数据库中可能存在一个对应的行。 对于脱管状态的实例,Hibernate不保证任何持久化标识和Java标识的关系。

 

 

2.3 JMX整合

JMX是管理Java组件(Java components)J2EE规范。 Hibernate 可以通过一个JMX标准服务来管理。 在这个发行版本中,我们提供了一个MBean接口的实现, org.hibernate.jmx.HibernateService

想要看如何在JBoss应用服务器上将Hibernate部署为一个JMX服务的例子,您可以参考JBoss用户指南。 我们现在说一下在Jboss应用服务器上,使用JMX来部署Hibernate的好处:

Session管理: HibernateSession对象的生命周期可以 自动跟一个JTA事务边界绑定。这意味着你无需手工开关Session, 这项 工作会由JBoss EJB 拦截器来完成。你再也不用担心你的代码中的事务边界了(除非你想利用Hibernate提供 Transaction API来自己写一个便于移植的的持久层) 你现在要通过 HibernateContext来操作Session了。

HAR 部署: 通常情况下,你会使用JBoss的服务部署描述符(在EAR/SAR文件中)来部署Hibernate JMX服务。 这种部署方式支持所有常见的Hibernate SessionFactory的配置选项。 不过,你需在部署描述符中,列出你所有的映射文件的名字。如果你使用HAR部署方式, JBoss 会自动探测出你的HAR文件中所有的映射文件

 

 

2.3 JCA的支持

Hibernate也可以被配置为一个JCA连接器(JCA connector)。

 

 

三、Hibernate相关配置

 

3.1  Configuration

         一个org.hibernate.cfg.Configuration实例代表了一个应用程序中Java类型 SQL数据库映射的完整集合. Configuration被用来构建一个(不可变的 (immutable))SessionFactory. 映射定义则由不同的XML映射定义文件编译而来.

Configuration实例是一个启动期间(startup-time)的对象, 一旦SessionFactory创建完成它就被丢弃了.

 

3.2  获得SessionFactory

当所有映射定义被Configuration解析后, 应用程序必须获得一个用于构造Session实例的工厂. 这个工厂将被应用程序的所有线程共享:

SessionFactory sessions = cfg.buildSessionFactory();

Hibernate允许你的应用程序创建多个SessionFactory实例. 这对 使用多个数据库的应用来说很有用.

 

3.3  JDBC连接

通常你希望SessionFactory来为你创建和缓存(pool)JDBC连接. 如果你采用这种方式, 只需要如下例所示那样,打开一个Session:

Session session = sessions.openSession(); // open a new Session

一旦你需要进行数据访问时, 就会从连接池(connection pool)获得一个JDBC连接.

 

为了使这种方式工作起来, 我们需要向Hibernate传递一些JDBC连接的属性. 所有Hibernate属性的名字和语义都在org.hibernate.cfg.Environment中定义. 我们现在将描述JDBC连接配置中最重要的设置

 

如果你设置如下属性,Hibernate将使用java.sql.DriverManager来获得(和缓存)JDBC连接 :

 4.1.  Hibernate JDBC属性 (xxx.cfg.xml)

属性名

用途

hibernate.connection.driver_class

jdbc驱动类

hibernate.connection.url

jdbc URL

hibernate.connection.username

数据库用户

hibernate.connection.password

数据库用户密码

hibernate.connection.pool_size

连接池容量上限数目

 

Hibernate自带的连接池算法相当不成熟. 它只是为了让你快些上手,不适合用于产品系统或性能测试中。 出于最佳性能和稳定性考虑你应该使用第三方的连接池。只需要连接池的特定设置替换 hibernate.connection.pool_size。这将关闭Hibernate自带的连接池. 例如, 你可能会想用C3P0.

C3P0是一个随Hibernate一同分发的开源的JDBC连接池, 它位于lib目录下。 如果你设置了hibernate.c3p0.*相关的属性, Hibernate将使用 C3P0ConnectionProvider来缓存JDBC连接. 如果你更原意使用Proxool, 请参考发 行包中的hibernate.properties并到Hibernate网站获取更多的信息

以下是一个使用C3P0hibernate.properties样例文件:

 

hibernate.connection.driver_class = org.postgresql.Driver

hibernate.connection.url = jdbc:postgresql://localhost/mydatabase

hibernate.connection.username = myuser

hibernate.connection.password = secret

hibernate.c3p0.min_size=5

hibernate.c3p0.max_size=20

hibernate.c3p0.timeout=1800

hibernate.c3p0.max_statements=50

hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect

 

为了能在应用程序服务器(application server)中使用Hibernate, 你应当总是将Hibernate 配置成注册在JNDI中的Datasource处获得连接,你至少需要设置下列属性中的一个:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 4.2.  Hibernate数据源属性

属性名

用途

hibernate.connection.datasource

数据源JNDI名字

hibernate.jndi.url

JNDI提供者的URL (可选)

hibernate.jndi.class

JNDI InitialContextFactory (可选)

hibernate.connection.username

数据库用户 (可选)

hibernate.connection.password

数据库用户密码 (可选)

 

这里有一个使用应用程序服务器JNDI数据源的hibernate.properties样例文件:

 

hibernate.connection.datasource = java:/comp/env/jdbc/test

hibernate.transaction.factory_class = /

    org.hibernate.transaction.JTATransactionFactory

hibernate.transaction.manager_lookup_class = /

    org.hibernate.transaction.JBossTransactionManagerLookup

hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect

 

 

 

3.4  其他可选配置

有大量属性能用来控制Hibernate在运行期的行为. 它们都是可选的, 并拥有适当的默认值.

 4.3.  Hibernate配置属性(xxx.cfg.xml)

 

 

 

 

 

hibernate.use_sql_comments

如果开启, Hibernate将在SQL中生成有助于调试的注释信息, 默认值为false.

取值 true | false

 

 

 

 

 

3.5  XML配置文件

另一个配置方法是在hibernate.cfg.xml文件中指定一套完整的配置. 这个文件可以当成hibernate.properties的替代。 若两个文件同时存在,它将重载前者的属性.

XML配置文件被默认是放在CLASSPATH的根目录下. 这是一个例子:

<?xml version='1.0' encoding='utf-8'?>

<!DOCTYPE hibernate-configuration PUBLIC

    "-//Hibernate/Hibernate Configuration DTD//EN"

    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

 

<hibernate-configuration>

 

    <!-- /jndi/name绑定到JNDISessionFactory实例 -->

    <session-factory

        name="java:hibernate/SessionFactory">

 

        <!-- 属性 -->

        <property name="connection.datasource">java:/comp/env/jdbc/MyDB</property>

        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>

        <property name="show_sql">false</property>

        <property name="transaction.factory_class">

            org.hibernate.transaction.JTATransactionFactory

        </property>

        <property name="jta.UserTransaction">java:comp/UserTransaction</property>

 

        <!-- 映射定义文件 -->

        <mapping resource="org/hibernate/auction/Item.hbm.xml"/>

        <mapping resource="org/hibernate/auction/Bid.hbm.xml"/>

 

        <!-- 缓存设置 -->

        <class-cache class="org.hibernate.auction.Item" usage="read-write"/>

        <class-cache class="org.hibernate.auction.Bid" usage="read-only"/>

        <collection-cache class="org.hibernate.auction.Item.bids" usage="read-write"/>

 

    </session-factory>

 

</hibernate-configuration>

 

 

使用XML配置,使得启动Hibernate变的异常简单, 如下所示,一行代码就可以搞定:

SessionFactory sf = new Configuration().configure().buildSessionFactory();

你可以使用如下代码来添加一个不同的XML配置文件

SessionFactory sf = new Configuration().configure("catdb.cfg.xml").buildSessionFactory();

 

 

3.6  J2EE应用程序服务器的集成

针对J2EE体系,Hibernate有如下几个集成的方面:

容器管理的数据源(Container-managed datasources): Hibernate能通过容器管理由JNDI提供的JDBC连接. 通常, 特别是当处理多个数据源的分布式事务的时候, 由一个JTA兼容的TransactionManager和一个 ResourceManager来处理事务管理(CMT, 容器管理的事务). 当然你可以通过 编程方式来划分事务边界(BMT, Bean管理的事务). 或者为了代码的可移植性,你也也许会想使用可选的 Hibernate Transaction API

自动JNDI绑定: Hibernate可以在启动后将 SessionFactory绑定到JNDI.

JTA Session绑定: 如果使用EJB, Hibernate Session 可以自动绑定到JTA事务作用的范围. 只需简单地从JNDI查找SessionFactory并获得当前的 Session. JTA事务完成时, Hibernate来处理 Session的清洗(flush)与关闭. EJB的部署描述符中事务边界是声明式的.

JMX部署: 如果你使用支持JMX应用程序服务器(, JBoss AS), 那么你可以选择将Hibernate部署成托管MBean. 这将为你省去一行从Configuration构建SessionFactory的启动代码. 容器将启动你的HibernateService, 并完美地处理好服务间的依赖关系 (Hibernate启动前,数据源必须是可用的等等).

 

3.6.1事务策略配置

在你的架构中,HibernateSession API是独立于任何事务分界系统的. 如果你让Hibernate通过连接池直接使用JDBC, 你需要调用JDBC API来打开和关闭你的事务. 如果你运行在J2EE应用程序服务器中, 你也许想用Bean管理的事务并在需要的时候调用JTA APIUserTransaction.

为了让你的代码在两种(或其他)环境中可以移植,我们建议使用可选的Hibernate Transaction API, 它包装并隐藏了底层系统. 你必须通过设置Hibernate配置属性hibernate.transaction.factory_class来指定 一个Transaction实例的工厂类.

存在着三个标准(内建)的选择:

委托给数据库(JDBC)事务(默认)

org.hibernate.transaction.JDBCTransactionFactory

 

委托给数据库(JDBC)事务(默认)

org.hibernate.transaction.JTATransactionFactory

 

如果在上下文环境中存在运行着的事务(, EJB会话Bean的方法), 则委托给容器管 理的事务, 否则,将启动一个新的事务,并使用Bean管理的事务.

org.hibernate.transaction.CMTTransactionFactory

 

委托给容器管理的JTA事务

你也可以定义属于你自己的事务策略 (, 针对CORBA的事务服务)

 

四、持久化类

 

在应用程序中,用来实现业务问题实体的(如,在电子商务应用程序中的CustomerOrder 类就是持久化类。不能认为所有的持久化类的实例都是持久的状态——一个实例的状态也可能 是瞬时的或脱管的。

 

这里要遵循四条主要的规则:

1、 为持久化字段声明访问器(accessors)和是否可变的标志(mutators)

 

2、实现一个默认的(即无参数的)构造方法(constructor

Cat有一个无参数的构造方法。所有的持久化类都必须有一个 默认的构造方法(可以不是public的),这样的话Hibernate就可以使用 Constructor.newInstance()来实例化它们。 我们建议,在Hibernate中,为了运行期代理的生成,构造方法至少是 (package)内可见的。

 

3提供一个标识属性(identifier property)(可选)

 

 

 

五、对象/关系数据库映射基础(Basic O/R Mapping)

 

5.1映射定义(Mapping declaration

对象和关系数据库之间的映射通常是用一个XML文档(XML document)来定义的。这个映射文档被设计为易读的, 并且可以手工修改。映射语言是以Java为中心,这意味着映射文档是按照持久化类的定义来创建的, 而非表的定义。

 

一个映射的例子

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC

      "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

          "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

 

<hibernate-mapping package="eg">

 

        <class name="Cat"

            table="cats"

            discriminator-value="C">

               

                <id name="id">

                        <generator class="native"/>

                </id>

 

                <discriminator column="subclass"

                     type="character"/>

 

                <property name="weight"/>

 

                <property name="birthdate"

                    type="date"

                    not-null="true"

                    update="false"/>

 

                <property name="color"

                    type="eg.types.ColorUserType"

                    not-null="true"

                    update="false"/>

 

                <property name="sex"

                    not-null="true"

                    update="false"/>

 

                <property name="litterId"

                    column="litterId"

                    update="false"/>

 

                <many-to-one name="mother"

                    column="mother_id"

                    update="false"/>

 

                <set name="kittens"

                    inverse="true"

                    order-by="litter_id">

                        <key column="mother_id"/>

                        <one-to-many class="Cat"/>

                </set>

 

                <subclass name="DomesticCat"

                    discriminator-value="D">

 

                        <property name="name"

                            type="string"/>

 

                </subclass>

 

        </class>

 

        <class name="Dog">

                <!-- mapping for Dog could go here -->

        </class>

 

</hibernate-mapping>

 

 

5.1.1 Doctype

所有的XML映射都需要定义如上所示的doctypeDTD可以从上述URL中获取, hibernate-x.x.x/src/net/sf/hibernate目录中、 hibernate.jar文件中找到。Hibernate总是会首先在它的classptah中搜索DTD文件。 如果你发现它是通过连接Internet查找DTD文件,就对照你的classpath目录检查XML文件里的DTD声明

 

5.1.2 hibernate-mapping

这个元素包括一些可选的属性。schemacatalog属性, 指明了这个映射所连接(refer)的表所在的schema/catalog名称。 假若指定了这个属性,表名会加上所指定的schemacatalog的名字扩展为全限定名。假若没有指定,表名就不会使用全限定名。 default-cascade指定了未明确注明cascade属性的Java属性和 集合类Hibernate会采取什么样的默认级联风格。auto-import属性默认让我们在查询语言中可以使用 非全限定名的类名。

<hibernate-mapping

         schema="schemaName"                          (1)

         catalog="catalogName"                        (2)

         default-cascade="cascade_style"              (3)

         default-access="field|property|ClassName"    (4)

         default-lazy="true|false"                    (5)

         auto-import="true|false"                     (6)

         package="package.name"                       (7)

 />

 

(1) schema (可选): 数据库schema的名称。

 

(2) catalog (可选): 数据库catalog的名称。

 

(3) default-cascade (可选 - 默认为 none): 默认的级联风格。

 

(4) default-access (可选 - 默认为 property): Hibernate用来访问属性的策略。可以通过实现PropertyAccessor接口 自定义。

 

(5) default-lazy (可选 - 默认为 true): 指定了未明确注明lazy属性的Java属性和集合类, Hibernate会采取什么样的默认加载风格。

 

(6) auto-import (可选 - 默认为 true): 指定我们是否可以在查询语言中使用非全限定的类名(仅限于本映射文件中的类)。

 

(7) package (可选): 指定一个包前缀,如果在映射文档中没有指定全限定的类名, 就使用这个作为包名。

 

 

假若你有两个持久化类,它们的非全限定名是一样的(就是两个类的名字一样,所在的包不一样--译者注), 你应该设置auto-import="false"。假若说你把一个“import过”的名字同时对应两个类, Hibernate会抛出一个异常。

 

注意hibernate-mapping 元素允许你嵌套多个如上所示的 <class>映射。但是最好的做法(也许一些工具需要的)是一个 持久化类(或一个类的继承层次)对应一个映射文件,并以持久化的超类名称命名,例如: Cat.hbm.xml Dog.hbm.xml,或者如果使用继承,Animal.hbm.xml

 

 

5.1.3 class

你可以使用class元素来定义一个持久化类:

<class

        name="ClassName"                              (1)

        table="tableName"                             (2)

        discriminator-value="discriminator_value"     (3)

        mutable="true|false"                          (4)

        schema="owner"                                (5)

        catalog="catalog"                             (6)

        proxy="ProxyInterface"                        (7)

        dynamic-update="true|false"                   (8)

        dynamic-insert="true|false"                   (9)

        select-before-update="true|false"             (10)

        polymorphism="implicit|explicit"              (11)

        where="arbitrary sql where condition"         (12)

        persister="PersisterClass"                    (13)

        batch-size="N"                                (14)

        optimistic-lock="none|version|dirty|all"      (15)

        lazy="true|false"                             (16)

        entity-name="EntityName"                      (17)

        check="arbitrary sql check condition"         (18)

        rowid="rowid"                                 (19)

        subselect="SQL expression"                    (20)

        abstract="true|false"                         (21)

        entity-name="EntityName"                      (22)

        node="element-name"                           (23)

/>

 

(1) name (可选): 持久化类(或者接口)的Java全限定名。 如果这个属性不存在,Hibernate将假定这是一个非POJO的实体映射。

 

(2) table (可选 - 默认是类的非全限定名): 对应的数据库表名。

 

(3) discriminator-value (可选 - 默认和类名一样): 一个用于区分不同的子类的值,在多态行为时使用。它可以接受的值包括 null not null

 

(4) mutable (可选,默认值为true): 表明该类的实例是可变的或者可变的。

 

(5) schema (可选): 覆盖在根<hibernate-mapping>元素中指定的schema名字。

 

(6) catalog (可选): 覆盖在根<hibernate-mapping>元素中指定的catalog名字。

 

(7) proxy (可选): 指定一个接口,在延迟装载时作为代理使用。 你可以在这里使用该类自己的名字。

 

(8) dynamic-update (可选, 默认为 false): 指定用于UPDATE SQL将会在运行时动态生成,并且只更新那些改变过的字段。

 

(9) dynamic-insert (可选, 默认为 false): 指定用于INSERT SQL 将会在运行时动态生成,并且只包含那些非空值字段。

 

(10) select-before-update (可选, 默认为 false): 指定Hibernate除非确定对象真正被修改了(如果该值为true-译注),否则不会执行SQL UPDATE操作。在特定场合(实际上,它只在一个瞬时对象(transient object)关联到一个 新的session中时执行的update()中生效),这说明Hibernate会在UPDATE 之前执行一次额外的SQL SELECT操作,来决定是否应该执行 UPDATE

 

(11) polymorphism(多态) (可选, 默认值为 implicit (隐式) ): 界定是隐式还是显式的使用多态查询(这只在Hibernate的具体表继承策略中用到-译注)。

 

(12) where (可选) 指定一个附加的SQLWHERE 条件, 在抓取这个类的对象时会一直增加这个条件。

 

(13) persister (可选): 指定一个定制的ClassPersister

 

(14) batch-size (可选,默认是1) 指定一个用于 根据标识符(identifier)抓取实例时使用的"batch size"(批次抓取数量)。

 

(15) optimistic-lock(乐观锁定) (可选,默认是version): 决定乐观锁定的策略。

 

(16) lazy (optional): 通过设置lazy="false" 所有的延迟加载(Lazy fetching)功能将未被激活(disabled)。

 

(17) entity-name (可选): Hibernate3允许一个类进行多次映射( 默认情况是映射到不同的表),并且允许使用MapsXML代替Java层次的实体映射 (也就是实现动态领域模型,不用写持久化类-译注)。 更多信息请看第 5.4 “动态模型(Dynamic models) and 19 XML映射。

 

(18) check (可选): 这是一个SQL表达式, 用于为自动生成的schema添加多行(multi-row)约束检查。

 

(19) rowid (可选): Hibernate可以使用数据库支持的所谓的ROWIDs,例如: Oracle数据库,如果你设置这个可选的rowid Hibernate可以使用额外的字段rowid实现快速更新。ROWID是这个功能实现的重点, 它代表了一个存储元组(tuple)的物理位置。

 

(20) subselect (可选): 它将一个不可变(immutable)并且只读的实体映射到一个数据库的 子查询中。它用于实现一个视图代替一张基本表,但是最好不要这样做。更多的介绍请看下面内容。

 

(21) abstract (可选): 用于在<union-subclass>的继承结构 hierarchies)中标识抽象超类。

 

(22) entity-name (可选, 默认为类名): 显式指定实体名

 

 

 

 

 

 

5.1.4 id

 

被映射的类必须定义对应数据库表主键字段。大多数类有一个JavaBeans风格的属性, 为每一个实例包含唯一的标识。<id> 元素定义了该属性到数据库表主键字段的映射。

<id

        name="propertyName"                                          (1)

        type="typename"                                              (2)

        column="column_name"                                         (3)

        unsaved-value="null|any|none|undefined|id_value"             (4)

        access="field|property|ClassName"                            (5)

        node="element-name|@attribute-name|element/@attribute|.">

 

        <generator class="generatorClass"/>

</id>

 

(1) name (可选): 标识属性的名字。

 

(2) type (可选): 标识Hibernate类型的名字。

 

(3) column (可选 - 默认为属性名): 主键字段的名字。

 

(4) unsaved-value (可选 - 默认为一个字段判断(sensible)的值): 一个特定的标识属性值,用来标志该实例是刚刚创建的,尚未保存。 这可以把这种实例和从以前的session中装载过(可能又做过修改--译者注) 但未再次持久化的实例区分开来。

 

(5) access (可选 - 默认为property): Hibernate用来访问属性值的策略。

 

 

如果 name属性不存在,会认为这个类没有标识属性。

 

unsaved-value 属性很重要!如果你的类的标识属性不是默认为 正常的Java默认值(null或零),你应该指定正确的默认值。

 

还有一个另外的<composite-id>定义可以访问旧式的多主键数据。 我们强烈不建议使用这种方式

 

 

 

5.1.5 Generator

可选的<generator>子元素是一个Java类的名字, 用来为该持久化类的实例生成唯一的标识。如果这个生成器实例需要某些配置值或者初始化参数, <param>元素来传递。

 

Hibernate提供了很多内置的实现。下面是一些内置生成器的快捷名字:

类似于:

<id name="id" type="java.lang.Long" column="EVENT_ID">

           <generator class="increment"></generator>

</id>

 

increment

用于为long, short或者int类型生成 唯一标识。只有在没有其他进程往同一张表中插入数据时才能使用。 在集群下不要使用。

 

identity

DB2,MySQL, MS SQL Server, SybaseHypersonicSQL的内置标识字段提供支持。 返回的标识符是long, short 或者int类型的。

 

sequence

DB2,PostgreSQL, Oracle, SAP DB, McKoi中使用序列(sequence) 而在Interbase中使用生成器(generator)。返回的标识符是long, short或者 int类型的。

 

hilo

使用一个高/低位算法高效的生成long, short 或者 int类型的标识符。给定一个表和字段(默认分别是是 hibernate_unique_key next_hi)作为高位值的来源。 /低位算法生成的标识符只在一个特定的数据库中是唯一的。

 

assigned

让应用程序在save()之前为对象分配一个标示符。这是 <generator>元素没有指定时的默认生成策略。(即自己指定值)

 

native

根据底层数据库的能力选择identity, sequence 或者hilo中的一个。

 

foreign

使用另外一个相关联的对象的标识符。通常和<one-to-one>联合起来使用。

 

 

 

5.1.6 property

<property>元素为类定义了一个持久化的,JavaBean风格的属性

<property

        name="propertyName"                                          (1)

        column="column_name"                                         (2)

        type="typename"                                              (3)

        update="true|false"                                          (4)

        insert="true|false"                                          (4)

        formula="arbitrary SQL expression"                           (5)

        access="field|property|ClassName"                            (6)

        lazy="true|false"                                            (7)

        unique="true|false"                                          (8)

        not-null="true|false"                                        (9)

        optimistic-lock="true|false"                                 (10)

        node="element-name|@attribute-name|element/@attribute|."

/>

 

(1) name: 属性的名字,以小写字母开头。

 

(2) column (可选 - 默认为属性名字): 对应的数据库字段名。 也可以通过嵌套的<column>元素指定。

 

(3) type (可选): 一个Hibernate类型的名字。

 

(4) update, insert (可选 - 默认为 true) : 表明用于UPDATE / INSERT SQL语句中是否包含这个被映射了的字段。这二者如果都设置为false 则表明这是一个“外源性(derived)”的属性,它的值来源于映射到同一个(或多个) 字段的某些其他属性,或者通过一个trigger(触发器)或其他程序。

 

(5) formula (可选): 一个SQL表达式,定义了这个计算 computed 属性的值。计算属性没有和它对应的数据库字段。

 

(6) access (可选 - 默认值为 property): Hibernate用来访问属性值的策略。

 

(7) lazy (可选 - 默认为 false): 指定 指定实例变量第一次被访问时,这个属性是否延迟抓取(fetched lazily)( 需要运行时字节码增强)。

 

(8) unique (可选): 使用DDL为该字段添加唯一的约束。 此外,这也可以用作property-ref的目标属性。

 

(9) not-null (可选): 使用DDL为该字段添加可否为空(nullability)的约束。

 

(10) optimistic-lock (可选 - 默认为 true): 指定这个属性在做更新时是否需要获得乐观锁定(optimistic lock)。 换句话说,它决定这个属性发生脏数据时版本(version)的值是否增长。

 

 

typename可以是如下几种:

 

Hibernate基础类型之一(比如:integer, string, character,date, timestamp, float, binary, serializable, object, blob)。

 

一个Java类的名字,这个类属于一种默认基础类型 (比如: int, float,char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob)

 

一个可以序列化的Java类的名字。

 

一个自定义类型的类的名字。(比如: com.illflow.type.MyCustomType)

 

 

如果你没有指定类型,Hibernarte会使用反射来得到这个名字的属性,以此来猜测正确的Hibernate类型。 Hibernate会按照规则2,3,4的顺序对属性读取器(getter方法)的返回类进行解释。然而,这还不够。 在某些情况下你仍然需要type属性。(比如,为了区别Hibernate.DATE Hibernate.TIMESTAMP,或者为了指定一个自定义类型。)

 

 

 

 

。。。。等等

 

 

 

5.2 Hibernate 的类型

5.2.1实体(Entities)和值(values)

为了理解很多与持久化服务相关的Java语言级对象的行为,我们需要把它们分为两类:

 

实体entity 独立于任何持有实体引用的对象。与通常的Java模型相比,不再被引用的对象会被当作垃圾收集掉。实体必须被显式的保存和删除(除非保存和删除是从父实体向子实体引发的级联)。这和ODMG模型中关于对象通过可触及保持持久性有一些不同——比较起来更加接近应用程序对象通常在一个大系统中的使用方法。实体支持循环引用和交叉引用,它们也可以加上版本信息。

 

一个实体的持久状态包含指向其他实体和值类型实例的引用。值可以是原始类型,集合(不是集合中的对象),组件或者特定的不可变对象。与实体不同,值(特别是集合和组件)是通过可触及性来进行持久化和删除的。因为值对象(和原始类型数据)是随着包含他们的实体而被持久化和删除的,他们不能被独立的加上版本信息。值没有独立的标识,所以他们不能被两个实体或者集合共享。

 

直到现在,我们都一直使用术语“持久类”(persistent class)来代表实体。我们仍然会这么做。 然而严格说来,不是所有的用户自定义的,带有持久化状态的类都是实体。组件就是用户自定义类,却是值语义的。java.lang.String类型的java属性也是值语义的。给了这个定义以后,我们可以说所有JDK提供的类型()都是值类型的语义,而用于自定义类型可能被映射为实体类型或值类型语义。采用哪种类型的语义取决于开发人员。在领域模型中,寻找实体类的一个好线索是共享引用指向这个类的单一实例,而组合或聚合通常被转化为值类型。

 

我们会在本文档中重复碰到这两个概念。

 

挑战在于将java类型系统(和开发者定义的实体和值类型)映射到 SQL/数据库类型系统。Hibernate提供了连接两个系统之间的桥梁:对于实体类型,我们使用<class>, <subclass> 等等。对于值类型,我们使用 <property>, <component> 及其他,通常跟随着type属性。这个属性的值是Hibernate 的映射类型的名字。Hibernate提供了许多现成的映射(标准的JDK值类型)。你也可以编写自己的映射类型并实现自定义的变换策略,随后我们会看到这点。

 

所有的Hibernate内建类型,除了collections以外,都支持空(null)语义。

 

 

5.2.2基本值类型

 

String

java.lang.String VARCHAR (或者 Oracle VARCHAR2)的映射。

 

date, time, timestamp

java.util.Date和其子类到SQL类型DATE, TIME TIMESTAMP (或等价类型)的映射。

 

text

把长Java字符串映射为SQLCLOB或者TEXT类型。

 

clob, blob

JDBC java.sql.Clob java.sql.Blob的映射。某些程序可能不适合使用这个类型,因为blobclob对象可能在一个事务之外是无法重用的。(而且, 驱动程序对这种类型的支持充满着补丁和前后矛盾。)

 

 

 

六、与对象共事

6.1 Hibernate对象状态(object states)

       瞬时(Transient) - new操作符创建,且尚未与Hibernate Session 关联的对象被认定为瞬时(Transient)的。

持久(Persistent) - 持久(Persistent)的实例在数据库中有对应的记录,并拥有一个持久化标识(identifier)

脱管(Detached) - 与持久(Persistent)对象关联的Session被关闭后,对象就变为脱管(Detached)的。

 

6.2使对象持久化

 

Hibernate认为持久化类(persistent class)新实例化的对象是瞬时(Transient)的。 我们可将瞬时(Transient)对象与session关联而变为持久(Persistent)的。

Long generatedId = (Long) sess.save(fritz);

 

如果Cat的持久化标识(identifier)generated类型的, 那么该标识(identifier)会自动在save()被调用时产生并分配给cat 如果Cat的持久化标识(identifier)assigned类型的,或是一个复合主键(composite key) 那么该标识(identifier)应当在调用save()之前手动赋予给cat 你也可以按照EJB3 early draft中定义的语义,使用persist()替代save()

 

6.3装载对象

如果你知道某个实例的持久化标识(identifier),你就可以使用Sessionload()方法 来获取它。 load()的另一个参数是指定类的.class对象。 本方法会创建指定类的持久化实例,并从数据库加载其数据(state)

 

long pkId = 1234;

DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );

 

从数据库中装载实例

 

 

 

 

6.4查询

6.4.1执行查询

HQL和原生SQL(native SQL)查询要通过为org.hibernate.Query的实例来表达。 这个接口提供了参数绑定、结果集处理以及运行实际查询的方法。 你总是可以通过当前Session获取一个Query对象:

List cats = session.createQuery("from Cat as cat where cat.birthdate < ?") .setDate(0, date) .list();

 

6.4.2迭代式获取结果(Iterating results)

一个查询通常在调用list()时被执行,执行结果会完全装载进内存中的一个集合(collection) 查询返回的对象处于持久(persistent)状态。如果你知道的查询只会返回一个对象,可使用list()的快捷方式uniqueResult()

某些情况下,你可以使用iterate()方法得到更好的性能. 这通常是你预期返回的结果在session,或二级缓存(second-level cache)中已经存在时的情况。 如若不然,iterate()会比list()慢,而且可能简单查询也需要进行多次数据库访问: iterate()会首先使用1条语句得到所有对象的持久化标识(identifiers),再根据持久化标识执行n条附加的select语句实例化实际的对象。

即查询结果已经在内存中了,就可以用iterate()得到数据,

// fetch ids

Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate();

while ( iter.hasNext() ) {

    Qux qux = (Qux) iter.next();  // fetch the object

    // something we couldnt express in the query

    if ( qux.calculateComplicatedAlgorithm() ) {

        // delete the current instance

        iter.remove();

        // dont need to process the rest

        break;

    }

}

 

 

6.4.3返回元组(tuples)的查询

元组(tuples)指一条结果行包含多个对象. Hibernate查询有时返回元组(tuples),每个元组(tuples)以数组的形式返回:

 

Iterator kittensAndMothers = sess.createQuery(

            "select kitten, mother from Cat kitten join kitten.mother mother")

            .list()

            .iterator();

 

while ( kittensAndMothers.hasNext() ) {

    Object[] tuple = (Object[]) kittensAndMothers.next();

    Cat kitten  = tuple[0];

    Cat mother  = tuple[1];

    ....

}

 

 

 

 

 

 

 

6.4.4 标量(Scalar)结果

查询可在select从句中指定类的属性,甚至可以调用SQL统计(aggregate)函数。 属性或统计结果被认定为"标量(Scalar)"的结果(而不是持久(persistent state)的实体)。

 

SQLQuery query = session.createSQLQuery(sql.toString());

       query.setString("billcyc", billcyc);

       query.addScalar("SubsID",Hibernate.LONG);

       query.addScalar("AcctID",Hibernate.LONG);

       query.addScalar("ValidBillCyc",Hibernate.LONG);

       query.addScalar("total",Hibernate.DOUBLE);

      

       List list = query.list();

       for (Iterator iter = list.iterator(); iter.hasNext();) {

           Object[] item = (Object[])iter.next();

           i = 0;

           recamtlogVo = new RecamtlogVo();

           recamtlogVo.setLogid(new Long(0));  //SEQUENCE控制,这里设置0是无意义的

           recamtlogVo.setSubsid((Long)item[i++]);    //用户Id

           recamtlogVo.setAcctid((Long)item[i++]);    //账单科目标识

           recamtlogVo.setBillcyc(""+item[i++]);      //账期

           recamtlogVo.setRecamt((Double)item[i++]);   //应收金额

           recamtlogVo.setSource(new Long(1));  //应收来源:全球通模式,固定值1

           session.save(recamtlogVo);

       }

 

 

6.4.5 绑定参数

接口Query提供了对命名参数(named parameters)JDBC风格的问号(?)参数进行绑定的方法。 不同于JDBCHibernate对参数从0开始计数。 命名参数(named parameters)在查询字符串中是形如:name的标识符。 命名参数(named parameters)的优点是:

命名参数(named parameters)与其在查询串中出现的顺序无关

它们可在同一查询串中多次出现

它们本身是自我说明的

 

//named parameter (preferred)

Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");

q.setString("name", "Fritz");

List list = q.list();

 

 

//positional parameter

Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");

q.setString(0, "Izi");

Iterator cats = q.iterate();

 

//named parameter list

List names = new ArrayList();

names.add("Izi");

names.add("Fritz");

Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)");

q.setParameterList("namesList", names);

List cats = q.list();

 

 

6.4.6分页

如果你需要指定结果集的范围(希望返回的最大行数/或开始的行数),应该使用Query接口提供的方法:

Query q = sess.createQuery("from DomesticCat cat");

q.setFirstResult(20);

q.setMaxResults(10);

List cats = q.list();

 

Hibernate 知道如何将这个有限定条件的查询转换成你的数据库的原生SQL(native SQL)

 

6.4.7可滚动遍历(Scrollable iteration)

如果你的JDBC驱动支持可滚动的ResuleSetQuery接口可以使用ScrollableResults,允许你在查询结果中灵活游走。

Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +

                            "order by cat.name");

ScrollableResults cats = q.scroll();

if ( cats.first() ) {

 

    // find the first name on each page of an alphabetical list of cats by name

    firstNamesOfPages = new ArrayList();

    do {

        String name = cats.getString(0);

        firstNamesOfPages.add(name);

    }

    while ( cats.scroll(PAGE_SIZE) );

 

    // Now get the first page of cats

    pageOfCats = new ArrayList();

    cats.beforeFirst();

    int i=0;

    while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );

 

}

cats.close()

 

注意,使用此功能需要保持数据库连接(以及游标(cursor))处于一直打开状态。 如果你需要断开连接使用分页功能,请使用setMaxResult()/setFirstResult()

 

6.4.8条件查询(Criteria queries)

HQL极为强大,但是有些人希望能够动态的使用一种面向对象API创建查询,而非在他们的Java代码中嵌入字符串。对于那部分人来说,Hibernate提供了直观的Criteria查询API

Criteria crit = session.createCriteria(Cat.class);

crit.add( Expression.eq( "color", eg.Color.BLACK ) );

crit.setMaxResults(10);

List cats = crit.list();

 

 

6.4.9使用原生SQL的查询

你可以使用createSQLQuery()方法,用SQL来描述查询,并由Hibernate处理将结果集转换成对象的工作。 请注意,你可以在任何时候调用session.connection()来获得并使用JDBC Connection对象。 如果你选择使用HibernateAPI, 你必须把SQL别名用大括号包围起来:

List cats = session.createSQLQuery(

"SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10",

"cat",

Cat.class

).list();

 

6.5修改持久对象(Update)

事务中的持久实例(就是通过session装载、保存、创建或者查询出的对象)被刷出(flushed的时候被持久化(本章后面会详细讨论)。 这里不需要调用某个特定的方法(比如update(),设计它的目的是不同的)将你的修改持久化。 所以最直接的更新一个对象的方法就是在Session处于打开状态时load()它,然后直接修改即可:

 

DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );  //实例,id

cat.setName("PK");

sess.flush();  // changes to cat are automatically detected and persisted

 

有时这种程序模型效率低下,因为它在同一Session里需要一条SQL SELECT语句(用于加载对象) 以及一条SQL UPDATE语句(持久化更新的状态) 为此Hibernate提供了另一种途径,使用脱管(detached)实例。

 

6.6修改脱管(Detached)对象

很多程序需要在某个事务中获取对象,然后将对象发送到界面层去操作,最后在一个新的事务保存所做的修改。 在高并发访问的环境中使用这种方式,通常使用附带版本信息的数据来保证这些“长“工作单元之间的隔离。

 

Hibernate通过提供使用Session.update()Session.merge()方法 重新关联脱管实例的办法来支持这种模型。

 

 

6.7 删除持久对象

使用Session.delete()会把对象的状态从数据库中移除。 当然,你的应用程序可能仍然持有一个指向已删除对象的引用。所以,最好这样理解:delete()的用途是把一个持久实例变成瞬时(transient)实例。

sess.delete(cat);

 

 

6.8 在两个不同数据库间复制对象

偶尔会用到不重新生成持久化标识(identifier),将持久实例以及其关联的实例持久到不同的数据库中的操作。

//retrieve a cat from one database

Session session1 = factory1.openSession();

Transaction tx1 = session1.beginTransaction();

Cat cat = session1.get(Cat.class, catId);

tx1.commit();

session1.close();

 

//reconcile with a second database

Session session2 = factory2.openSession();

Transaction tx2 = session2.beginTransaction();

session2.replicate(cat, ReplicationMode.LATEST_VERSION);

tx2.commit();

session2.close();

 

ReplicationMode决定数据库中已存在相同行时,replicate()如何处理。

ReplicationMode.IGNORE - 忽略它

ReplicationMode.OVERWRITE - 覆盖相同的行

ReplicationMode.EXCEPTION - 抛出异常

ReplicationMode.LATEST_VERSION - 如果当前的版本较新,则覆盖,否则忽略

 

 

 

6.9 Session刷出(flush)

每间隔一段时间,Session会执行一些必需的SQL语句来把内存中的对象的状态同步到JDBC连接中。这个过程被称为刷出(flush),默认会在下面的时间点执行:

在某些查询执行之前

在调用org.hibernate.Transaction.commit()的时候

在调用Session.flush()的时候

 

涉及的SQL语句会按照下面的顺序发出执行:

1、所有对实体进行插入的语句,其顺序按照对象执行Session.save()的时间顺序

2所有对实体进行更新的语句

3所有进行集合删除的语句

4所有对集合元素进行删除,更新或者插入的语句

5所有进行集合插入的语句

6所有对实体进行删除的语句,其顺序按照对象执行Session.delete()的时间顺序

(有一个例外是,如果对象使用native方式来生成ID(持久化标识)的话,它们一执行save就会被插入。)

 

除非你明确地发出了flush()指令,关于Session何时会执行这些JDBC调用是完全无法保证的,只能保证它们执行的前后顺序。 当然,Hibernate保证,Query.list(..)绝对不会返回已经失效的数据,也不会返回错误数据。

 

也可以改变默认的设置,来让刷出(flush)操作发生的不那么频繁。 FlushMode类定义了三种不同的方式。 仅在提交时刷出(仅当HibernateTransaction API被使用时有效) 按照刚才说的方式刷出, 以及除非明确使用flush()否则从不刷出。 最后一种模式对于那些需要长时间保持Session为打开或者断线状态的长时间运行的工作单元很有用。

sess = sf.openSession();

Transaction tx = sess.beginTransaction();

sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state

 

Cat izi = (Cat) sess.load(Cat.class, id);

izi.setName(iznizi);

 

// might return stale data

sess.find("from Cat as cat left outer join cat.kittens kitten");

 

tx.commit(); // flush occurs

 

刷出(flush)期间,可能会抛出异常。(例如一个DML操作违反了约束) 异常处理涉及到对Hibernate事务性行为的理解

 

 

6.10 使用元数据

Hibernate中有一个非常丰富的元级别(meta-level)的模型,含有所有的实体和值类型数据的元数据。 有时这个模型对应用程序本身也会非常有用。 比如说,应用程序可能在实现一种“智能”的深度拷贝算法时, 通过使用Hibernate的元数据来了解哪些对象应该被拷贝(比如,可变的值类型数据), 那些不应该(不可变的值类型数据,也许还有某些被关联的实体)。

Hibernate提供了ClassMetadata接口,CollectionMetadata接口和Type层次体系来访问元数据。 可以通过SessionFactory获取元数据接口的实例。

Cat fritz = ......;

ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class);

 

Object[] propertyValues = catMeta.getPropertyValues(fritz);

String[] propertyNames = catMeta.getPropertyNames();

Type[] propertyTypes = catMeta.getPropertyTypes();

 

// get a Map of all properties which are not collections or associations

Map namedValues = new HashMap();

for ( int i=0; i<propertyNames.length; i++ ) {

if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) {

namedValues.put( propertyNames[i], propertyValues[i] );

 

 

七、事务和并发

 

7.1 Session和事务范围(transaction scopes)

一个SessionFactory对象的创建代价很昂贵,它是线程安全的对象,它被设计成可以 为所有的应用程序线程所共享。它只创建一次,通常是在应用程序启动的时候,由一个 Configuraion的实例来创建。

一个Session的对象是轻型的,非线程安全的,对于单个业务进程,单个的 工作单元而言,它只被使用一次,然后就丢弃。只有在需要的时候,Session 才会获取一个JDBCConnection(或一个Datasource 对象。所以你可以放心的打开和关闭Session,甚至当你并不确定一个特定的请 求是否需要数据访问时,你也可以这样做。

我们推荐 使用一个ThreadLocal 变量,把 Session绑定到处理客户端请求的线 程上去。这种方式可以让运行在该线程上的所有程序代码轻松的访问Session(就像访问一 个静态变量那样)。你也可以在一个ThreadLocal 变量中保持事务上下文环境,不过这依赖 于你所选择的数据库事务划分机制。这种实现模式被称之为 ThreadLocal Session Open Session in View

 

 

7.2数据库事务声明

结束 Session 包含了四个不同的阶段:

同步session(flush,刷出到磁盘)

提交事务

关闭session

处理异常

 

 

7.2.1非托管环境

如果Hibernat持久层运行在一个非托管环境中,数据库连接通常由Hibernate的连接池机制 来处理。session/transaction处理方式如下所示:

//Non-managed environment idiom

Session sess = factory.openSession();

Transaction tx = null;

try {

         tx = sess.beginTransaction();

         // do some work

         ...

         tx.commit();

}

catch (RuntimeException e) {

    if (tx != null) tx.rollback();

    throw e; // or display error message

}

finally {

    sess.close();

}

 

不需要显式flush() Session - commit()的调用会自动触发session的同步。

 

 

 

八、批量处理

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();

for ( int i=0; i<100000; i++ ) {

    Customer customer = new Customer(.....);

    session.save(customer);

}

tx.commit();

session.close();

 

 

这段程序大概运行到 50 000 条记录左右会失败并抛出 内存溢出异常(OutOfMemoryException 这是因为 Hibernate 把所有新插入的 客户(Customer)实例在 session级别的缓存区进行了缓存的缘故。

 

 

 

 

8.1. 批量插入(Batch inserts

如果要将很多对象持久化,你必须通过经常的调用 flush() 以及稍后调用 clear() 来控制第一级缓存的大小。

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();

  

for ( int i=0; i<100000; i++ ) {

    Customer customer = new Customer(.....);

    session.save(customer);

    if ( i % 20 == 0 ) { //20, same as the JDBC batch size //20,JDBC批量设置相同

        //flush a batch of inserts and release memory:

        //将本批插入的对象立即写入数据库并释放内存

        session.flush();

        session.clear();

    }

}

  

tx.commit();

session.close();

 

 

 

8.2. 批量更新(Batch updates

 

此方法同样适用于检索和更新数据。此外,在进行会返回很多行数据的查询时, 你需要使用 scroll() 方法以便充分利用服务器端游标所带来的好处。

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();

  

ScrollableResults customers = session.getNamedQuery("GetCustomers")

    .setCacheMode(CacheMode.IGNORE)

    .scroll(ScrollMode.FORWARD_ONLY);

int count=0;

while ( customers.next() ) {

    Customer customer = (Customer) customers.get(0);

    customer.updateStuff(...);

    if ( ++count % 20 == 0 ) {

        //flush a batch of updates and release memory:

        session.flush();

        session.clear();

    }

}

  

tx.commit();

session.close();

 

8.3. 大批量更新/删除(Bulk update/delete

Hibernate提供通过Hibernate查询语言来执行大批 SQL风格的(UPDATE)和(DELETE 语句的方法。

UPDATE DELETE语句的语法为: ( UPDATE | DELETE ) FROM? ClassName (WHERE WHERE_CONDITIONS)? 有几点说明:

 

FROM子句(from-clause)中,FROM关键字是可选的

 

FROM子句(from-clause)中只能有一个类名,并且它不能有别名

 

不能在大批量HQL语句中使用连接(显式或者隐式的都不行)。不过在WHERE子句中可以使用子查询。

 

整个WHERE子句是可选的。

 

 

举个例子,使用Query.executeUpdate()方法执行一个HQL UPDATE语句:

 

Session session = sessionFactory.openSession();

        Transaction tx = session.beginTransaction();

 

        String hqlUpdate = "update Customer set name = :newName where name = :oldName";

        int updatedEntities = s.createQuery( hqlUpdate )

                            .setString( "newName", newName )

                            .setString( "oldName", oldName )

                            .executeUpdate();

        tx.commit();

        session.close();

 

执行一个HQL DELETE,同样使用 Query.executeUpdate() 方法 (此方法是为 那些熟悉JDBC PreparedStatement.executeUpdate() 的人们而设定的)

 

Session session = sessionFactory.openSession();

        Transaction tx = session.beginTransaction();

 

        String hqlDelete = "delete Customer where name = :oldName";

        int deletedEntities = s.createQuery( hqlDelete )

                            .setString( "oldName", oldName )

                            .executeUpdate();

        tx.commit();

        session.close();

 

 

 

Query.executeUpdate()方法返回的整型值表明了受此操作影响的记录数量。 注意这个数值可能与数据库中被(最后一条SQL语句)影响了的“行”数有关,也可能没有。一个大批量HQL操作可能导致多条实际的SQL语句被执行, 举个例子,对joined-subclass映射方式的类进行的此类操作。这个返回值代表了实际被语句影响了的记录数量。在那个joined-subclass的例子中, 对一个子类的删除实际上可能不仅仅会删除子类映射到的表而且会影响“根”表,还有可能影响与之有继承关系的joined-subclass映射方式的子类的表。

 

. HQL: Hibernate查询语言

Hibernate配备了一种非常强大的查询语言,这种语言看上去很像SQL。但是不要被语法结构 上的相似所迷惑,HQL是非常有意识的被设计为完全面向对象的查询,它可以理解如继承、多态 和关联之类的概念。

9.1大小写敏感

除了Java类与属性的名称外,查询语句对大小写并不敏感。 所以 SeLeCT sELEct 以及 SELECT 是相同的,但是 org.hibernate.eg.FOO 并不等价于 org.hibernate.eg.Foo 并且 foo.barSet 也不等价于 foo.BARSET

 

本手册中的HQL关键字将使用小写字母. 很多用户发现使用完全大写的关键字会使查询语句 的可读性更强, 但我们发现,当把查询语句嵌入到Java语句中的时候使用大写关键字比较难看。

 

9.2 from子句

Hibernate中最简单的查询语句的形式如下:

from eg.Cat

该子句简单的返回eg.Cat类的所有实例。 通常我们不需要使用类的全限定名, 因为 auto-import(自动引入) 是缺省的情况。 所以我们几乎只使用如下的简单写法:

 

from Cat

 

大多数情况下, 你需要指定一个别名, 原因是你可能需要 在查询语句的其它部分引用到Cat

 

from Cat as cat

 

这个语句把别名cat指定给类Cat 的实例, 这样我们就可以在随后的查询中使用此别名了。 关键字as 是可选的,我们也可以这样写:

 

from Cat cat

 

子句中可以同时出现多个类, 其查询结果是产生一个笛卡儿积或产生跨表的连接。

 

from Formula, Parameterfrom Formula as form, Parameter as param

 

查询语句中别名的开头部分小写被认为是实践中的好习惯, 这样做与Java变量的命名标准保持了一致 (比如,domesticCat)

 

 

9.3 关联(Association)与连接(Join)

我们也可以为相关联的实体甚至是对一个集合中的全部元素指定一个别名, 这时要使用关键字join

 

from Cat as cat

    inner join cat.mate as mate

    left outer join cat.kittens as kittenfrom Cat as cat left join cat.mate.kittens as kittensfrom Formula form full join form.parameter param受支持的连接类型是从ANSI SQL中借鉴来的。

 

inner join(内连接)

 

left outer join(左外连接)

 

right outer join(右外连接)

 

full join (全连接,并不常用)

 

语句inner join, left outer join 以及 right outer join 可以简写。

 

from Cat as cat

    join cat.mate as mate

    left join cat.kittens as kitten还有,一个"fetch"连接允许仅仅使用一个选择语句就将相关联的对象或一组值的集合随着他们的父对象的初始化而被初始化,这种方法在使用到集合的情况下尤其有用,对于关联和集合来说,它有效的代替了映射文件中的外联接 与延迟声明(lazy declarations. 查看 20.1 抓取策略(Fetching strategies) 以获得等多的信息。

 

from Cat as cat

    inner join fetch cat.mate

    left join fetch cat.kittens一个fetch连接通常不需要被指定别名, 因为相关联的对象不应当被用在 where 子句 (或其它任何子句)中。同时,相关联的对象 并不在查询的结果中直接返回,但可以通过他们的父对象来访问到他们。

 

注意fetch构造变量在使用了scroll() iterate()函数 的查询中是不能使用的。最后注意,使用full join fetch right join fetch是没有意义的。

 

如果你使用属性级别的延迟获取(lazy fetching)(这是通过重新编写字节码实现的),可以使用 fetch all properties 来强制Hibernate立即取得那些原本需要延迟加载的属性(在第一个查询中)。

 

from Document fetch all properties order by namefrom Document doc fetch all properties where lower(doc.name) like '%cats%'

 

 

9.4 select子句

select 子句选择将哪些对象与属性返 回到查询结果集中. 考虑如下情况:

查询语句可以返回值为任何类型的属性,包括返回类型为某种组件(Component)的属性:

select cat.name from DomesticCat cat

where cat.name like 'fri%'

 

 

select max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n

from Cat cat

 

select max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n

from Cat cat

 

select new map( max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n )

from Cat cat

该查询返回了一个Map的对象,内容是别名与被选择的值组成的名-值映射。

 

 

9.5聚集函数

 

HQL查询甚至可以返回作用于属性之上的聚集函数的计算结果:

select avg(cat.weight), sum(cat.weight), max(cat.weight), count(cat)

from Cat cat

受支持的聚集函数如下:

 

avg(...), sum(...), min(...), max(...)

 

count(*)

 

count(...), count(distinct ...), count(all...)

 

 

关键字distinctall 也可以使用,它们具有与SQL相同的语义.

 

select distinct cat.name from Cat cat

 

select count(distinct cat.name), count(cat) from Cat cat

 

 

 

 

9.6多态查询

一个如下的查询语句:

 

from Cat as cat不仅返回Cat类的实例, 也同时返回子类 DomesticCat的实例. Hibernate 可以在from子句中指定任何 Java 类或接口. 查询会返回继承了该类的所有持久化子类 的实例或返回声明了该接口的所有持久化类的实例。下面的查询语句返回所有的被持久化的对象:

 

from java.lang.Object o

 

接口Named 可能被各种各样的持久化类声明:

 

from Named n, Named m where n.name = m.name

 

注意,最后的两个查询将需要超过一个的SQL SELECT.这表明order by子句 没有对整个结果集进行正确的排序. (这也说明你不能对这样的查询使用Query.scroll()方法.)

 

 

9.7 where子句

where子句允许你将返回的实例列表的范围缩小. 如果没有指定别名,你可以使用属性名来直接引用属性:

from Cat where name='Fritz'

 

 

如果指派了别名,需要使用完整的属性名:

 

from Cat as cat where cat.name='Fritz'返回名为(属性name等于)'Fritz'Cat类的实例。

 

select foo

from Foo foo, Bar bar

where foo.startDate = bar.date

 

将返回所有满足下面条件的Foo类的实例: 存在如下的bar的一个实例,其date属性等于 FoostartDate属性。 复合路径表达式使得where子句非常的强大,考虑如下情况:

 

from Cat cat where cat.mate.name is not null

该查询将被翻译成为一个含有表连接(内连接)的SQL查询。如果你打算写像这样的查询语句

 

from Foo foo 

where foo.bar.baz.customer.address.city is not null

SQL中,你为达此目的将需要进行一个四表连接的查询。

 

=运算符不仅可以被用来比较属性的值,也可以用来比较实例:

 

from Cat cat, Cat rival where cat.mate = rival.mateselect cat, mate

from Cat cat, Cat mate

where cat.mate = mate

 

特殊属性(小写)id可以用来表示一个对象的唯一的标识符。(你也可以使用该对象的属性名。)

 

from Cat as cat where cat.id = 123

 

from Cat as cat where cat.mate.id = 69

第二个查询是有效的。此时不需要进行表连接!

 

同样也可以使用复合标识符。比如Person类有一个复合标识符,它由country属性 medicareNumber属性组成。

 

from bank.Person person

where person.id.country = 'AU'

    and person.id.medicareNumber = 123456from bank.Account account

where account.owner.id.country = 'AU'

    and account.owner.id.medicareNumber = 123456第二个查询也不需要进行表连接。

 

同样的,特殊属性class在进行多态持久化的情况下被用来存取一个实例的鉴别值(discriminator value)。 一个嵌入到where子句中的Java类的名字将被转换为该类的鉴别值。

 

from Cat cat where cat.class = DomesticCat

+你也可以声明一个属性的类型是组件或者复合用户类型(以及由组件构成的组件等等)。永远不要尝试使用以组件类型来结尾的路径表达式(path-expression (与此相反,你应当使用组件的一个属性来结尾)。 举例来说,如果store.owner含有一个包含了组件的实体address

 

store.owner.address.city    // 正确

store.owner.address         // 错误!一个“任意”类型有两个特殊的属性idclass, 来允许我们按照下面的方式表达一个连接(AuditLog.item 是一个属性,该属性被映射为<any>)。

 

from AuditLog log, Payment payment

where log.item.class = 'Payment' and log.item.id = payment.id

 

注意,在上面的查询与句中,log.item.class payment.class 将涉及到完全不同的数据库中的列。

 

 

 

 

9.8表达式

where子句中允许使用的表达式包括 大多数你可以在SQL使用的表达式种类:

 

数学运算符+, -, *, /

 

二进制比较运算符=, >=, <=, <>, !=, like

 

逻辑运算符and, or, not

 

in, not in, between, is null, is not null, is empty, is not empty, member of and not member of

 

"简单的" case, case ... when ... then ... else ... end, "搜索" case, case when ... then ... else ... end

 

字符串连接符...||... or concat(...,...)

 

current_date(), current_time(), current_timestamp()

 

second(...), minute(...), hour(...), day(...), month(...), year(...),

 

EJB-QL 3.0定义的任何函数或操作:substring(), trim(), lower(), upper(), length(), locate(), abs(), sqrt(), bit_length()

 

coalesce() nullif()

 

cast(... as ...), 其第二个参数是某Hibernate类型的名字,以及extract(... from ...),只要ANSI cast() extract() 被底层数据库支持

 

任何数据库支持的SQL标量函数,比如sign(), trunc(), rtrim(), sin()

 

JDBC参数传入 ?

 

命名参数:name, :start_date, :x1

 

SQL 直接常量 'foo', 69, '1970-01-01 10:00:01.0'

 

Java public static final 类型的常量 eg.Color.TABBY

 

 

 

 

 

关键字inbetween可按如下方法使用:

 

from DomesticCat cat where cat.name between 'A' and 'B'

 

from DomesticCat cat where cat.name in ( 'Foo', 'Bar', 'Baz' )

 

而且否定的格式也可以如下书写:

 

from DomesticCat cat where cat.name not between 'A' and 'B'

 

from DomesticCat cat where cat.name not in ( 'Foo', 'Bar', 'Baz' )

 

同样, 子句is nullis not null可以被用来测试空值(null).

 

Hibernate配置文件中声明HQL“查询替代(query substitutions)”之后, 布尔表达式(Booleans)可以在其他表达式中轻松的使用:

 

<property name="hibernate.query.substitutions">true 1, false 0</property>系统将该HQL转换为SQL语句时,该设置表明将用字符 1 0 取代关键字true false:

 

from Cat cat where cat.alive = true

 

你可以用特殊属性size, 或是特殊函数size()测试一个集合的大小。

 

from Cat cat where cat.kittens.size > 0

 

from Cat cat where size(cat.kittens) > 0

 

对于索引了(有序)的集合,你可以使用minindex maxindex函数来引用到最小与最大的索引序数。 同理,你可以使用minelement maxelement函数来 引用到一个基本数据类型的集合中最小与最大的元素。

 

from Calendar cal where maxelement(cal.holidays) > current date

 

from Order order where maxindex(order.items) > 100

 

from Order order where minelement(order.items) > 10000

 

在传递一个集合的索引集或者是元素集(elementsindices 函数) 或者传递一个子查询的结果的时候,可以使用SQL函数any, some, all, exists, in

 

select mother from Cat as mother, Cat as kit

where kit in elements(foo.kittens)

 

select p from NameList list, Person p

where p.name = some elements(list.names)

 

from Cat cat where exists elements(cat.kittens)

 

from Player p where 3 > all elements(p.scores)

 

from Show show where 'fizard' in indices(show.acts)

 

注意,在Hibernate3种,这些结构变量- size, elements, indices, minindex, maxindex, minelement, maxelement - 只能在where子句中使用。

 

一个被索引过的(有序的)集合的元素(arrays, lists, maps)可以在其他索引中被引用(只能在where子句中):

 

from Order order where order.items[0].id = 1234

 

select person from Person person, Calendar calendar

where calendar.holidays['national day'] = person.birthDay

    and person.nationality.calendar = calendarselect item

 

 from Item item, Order order

where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11select item from Item item, Order order

where order.items[ maxindex(order.items) ] = item and order.id = 11

 

[]中的表达式甚至可以是一个算数表达式。

 

select item from Item item, Order order

where order.items[ size(order.items) - 1 ] = item对于一个一对多的关联(one-to-many association)或是值的集合中的元素, HQL也提供内建的index()函数,

 

select item, index(item) from Order order

    join order.items item

where index(item) < 5如果底层数据库支持标量的SQL函数,它们也可以被使用

 

from DomesticCat cat where upper(cat.name) like 'FRI%'

 

如果你还不能对所有的这些深信不疑,想想下面的查询。如果使用SQL,语句长度会增长多少,可读性会下降多少:

 

select cust

from Product prod,

    Store store

    inner join store.customers cust

where prod.name = 'widget'

    and store.location.name in ( 'Melbourne', 'Sydney' )

    and prod = all elements(cust.currentOrder.lineItems)提示: 会像如下的语句

 

SELECT cust.name, cust.address, cust.phone, cust.id, cust.current_order

FROM customers cust,

    stores store,

    locations loc,

    store_customers sc,

    product prod

WHERE prod.name = 'widget'

    AND store.loc_id = loc.id

    AND loc.name IN ( 'Melbourne', 'Sydney' )

    AND sc.store_id = store.id

    AND sc.cust_id = cust.id

    AND prod.id = ALL(

        SELECT item.prod_id

        FROM line_items item, orders o

        WHERE item.order_id = o.id

            AND cust.current_order = o.id

    )

 

 

 

 

 

 

9.9 order by子句

 

查询返回的列表(list)可以按照一个返回的类或组件(components)中的任何属性(property)进行排序:

 

from DomesticCat cat

order by cat.name asc, cat.weight desc, cat.birthdate

 

可选的ascdesc关键字指明了按照升序或降序进行排序.

 

 

9.10 group by子句

一个返回聚集值(aggregate values)的查询可以按照一个返回的类或组件(components)中的任何属性(property)进行分组:

 

select cat.color, sum(cat.weight), count(cat)

from Cat cat

group by cat.color

 

select foo.id, avg(name), max(name)

from Foo foo join foo.names name

group by foo.id

 

having子句在这里也允许使用.

 

select cat.color, sum(cat.weight), count(cat)

from Cat cat

group by cat.color

having cat.color in (eg.Color.TABBY, eg.Color.BLACK)

 

如果底层的数据库支持的话(例如不能在MySQL中使用)SQL的一般函数与聚集函数也可以出现 havingorder by 子句中。

 

select cat

from Cat cat

    join cat.kittens kitten

group by cat

having avg(kitten.weight) > 100

order by count(kitten) asc, sum(kitten.weight) desc

 

注意group by子句与 order by子句中都不能包含算术表达式(arithmetic expressions.

 

 

 

9.11 子查询

对于支持子查询的数据库,Hibernate支持在查询中使用子查询。一个子查询必须被圆括号包围起来(经常是SQL聚集函数的圆括号)。 甚至相互关联的子查询(引用到外部查询中的别名的子查询)也是允许的。

 

from Cat as fatcat

where fatcat.weight > (

    select avg(cat.weight) from DomesticCat cat

)

 

from DomesticCat as cat

where cat.name = some (

    select name.nickName from Name as name

)

 

from Cat as cat

where not exists (

    from Cat as mate where mate.mate = cat

)

 

from DomesticCat as cat

where cat.name not in (

    select name.nickName from Name as name

)

 

select列表中包含一个表达式以上的子查询,你可以使用一个元组构造符(tuple constructors):

 

from Cat as cat

where not ( cat.name, cat.color ) in (

    select cat.name, cat.color from DomesticCat cat

)

 

注意在某些数据库中(不包括OracleHSQL),你也可以在其他语境中使用元组构造符, 比如查询用户类型的组件与组合:

 

from Person where name = ('Gavin', 'A', 'King')

 

该查询等价于更复杂的:

 

from Person where name.first = 'Gavin' and name.initial = 'A' and name.last = 'King')

 

有两个很好的理由使你不应当作这样的事情:首先,它不完全适用于各个数据库平台;其次,查询现在依赖于映射文件中属性的顺序。

 

 

 

9.12 HQL示例

Hibernate查询可以非常的强大与复杂。实际上,Hibernate的一个主要卖点就是查询语句的威力。这里有一些例子,它们与我在最近的 一个项目中使用的查询非常相似。注意你能用到的大多数查询比这些要简单的多!

 

下面的查询对于某个特定的客户的所有未支付的账单,在给定给最小总价值的情况下,返回订单的id,条目的数量和总价值, 返回值按照总价值的结果进行排序。为了决定价格,查询使用了当前目录。作为转换结果的SQL查询,使用了ORDER, ORDER_LINE, PRODUCT, CATALOG PRICE 库表。

 

select order.id, sum(price.amount), count(item)

from Order as order

    join order.lineItems as item

    join item.product as product,

    Catalog as catalog

    join catalog.prices as price

where order.paid = false

    and order.customer = :customer

    and price.product = product

    and catalog.effectiveDate < sysdate

    and catalog.effectiveDate >= all (

        select cat.effectiveDate

        from Catalog as cat

        where cat.effectiveDate < sysdate

    )

group by order

having sum(price.amount) > :minAmount

order by sum(price.amount) desc

 

这简直是一个怪物!实际上,在现实生活中,我并不热衷于子查询,所以我的查询语句看起来更像这个:

 

select order.id, sum(price.amount), count(item)

from Order as order

    join order.lineItems as item

    join item.product as product,

    Catalog as catalog

    join catalog.prices as price

where order.paid = false

    and order.customer = :customer

    and price.product = product

    and catalog = :currentCatalog

group by order

having sum(price.amount) > :minAmount

order by sum(price.amount) desc

 

下面一个查询计算每一种状态下的支付的数目,除去所有处于AWAITING_APPROVAL状态的支付,因为在该状态下 当前的用户作出了状态的最新改变。该查询被转换成含有两个内连接以及一个相关联的子选择的SQL查询,该查询使用了表 PAYMENT, PAYMENT_STATUS 以及 PAYMENT_STATUS_CHANGE

 

select count(payment), status.name

from Payment as payment

    join payment.currentStatus as status

    join payment.statusChanges as statusChange

where payment.status.name <> PaymentStatus.AWAITING_APPROVAL

    or (

        statusChange.timeStamp = (

            select max(change.timeStamp)

            from PaymentStatusChange change

            where change.payment = payment

        )

        and statusChange.user <> :currentUser

    )

group by status.name, status.sortOrder

order by status.sortOrder如果我把statusChanges实例集映射为一个列表(list)而不是一个集合(set, 书写查询语句将更加简单.

 

select count(payment), status.name

from Payment as payment

    join payment.currentStatus as status

where payment.status.name <> PaymentStatus.AWAITING_APPROVAL

    or payment.statusChanges[ maxIndex(payment.statusChanges) ].user <> :currentUser

group by status.name, status.sortOrder

order by status.sortOrder

 

下面一个查询使用了MS SQL Server isNull()函数用以返回当前用户所属组织的组织帐号及组织未支付的账。 它被转换成一个对表ACCOUNT, PAYMENT, PAYMENT_STATUS, ACCOUNT_TYPE, ORGANIZATION 以及 ORG_USER进行的三个内连接, 一个外连接和一个子选择的SQL查询。

 

select account, payment

from Account as account

    left outer join account.payments as payment

where :currentUser in elements(account.holder.users)

    and PaymentStatus.UNPAID = isNull(payment.currentStatus.name, PaymentStatus.UNPAID)

order by account.type.sortOrder, account.accountNumber, payment.dueDate

 

对于一些数据库,我们需要弃用(相关的)子选择。

 

select account, payment

from Account as account

    join account.holder.users as user

    left outer join account.payments as payment

where :currentUser = user

    and PaymentStatus.UNPAID = isNull(payment.currentStatus.name, PaymentStatus.UNPAID)

order by account.type.sortOrder, account.accountNumber, payment.dueDate

 

 

 

. 条件查询(Criteria Queries)

10.1创建一个Criteria 实例

org.hibernate.Criteria接口表示特定持久类的一个查询Session Criteria实例的工厂。

        

Criteria crit = sess.createCriteria(Cat.class);

crit.setMaxResults(50);

List cats = crit.list();

 

 

10.2限制结果集内容

一个单独的查询条件是org.hibernate.criterion.Criterion 接口的一个实例。org.hibernate.criterion.Restrictions 定义了获得某些内置Criterion类型的工厂方法

List cats = sess.createCriteria(Cat.class)

    .add( Restrictions.like("name", "Fritz%") )

    .add( Restrictions.between("weight", minWeight, maxWeight) )

    .list();

 

约束可以按逻辑分组。

 

List cats = sess.createCriteria(Cat.class)

    .add( Restrictions.like("name", "Fritz%") )

    .add( Restrictions.or(

        Restrictions.eq( "age", new Integer(0) ),

        Restrictions.isNull("age")

    ) )

    .list();List cats = sess.createCriteria(Cat.class)

    .add( Restrictions.in( "name", new String[] { "Fritz", "Izi", "Pk" } ) )

    .add( Restrictions.disjunction()

        .add( Restrictions.isNull("age") )

    .add( Restrictions.eq("age", new Integer(0) ) )

    .add( Restrictions.eq("age", new Integer(1) ) )

    .add( Restrictions.eq("age", new Integer(2) ) )

    ) )

    .list();

 

 

Hibernate提供了相当多的内置criterion类型(Restrictions 子类), 但是尤其有用的是可以允许你直接使用SQL

 

List cats = sess.createCriteria(Cat.class)

    .add( Restrictions.sql("lower({alias}.name) like lower(?)", "Fritz%", Hibernate.STRING) )

    .list();{alias}占位符应当被替换为被查询实体的列别名。

 

Property实例是获得一个条件的另外一种途径。你可以通过调用Property.forName() 创建一个Property

 

Property age = Property.forName("age");

List cats = sess.createCriteria(Cat.class)

    .add( Restrictions.disjunction()

        .add( age.isNull() )

    .add( age.eq( new Integer(0) ) )

    .add( age.eq( new Integer(1) ) )

    .add( age.eq( new Integer(2) ) )

    ) )

    .add( Property.forName("name").in( new String[] { "Fritz", "Izi", "Pk" } ) )

    .list();

 

 

 

 

 

10.3 结果集排序

你可以使用org.hibernate.criterion.Order来为查询结果排序

List cats = sess.createCriteria(Cat.class)

    .add( Restrictions.like("name", "F%")

    .addOrder( Order.asc("name") )

    .addOrder( Order.desc("age") )

    .setMaxResults(50)

    .list();

 

List cats = sess.createCriteria(Cat.class)

    .add( Property.forName("name").like("F%") )

    .addOrder( Property.forName("name").asc() )

    .addOrder( Property.forName("age").desc() )

    .setMaxResults(50)

    .list();

 

 

 

 

 

 

十一. Native SQL查询

        

你也可以使用你的数据库的Native SQL语言来查询数据。这对你在要使用数据库的某些特性的时候(比如说在查询提示或者Oracle中的 CONNECT关键字),这是非常有用的。这就能够扫清你把原来直接使用SQL/JDBC 的程序迁移到基于 Hibernate应用的道路上的障碍。

 

Hibernate3允许你使用手写的sql来完成所有的create,update,delete,load操作(包括存储过程)

 

11.1创建一个基于SQLQuery

SQL查询是通过SQLQuery接口来控制的,它是通过调用Session.createSQLQuery()方法来获得

List cats = sess.createSQLQuery("select {cat.*} from cats cat")

        .addEntity("cat", Cat.class);

        .setMaxResults(50);

        .list();这个查询指定了:

 

SQL查询语句,它带一个占位符,可以让Hibernate使用字段的别名.

 

查询返回的实体,和它的SQL表的别名.

 

addEntity()方法将SQL表的别名和实体类联系起来,并且确定查询结果集的形态。

 

addJoin()方法可以被用于载入其他的实体和集合的关联,TODO:examples!

 

原生的SQL查询可能返回一个简单的标量值或者一个标量和实体的结合体。

 

Double max = (Double) sess.createSQLQuery("select max(cat.weight) as maxWeight from cats cat")

        .addScalar("maxWeight", Hibernate.DOUBLE);

        .uniqueResult();

 

11.2别名和属性引用

上面使用的{cat.*}标记是 "所有属性" 的简写.你可以显式地列出需要的字段,但是你必须让Hibernate 为每一个属性注入字段的别名.这些字段的站位符是以字段别名为前导,再加上属性名.在下面的例子里,我们从一个其他的表(cat_log) 中获取Cat对象,而非Cat对象原本在映射元数据中声明的表.注意我们甚至在where子句中也可以使用属性别名. 对于命名查询,{}语法并不是必需的.

String sql = "select cat.originalId as {cat.id}, " +

    "cat.mateid as {cat.mate}, cat.sex as {cat.sex}, " +

    "cat.weight*10 as {cat.weight}, cat.name as {cat.name} " +

    "from cat_log cat where {cat.mate} = :catId"

   

List loggedCats = sess.createSQLQuery(sql)

    .addEntity("cat", Cat.class)

    .setLong("catId", catId)

    .list();

注意:如果你明确地列出了每个属性,你必须包含这个类和它的子类的属性! and its subclasses!

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值