1. 程式人生 > >Mybatis操作Sybase資料庫遊標分頁

Mybatis操作Sybase資料庫遊標分頁

宣告

歡迎轉載請註明原文地址,謝謝。

簡介

sybase資料庫本身是不支援分頁sql語句,想用mybatis操作sybase分頁很麻煩,在網上也看了不少使用資料庫儲存過程解決的。
但總覺得侷限性太大出現很多問題,所以本文介紹結合mybatis進行遊標分頁處理,不影響mybatis本身寫法的限制,
只需遵循分頁介面規範即可(可支援多資料庫分頁,不過效率沒有資料庫本身分頁效率高)。
本文原始碼地址:https://github.com/ssp1523/sybase-mybatis-pagination,最好先下載原始碼結合文字效果更佳。

第一步:建立maven web專案
maven pom檔案配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId
>
com.smp</groupId> <artifactId>sybase-mybatis-pagination</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <description>Mybatis 基於sybase資料庫遊標分頁例子</description> <build> <plugins> <plugin
>
<groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <javax-activation.version>1.1</javax-activation.version> <spring.version>4.0.4.RELEASE</spring.version> <mysql-connector-java.version>5.1.34</mysql-connector-java.version> <java.version>1.7</java.version> </properties> <dependencies> <!-- spring start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-dao</artifactId> <version>2.0.8</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-mock</artifactId> <version>2.0.8</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- spring end --> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>net.sourceforge.jtds</groupId> <artifactId>jtds</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.0</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> </dependencies> </project>

web.xml檔案配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
  <context-param>
    <param-name>log4jConfigLocation</param-name>
    <param-value>classpath:log4j.properties</param-value>
  </context-param>
  <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <servlet>
    <servlet-name>springMvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring/ApplicationContext.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>springMvc</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

resources/spring/ApplicationContext.xml spring xml檔案配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd


                        http://www.springframework.org/schema/context 
                        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 啟用註解 -->
    <context:annotation-config/>
    <context:component-scan base-package="com.smp"/>


    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:dbconfig.properties" />
    </bean>

    <bean id="dataSource"                class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
        <property name="driverClassName" value="${driverClassName}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
    </bean>

    <!-- 配置mybatis -->
    <bean id="sqlSessionFactory" 
        class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!-- mapper掃描 -->
        <property name="mapperLocations" value="classpath:com/smp/mapper/*.xml"/>
    </bean>
    <!--mapper掃描-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.smp.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

    <bean id="sqlSessionTemplate" 
        class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg ref="sqlSessionFactory"/>
    </bean>
</beans>

第二步:建立分頁相關配置和java類

資料庫連線配置檔案 resources/dbconfig.properties,由於是演示專案所以配置較簡單

url=你的資料庫url地址
driverClassName=net.sourceforge.jtds.jdbc.Driver
username=你的資料庫使用者名稱
password=你的資料庫密碼

分頁核心類 SybasePagination

package com.smp;

import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.jdbc.core.*;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

/**
 * sybase 分頁
 * Created by ssp on 2017/3/17.
 */
@Component
public class SybasePagination {

    @Resource(name = "sqlSessionFactory")
    private SqlSessionFactory sqlSessionFactory;

    private BoundSql getBoundSql(String mapperId, Object parameter) {
        return getMappedStatement(mapperId).getBoundSql(parameter);
    }

    private MappedStatement getMappedStatement(String mapperId) {
        return sqlSessionFactory.getConfiguration().getMappedStatement(mapperId);
    }

    /**
     * mybatis遊標分頁方法
     * @param mapperId mybatis 對映id 如:com.smp.mapper.CustomMapper.findByListPage
     * @param params 分頁引數 暫時只支援Page型別引數
     * @return 分頁結果
     */
    public List<PageData> listPage(String mapperId, Object params) throws SQLException, NoSuchFieldException, IllegalAccessException {
        return queryForListPage(sqlSessionFactory.openSession().getConnection(), getBoundSql(mapperId, params), getMappedStatement(mapperId), (Page) params);
    }

    /**
     * 分頁核心方法,處理遊標分頁操作
     * 由於是使用jdbc遊標分頁,所以此處使用JdbcTemplate簡化操作。
     * @param connection 資料庫連線
     * @param boundSql 分頁BoundSql
     * @param mappedStatement 分頁 MappedStatement
     * @param page 分頁Page類
     * @return 分頁結果
     */
    @SuppressWarnings("unchecked")
    private List<PageData> queryForListPage(final Connection connection, final BoundSql boundSql, final MappedStatement mappedStatement, final Page page) throws SQLException, NoSuchFieldException, IllegalAccessException {
        //分頁count
        PagePlugin.pageCount(connection, mappedStatement, boundSql, page);

        return (List) new JdbcTemplate(new JdbcTemplateDataSource(connection))
                .query(new MyPreparedStatementCreator(boundSql, mappedStatement, page)
                , new RowMapperResultSetExtractor(new ColumnPageDataRowMapper(page)) {
                    @Override
                    public List extractData(ResultSet rs) throws SQLException {
                        if (page.getCurrentResult() != 0) {
                            //將遊標移動到第一條記錄
                            rs.first();
                            //   遊標移動到要輸出的第一條記錄
                            rs.relative(page.getCurrentResult() - 1);
                        }
                        return super.extractData(rs);
                    }
                });

    }

