posts - 58,  comments - 8,  trackbacks - 0

第 13 章 原生SQL查询

你也可以直接使用你的数据库方言表达查询。在你想使用数据库的某些特性的时候,这是非常有用的, 比如Oracle中的CONNECT关键字。这也会扫清你把原来直接使用SQL/JDBC 的程序移植到Hibernate道路上的障碍。

13.1. 创建一个基于SQL的Query

和普通的HQL查询一样,SQL查询同样是从Query接口开始的。惟一的区别是使用Session.createSQLQuery()方法。

Query sqlQuery = sess.createSQLQuery("select {cat.*} from cats {cat}", "cat", Cat.class);
sqlQuery.setMaxResults(50);
List cats = sqlQuery.list();

传递给createSQLQuer()的三个参数是:

  • SQL查询语句

  • 表的别名

  • 查询返回的持久化类

别名是为了在SQL语句中引用对应的类(本例中是Cat)的属性的。你也可以传递一个别名的String 数组和一个对应的Class的数组进去,每行就可以得到多个对象。

13.2. 别名和属性引用

上面使用的{cat.*}标记是“所有属性的”的简写。你可以显式的列出需要的属性,但是你必须让Hibernate为每个 属性提供SQL列别名。这些列的的占位表示符是以表别名为前导,再加上属性名。下面的例子中,我们从一个其它的表(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, "cat", Cat.class)
    .setLong("catId", catId)
    .list();

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

13.3. 为SQL查询命名

可以在映射文档中定义SQL查询的名字,然后就可以像调用一个命名HQL查询一样直接调用命名SQL查询。

<sql-query name="mySqlQuery">
    <return alias="person" class="eg.Person"/>
    SELECT {person}.NAME AS {person.name},
           {person}.AGE AS {person.age},
           {person}.SEX AS {person.sex}
    FROM PERSON {person} WHERE {person}.NAME LIKE 'Hiber%'
</sql-query>

第 14 章 性能提升(Improving performance)

14.1. 理解集合的性能

我们已经花了很长时间在讨论集合(collections)了。在本章,我们会特别关注一些关于集合在运行时如何运作的问题。

14.1.1. 分类

Hibernate定义了三种基本类型的集合:

  • 值集合

  • 一对多关联

  • 多对多关联

这个分类是按照不同的表和外键关系类型来区分的,但是没有告诉我们关于关系模型的一切。要完全理解他们的关系结构和性能特点,我们必须思考用于更新或删除集合行的主键的结构。这得到了如下的分类:

  • 有序集合类

  • 集合(sets)

  • 包(bags)

有序集合类(maps, lists, arrays)有一个包含有<key><index>字段的主键。这种情况下集合类更新是特别高效的——主键会有效索引,当Hibernate试图更新或删除一行时,可以迅速找到这一行。

集合(sets)的主键包含有 <key> 和元素字段。对于有些元素类型来说,这会变得低效,特别是组合元素或者大文本、大二进制字段;数据库可能无法有效对复杂的主键进行索引。另一方面,对于一对多或多对多关联,特别是合成的标识符来说,它会达到同样的高效。(附注:如果你希望SchemaExport为你的<set>创建主键,你必须把所有的字段都声明为not-null="true"。)

Bag是最差的。因为bag允许重复的元素值,也没有索引字段,不可能定义主键。Hibernate没有办法来判断出重复的行。每当这种集合被更改,Hibernate会完整地移除(通过一个DELETE),再重建整个集合。这会非常低效。

请注意对一对多关联来说,“主键”可能是数据库表的物理主键——但就算在这种情况下,上面的分类仍然是有用的。(它会反映Hibernate是如何在集合的各个行中“定位”的。)

14.1.2. Lists, maps 和sets用于更新效率最高

根据我们上面的讨论,显然有序类型和大多数set可以在增加/删除/修改元素的时候得到最好的性能。

但是,在多对多关联,或者对值元素而言,有序集合类比集合(set)有一个好处。因为Set的结构,如果“改变”了一个元素,Hibernate并不会UPDATE这一行。对Set来说,只有INSERTDELETE才有效。注意这一段描述对一对多关联并不适用。

注意到数组无法延迟转载,我们可以得出结论,list, map和set是最高效的集合类型。(当然,我们警告过了,由于集合中的值的关系,set可能性能下降。)

Set可以被看作是Hibernate程序中最普遍的集合类型。

