[DAO-145] MySQL専用のSQL_CALC_FOUND_ROWSを使用する、MySQLLimitOffsetPagingSqlRewriter を追加しました。 Created: 2008-10-07  Updated: 2009-03-16  Resolved: 2009-03-15

Status: Resolved
Project: S2Dao
Component/s: s2dao
Affects Version/s: None
Fix Version/s: 1.0.50

Type: New Feature Priority: Minor
Reporter: yamamoto Assignee: azusa
Resolution: Fixed Votes: 0
Labels: None


 Description   

MySQL専用になるのですが、LIMITを指定したSQLについて、
LIMITを指定しなかった場合の件数を取得できる機能があります。

例えば

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

とすることで総件数が取得できます。

これを利用したPagerResultSetFactoryLimitOffsetWrapperがあればうれしいな、という要望です。



 Comments   
Comment by yamamoto [ 2009-03-16 ]

対応ありがとうございます!
近々動作確認させていただきます。

Comment by azusa [ 2009-03-06 ]

現在のS2DaoはSqlRewriterを使用することを推奨していますので、
org.seasar.dao.pager.MySQLLimitOffsetPagingSqlRewriter を追加しました。

http://svn.seasar.org/browse?view=rev&root=s2dao&revision=1290

Comment by yamamoto [ 2008-10-07 ]

以下、パッチです。(添付できないのですね)