    private class JdbcTemplateDataSource extends AbstractDataSource {

        private Connection connection;

        JdbcTemplateDataSource(Connection connection) {
            this.connection = connection;
        }

        public Connection getConnection() throws SQLException {
            return connection;
        }

        public Connection getConnection(String username, String password) throws SQLException {
            return connection;
        }
    }

    private class MyPreparedStatementCreator implements PreparedStatementCreator {

        private BoundSql boundSql;

        private MappedStatement mappedStatement;

        private Page page;

        MyPreparedStatementCreator(BoundSql boundSql, MappedStatement mappedStatement, Page page) {
            this.boundSql = boundSql;
            this.mappedStatement = mappedStatement;
            this.page = page;
        }

        public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
            PreparedStatement pstat = con.prepareStatement(boundSql.getSql(), ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
            PagePlugin.setParameters(pstat, mappedStatement, boundSql, boundSql.getParameterObject());
            pstat.setMaxRows(page.getCurrentResult() + page.getShowCount());
            return pstat;
        }
    }

    private class ColumnPageDataRowMapper implements RowMapper<PageData> {

        private Page page;

        ColumnMapRowMapper columnMapRowMapper = new ColumnMapRowMapper() {
            @Override
            protected Map<String, Object> createColumnMap(int columnCount) {
                return new PageData();
            }

            @Override
            protected String getColumnKey(String columnName) {
                if (page.getCamelName())
                    return StringUtil.camelName(columnName);
                else
                    return super.getColumnKey(columnName);
            }
        };

        private ColumnPageDataRowMapper(Page page) {
            this.page = page;
        }


        public PageData mapRow(ResultSet rs, int rowNum) throws SQLException {
            return (PageData) columnMapRowMapper.mapRow(rs, rowNum);
        }
    }
}

Page分頁vo類,儲存分頁相關資訊,作為分頁引數使用

package com.smp;

public class Page {

    private int showCount; //每頁顯示記錄數
    private int totalPage;        //總頁數
    private int totalResult;    //總記錄數
    private int currentPage;    //當前頁
    private int currentResult;    //當前記錄起始索引
    private boolean entityOrField;    //true:需要分頁的地方,傳入的引數就是Page實體;false:需要分頁的地方,傳入的引數所代表的實體擁有Page屬性
    private PageData pd = new PageData();

    private Boolean camelName = false;

    public Page() {

    }

    public int getTotalPage() {
        if (totalResult % showCount == 0)
            totalPage = totalResult / showCount;
        else
            totalPage = totalResult / showCount + 1;
        return totalPage;
    }

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

    public int getTotalResult() {
        return totalResult;
    }

    public void setTotalResult(int totalResult) {
        this.totalResult = totalResult;
    }

    public int getCurrentPage() {
        if (currentPage <= 0)
            currentPage = 1;
        if (currentPage > getTotalPage())
            currentPage = getTotalPage();
        return currentPage;
    }

    public void setCurrentPage(int currentPage) {
        this.currentPage = currentPage;
    }


    public int getShowCount() {
        return showCount;
    }

    public void setShowCount(int showCount) {

        this.showCount = showCount;
    }

    public int getCurrentResult() {
        currentResult = (getCurrentPage() - 1) * getShowCount();
        if (currentResult < 0)
            currentResult = 0;
        return currentResult;
    }

    public void setCurrentResult(int currentResult) {
        this.currentResult = currentResult;
    }

    public boolean isEntityOrField() {
        return entityOrField;
    }

    public void setEntityOrField(boolean entityOrField) {
        this.entityOrField = entityOrField;
    }

    public PageData getPd() {
        return pd;
    }

    public void setPd(PageData pd) {
        this.pd = pd;
    }

    public Boolean getCamelName() {
        return camelName;
    }

    public void setCamelName(Boolean camelName) {
        this.camelName = camelName;
    }
}

PageData類,分頁查詢的方法結果一條記錄為一個PageData

