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章第一部分的归纳。