HQL & JPQL - Part Ⅰ

Creating and running queries

Hibernate中三类查询:

// HQL, JPQL
session.createQuery("from Category c where c.name like 'Laptop%'");
entityManager.createQuery("select c from Category c where c.name like 'Laptop%'");

// Criteria for query by criteria(QBC) and query by example(QBE)
session.createCriteria(Category.class).add( Restrictions.like("name", "Laptop%") );

// Direct SQL with or without automatic mapping of resultsets to objects
session.createSQLQuery("select {c.*} from CATEGORY {c} where NAME like 'Laptop%'").addEntity("c", Category.class);

Preparing a query

Hibernate中有两大接口来创建查询:

org.hibernate.Query

org.hibernate.Criteria

JPA标准中没有Criteria

javax.persistence.Query

Creating a query object

Hiberante创建Query及Criteria实例:

// 创建HQL Query
Query hqlQuery = session.createQuery("from User");

// 创建SQL Query
Query sqlQuery = session.createSQLQuery("select {user.*} from USERS {user}").addEntity("user", User.class);

// Criteria
Criteria crit = session.createCriteria(User.class);

JPA利用EntityManager创建javax.persistence.Query实例:

// 创建JPA Query
Query ejbQuery = entityManager.createQuery("select u from User u");

// 创建Native SQL query
Query sqlQuery = entityManager.createNativeQuery("select u.USER_ID, u.FIRSTNAME, u.LASTNAME from USERS u", User.class);

注意Hibernate的SQL query同JPA的不同,除了方法不同,SQL语句也不同。

Paging the result

不同数据库的分页SQL是不一样的,Hibernate统一了不同数据库的分页。

// 查询前10条数据
Query query = session.createQuery("from User u order by u.name asc");
query.setMaxResults(10);

// Criteria,从第40条开始,查询之后的20条数据
Criteria crit = session.createCriteria(User.class);
crit.addOrder(Order.asc("name"));
crit.setFirstResult(40);
crit.setMaxResults(20);

// SQL Query
Query sqlQuery = session.createSQLQuery("select {u.*} from USERS {u}").addEntity("u", User.class);
sqlQuery.setFirstResult(40);
sqlQuery.setMaxResults(20);

// method-chaining coding style
Query query = session.createQuery("from User u order by u.name asc").setMaxResults(10);
Criteria crit = session.createCriteria(User.class).addOrder(Order.asc("name")).setFirstResult(40).setMaxResults(20);

// JPQL分页
Query query = entityManager.createQuery("select u from User u order by u.name asc").setFirstResult(40).setMaxResults(20);

Considering parameter binding

使用参数绑定可以避免SQL注入,数据库还可以缓存precompiled prepared statements,提高查询效率。

// Hibernate参数绑定
String queryString = "from Item item where item.description like :search and item.date > :minDate";
Query q = session.createQuery(queryString).setString("search", searchString).setDate("minDate", mDate);

// JPA参数绑定,利用TemporalType来指定参数是date, time or timestamp
Query q = entityManager.createQuery(queryString).setParameter("search", searchString).setParameter("minDate", mDate, TemporalType.DATE);

JPA只有setParameter()方法来绑定参数,Hibernate也有此方法,不过还提供了更加具体的方法来绑定不同类型参数。当参数是日期类型时,需要javax.persistence.TemporalType来指定参数是date, time or timestamp。

Using Hibernate parameter binding

Hibernate除了可以绑定普通参数,还可以绑定实体对象(entity),Hibernate type或自定义类型,JPA没有此功能。

// setEntity()绑定实体对象
session.createQuery("from Item item where item.seller = :seller").setEntity("seller", theSeller);

// setParameter()绑定Hibernate type
String queryString = "from Item item where item.seller = :seller and item.description like :desc";
session.createQuery(queryString)
        .setParameter("seller", theSeller, Hibernate.entity(User.class))
        .setParameter("desc", description, Hibernate.STRING);

// setParameter()绑定自定义类型
Query q = session.createQuery("from Bid where amount > :amount");
q.setParameter("amount", givenAmount, Hibernate.custom(MonetaryAmountUserType.class));

// 利用setProperties(),不过其他内部仍然是调用setParameter()
Item item = new Item();
item.setSeller(seller);
item.setDescription(description);

String queryString = "from Item item where item.seller = :seller and item.description like :desccription";
session.createQuery(queryString).setProperties(item);

Using positional parametersHibernate starts

按照位置绑定参数,Hibernate从0开始绑定参数;JPA从1开始绑定参数,并且要在问号后加上数字。