这个版本的Hibernate有一个没有写在文档中的功能。<idbag>可以对值集合和多对多关联实现bag语义,并且性能比上面任何类型都高!

14.1.3. Bag和list是反向集合类中效率最高的

好了,在你把bag扔到水沟里面再踩上一只脚之前要了解,有一种情况下bag(包括list)要比set性能高得多。对于指明了inverse="true"的集合类(比如说,标准的双向一对多关联),我们可以在不初始化(fetch)包元素的情况下就增加新元素!这是因为Collection.add()或者Collection.addAll()对bag或者List总是返回true的(与Set不同)。对于下面的代码来说,速度会快得多。

Parent p = (Parent) sess.load(Parent.class, id);
    Child c = new Child();
    c.setParent(p);
    p.getChildren().add(c);  //no need to fetch the collection!
    sess.flush();

14.1.4. 一次性删除(One shot delete)

有时候,一个一个的删除集合类中的元素是极度低效的。Hibernate没那么笨,如果你想要把整个集合都删除(比如说调用list.clear()),Hibernate只需要一个DELETE就搞定了。

假设我们在一个长度为20的集合类中新增加了一个元素,然后删除了两个。Hibernate会安排一个INSERT语句和两条DELETE语句(除非集合类是一个bag)。这当然是可以想见的。

但是,如果假设我们删除了18个元素,只剩下2个,然后新增3个。有两种处理方式:

  • 把这18个元素一个一个的干掉,再新增三个

  • 把整个集合类都咔嚓掉(只用一句DELETE语句),然后增加5个元素。

Hibernate还没那么聪明,知道第二种选择可能会比较快。(也许让Hibernate不要这么聪明也是好事,否则可能会引发意外的数据库触发器什么的。)

幸运的是,你可以强制使用第二种策略。你需要把原来的整个集合类都取消(取消其引用),然后返回一个新实例化的集合类,只包含需要的元素。有些时候这是非常有用的。

我们已经为您展示了如何在对集合持久化时使用延迟装载(lazy initialization)。对于通常的对象引用,使用CGLIB代理可以达到类似的效果。我们也提到过Hibernate在Session级别缓存持久化对象。还有更多先进的缓存策略,你可以为每一个类单独配置。

这本章里,我们来教你如何使用这些特性,在必要的时候得到高得多的性能。

14.2. 用于延迟装载的代理

Hibernate使用动态字节码增强技术来实现持久化对象的延迟装载代理(使用优秀的CGLIB库)。

映射文件为每一个类声明一个类或者接口作为代理接口。建议使用这个类自身:

<class name="eg.Order" proxy="eg.Order">

运行时的代理应该是Order的子类。注意被代理的类必须实现一个默认的构造器,并且至少在包内可见。

在扩展这种方法来对应多形的类时,要注意一些细节,比如:

<class name="eg.Cat" proxy="eg.Cat">
    ......
    <subclass name="eg.DomesticCat" proxy="eg.DomesticCat">
        .....
    </subclass>
</class>

首先,Cat永远不能被强制转换为DomesticCat,即使实际上该实例就是一个DomesticCat实例。

Cat cat = (Cat) session.load(Cat.class, id);  // instantiate a proxy (does not hit the db)
if ( cat.isDomesticCat() ) {                  // hit the db to initialize the proxy
    DomesticCat dc = (DomesticCat) cat;       // Error!
    ....
}

其次,代理的==可能不再成立。

Cat cat = (Cat) session.load(Cat.class, id);            // instantiate a Cat proxy
DomesticCat dc = 
    (DomesticCat) session.load(DomesticCat.class, id);  // required new DomesticCat proxy!
System.out.println(cat==dc);                            // false

虽然如此,这种情况并不像看上去得那么糟。虽然我们现在有两个不同的引用来指向不同的代理对象,实际上底层的实例应该是同一个对象:

cat.setWeight(11.0);  // hit the db to initialize the proxy
System.out.println( dc.getWeight() );  // 11.0

第三,你不能对final的类或者具有final方法的类使用CGLIB代理。

最后,假如你的持久化对象在实例化的时候需要某些资源(比如,在实例化方法或者默认构造方法中),这些资源也会被代理需要。代理类实际上是持久化类的子类。

