posts - 58,  comments - 8,  trackbacks - 0

第 10 章 事务和并行(Transactions And Concurrency)

Hibernate本身并不是数据库,它只是一个轻量级的对象-关系数据库映射(object-relational)工具。它的事务交由底层的数据库连接管理,如果数据库连接有JTA的支持,那么在Session中进行的操作将是整个原子性JTA事务的一部分。Hibernate可以看作是添加了面向对象语义的JDBC瘦适配器(thin adapter)。

10.1. 配置,会话和工厂(Configurations, Sessions and Factories)

SessionFactory的创建需要耗费大量资源,它是线程安全(threadsafe)的对象,在应用中它被所有线程共享。而Session的创建耗费资源很少,它不是线程安全的对象,对于一个简单商业过程(business process),它应该只被使用一次,然后被丢弃。举例来说,当Hibernate在基于servlet的应用中,servlet能够以下面的方式得到SessionFactory

SessionFactory sf = (SessionFactory)getServletContext().getAttribute("my.session.factory");

每次调用SessionFactory的service方法能够生成一个新的Session对象,然后调用Session的flush(),调用commit()提交它的连接,调用close()关闭它,最终丢弃它。(SessionFactory可能被保存在JNDI或者一个静态的单例(Singleton)辅助变量中。)

在无状态的session bean中,可以同样使用类似的方法。bean在setSessionContext()中得到SessionFactory的实例,每个商业方法会生成一个Session对象,调用它的flush()close(),当然,应用不应该commit()connection. (把它留给JTA.在容器管理的事务中,数据库连接会自动完成事务。)

我们用上述方法使用Hibernate 的Transaction API,对Transaction执行一次commit()会把所有状态同步,把底层的数据库连接提交(对JTA 事务会特殊处理。)

这里需要理解flush()的含义。 flush()将持久化存储与内存中的变化进行同步,但不是将内存的变化与持久化存储进行同步。注意对所有的Hibernate JDBD 连接/事务来说,其隔离级别将施加于所有的Hibernate执行的操作之上!

接下来的几小节将讨论利用版本化的方法来确保事务原子性,这些“高级”方法需要小心使用。

10.2. 线程和连接(Threads and connections)

在创建Hibernate会话(Session)时,你应该留意以下的实践(practices):

  • 对于一个数据库连接,不要创建一个以上的SessionTransaction

  • 在对于一个数据库连接、一个事务使用多个Session时,你尤其需要格外地小心。Session对象会记录下调入数据更新的情况,所以另一个Session对象可能会遇到过时的数据。

  • Session 不是 线程安全的。决不要在两个并发的线程中访问同一个Session。一个Session一般只对应一批需要一次性完成的单元操作!

10.3. 考虑对象辨别

程序可能在两批单元操作中并发访问同一个对象的持久化状态。不管怎样,持久化类的一个实例不可能在两个Session中共享。所以有两种不同的辨别方式:

数据库辨别

foo.getId().equals( bar.getId() )

JVM 辨别

foo==bar

对于依附于某个特定Session的对象,两种辨别方式是等价的。然而,当程序可能在两个不同的session中并发访问“同一个”(持久化辨别)商业对象时,两个实例(对于JVM辨别来说)却可能是“不同”的。

这种方式把关于并发的头疼问题留给了Hibernate和数据库。程序不需要对任何商业对象进行同步,只要程序坚持每个Session一个线程,或者对象辨别的策略(在一个Session重,程序可以安全的使用==来比较对象)。

10.4. 乐观并发控制(Optimistic concurrency control)

许多商业过程需要一系列与用户进行交互的过程,数据库访问穿插在这些过程中。对于web和企业应用来说,跨一个用户交互过程的数据事务是不可接受的。

维护各商业事务间的隔离(isolocation)就成为应用层的部分责任,我们把这种过程称为长时间运行的应用事务(application transaction)。单一的应用事务可能跨越多个数据库事务。如果这些数据库事务中只有一个(最后一个)保存了被修改的数据,其他事务只是简单地读数据,则这个应用事务就是原子性的。