// HQL
String queryString = "from Item item where item.description like ? and item.date > ?";
Query q = session.createQuery(queryString).setString(0, searchString).setDate(1, minDate);

// JPQL
String queryString = "from Item item where item.description like ?1 and item.date > ?2";
Query q = entityManager.createQuery(queryString).setParameter(1, searchString).setParameter(2, minDate, TemporalType.DATE);

Setting query hints

Hibernate在执行Query之前会默认flush persistence context,从而在query执行之前,将修改同步到数据库中。
可以为Session/EntityManager或具体的Query设置FlushMode,从而在执行query之前不刷新persistence context。

// Hibernate
Query q = session.createQuery(hql).setFlushMode(FlushMode.COMMIT);
Criteria criteria = session.createCriteria(Item.class).setFlushMode(FlushMode.COMMIT);
// JPA
Query q = entityManager.createQuery(jpaql).setFlushMode(FlushModeType.COMMIT);

为Query设置CacheMode

// Hibernate
Query q = session.createQuery("from Item").setCacheMode(CacheMode.IGNORE);
Criteria criteria = session.createCriteria(Item.class).setCacheMode(CacheMode.IGNORE);

// JPA
Query q = entityManager.createQuery(queryString).setHint("org.hibernate.cacheMode", org.hibernate.CacheMode.IGNORE);

The CacheMode controls how a particular session interacts with the second-level cache:

CacheMode description
CacheMode.NORMAL will read items from and write items to the second-level cache
CacheMode.IGNORE Hibernate never interacts with the second-level cache except to invalidate cached items when updates occur.
CacheMode.GET Hibernate may read items from the second-level cache,but it won’t add items except to invalidate items when updates occur.
CacheMode.PUT Hibernate never reads items from the second-level cache, but it adds items to the cache as it reads them from the database.
CacheMode.REFRESH Hibernate never reads items from the second-level cache, but it adds items to the cache as it reads them from the database. In this mode, the effect of hibernate.cache.use_minimal_puts is bypassed, in order to force a cache refresh in a replicated cluster cache.

所有的Hint可以查看org.hibernate.ejb.QueryHints的javadoc。

Hibernate查询出实体后,persistence context会维护实体的状态,对实体的修改会被同步到数据库(dirty checking)。但如果设置setReadOnly(true),Hibernate就不再同步实体的修改到数据库,尽管还是persistent state,除非调用session.setReadOnly(object, false)再启用dirty checking。

// Hibernate
Query q = session.createQuery("from Item").setReadOnly(true);
Criteria criteria = session.createCriteria(Item.class).setReadOnly(true);

// JPA
Query q = entityManager.createQuery("select i from Item i").setHint("org.hibernate.readOnly", true);

设置查询超时时间:

// Hibernate
Query q = session.createQuery("from Item").setTimeout(60); // 1 minute
Criteria criteria = session.createCriteria(Item.class).setTimeout(60);

// JPA
Query q = entityManager.createQuery("select i from Item i").setHint("org.hibernate.timeout", 60);

设置fetch size,Hibernate底层依然是JDBC,不同数据库的JDBC driver都有默认的fetch size,ORACLE是10,所以如果你要查询100条数据,就要在application和database之间往返十次,这将严重影响性能,所以要设置合适的fetch size。

http://webmoli.com/2009/02/01/jdbc-performance-tuning-with-optimal-fetch-size/

// Hibernate
Query q = session.createQuery("from Item").setFetchSize(50);
Criteria criteria = session.createCriteria(Item.class).setFetchSize(50);

// JPA
Query q = entityManager.createQuery("select i from Item i").setHint("org.hibernate.fetchSize", 50);

强制使用pessimistic lock,JPA没有相关实现。

// Hibernate
Query q = session.createQuery("from Item item").setLockMode("item", LockMode.UPGRADE);
Criteria criteria = session.createCriteria(Item.class).setLockMode(LockMode.UPGRADE);

如果你的数据库支持悲观锁,生成的SQL就会包含FOR UPDATE

Executing a query

Query和Criteria创建好后,下面该真正执行查询了。通常是一次将所有结果查询出来,称之为listing;还有其他方式:iterating and scrolling.

Listing all results

Hiberante JAP description
list() getResultList() 返回所有查询结果
uniqueResult() getSingleResult() 如果返回多个结果集,抛出异常;如果查询结果为空,Hiberante返回null,JPA抛出异常
// Hibernate,list()
List result = myQuery.list();
List result = myCriteria.list();
// JPA,getResultList()
List result = myJPAQuery.getResultList();