=== s2-dao/src/main/java/org/seasar/dao/pager/PagerResultSetFactoryLimitOffsetMySQLWrapper.java
==================================================================
--- s2-dao/src/main/java/org/seasar/dao/pager/PagerResultSetFactoryLimitOffsetMySQLWrapper.java	(revision 10567)
+++ s2-dao/src/main/java/org/seasar/dao/pager/PagerResultSetFactoryLimitOffsetMySQLWrapper.java	(local)
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2004-2008 the Seasar Foundation and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.seasar.dao.pager;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.sql.DataSource;
+
+import org.seasar.dao.Dbms;
+import org.seasar.dao.dbms.DbmsManager;
+import org.seasar.extension.jdbc.ResultSetFactory;
+import org.seasar.framework.exception.SQLRuntimeException;
+import org.seasar.framework.log.Logger;
+import org.seasar.framework.util.ResultSetUtil;
+import org.seasar.framework.util.StatementUtil;
+
+/**
+ * 
+ * 
+ */
+public class PagerResultSetFactoryLimitOffsetMySQLWrapper implements
+        ResultSetFactory {
+
+    private static final Logger LOGGER = Logger
+            .getLogger(PagerResultSetFactoryLimitOffsetMySQLWrapper.class);
+    
+    private static final Pattern baseSqlPattern = Pattern.compile(
+            "^.*?(select)", Pattern.CASE_INSENSITIVE);
+
+    /** オリジナルのResultSetFactory */
+    private ResultSetFactory resultSetFactory;
+
+    private Dbms dbms;
+ 
+    /**
+     * コンストラクタ(test only)
+     * 
+     * @param resultSetFactory
+     *            オリジナルのResultSetFactory
+     */
+    PagerResultSetFactoryLimitOffsetMySQLWrapper(ResultSetFactory resultSetFactory,
+            String productName) {
+        this.resultSetFactory = resultSetFactory;
+        this.dbms = DbmsManager.getDbms(productName);
+    }
+
+    /**
+     * コンストラクタ
+     * 
+     * @param resultSetFactory
+     *            オリジナルのResultSetFactory
+     */
+    public PagerResultSetFactoryLimitOffsetMySQLWrapper(
+            ResultSetFactory resultSetFactory, DataSource dataSource) {
+        this.resultSetFactory = resultSetFactory;
+        this.dbms = DbmsManager.getDbms(dataSource);
+    }
+    
+    public ResultSet getResultSet(Statement statement) {
+        return resultSetFactory.getResultSet(statement);
+    }
+
+    /**
+     * ResultSetを生成します。<br>
+     * PagerContextにPagerConditionがセットされている場合、
+     * <ul>
+     * <li>検索結果件数を取得しPagerConditionにセットします。</li>
+     * <li>LIMIT OFFSET 条件を付加したSQLを実行し、結果のResultSetを返します。</li>
+     * </ul>
+     * 
+     * @param PreparedStatement
+     * @return ResultSet
+     */
+    public ResultSet createResultSet(PreparedStatement ps) {
+
+        Object[] args = PagerContext.getContext().peekArgs();
+
+        if (PagerContext.isPagerCondition(args)) {
+            try {
+                if (LOGGER.isDebugEnabled()) {
+                    String nativeSql = ps.toString();
+                    LOGGER.debug("S2Pager native SQL : " + nativeSql);
+                }
+
+                String baseSQL = dbms.getBaseSql(ps);
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug("S2Pager base SQL : " + baseSQL);
+                }
+
+                baseSQL = makeCalcFoundRowsSQL(baseSQL);
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug("S2Pager base SQL for MySQL : " + baseSQL);
+                }
+
+				ResultSet resultSet = null;
+                PagerCondition dto = PagerContext.getPagerCondition(args);
+                if (dto.getLimit() > 0 && dto.getOffset() > -1) {
+                    String limitOffsetSql = makeLimitOffsetSql(baseSQL, dto
+                            .getLimit(), dto.getOffset());
+                    if (LOGGER.isDebugEnabled()) {
+                        LOGGER.debug("S2Pager execute SQL : " + limitOffsetSql);
+                    }
+                    resultSet = resultSetFactory.createResultSet(ps.getConnection()
+                            .prepareStatement(limitOffsetSql));
+                } else {
+                    resultSet = resultSetFactory.createResultSet(ps);
+                }
+                dto.setCount(getCount(ps));
+				return resultSet;
+
+            } catch (SQLException e) {
+                throw new SQLRuntimeException(e);
+            }
+        } else {
+            return resultSetFactory.createResultSet(ps);
+        }
+    }
+    
+    String makeCalcFoundRowsSQL(String baseSQL) {
+        Matcher matcher = baseSqlPattern.matcher(baseSQL);
+        if (matcher.find()) {
+            baseSQL = matcher.replaceFirst(matcher.group(1) + " SQL_CALC_FOUND_ROWS");
+        }
+        return baseSQL;
+    }
+
+    /**
+     * limit offsetを付加したSQLを作成します。
+     * 
+     * @param baseSQL
+     * @param limit
+     * @param offset
+     * @return
+     */
+    String makeLimitOffsetSql(String baseSQL, int limit, int offset) {
+        StringBuffer sqlBuf = new StringBuffer(baseSQL);
+        sqlBuf.append(" LIMIT ");
+        sqlBuf.append(limit);
+        sqlBuf.append(" OFFSET ");
+        sqlBuf.append(offset);
+        return sqlBuf.toString();
+    }
+
+    /**
+     * "SELECT FOUND_ROWS()"により元SQL結果総件数を取得します
+     * 
+     * @param ps
+     *            元のPreparedStatement
+     * @param baseSQL
+     *            元のSQL
+     * @return 結果総件数
+     * @throws SQLException
+     */
+    private int getCount(PreparedStatement ps)
+            throws SQLException {
+        String countSQL = "SELECT FOUND_ROWS()";
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug("S2Pager execute SQL : " + countSQL);
+        }
+
+        PreparedStatement psCount = null;
+        ResultSet rs = null;
+        try {
+            psCount = ps.getConnection().prepareStatement(countSQL);
+            rs = resultSetFactory.createResultSet(psCount);
+            if (rs.next()) {
+                return rs.getInt(1);
+            } else {
+                throw new SQLException("[S2Pager]Result not found.");
+            }
+        } finally {
+            ResultSetUtil.close(rs);
+            StatementUtil.close(psCount);
+        }
+    }
+}
=== s2-dao/src/test/java/org/seasar/dao/pager/PagerResultSetFactoryLimitOffsetMySQLWrapperTest.java
==================================================================
--- s2-dao/src/test/java/org/seasar/dao/pager/PagerResultSetFactoryLimitOffsetMySQLWrapperTest.java	(revision 10567)
+++ s2-dao/src/test/java/org/seasar/dao/pager/PagerResultSetFactoryLimitOffsetMySQLWrapperTest.java	(local)
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2004-2008 the Seasar Foundation and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.seasar.dao.pager;
+
+import junit.framework.TestCase;
+
+/**
+ * 
+ */
+public class PagerResultSetFactoryLimitOffsetMySQLWrapperTest extends TestCase {
+
+    MockResultSetFactory original;
+
+    PagerResultSetFactoryLimitOffsetMySQLWrapper wrapper;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        original = new MockResultSetFactory();
+        wrapper = new PagerResultSetFactoryLimitOffsetMySQLWrapper(original,
+                "MySQL");
+        PagerContext.start();
+        PagerContext.getContext().pushArgs(createNormalArgs());
+    }
+
+    protected void tearDown() throws Exception {
+        PagerContext.getContext().popArgs();
+        PagerContext.end();
+        super.tearDown();
+    }
+
+    public void testLimitOffsetSql() throws Exception {
+        assertEquals(
+                "指定されたlimit offsetが付加されたSQLを生成",
+                "SELECT * FROM DEPARTMENT LIMIT 10 OFFSET 55",
+                wrapper.makeLimitOffsetSql("SELECT * FROM DEPARTMENT", 10, 55));
+    }
+
+    public void testCalcFoundRowsSQL() throws Exception {
+        assertEquals(
+                "SQL_CALC_FOUND_ROWSが付加されたSQLを生成",
+                "SELECT SQL_CALC_FOUND_ROWS * FROM DEPARTMENT",
+                wrapper.makeCalcFoundRowsSQL("SELECT * FROM DEPARTMENT"));
+        assertEquals(
+                "SQL_CALC_FOUND_ROWSが付加されたSQLを生成 (select小文字)",
+                "select SQL_CALC_FOUND_ROWS * FROM DEPARTMENT",
+                wrapper.makeCalcFoundRowsSQL("select * FROM DEPARTMENT"));
+    }
+    
+    private Object[] createNormalArgs() {
+        return new Object[] {};
+    }
+
+}
Generated at Sat Apr 20 23:31:53 JST 2024 using Jira 9.15.0#9150000-sha1:9ead8528714127d8cfabf2446010d7e62c0a195c.