唯一满足高并发性以及高可扩展性的方法是使用带有版本化的乐观并发控制。Hibernate为使用乐观并发控制的代码提供了三种可能的方法。

10.4.1. 使用长生命周期带有自动版本化的会话

在整个商业过程中使用一个单独的Session实例以及它的持久化实例,这个Session使用带有版本化的乐观锁定机制,来确保多个数据库事务对于应用来说只是一个逻辑上的事务。在等待用户交互时,Session断开与数据库的连接。这个方法从数据库访问方面来看是最有效的,应用不需要关心对自己的版本检查或是重新与不需要序列化(transient)的实例进行关联。

在整个应用事务中,使用单一的Session 实例和它的持久化实例。

Session 使用带有版本化的乐观锁定来保证多个数据库事务对程序来说就如同是单一的逻辑应用事务。在等待用户交互的时候,Session 脱离所有的底层JDBC连接。对于数据库访问来说,这种方法是最高效的。程序自己不需要关心版本检查或者把已经脱离session的实例重新关联到session。

// foo is an instance loaded earlier by the Session
session.reconnect();
foo.setProperty("bar");
session.flush();
session.connection().commit();
session.disconnect();

foo对象仍然知道是哪个Session把自己装载的。 只要Session 拥有一个JDBC连接,我们可以把对象的更改提交。

如果我们的 Session 太大,以至于在用户思考的时间内无法保存住,这种模式就会出现问题。比如,HttpSession应该保持尽量小。因为Session也持有(必须的)第一级缓存,包含所有被装载的对象,我们只能在很少的request/response周期中使用这一策略。这种少用是被鼓励的,因为Session 很快就会出现过时的数据。

10.4.2. 使用带有自动版本化的多个会话

每个与持久化存储的交互出现在一个新的Session中,在每次与数据库的交互中,使用相同的持久化实例。应用操作那些从其它Session调入的已经脱离session的实例的状态,通过使用Session.update()或者Session.saveOrUpdate()来重新建立与它们的关联。

// foo is an instance loaded by a previous Session
foo.setProperty("bar");
session = factory.openSession();
session.saveOrUpdate(foo);
session.flush();
session.connection().commit();
session.close();

你也可以调用lock()而非update(),如果你确信对象没有被修改过,可以使用LockMode.READ(进行一次版本检查,而跳过所有的缓存)。

10.4.3. 应用程序自己进行版本检查

每当一个新的Session中与数据库出现交互的时候,这个session会在操作持久化实例前重新把它们从数据库中装载进来。我们现在所说的方式就是你的应用程序自己使用版本检查来确保应用事务的隔离性。(当然,Hibernate仍会为你更新版本号)。从数据库访问方面来看,这种方法是最没有效率的,与entity EJB方式类似。

// foo is an instance loaded by a previous Session
session = factory.openSession();
int oldVersion = foo.getVersion();
session.load( foo, foo.getKey() );
if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException();
foo.setProperty("bar");
session.flush();
session.connection().commit();
session.close();

当然,如果在低数据并行(low-data-concurrency)的环境中,并不需要版本检查,你仍可以使用这个方法,只需要忽略版本检查。

10.5. 会话断开连接(Session disconnection)

