[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[] {}; + } + +} |