// Hibernate 获得唯一值,uniqueResult()
Bid maxBid = (Bid) session.createQuery("from Bid b order by b.amount desc").setMaxResults(1).uniqueResult();
Bid bid = (Bid) session.createCriteria(Bid.class).add(Restrictions.eq("id", id)).uniqueResult();
// JPA 获得唯一值,getSingleResult()
Bid maxBid = (Bid) entityManager.createQuery("select b from Bid b order by b.amount desc").setMaxResults(1).getSingleResult();

Iterating through the results

iterate()方法,只查询出主键值,然后迭代时,再根据主键从一级或二级缓存中查找该对象,如果没有被缓存,就再次查询数据库。这种优化非常有限,搞不好就会导致N+1 SELECT问题,不建议使用。

Query categoryByName = session.createQuery("from Category c where c.name like :name");
categoryByName.setString("name", categoryNamePattern);
Iterator categories = categoryByName.iterate();

Scrolling with database cursors

ScrollableResults就像游标一样:

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
ScrollableResults itemCursor = session.createQuery("from Item").scroll();
int count = 0;
while (itemCursor.next()) {
    Item item = (Item) itemCursor.get(0);
    modifyItem(item);
    if (++count % 100 == 0) {
        session.flush();
        session.clear();
    }
}
tx.commit();
session.close();

ScrollableResults的常用方法:

itemCursor.first();
itemCursor.last();
itemCursor.get();
itemCursor.next();
itemCursor.scroll(3);
itemCursor.getRowNumber();
itemCursor.setRowNumber(5);
itemCursor.previous();
itemCursor.scroll(-3);
itemCursor.close(); // 关闭游标

You can set the cursor to the first and last Item object in the result, or get the Item the cursor is currently pointing to with get(). You can go to a particular Item by jumping to a position with setRowNumber() or scroll backward and forward with next() and previous(). Another option is scrolling forward and backward by an offset, with scroll().

Criteria也支持scroll:

ScrollableResults itemCursor = session.createCriteria(Item.class).scroll(ScrollMode.FORWARD_ONLY);
// ..
itemCursor.close();

ScrollMode.SCROLL_INSENSITIVE,无法看到其他事务提交的数据。
ScrollMode.SCROLL_SENSITIVE,可以看到其他事务提交的数据。

Using named queries

如果不想将HQL/JPQL和Java代码混合在一起,可以将其独立到映射文件中。

Calling a named query

// Hibernate
session.getNamedQuery("findItemsByDescription").setString("desc", description);
// JPA
entityManager.createNamedQuery("findItemsByDescription").setParameter("desc", description);

Defining a named query in XML metadata

在XML中定义HQL, JPQL, SQL。

<query name="findItemsByDescription"><![CDATA[
    from Item item where item.description like :desc
]]></query>

如果将命名Query放在<class>元素下,则query的名称会加上实体名前缀:

auction.model.Item.findItemsByDescription

确保Query的命名唯一。
<query>元素中还可以定义之前讲过的各种Hint。

<query name="findItemsByDescription"
        cache-mode="ignore"
        comment="My Comment..."
        fetch-size="50"
        read-only="true"
        timeout="60"><![CDATA[
    from Item item where item.description like :desc
]]></query>

SQL Query:

<sql-query name="findItemsByDescription">
    <return alias="item" class="Item"/>
    <![CDATA[
        select {item.*} from item where description like :desc
    ]]>
</sql-query>

Defining a named query with annotations

在注解中定义SQL/HQL,利用@NamedQuery, @NamedNativeQuery:

@NamedQueries({
    @NamedQuery(
        name = "findItemsByDescription",
        query = "select i from Item i where i.description like :desc"
    ),
    @NamedQuery(
        name = "findItemsByDescription",
        query = "select i from Item i where i.description like :desc)",
        hints = {
            @QueryHint(name = "org.hibernate.comment", value = "My Comment"),
            @QueryHint(name = "org.hibernate.fetchSize", value = "50"),
            @QueryHint(name = "org.hibernate.flushMode", value = "never"),
            @QueryHint(name = "org.hibernate.readOnly", value = "true"),
            @QueryHint(name = "org.hibernate.timeout", value = "60")
        }
    )
})
/*
@NamedNativeQueries({
    @NamedNativeQuery(
         name = "findItemsByDescriptionWithSQL",
         query = "select i.NAME, i.PRICE ... from ITEM i where i.DESC = :desc",
         resultClass = auction.model.Item.class
    )
})
*/
@Entity
@Table(name = "ITEM")
public class Item { ... }

此文是对《Java Persistence with Hibernate》第14章第一部分的归纳。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容