The first approach described above is to maintain a single Session for a whole business process thats spans user think time. (For example, a servlet might keep a Session in the user's HttpSession.) For performance reasons you should

上面提到的第一种方法是对于对一个用户的一次登录产生的整个商业过程维护一个Session。(举例来说,servlet有可能会在用户的HttpSession中保留一个Session)。为性能考虑,你必须

  1. 提交Transaction(或者JDBC连接),然后

  2. (在等待用户操作前,)断开Session与JDBC连接。

Session.disconnect()方法会断开会话与JDBC的连接,把连接返还给连接池(除非是你自己提供这个连接的)。

Session.reconnect()方法会得到一个新的连接(你也可以自己提供一个),重新开始会话。在重新连接后,你可以通过对任何可能被其它事务更新的对象调用Session.lock()方法,来强迫对你没有更新的数据进行版本检查。你不需要对正在更新的数据调用lock()。

这是一个例子:

SessionFactory sessions;
List fooList;
Bar bar;
....
Session s = sessions.openSession();

Transaction tx = null;
try {
    tx = s.beginTransaction();

    fooList = s.find(
    	"select foo from eg.Foo foo where foo.Date = current date"
        // uses db2 date function
    );
    bar = (Bar) s.create(Bar.class);

    tx.commit();
}
catch (Exception e) {
    if (tx!=null) tx.rollback();
    s.close();
    throw e;
}
s.disconnect();

接下来:

s.reconnect();

try {
    tx = s.beginTransaction();

    bar.setFooTable( new HashMap() );
    Iterator iter = fooList.iterator();
    while ( iter.hasNext() ) {
        Foo foo = (Foo) iter.next();
        s.lock(foo, LockMode.READ);    //check that foo isn't stale
        bar.getFooTable().put( foo.getName(), foo );
    }

    tx.commit();
}
catch (Exception e) {
    if (tx!=null) tx.rollback();
    throw e;
}
finally {
    s.close();
}

从上面的例子可以看到TransactionSession之间是多对一的关系。一个Session表示了应用程序与数据库之间的一个对话,Transaction把这个对话分隔成一个个在数据库级别具有原子性的单元。

10.6. 悲观锁定(Pessimistic Locking)

用户不需要在锁定策略上花费过多时间,通常我们可以对JDBC连接选定一种隔离级别(isolationn level),然后让数据库完成所有的工作。高级用户可能希望得到悲观锁定或者在新的事务开始时重新得到锁。

Hibernate一直都会使用数据库的锁定机制,而不会在内存中锁定对象。

LockMode类定义了Hibernate需要的不同的锁级别。锁由以下的机制得到:

  • LockMode.WRITE在Hibernate更新或插入一行数据时自动得到。

  • LockMode.UPGRADE在用户通过SELECT ... FOR UPDATE这样的特定请求得到,需要数据库支持这种语法。

  • LockMode.UPGRADE_NOWAIT在用户通过SELECT ... FOR UPDATE NOWAIT这样的特定请求在Oracle数据库环境下得到。

  • LockMode.READ在Hibernate在不断读(Repeatable Read)和序列化(Serializable)的隔离级别下读取数据时得到。也可以通过用户的明确请求重新获得。

  • LockMode.NONE表示没有锁。所有对象在Transaction结束时会切换到这种锁模式,通过调用update()或者saveOrUpdate()与会话进行关联的对象,开始时也会在这种锁模式。

“明确的用户请求”会以下的几种方式出现:

  • 调用Session.load(),指定一种LockMode

  • 调用Session.lock()

  • 调用Query.setLockMode()

如果在调用Session.load()时指定了UPGRADE或者UPGRADE_NOWAIT,并且请求的对象还没有被会话调入,那么这个对象会以SELECT ... FOR UPDATE的方式调入。如果调用load()在一个已经调入的对象,并且这个对象调入时的锁级别没有请求时来得严格,Hibernate会对这个对象调用lock()

Session.lock()会执行版本号检查的特定的锁模式是:READUPGRADE或者UPGRADE_NOWAIT。(在UPGRADE或者UPGRADE_NOWAITSELECT ... FOR UPGRADE使用的情况下。)

如果数据库不支持所请求的锁模式,Hibernate将会选择一种合适的受支持的锁模式替换(而不是抛出一个异常)。这确保了应用具有可移植性

第 11 章 Hibernate查询语言(Query Language), 即HQL

Hibernate装备了一种极为有力的查询语言,(有意地)看上去很像SQL。但是别被语法蒙蔽,HQL是完全面向对象的,具备继承、多态和关联等特性。

11.1. 大小写敏感性(Case Sensitivity)

除了Java类和属性名称外,查询都是大小写不敏感的。 所以, SeLeCTsELEct 以及 SELECT 相同的,但是 net.sf.hibernate.eg.FOOnet.sf.hibernate.eg.Foo 是不同的, foo.barSetfoo.BARSET也是不同的。

本手册使用小写的HQL关键词。有些用户认为在查询中使用大写的关键字更加易读,但是我们认为嵌入在Java代码中这样很难看。

11.2. from 子句

可能最简单的Hibernate查询是这样的形式:

from eg.Cat

它简单的返回所有eg.Cat类的实例。

大部分情况下,你需要赋予它一个别名(alias),因为你在查询的其他地方也会引用这个Cat

from eg.Cat as cat

上面的语句为Cat赋予了一个别名cat 。所以后面的查询可以用这个简单的别名了。as关键字是可以省略的,我们也可以写成这样:

from eg.Cat cat

可以出现多个类,结果是它们的笛卡尔积,或者称为“交叉”连接。

from Formula, Parameter
from Formula as form, Parameter as param

让查询中的别名服从首字母小写的规则,我们认为这是一个好习惯。这和Java对局部变量的命名规范是一致的。(比如,domesticCat).

11.3. 联合(Associations)和连接(joins)

你可以使用join定义两个实体的连接,同时指明别名。

from eg.Cat as cat 
    inner join cat.mate as mate
    left outer join cat.kittens as kitten

from eg.Cat as cat left join cat.mate.kittens as kittens

from Formula form full join form.parameter param

支持的连接类型是从ANSI SQL借用的:

  • 内连接,inner join

  • 左外连接,left outer join

  • 右外连接,right outer join

  • 全连接,full join (不常使用)

inner join, left outer joinright outer join 都可以简写。

from eg.Cat as cat 
    join cat.mate as mate
    left join cat.kittens as kitten

并且,加上 "fetch"后缀的抓取连接可以让联合的对象随着它们的父对象的初始化而初始化,只需要一个select语句。这在初始化一个集合的时候特别有用。它有效地覆盖了映射文件中对关联和集合的外连接定义。

from eg.Cat as cat 
    inner join fetch cat.mate
    left join fetch cat.kittens

抓取连接一般不需要赋予别名,因为被联合的对象应该不会在where子句(或者任何其它子句)中出现。并且,被联合的对象也不会在查询结果中直接出现。它们是通过父对象进行访问的。

请注意,目前的实现中,在一次查询中只会抓取一个集合(其他的一切都做不到。)(?原文为:only one collection role may be fetched in a query)。也请注意,在使用scroll()或者 iterate()方式调用的查询中,是禁止使用fetch构造的。最后,请注意full join fetchright join fetch是没有意义的。

11.4. select子句

select子句选择在结果集中返回哪些对象和属性。思考一下下面的例子:

select mate 
from eg.Cat as cat 
    inner join cat.mate as mate

这个查询会选择出作为其它猫(Cat)朋友(mate)的那些猫。当然,你可以更加直接的写成下面的形式:

select cat.mate from eg.Cat cat

你甚至可以选择集合元素,使用特殊的elements功能。下面的查询返回所有猫的小猫。

select elements(cat.kittens) from eg.Cat cat

查询可以返回任何值类型的属性,包括组件类型的属性:

select cat.name from eg.DomesticCat cat
where cat.name like 'fri%'

select cust.name.firstName from Customer as cust

查询可以用元素类型是Object[]的一个数组返回多个对象和/或多个属性。

select mother, offspr, mate.name 
from eg.DomesticCat as mother
    inner join mother.mate as mate
    left outer join mother.kittens as offspr

或者实际上是类型安全的Java对象

select new Family(mother, mate, offspr)
from eg.DomesticCat as mother
    join mother.mate as mate
    left join mother.kittens as offspr

上面的代码假定Family有一个合适的构造函数。

11.5. 统计函数(Aggregate functions)

HQL查询可以返回属性的统计函数的结果。

select avg(cat.weight), sum(cat.weight), max(cat.weight), count(cat)
from eg.Cat cat

select子句中,统计函数的变量也可以是集合。

select cat, count( elements(cat.kittens) ) 
from eg.Cat cat group by cat

下面是支持的统计函数列表:

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

  • count(*)

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

distinctall关键字的用法和语义与SQL相同。

select distinct cat.name from eg.Cat cat

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

11.6. 多态(polymorphism)查询

类似下面的查询:

from eg.Cat as cat

返回的实例不仅仅是Cat,也有可能是子类的实例,比如DomesticCat。Hibernate查询可以在from子句中使用任何Java类或者接口的名字。查询可能返回所有继承自这个类或者实现这个接口的持久化类的实例。下列查询会返回所有的持久化对象:

from java.lang.Object o

可能有多个持久化类都实现了Named接口:

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

请注意,上面两个查询都使用了超过一个SQL的SELECT。这意味着order by子句将不会正确排序。(这也意味着你不能对这些查询使用Query.scroll()。)

11.7. where子句

where子句让你缩小你要返回的实例的列表范围。

from eg.Cat as cat where cat.name='Fritz'

返回所有名字为'Fritz'的Cat的实例。

select foo 
from eg.Foo foo, eg.Bar bar
where foo.startDate = bar.date

会返回所有的满足下列条件的Foo实例,它们存在一个对应的bar实例,其date属性与FoostartDate属性相等。复合路径表达式令where子句变得极为有力。思考下面的例子:

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

这个查询会被翻译为带有一个表间(inner)join的SQL查询。如果你写下类似这样的语句:

from eg.Foo foo  
where foo.bar.baz.customer.address.city is not null

你最终会得到的查询,其对应的SQL需要4个表间连接。

=操作符不仅仅用于判断属性是否相等,也可以用于实例:

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

select cat, mate 
from eg.Cat cat, eg.Cat mate
where cat.mate = mate

特别的,小写的id可以用来表示一个对象的惟一标识。(你可以使用它的属性名。)

from eg.Cat as cat where cat.id = 123

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

第二个查询是很高效的。不需要进行表间连接!

组合的标示符也可以使用。假设Person有一个组合标示符,是由countrymedicareNumber组合而成的。

from bank.Person person
where person.id.country = 'AU' 
    and person.id.medicareNumber = 123456

from bank.Account account
where account.owner.id.country = 'AU' 
    and account.owner.id.medicareNumber = 123456

又一次,第二个查询不需要表间连接。

类似的,在存在多态持久化的情况下,特殊属性class用于获取某个实例的辨识值。在where子句中嵌入的Java类名将会转换为它的辨识值。

from eg.Cat cat where cat.class = eg.DomesticCat

你也可以指定组件(或者是组件的组件,依次类推)或者组合类型中的属性。但是在一个存在路径的表达式中,最后不能以一个组件类型的属性结尾。(这里不是指组件的属性)。比如,假若store.owner这个实体的的address是一个组件

store.owner.address.city    //okay
store.owner.address         //error!

“任意(any)”类型也有特殊的id属性和class属性,这可以让我们用下面的形式来表达连接(这里AuditLog.item是一个对应到<ant>的属性)。

from eg.AuditLog log, eg.Payment payment 
where log.item.class = 'eg.Payment' and log.item.id = payment.id

注意上面查询中,log.item.classpayment.class会指向两个值,代表完全不同的数据库字段。

11.8. 表达式(Expressions)

where子句允许出现的表达式包括了你在SQL中可以使用的大多数情况:

  • 数学操作+, -, *, /

  • 真假比较操作 =, >=, <=, <>, !=, like

  • 逻辑操作 and, or, not

  • 字符串连接 ||

  • SQL标量( scalar)函数,例如 upper()lower()

  • 没有前缀的 ( )表示分组

  • in, between, is null

  • JDBC 传入参数?

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

  • SQL 文字 'foo', 69, '1970-01-01 10:00:01.0'

  • Java的public static final常量 比如 Color.TABBY

inbetween 可以如下例一样使用:

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

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

其否定形式为

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

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

类似的,is nullis not null可以用来测试null值。

通过在Hibernate配置中声明HQL查询的替换方式,Boolean也是很容易在表达式中使用的:

<property name="hibernate.query.substitutions">true 1, false 0</property>

在从HQL翻译成SQL的时候,关键字truefalse就会被替换成10

from eg.Cat cat where cat.alive = true

你可以用特殊属性size来测试一个集合的长度,或者用特殊的size()函数也可以。

from eg.Cat cat where cat.kittens.size > 0

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

对于排序集合,你可以用minIndexmaxIndex来获取其最大索引值和最小索引值。类似的,minElementmaxElement 可以用来获取集合中最小和最大的元素,前提是必须是基本类型的集合。

from Calendar cal where cal.holidays.maxElement > current date

也有函数的形式(和上面的形式不同,函数形式是大小写不敏感的):

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

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

SQL中的any, some, all, exists, in功能也是支持的,前提是必须把集合的元素或者索引集作为它们的参数(使用elementindices函数),或者使用子查询的结果作为参数。

select mother from eg.Cat as mother, eg.Cat as kit
where kit in elements(foo.kittens)

select p from eg.NameList list, eg.Person p
where p.name = some elements(list.names)

from eg.Cat cat where exists elements(cat.kittens)

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

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

请注意这些设施:size,elements,indices,minIndex,maxIndex,minElement,maxElement 都有一些使用限制:

  • where子句中: 只对支持子查询的数据库有效

  • select子句中:只有elementsindices有效

有序的集合(数组、list、map)的元素可以用索引来进行引用(只限于在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 = calendar

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

select 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

HQL也对一对多关联或者值集合提供内置的index()函数。

select item, index(item) from Order order 
    join order.items item
where index(item) < 5

底层数据库支持的标量SQL函数也可以使用

from eg.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)

提示: 对应的SQL语句可能是这样的

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
    )

