1. 程式人生 > >MyBatis分頁的拓展--合併高階查詢

MyBatis分頁的拓展--合併高階查詢

MyBatis分頁的拓展–合併查詢
在網上有很多關於MyBatis攔截器分頁的辦法,可缺少關於合併查詢的方法。本文將講述這一過程的具體實現。
話不多說,直接貼程式碼:

applicationContext.xml裡這樣配置:

    <!-- MyBatis配置 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- mybatis配置檔案路徑 -->
<property name="configLocation" value="WEB-INF/conf/myBatisConfig.xml"/> </bean> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> <!-- 這個執行器會批量執行更新語句, 還有SIMPLE 和 REUSE -->
<constructor-arg index="1" value="BATCH" /> </bean> <!-- 掃描basePackage介面 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="annotationClass" value="org.springframework.stereotype.Repository" /> <!-- 對映器介面檔案的包路徑, -->
<property name="basePackage" value="com.stock.dao" /> </bean>


接下來是Page分頁部分:

public class Page<T> {

    private int pageNo = 1;//頁碼,預設是第一頁
    private int pageSize = 15;//每頁顯示的記錄數,預設是15
    private int totalRecord;//總記錄數
    private int totalPage;//總頁數
    private List<?> results;//對應的當前頁記錄
    private Map<String, Object> params = new HashMap<String, Object>();//其他的引數我們把它分裝成一個Map物件

    /** 頁碼*/
    public int getPageNo() {
       return pageNo;
    }

    public void setPageNo(int pageNo) {
       this.pageNo = pageNo;
    }

    /** 每頁記錄數*/
    public int getPageSize() {
       return pageSize;
    }

    public void setPageSize(int pageSize) {
       this.pageSize = pageSize;
    }

    /** 總頁數*/
    public int getTotalRecord() {
       return totalRecord;
    }

    public void setTotalRecord(int totalRecord) {

       this.totalRecord = totalRecord;
       //在設定總頁數的時候計算出對應的總頁數,在下面的三目運算中加法擁有更高的優先順序,所以最後可以不加括號。
       int totalPage = totalRecord%pageSize==0 ? totalRecord/pageSize : totalRecord/pageSize + 1;
       this.setTotalPage(totalPage);

       if(0!=totalRecord)
           params.clear();
    }

    public int getTotalPage() {
       return totalPage;
    }

    public void setTotalPage(int totalPage) {
       this.totalPage = totalPage;
    }

    public List<?> getResults() {
       return results;
    }

    public void setResults(List<?> results) {
       this.results = results;
    }

    public Map<String, Object> getParams() {
       return params;
    }

    public void setParams(Map<String, Object> params) {
       this.params = params;
    }

    @Override
    public String toString() {
       StringBuilder builder = new StringBuilder();
       builder.append("Page [pageNo=").append(pageNo).append(", pageSize=")
              .append(pageSize).append(", results=").append(results).append(
                     ", totalPage=").append(totalPage).append(
                     ", totalRecord=").append(totalRecord).append("]");
       return builder.toString();
    }
}

接著,是攔截器

@Intercepts( {
    @Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class}) })
public class PageInterceptor implements Interceptor {

    private static Logger log = Logger.getLogger(PageInterceptor.class);
    private String databaseType;//資料庫型別,不同的資料庫有不同的分頁方法

    /**
     * 攔截後要執行的方法
     */
    public Object intercept(Invocation invocation) throws Throwable {

       RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
       //通過反射獲取到當前RoutingStatementHandler物件的delegate屬性
       StatementHandler delegate = (StatementHandler)ReflectUtil.getFieldValue(handler, "delegate");


       BoundSql boundSql = delegate.getBoundSql();

       Object obj = boundSql.getParameterObject();

       if (obj instanceof Page<?>) {
           Page<?> page = (Page<?>) obj;

           MappedStatement mappedStatement = (MappedStatement)ReflectUtil.getFieldValue(delegate, "mappedStatement");

           Connection connection = (Connection)invocation.getArgs()[0];

           String sql = boundSql.getSql();

           this.setTotalRecord(page,mappedStatement, connection);

           String pageSql = this.getPageSql(page, sql);

           ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
       }
       return invocation.proceed();
    }


