通常分页查询看起来像这样。有没有更好的方法,而不是两个几乎相同的方法,其中一个执行“select * ...”而另一个“count * ...”?
public List<Cat> findCats(String name, int offset, int limit) {
Query q = session.createQuery("from Cat where name=:name");
q.setString("name", name);
if (offset > 0) {
q.setFirstResult(offset);
}
if (limit > 0) {
q.setMaxResults(limit);
}
return q.list();
}
public Long countCats(String name) {
Query q = session.createQuery("select count(*) from Cat where name=:name");
q.setString("name", name);
return (Long) q.uniqueResult();
}
答案 0 :(得分:11)
MySQLPerformanceBlog.com的Baron Schwartz就此发表了post。我希望这个问题有一个神奇的子弹,但没有。他提出的选项摘要:
答案 1 :(得分:6)
我的解决方案适用于Hibernate + Spring + MySQL
的常见用例与上述答案类似,我的解决方案基于Dr Richard Kennar's。但是,由于Hibernate经常与Spring一起使用,我希望我的解决方案能够很好地运行Spring和使用Hibernate的标准方法。因此,我的解决方案使用线程局部和单例bean的组合来实现结果。从技术上讲,拦截器是在SessionFactory的每个准备好的SQL语句上调用的,但它会跳过所有逻辑并且不会初始化任何ThreadLocal,除非它是专门设置为计算总行数的查询。
使用以下类,您的Spring配置如下:
<bean id="foundRowCalculator" class="my.hibernate.classes.MySQLCalcFoundRowsInterceptor" />
<!-- p:sessionFactoryBeanName="mySessionFactory"/ -->
<bean id="mySessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
p:dataSource-ref="dataSource"
p:packagesToScan="my.hibernate.classes"
p:entityInterceptor-ref="foundRowCalculator"/>
基本上,您必须声明拦截器bean,然后在SessionFactoryBean的“entityInterceptor”属性中引用它。如果Spring上下文中有多个SessionFactory,并且要引用的会话工厂不称为“sessionFactory”,则只能设置“sessionFactoryBeanName”。您无法设置引用的原因是这会导致无法解析的bean之间的相互依赖性。
使用包装bean作为结果:
package my.hibernate.classes;
public class PagedResponse<T> {
public final List<T> items;
public final int total;
public PagedResponse(List<T> items, int total) {
this.items = items;
this.total = total;
}
}
然后使用抽象基类DAO类,在进行查询之前必须调用“setCalcFoundRows(true)”,并且在[在finally块中以确保调用后]之后调用“reset()”:
package my.hibernate.classes;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.springframework.beans.factory.annotation.Autowired;
public abstract class BaseDAO {
@Autowired
private MySQLCalcFoundRowsInterceptor rowCounter;
public <T> PagedResponse<T> getPagedResponse(Criteria crit, int firstResult, int maxResults) {
rowCounter.setCalcFoundRows(true);
try {
@SuppressWarnings("unchecked")
return new PagedResponse<T>(
crit.
setFirstResult(firstResult).
setMaxResults(maxResults).
list(),
rowCounter.getFoundRows());
} finally {
rowCounter.reset();
}
}
public <T> PagedResponse<T> getPagedResponse(Query query, int firstResult, int maxResults) {
rowCounter.setCalcFoundRows(true);
try {
@SuppressWarnings("unchecked")
return new PagedResponse<T>(
query.
setFirstResult(firstResult).
setMaxResults(maxResults).
list(),
rowCounter.getFoundRows());
} finally {
rowCounter.reset();
}
}
}
然后一个名为MyEntity的@Entity的具体DAO类示例,其String属性为“prop”:
package my.hibernate.classes;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions
import org.springframework.beans.factory.annotation.Autowired;
public class MyEntityDAO extends BaseDAO {
@Autowired
private SessionFactory sessionFactory;
public PagedResponse<MyEntity> getPagedEntitiesWithPropertyValue(String propVal, int firstResult, int maxResults) {
return getPagedResponse(
sessionFactory.
getCurrentSession().
createCriteria(MyEntity.class).
add(Restrictions.eq("prop", propVal)),
firstResult,
maxResults);
}
}
最后执行所有工作的拦截器类:
package my.hibernate.classes;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.hibernate.EmptyInterceptor;
import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.jdbc.Work;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
public class MySQLCalcFoundRowsInterceptor extends EmptyInterceptor implements BeanFactoryAware {
/**
*
*/
private static final long serialVersionUID = 2745492452467374139L;
//
// Private statics
//
private final static String SELECT_PREFIX = "select ";
private final static String CALC_FOUND_ROWS_HINT = "SQL_CALC_FOUND_ROWS ";
private final static String SELECT_FOUND_ROWS = "select FOUND_ROWS()";
//
// Private members
//
private SessionFactory sessionFactory;
private BeanFactory beanFactory;
private String sessionFactoryBeanName;
private ThreadLocal<Boolean> mCalcFoundRows = new ThreadLocal<Boolean>();
private ThreadLocal<Integer> mSQLStatementsPrepared = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return Integer.valueOf(0);
}
};
private ThreadLocal<Integer> mFoundRows = new ThreadLocal<Integer>();
private void init() {
if (sessionFactory == null) {
if (sessionFactoryBeanName != null) {
sessionFactory = beanFactory.getBean(sessionFactoryBeanName, SessionFactory.class);
} else {
try {
sessionFactory = beanFactory.getBean("sessionFactory", SessionFactory.class);
} catch (RuntimeException exp) {
}
if (sessionFactory == null) {
sessionFactory = beanFactory.getBean(SessionFactory.class);
}
}
}
}
@Override
public String onPrepareStatement(String sql) {
if (mCalcFoundRows.get() == null || !mCalcFoundRows.get().booleanValue()) {
return sql;
}
switch (mSQLStatementsPrepared.get()) {
case 0: {
mSQLStatementsPrepared.set(mSQLStatementsPrepared.get() + 1);
// First time, prefix CALC_FOUND_ROWS_HINT
StringBuilder builder = new StringBuilder(sql);
int indexOf = builder.indexOf(SELECT_PREFIX);
if (indexOf == -1) {
throw new HibernateException("First SQL statement did not contain '" + SELECT_PREFIX + "'");
}
builder.insert(indexOf + SELECT_PREFIX.length(), CALC_FOUND_ROWS_HINT);
return builder.toString();
}
case 1: {
mSQLStatementsPrepared.set(mSQLStatementsPrepared.get() + 1);
// Before any secondary selects, capture FOUND_ROWS. If no secondary
// selects are
// ever executed, getFoundRows() will capture FOUND_ROWS
// just-in-time when called
// directly
captureFoundRows();
return sql;
}
default:
// Pass-through untouched
return sql;
}
}
public void reset() {
if (mCalcFoundRows.get() != null && mCalcFoundRows.get().booleanValue()) {
mSQLStatementsPrepared.remove();
mFoundRows.remove();
mCalcFoundRows.remove();
}
}
@Override
public void afterTransactionCompletion(Transaction tx) {
reset();
}
public void setCalcFoundRows(boolean calc) {
if (calc) {
mCalcFoundRows.set(Boolean.TRUE);
} else {
reset();
}
}
public int getFoundRows() {
if (mCalcFoundRows.get() == null || !mCalcFoundRows.get().booleanValue()) {
throw new IllegalStateException("Attempted to getFoundRows without first calling 'setCalcFoundRows'");
}
if (mFoundRows.get() == null) {
captureFoundRows();
}
return mFoundRows.get();
}
//
// Private methods
//
private void captureFoundRows() {
init();
// Sanity checks
if (mFoundRows.get() != null) {
throw new HibernateException("'" + SELECT_FOUND_ROWS + "' called more than once");
}
if (mSQLStatementsPrepared.get() < 1) {
throw new HibernateException("'" + SELECT_FOUND_ROWS + "' called before '" + SELECT_PREFIX + CALC_FOUND_ROWS_HINT + "'");
}
// Fetch the total number of rows
sessionFactory.getCurrentSession().doWork(new Work() {
@Override
public void execute(Connection connection) throws SQLException {
final Statement stmt = connection.createStatement();
ResultSet rs = null;
try {
rs = stmt.executeQuery(SELECT_FOUND_ROWS);
if (rs.next()) {
mFoundRows.set(rs.getInt(1));
} else {
mFoundRows.set(0);
}
} finally {
if (rs != null) {
rs.close();
}
try {
stmt.close();
} catch (RuntimeException exp) {
}
}
}
});
}
public void setSessionFactoryBeanName(String sessionFactoryBeanName) {
this.sessionFactoryBeanName = sessionFactoryBeanName;
}
@Override
public void setBeanFactory(BeanFactory arg0) throws BeansException {
this.beanFactory = arg0;
}
}
答案 2 :(得分:5)
如果您不需要显示总页数,那么我不确定您是否需要计数查询。包括谷歌在内的很多网站都没有在分页结果中显示总数。相反,他们只是说“下一个&gt;”。
答案 3 :(得分:3)
您可以使用MultiQuery在单个数据库调用中执行两个查询,这样效率更高。您还可以生成计数查询,因此您不必每次都编写它。这是一般的想法...
var hql = "from Item where i.Age > :age"
var countHql = "select count(*) " + hql;
IMultiQuery multiQuery = _session.CreateMultiQuery()
.Add(s.CreateQuery(hql)
.SetInt32("age", 50).SetFirstResult(10))
.Add(s.CreateQuery(countHql)
.SetInt32("age", 50));
var results = multiQuery.List();
var items = (IList<Item>) results[0];
var count = (long)((IList<Item>) results[1])[0];
我认为将它包装成一些易于使用的方法很容易,因此您可以在一行代码中进行可分页,可数的查询。
作为替代,如果您愿意在nhcontrib中测试正在进行的Linq for NHibernate,您可能会发现可以执行以下操作:
var itemSpec = (from i in Item where i.Age > age);
var count = itemSpec.Count();
var list = itemSpec.Skip(10).Take(10).AsList();
显然没有批处理,所以效率不高,但它仍然可以满足您的需求?
希望这有帮助!
答案 4 :(得分:2)
有一种方法
mysql> SELECT SQL_CALC_FOUND_ROWS * FROM tbl_name
-> WHERE id > 100 LIMIT 10;
mysql> SELECT FOUND_ROWS();
第二个SELECT返回一个数字,表示如果没有LIMIT子句,第一个SELECT将返回多少行。
参考:FOUND_ROWS()
答案 5 :(得分:2)
我知道这个问题并且之前已经面对过了。对于初学者来说,它执行相同SELECT条件的双查询机制确实不是最佳的。但是,它起作用,在你离开并做一些巨大的改变之前,只是意识到它可能不值得。
但是,无论如何:
1)如果您正在处理客户端上的小数据,请使用结果集实现,该实现允许您将光标设置为集合的末尾,获取其行偏移,然后将光标重置为首先。
2)重新设计查询,以便将COUNT(*)作为正常行中的额外列。是的,它包含每行的相同值,但它只涉及1个额外的整数列。这是一个不正确的SQL来表示具有非聚合值的聚合值,但它可能有效。
3)重新设计查询以使用估计的限制,类似于提到的内容。每页使用行数和一些上限。例如。只是说“显示500或更多的1到10”。当他们浏览到“显示25到260的X”时,它是一个后来的查询,所以你可以通过设置相对于page * rows / page的上限来更新X估计。
答案 6 :(得分:1)
我认为解决方案取决于您使用的数据库。例如,我们正在使用MS SQL并使用下一个查询
select
COUNT(Table.Column) OVER() as TotalRowsCount,
Table.Column,
Table.Column2
from Table ...
可以使用数据库指定的SQL更改该部分查询。
我们还设置了我们期望看到的查询最大结果,例如
query.setMaxResults(pageNumber * itemsPerPage)
获取ScrollableResults实例作为查询执行的结果:
ScrollableResults result = null;
try {
result = query.scroll();
int totalRowsNumber = result.getInteger(0);
int from = // calculate the index of row to get for the expected page if any
/*
* Reading data form page and using Transformers.ALIAS_TO_ENTITY_MAP
* to make life easier.
*/
}
finally {
if (result != null)
result.close()
}
答案 7 :(得分:1)
在这个Hibernate wiki页面:
https://www.hibernate.org/314.html
我提出了一个完整的分页解决方案;特别是,元素的总数是通过滚动到结果集的末尾来计算的,现在由几个JDBC驱动程序支持。这避免了第二次“计数”查询。
答案 8 :(得分:0)
我找到了一种在hibernate中进行分页的方法,而不需要在大数据集大小上进行选择计数(*)。看看我在这里发布的解决方案。
processing a large number of database entries with paging slows down with time
您可以一次执行一个分页,而无需知道最初需要多少页
答案 9 :(得分:0)
总结一下,你将sessionFactory绑定到拦截器类,以便你的拦截器可以在以后为你提供找到的行数。
您可以在解决方案链接上找到代码。以下是一个示例用法。
SessionFactory sessionFactory = ((org.hibernate.Session) mEntityManager.getDelegate()).getSessionFactory();
MySQLCalcFoundRowsInterceptor foundRowsInterceptor = new MySQLCalcFoundRowsInterceptor( sessionFactory );
Session session = sessionFactory.openSession( foundRowsInterceptor );
try {
org.hibernate.Query query = session.createQuery( ... ) // Note: JPA-QL, not createNativeQuery!
query.setFirstResult( ... );
query.setMaxResults( ... );
List entities = query.list();
long foundRows = foundRowsInterceptor.getFoundRows();
...
} finally {
// Disconnect() is good practice, but close() causes problems. Note, however, that
// disconnect could lead to lazy-loading problems if the returned list of entities has
// lazy relations
session.disconnect();
}
答案 10 :(得分:-4)
这是在hibernate中完成分页的方式
Query q = sess.createQuery("from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
List cats = q.list();
您可以从以下网址获取hibernate文档的更多信息:http://www.hibernate.org/hib_docs/v3/reference/en-US/html_single/#objectstate-querying-executing-pagination 10.4.1.5和10.4.1.6部分为您提供了更多的灵活选项。
BR,
〜A