11.9. order by 子句

查询返回的列表可以按照任何返回的类或者组件的属性排序:

from eg.DomesticCat cat
order by cat.name asc, cat.weight desc, cat.birthdate

ascdesc是可选的,分别代表升序或者降序。

11.10. group by 子句

返回统计值的查询可以按照返回的类或者组件的任何属性排序:

select cat.color, sum(cat.weight), count(cat) 
from eg.Cat cat
group by cat.color

select foo.id, avg( elements(foo.names) ), max( indices(foo.names) ) 
from eg.Foo foo
group by foo.id

请注意:你可以在select子句中使用elementsindices指令,即使你的数据库不支持子查询也可以。

having子句也是允许的。

select cat.color, sum(cat.weight), count(cat) 
from eg.Cat cat
group by cat.color 
having cat.color in (eg.Color.TABBY, eg.Color.BLACK)

having子句中允许出现SQL函数和统计函数,当然这需要底层数据库支持才行。(比如说,MySQL就不支持)

select cat
from eg.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子句都不支持数学表达式。

11.11. 子查询

对于支持子查询的数据库来说,Hibernate支持在查询中嵌套子查询。子查询必须由圆括号包围(常常是在一个SQL统计函数中)。也允许关联子查询(在外部查询中作为一个别名出现的子查询)。

