[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について、 例えば 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を使用することを推奨していますので、 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[] {};
+ }
+
+}
|