java - يخرج - هل هناك طريقة أكثر فاعلية لإنشاء ترقيم الصفحات في السبات من تنفيذ الاستعلامات المختارة والعد؟




وضع الاسبات في ويندوز 7 (8)

عادةً ما تبدو استعلامات الحدود الصفحات مثل هذا. هل هناك طريقة أفضل بدلاً من عمل طريقتين متساويتين تقريبًا ، أحدهما تنفيذ "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();
}

أعتقد أن الحل يعتمد على قاعدة البيانات التي تستخدمها. على سبيل المثال ، نحن نستخدم 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()
}

أنا أعرف هذه المشكلة وقد واجهتها من قبل. بالنسبة للمبتدئين ، فإن آلية الاستعلام المزدوجة حيث تقوم بنفس شروط SELECT ليست في الواقع مثالية. لكن ، إنها تعمل ، وقبل أن تنطلق وتجري بعض التغيير العملاق ، فقط أدرك أنه قد لا يستحق ذلك.

لكن على أي حال:

1) إذا كنت تتعامل مع بيانات صغيرة على جانب العميل ، فاستخدم تنفيذ مجموعة نتائج يسمح لك بتعيين المؤشر إلى نهاية المجموعة ، والحصول على إزاحة الصف ، ثم إعادة تعيين المؤشر إلى قبل أول مرة.

2) إعادة تصميم الاستعلام بحيث تحصل على COUNT (*) كعمود إضافي في الصفوف العادية. نعم ، إنها تحتوي على نفس القيمة لكل صف ، ولكنها لا تتضمن سوى عمود إضافي واحد هو عدد صحيح. هذا هو SQL غير لائقة لتمثيل قيمة مجمعة مع قيم غير مجمعة ، ولكنها قد تعمل.

3) إعادة تصميم الاستعلام لاستخدام الحد المقدر ، على غرار ما تم ذكره. استخدم الصفوف لكل صفحة وبعض الحدود العليا. على سبيل المثال ، يمكنك قول شيء مثل "عرض من 1 إلى 10 من 500 أو أكثر". عندما يتصفحون إلى "عرض 25 إلى 260 من X" ، فإن استعلامًا لاحقًا يمكنك فقط تحديث تقدير X من خلال جعل الحد الأعلى المرتبط بالصفوف / الصفحة *.


سوف يعمل حل بلدي لحالة استخدام شائعة جدا من Hibernate + Spring + MySQL

على غرار الإجابة المذكورة أعلاه ، استندت في حلّي إلى الدكتور ريتشارد كينار . ومع ذلك ، بما أن سبات الإسبات غالبًا ما يستخدم مع الربيع ، كنت أرغب في أن يعمل حلّي بشكل جيد مع الربيع والطريقة القياسية لاستخدام السبات. لذلك يستخدم حل بلدي مزيج من السكان المحليين الخيط والفول singleton لتحقيق النتيجة. من الناحية الفنية يتم استدعاء المعترض على كل عبارة SQL المعدة لـ SessionFactory ، ولكنه يتخطى كل المنطق ولا يقوم بتهيئة أي ThreadLocal (s) إلا إذا كان عبارة عن استعلام تم تعيينه خصيصًا لحساب عدد الصفوف.

باستخدام الفئة التالية ، يبدو تكوين 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"/>

في الأساس ، يجب أن تقوم بتعريف حبة المعترض ثم الرجوع إليها في خاصية "entityInterceptor" في SessionFactoryBean. يجب عليك فقط تعيين "sessionFactoryBeanName" إذا كان هناك أكثر من جلسة SessionFactory في سياق Spring الخاص بك ولا يسمى مصنع جلسة العمل الذي تريد الإشارة إليه "sessionFactory". السبب الذي لا يمكنك تعيين مرجع هو أن هذا سيؤدي إلى ترابط بين الفاصوليا التي لا يمكن حلها.

باستخدام bevel wrapper للنتيجة:

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)" قبل إجراء الاستعلام و "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();
        }
    }
}

ثم مثال فئة DAO ملموسة لـEntity يسمى MyEntity مع خاصية سلسلة "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;
    }

}

في صفحة الويكي هذه السبات:

https://www.hibernate.org/314.html

أقدم حل ترقيم الصفحات الكامل ؛ على وجه الخصوص ، يتم احتساب العدد الإجمالي للعناصر عن طريق التمرير إلى نهاية مجموعة النتائج ، والتي يتم دعمها الآن من قبل العديد من برامج تشغيل JDBC. هذا يتجنب استعلام "count" الثاني.


لقد وجدت طريقة للقيام بالاستدعاء في السبات دون إجراء عدد محدد (*) على حجم مجموعة بيانات كبيرة. انظر إلى الحل الذي نشرته لإجابتي هنا.

معالجة عدد كبير من إدخالات قاعدة البيانات مع الترحيل يبطئ مع مرور الوقت

يمكنك تنفيذ ترحيل واحد في كل مرة دون معرفة عدد الصفحات التي ستحتاجها في الأصل


هناك طريقة

mysql> SELECT SQL_CALC_FOUND_ROWS * FROM tbl_name
    -> WHERE id > 100 LIMIT 10;
mysql> SELECT FOUND_ROWS();

تقوم الدالة SELECT الثانية بإرجاع رقم يشير إلى عدد الصفوف التي كان قد تم إرجاعها إلى SELECT الأولى لو تمت كتابتها بدون جملة LIMIT.

المرجع: FOUND_ROWS()


يمكنك استخدام 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];

أتخيل أنه سيكون من السهولة بما يكفي لفك هذا الأمر في طريقة سهلة الاستخدام بحيث يمكنك الحصول على طلبات بحث معدودة قابلة للطباعة في سطر واحد من الكود.

كبديل ، إذا كنت على استعداد لاختبار Linq للعمل في التقدم لـ NHibernate في nhcontrib ، فقد تجد أنه يمكنك القيام بشيء كالتالي:

var itemSpec = (from i in Item where i.Age > age);
var count = itemSpec.Count();
var list = itemSpec.Skip(10).Take(10).AsList(); 

من الواضح أنه لا توجد عملية تجميع ، لذلك هذا ليس بنفس الكفاءة ، لكنه قد لا يزال يلبي احتياجاتك؟

أتمنى أن يساعدك هذا!


هنا هو الحل من قبل الدكتور ريتشارد كينارد (العقل في حل الشوائب في تعليق بلوق!) ، وذلك باستخدام Hibernate Interceptors

للحصول على ملخص ، يمكنك ربط جلسة العمل الخاصة بك بفئة اعتراضية ، بحيث يمكن أن يعطيك المعترض عدد الصفوف التي تم العثور عليها فيما بعد.

يمكنك العثور على الرمز على رابط الحل. وفيما يلي مثال للاستخدام.

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();
}






hql