from eg.Cat as fatcat 
where fatcat.weight > ( 
    select avg(cat.weight) from eg.DomesticCat cat 
)

from eg.DomesticCat as cat 
where cat.name = some ( 
    select name.nickName from eg.Name as name 
)
    
from eg.Cat as cat 
where not exists ( 
    from eg.Cat as mate where mate.mate = cat 
)

from eg.DomesticCat as cat 
where cat.name not in ( 
    select name.nickName from eg.Name as name 
)

11.12. HQL示例

Hibernate查询可以非常强大复杂。实际上,强有力的查询语言是Hibernate的主要卖点之一。下面给出的示例与我在近期实际项目中使用的一些查询很类似。请注意你编写的查询大部分等都不会这么复杂!

下面的查询对特定的客户,根据给定的最小总计值(minAmount),查询出所有未付订单,返回其订单号、货品总数、订单总金额,结果按照总金额排序。在决定价格的时候,参考当前目录。产生的SQL查询,在ORDER,ORDER_LINE,PRODUCT,CATALOGPRICE表之间有四个内部连接和一个没有产生关联的字查询。

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_STATUSPAYMENT_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

假若我已经把statusChange集合映射为一个列表而不是一个集合的话,查询写起来会简单很多。

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()函数,返回当前用户所属的组织所有账户和未付支出。翻译为SQL查询后,在ACCOUNT, PAYMENT, PAYMENT_STATUS,ACCOUNT_TYPE, ORGANIZATIONORG_USER表之间有三个内部连接,一个外部连接和一个子查询。

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