    /**
     * 攔截器對應的封裝原始物件的方法
     */
    public Object plugin(Object target) {
       return Plugin.wrap(target, this);
    }

    /**
     * 設定註冊攔截器時設定的屬性
     */
    public void setProperties(Properties properties) {
       this.databaseType = properties.getProperty("databaseType");
    }

    /**
     * 根據page物件獲取對應的分頁查詢Sql語句,這裡只做了兩種資料庫型別,Mysql和Oracle
     * 其它的資料庫都 沒有進行分頁
     *
     * @param page 分頁物件
     * @param sql 原sql語句
     * @return
     */
    private String getPageSql(Page<?> page, String sql) {
       StringBuffer sqlBuffer = new StringBuffer(sql);
       if ("mysql".equalsIgnoreCase(databaseType)) {
           return getMysqlPageSql(page, sqlBuffer);
       } else if ("oracle".equalsIgnoreCase(databaseType)) {
           return getOraclePageSql(page, sqlBuffer);
       }
       return sqlBuffer.toString();
    }

    /**
     * 獲取Mysql資料庫的分頁查詢語句
     * @param page 分頁物件
     * @param sqlBuffer 包含原sql語句的StringBuffer物件
     * @return Mysql資料庫分頁語句
     */
    private String getMysqlPageSql(Page<?> page, StringBuffer sqlBuffer) {

       sqlBuffer =new StringBuffer(getParamSql(page, sqlBuffer));
        //計算第一條記錄的位置,Mysql中記錄的位置是從0開始的。
       int offset = (page.getPageNo() - 1) * page.getPageSize();
       sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());
       return sqlBuffer.toString();
    }

    /**
     * 獲取Oracle資料庫的分頁查詢語句
     * @param page 分頁物件
     * @param sqlBuffer 包含原sql語句的StringBuffer物件
     * @return Oracle資料庫的分頁查詢語句
     */
    private String getOraclePageSql(Page<?> page, StringBuffer sqlBuffer) {
       //計算第一條記錄的位置,Oracle分頁是通過rownum進行的,而rownum是從1開始的
       int offset = (page.getPageNo() - 1) * page.getPageSize() + 1;
       sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum < ").append(offset + page.getPageSize());
       sqlBuffer.insert(0, "select * from (").append(") where r >= ").append(offset);
       //上面的Sql語句拼接之後大概是這個樣子:
       //select * from (select u.*, rownum r from (select * from t_user) u where rownum < 31) where r >= 16
       return sqlBuffer.toString();
    }

    /**
     * 給當前的引數物件page設定總記錄數
     *
     * @param page Mapper對映語句對應的引數物件
     * @param mappedStatement Mapper對映語句
     * @param connection 當前的資料庫連線
     */
    private void setTotalRecord(Page<?> page,
           MappedStatement mappedStatement, Connection connection) {

       BoundSql boundSql = mappedStatement.getBoundSql(page);

       String sql = boundSql.getSql();

       String countSql = this.getCountSql(page,sql);

       List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

       BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page);

       ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql);
       //通過connection建立一個countSql對應的PreparedStatement物件。
       PreparedStatement pstmt = null;
       ResultSet rs = null;
       try {
           pstmt = connection.prepareStatement(countSql);
           //通過parameterHandler給PreparedStatement物件設定引數
           parameterHandler.setParameters(pstmt);

           rs = pstmt.executeQuery();
           if (rs.next()) {
              int totalRecord = rs.getInt(1);
              //給當前的引數page物件設定總記錄數
              page.setTotalRecord(totalRecord);
           }
       } catch (SQLException e) {
           e.printStackTrace();
       } finally {
           try {
              if (rs != null)
                  rs.close();
               if (pstmt != null)
                  pstmt.close();
           } catch (SQLException e) {
              e.printStackTrace();
           }
       }
    }

    /**
     * 拼接引數 
     * @param page
     * @param sql
     * @return
     */
   private String getParamSql(Page<?> page, StringBuffer sqlBuffer){

       Map<String, Object> params =page.getParams();

        if(null!=params){
            boolean first= true; 
            for(Map.Entry<String, Object> entry :params.entrySet() ){
                if(first){
                    sqlBuffer.append(" where ").append(entry.getKey()).append(" = ").append(entry.getValue());
                    first=!first;
                }else{
                    sqlBuffer.append(" and ").append(entry.getKey()).append(" = ").append(entry.getValue());
                }
            }
        }

       return sqlBuffer.toString();  
   }

    /**
     * 根據原Sql語句獲取對應的查詢總記錄數的Sql語句
     * @param sql
     * @return
     */
    private String getCountSql(Page<?> page,String sql) {

        String countSql =   null;
        int index       =   sql.indexOf("from");
        countSql        =   "select count(*) " + sql.substring(index);
        countSql        =   getParamSql(page, new StringBuffer(countSql));

       return countSql;
    }

    /**
     * 利用反射進行操作的一個工具類
     *
     */
    private static class ReflectUtil {
       /**
        * 利用反射獲取指定物件的指定屬性
        * @param obj 目標物件
        * @param fieldName 目標屬性
        * @return 目標屬性的值
        */
       public static Object getFieldValue(Object obj, String fieldName) {
           Object result = null;
           Field field = ReflectUtil.getField(obj, fieldName);
           if (field != null) {
              field.setAccessible(true);
              try {
                  result = field.get(obj);
              } catch (IllegalArgumentException e) {
                  log.error(e.getMessage());
                  e.printStackTrace();
              } catch (IllegalAccessException e) {
                  log.error(e.getMessage());
                  e.printStackTrace();
              }
           }
           return result;
       }

       /**
        * 利用反射獲取指定物件裡面的指定屬性
        * @param obj 目標物件
        * @param fieldName 目標屬性
        * @return 目標欄位
        */
       private static Field getField(Object obj, String fieldName) {
           Field field = null;
          for (Class<?> clazz=obj.getClass(); clazz != Object.class; clazz=clazz.getSuperclass()) {
              try {
                  field = clazz.getDeclaredField(fieldName);
                  break;
              } catch (NoSuchFieldException e) {
                  //這裡不用做處理,子類沒有該欄位可能對應的父類有,都沒有就返回null。
              }
           }
           return field;
       }

       /**
        * 利用反射設定指定物件的指定屬性為指定的值
        * @param obj 目標物件
        * @param fieldName 目標屬性
         * @param fieldValue 目標值
        */
       public static void setFieldValue(Object obj, String fieldName,
              String fieldValue) {
           Field field = ReflectUtil.getField(obj, fieldName);
           if (field != null) {
              try {
                  field.setAccessible(true);
                  field.set(obj, fieldValue);
              } catch (IllegalArgumentException e) {
                  log.error(e.getMessage());
                  e.printStackTrace();
              } catch (IllegalAccessException e) {
                  log.error(e.getMessage());
                  e.printStackTrace();
              }
           }
        }
    }
}

然後是mybatis配置檔案在這裡我命名為myBatisConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!-- 開啟懶載入 -->
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
        <setting name="cacheEnabled" value="true" />
    </settings>
      <typeAliases>
       <typeAlias type="com.stock.common.mybatis.Page" alias="page"/>
    </typeAliases>
    <plugins>
       <plugin interceptor="com.stock.common.mybatis.PageInterceptor">
           <property name="databaseType" value="Mysql"/>
       </plugin>
    </plugins>
    <mappers>
         <mapper resource="com/stock/mybatis-conf/jikeUser.xml" />
    </mappers>
</configuration>

最後是Mappers檔案 xx.xml

<mapper namespace="com.xx.dao.xxDao">
<select id="findBy" parameterType="page" resultType="java.util.LinkedHashMap" useCache="true">
        <![CDATA[
            select * from jikeuser 
        ]]>
    </select>

對應DAO介面

@Repository
public interface xxDao {

    List<Map<String, Object>> findBy(Page<Map<String, Object>> params);
}

如此以來,就成功啦!