这些问题都来源于Java的单根继承模型的天生限制。如果你希望避免这些问题,你的每个持久化类必须抽象出一个接口,声明商业逻辑方法。你应该在映射文件中指定这些接口,比如:

<class name="eg.Cat" proxy="eg.ICat">
    ......
    <subclass name="eg.DomesticCat" proxy="eg.IDomesticCat">
        .....
    </subclass>
</class>

这里Cat实现ICat接口,并且DomesticCat实现IDomesticCat接口。于是 load()或者iterate()就会返回CatDomesticCat的实例的代理。(注意find()不会返回代理。)

ICat cat = (ICat) session.load(Cat.class, catid);
Iterator iter = session.iterate("from cat in class eg.Cat where cat.name='fritz'");
ICat fritz = (ICat) iter.next();

关系也是延迟装载的。这意味着你必须把任何属性声明为ICat类型,而非Cat

某些特定操作需要初始化代理

  • equals(), 假如持久化类没有重载equals()

  • hashCode(), 假如持久化类没有重载hashCode()

  • 标识符的get方法

Hibernate会识别出重载了equals() 或者 hashCode()方法的持久化类。

在初始化代理的时候发生的异常会被包装成LazyInitializationException

有时候我们需要保证在Session关闭前某个代理或者集合已经被初始化了。当然,我们总是可以通过调用cat.getSex()或者 cat.getKittens().size()之类的方法来确保这一点。但是这样程序可读性不佳,也不符合通常的代码规范。静态方法Hibernate.initialize()Hibernate.isInitialized()给你的应用程序一个正常的途径来加载集合或代理。Hibernate.initialize(cat) 会强制初始化一个代理,cat,只要它的Session仍然打开。Hibernate.initialize( cat.getKittens() )对kittens的集合具有同样的功能。

14.3. 第二层缓存(The Second Level Cache)s

HibernateSession是事务级别的持久化数据缓存。再为每个类或者每个集合配置一个集群或者JVM级别(SessionFactory级别)的缓存也是有可能的。你甚至可以插入一个集群的缓存。要小心,缓存永远不会知道其他进程可能对持久化仓库(数据库)进行的修改(即使他们可能设定为经常对缓存的数据进行失效)。

默认情况下,Hibernate使用EHCache进行JVM级别的缓存。但是,对JCS的支持现在已经被废弃了,未来版本的Hibernate将会去掉它。通过hibernate.cache.provider_class属性,你也可以指定其他缓存,只要其实现了net.sf.hibernate.cache.CacheProvider接口。

表 14.1. Cache Providers

Cache Provider class Type Cluster Safe Query Cache Supported
Hashtable (not intended for production use) net.sf.hibernate.cache.HashtableCacheProvider memory   yes
EHCache net.sf.hibernate.cache.EhCacheProvider memory, disk   yes
OSCache net.sf.hibernate.cache.OSCacheProvider memory, disk   yes
SwarmCache net.sf.hibernate.cache.SwarmCacheProvider clustered (ip multicast) yes (clustered invalidation)  
JBoss TreeCache net.sf.hibernate.cache.TreeCacheProvider clustered (ip multicast), transactional yes (replication)  

14.3.1. 对映射(Mapping)缓冲

类或者集合映射的<cache>元素可能有下列形式:

<cache                                                      (1)
                usage="transactional|read-write|nonstrict-read-write|read-only" />
(1)

usage 指定了缓存策略: transactional, read-write, nonstrict-read-write 或者 read-only

另外 (推荐首选?), 你可以在hibernate.cfg.xml中指定<class-cache><collection-cache> 元素。

usage属性指明了缓存并发策略(cache concurrency strategy)

14.3.2. 策略:只读缓存

如果你的应用程序需要读取一个持久化类的实例,但是并不打算修改它们,可以使用read-only 缓存。这是最简单,也是实用性最好的策略。甚至在集群中,它也能完美地运作。

<class name="eg.Immutable" mutable="false">
    <cache usage="read-only"/>
    ....
</class>

14.3.3. 策略:读/写缓存

如果应用程序需要更新数据,可能read-write缓存比较合适。如果需要可序列化事务隔离级别(serializable transaction isolation level),这种缓存决不能使用。如果在JTA环境中使用这种缓存,你必须指定hibernate.transaction.manager_lookup_class属性的值,给出得到JTA TransactionManager的策略。在其它环境中,你必须确保在Session.close()或者Session.disconnect()调用前,事务已经结束了。 如果你要在集群环境下使用这一策略,你必须确保底层的缓存实现支持锁定(locking)。内置的缓存提供器并不支持。