package com.smp;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class PageData extends HashMap<String, Object> implements Map<String, Object> {

    private static final long serialVersionUID = 1L;

    private Map<String, Object> map = null;

    public PageData() {
        map = new HashMap<>();
    }

    @Override
    public Object get(Object key) {
        return map.get(key);
    }

    public String getString(Object key) {
        return (String) get(key);
    }

    @SuppressWarnings("unchecked")
    @Override
    public Object put(String key, Object value) {
        return map.put(key, value);
    }

    @Override
    public Object remove(Object key) {
        return map.remove(key);
    }

    public void clear() {
        map.clear();
    }

    public boolean containsKey(Object key) {
        return map.containsKey(key);
    }

    public boolean containsValue(Object value) {
        return map.containsValue(value);
    }

    public Set<Map.Entry<String, Object>> entrySet() {
        return map.entrySet();
    }

    public boolean isEmpty() {
        return map.isEmpty();
    }

    public Set<String> keySet() {
        return map.keySet();
    }

    @SuppressWarnings("unchecked")
    public void putAll(Map t) {
        map.putAll(t);
    }

    public int size() {
        return map.size();
    }

    public Collection<Object> values() {
        return map.values();
    }

}

PagePlugin類,分頁工具類,處理總條數和引數設定。

package com.smp;


import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

class PagePlugin  {

    static void pageCount(Connection connection, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws NoSuchFieldException, IllegalAccessException, SQLException {
        String countSql = "select count(0) from (" + sqlProcess(boundSql.getSql()) + ")  tmp_count"; //記錄統計 == oracle 加 as 報錯(SQL command not properly ended)
        PreparedStatement countStmt = connection.prepareStatement(countSql);
        BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject);
        setParameters(countStmt, mappedStatement, countBS, parameterObject);
        ResultSet rs = countStmt.executeQuery();
        int count = 0;
        if (rs.next()) {
            count = rs.getInt(1);
        }
        rs.close();
        countStmt.close();
        Page page;
        if (parameterObject instanceof Page) {    //引數就是Page實體
            page = (Page) parameterObject;
            page.setEntityOrField(true);
            page.setTotalResult(count);
        } else {    //引數為某個實體,該實體擁有Page屬性
            Field pageField = ReflectHelper.getFieldByFieldName(parameterObject, "page");
            if (pageField != null) {
                page = (Page) ReflectHelper.getValueByFieldName(parameterObject, "page");
                if (page == null)
                    page = new Page();
                page.setEntityOrField(false);
                page.setTotalResult(count);
                ReflectHelper.setValueByFieldName(parameterObject, "page", page); //通過反射,對實體物件設定分頁物件
            } else {
                throw new NoSuchFieldException(parameterObject.getClass().getName() + "不存在 page 屬性!");
            }
        }
    }

    /**
     * sql處理
     *
     * @param sql 被處理的sql
     * @return 處理後的sql語句
     */
    private static String sqlProcess(String sql) {
        //去掉sql語句 order by 語句
        int orderIndex;
        if ((orderIndex = sql.toLowerCase().lastIndexOf("order")) != -1) {
            return sql.substring(0, orderIndex);
        }
        return sql;
    }


    /**
     * 對SQL引數(?)設值,參考org.apache.ibatis.executor.parameter.DefaultParameterHandler
     */
    @SuppressWarnings("unchecked")
    static void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            Configuration configuration = mappedStatement.getConfiguration();
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    PropertyTokenizer prop = new PropertyTokenizer(propertyName);
                    if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX) && boundSql.hasAdditionalParameter(prop.getName())) {
                        value = boundSql.getAdditionalParameter(prop.getName());
                        if (value != null) {
                            value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));
                        }
                    } else {
                        value = metaObject == null ? null : metaObject.getValue(propertyName);
                    }
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    if (typeHandler == null) {
                        throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId());
                    }
                    typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
                }
            }
        }
    }


}

ReflectHelper類,反射工具類

package com.smp;


import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

class PagePlugin  {

    static void pageCount(Connection connection, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws NoSuchFieldException, IllegalAccessException, SQLException {
        String countSql = "select count(0) from (" + sqlProcess(boundSql.getSql()) + ")  tmp_count"; //記錄統計 == oracle 加 as 報錯(SQL command not properly ended)
        PreparedStatement countStmt = connection.prepareStatement(countSql);
        BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject);
        setParameters(countStmt, mappedStatement, countBS, parameterObject);
        ResultSet rs = countStmt.executeQuery();
        int count = 0;
        if (rs.next()) {
            count = rs.getInt(1);
        }
        rs.close();
        countStmt.close();
        Page page;
        if (parameterObject instanceof Page) {    //引數就是Page實體
            page = (Page) parameterObject;
            page.setEntityOrField(true);
            page.setTotalResult(count);
        } else {    //引數為某個實體,該實體擁有Page屬性
            Field pageField = ReflectHelper.getFieldByFieldName(parameterObject, "page");
            if (pageField != null) {
                page = (Page) ReflectHelper.getValueByFieldName(parameterObject, "page");
                if (page == null)
                    page = new Page();
                page.setEntityOrField(false);
                page.setTotalResult(count);
                ReflectHelper.setValueByFieldName(parameterObject, "page", page); //通過反射,對實體物件設定分頁物件