11.13. 提示和技巧(Tips & Tricks)

你不返回结果集也可以查询结果集的大小:

( (Integer) session.iterate("select count(*) from ....").next() ).intValue()

要依据一个集合的大小对结果集排序,可以用下面的查询来对付一对多或多对多的关联:

select usr
from User as usr 
    left join usr.messages as msg
group by usr
order by count(msg)

如果你的数据库支持子查询,你可以在查询的where子句中对选择的大小进行条件限制:

from User usr where size(usr.messages) >= 1

如果你的数据库不支持子查询,可以使用下列查询:

select usr.id, usr.name
from User usr.name
    join usr.messages msg
group by usr.id, usr.name
having count(msg) >= 1

因为使用了inner join,这个解决方法不能返回没有message的User.下面的方式就可以:

select usr
from User as usr
    left join usr.messages as msg
group by usr
having count(msg) = 0

JavaBean的属性可以直接作为命名的查询参数:

Query q = s.createQuery("from foo in class Foo where foo.name=:name and foo.size=:size");
q.setProperties(fooBean); // fooBean has getName() and getSize()
List foos = q.list();

Query接口中使用过滤器(filter),可以对集合分页:

Query q = s.createFilter( collection, "" ); // the trivial filter
q.setMaxResults(PAGE_SIZE);
q.setFirstResult(PAGE_SIZE * pageNumber);
List page = q.list();

集合元素可以使用查询过滤器(query filter)进行排序或者分组:

Collection orderedCollection = s.filter( collection, "order by this.amount" );
Collection counts = s.filter( collection, "select this.type, count(this) group by this.type" );

不用初始化集合就可以得到其大小:

( (Integer) session.iterate("select count(*) from ....").next() ).intValue();
posted on 2006-08-31 18:39 forrest 阅读(1583) 评论(0)  编辑 收藏 引用 所属分类: TomcaT
只有注册用户登录后才能发表评论。
<2024年12月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

常用链接

留言簿(5)

随笔分类(59)

随笔档案(58)

文章分类(41)

文章档案(52)

相册

收藏夹

postfix

windows 系统

编程

  • how to be a programmer
  • 写给想当程序员的朋友
  • 谨以此文献给所有想当程序员的朋友 (一) 文章由来及个人经历 我是一名计算机专业的本科毕业生,毕业已经1年多了。毕业后从事的是软件编程工作,经常有其他专业的朋友想从事软件编程工作,向我请教如何,因为我自觉涉行不深,不敢信口开河,无奈朋友信任,我不得不郑重考虑一下这个问题了,来帮助朋友选择和回报朋友的信任。

搜索

  •  

最新评论

阅读排行榜

评论排行榜