<class name="eg.Cat" .... >
    <cache usage="read-write"/>
    ....
    <set name="kittens" ... >
        <cache usage="read-write"/>
        ....
    </set>
</class>

14.3.4. 策略:不严格的读/写缓存

如果程序偶尔需要更新数据(也就是说,出现两个事务同时更新同一个条目的现象很不常见),也不需要十分严格的事务隔离,可能适用nonstrict-read-write缓存。如果在JTA环境中使用这种缓存,你必须指定hibernate.transaction.manager_lookup_class属性的值,给出得到JTA TransactionManager的策略。在其它环境中,你必须确保在Session.close()或者Session.disconnect()调用前,事务已经结束了。

14.3.5. 策略:事务缓存(transactional)

transactional缓存策略提供了对全事务缓存提供,比如JBoss TreeCache的支持。这样的缓存只能用于JTA环境,你必须指定hibernate.transaction.manager_lookup_class

没有一种缓存提供器能够支持所有的缓存并发策略。下面的表列出每种提供器与各种并发策略的兼容性。

表 14.2. 缓存并发策略支持(Cache Concurrency Strategy Support)

Cache read-only nonstrict-read-write read-write transactional
Hashtable (not intended for production use) yes yes yes  
EHCache yes yes yes  
OSCache yes yes yes  
SwarmCache yes yes    
JBoss TreeCache yes     yes

14.4. 管理Session缓存

不管何时你传递一个对象给save(), update()或者 saveOrUpdate() ,或者不管何时你使用load(), find(), iterate()或者filter()取得一个对象的时候,该对象被加入到Session的内部缓存中。当后继的flush()被调用时,对象的状态会和数据库进行同步。如果你在处理大量对象并且需要有效的管理内存的时候,你可能不希望发生这种同步,evict()方法可以从缓存中去掉对象和它的集合。

Iterator cats = sess.iterate("from eg.Cat as cat"); //a huge result set
while ( cats.hasNext() ) {
    Cat cat = (Cat) iter.next();
    doSomethingWithACat(cat);
    sess.evict(cat);
}

Hibernate will evict associated entities automatically if the association is mapped with cascade="all" or cascade="all-delete-orphan". 如果关联通过cascade="all" 或者 cascade="all-delete-orphan"实现,Hibernate会自动删除关联的实体。

Session也提供了一个contains()方法来判断是否一个实例处于这个session的缓存中。

要把所有的对象从session缓存中完全清除,请调用Session.clear()

对于第二层缓存来说,在SessionFactory中定义了一些方法来从缓存中清除一个实例、整个类、集合实例或者整个集合。

14.5. 查询缓存(Query Cache)

查询结果集也可以被缓存。只有当经常使用同样的参数进行查询时,这才会有些用处。要使用查询缓存,首先你要打开它,设置hibernate.cache.use_query_cache=true这个属性。这样会创建两个缓存区域——一个保存查询结果集(net.sf.hibernate.cache.QueryCache),另一个保存最近查询的表的时间戳(net.sf.hibernate.cache.UpdateTimestampsCache)。请注意查询缓存并不缓存结果集中包含实体的状态;它只缓存标识符属性的值和值类型的结果。所以查询缓存通常会和第二层缓存一起使用。

大多数查询并不会从缓存中获得什么好处,所以默认查询是不进行缓存的。要进行缓存,调用 Query.setCacheable(true)。这个调用会让查询在执行时去从缓存中查找结果,或者把结果集放到缓存去。

如果你要对查询缓存的失效政策进行精确的控制,你必须调用Query.setCacheRegion()来为每个查询指定一个命名的缓存区域。

List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
    .setEntity("blogger", blogger)
    .setMaxResults(15)
    .setCacheable(true)
    .setCacheRegion("frontpages")
    .list();
posted on 2006-08-31 18:41 forrest 阅读(823) 评论(0)  编辑 收藏 引用 所属分类: TomcaT
只有注册用户登录后才能发表评论。
<2024年5月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

常用链接

留言簿(5)

随笔分类(59)

随笔档案(58)

文章分类(41)

文章档案(52)

相册

收藏夹

postfix

windows 系统

编程

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

搜索

  •  

最新评论

阅读排行榜

评论排行榜