扩展Myabatis Pagehelper支持多数据源的分页插件

http://git.oschina.net/free/Mybatis_PageHelper/blob/master/wikis/Important.markdownjava

结合spring使用mybatis pagehelper只能配置一个bean,当一个项目使用多种数据库时就比较纠结了,下面是我参考spring支持多数据源的思想封装一个支持多数据源的PageHelper。mysql

实现一个mybatis插件,参考mybatis pagehelper,实现根据不一样的数据源切换使用不一样的pagehelper。git

@SuppressWarnings({"rawtypes", "unchecked"})
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class CustomPageHelper implements Interceptor {

    private PageHelper mysqlPageHelper = new PageHelper();

    private PageHelper oraclePageHelper = new PageHelper();

    private PageHelper postgresqlPageHelper = new PageHelper();

    private Map<Object, PageHelper> targetPageHelper = new HashMap<>();

    private PageHelper defaultPageHelper;


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return determinePageHelper().intercept(invocation);
    }

    @Override
    public Object plugin(Object target) {
        /*return determinePageHelper().plugin(target);*/
        //determinePageHelper();
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {
        targetPageHelper.put(Dialect.mysql.name(), mysqlPageHelper);
        targetPageHelper.put(Dialect.oracle.name(), oraclePageHelper);
        targetPageHelper.put(Dialect.postgresql.name(), postgresqlPageHelper);
        //数据库方言
        String dialect = properties.getProperty("dialect");
        if(Dialect.oracle.equals(Dialect.valueOf(dialect.toLowerCase()))) {
            defaultPageHelper = oraclePageHelper;
        } else if(Dialect.postgresql.equals(Dialect.valueOf(dialect.toLowerCase()))) {
            defaultPageHelper = postgresqlPageHelper;
        } else {
            defaultPageHelper = mysqlPageHelper;
        }

        properties.put("dialect", Dialect.mysql.name());
        mysqlPageHelper.setProperties(properties);

        properties.put("dialect", Dialect.oracle.name());
        oraclePageHelper.setProperties(properties);

        properties.put("dialect", Dialect.postgresql.name());
        postgresqlPageHelper.setProperties(properties);

        properties.put("dialect", dialect);
    }

    private PageHelper determinePageHelper() {
        String pageType = PageHelperHolder.getPagerType();
        PageHelper pageHelper = targetPageHelper.get(pageType);
        if (pageHelper != null) {
            return pageHelper;
        } else {
            return defaultPageHelper;
        }
    }
}
<!-- Oracle配置数据源-->
    <bean id="oracleDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
          destroy-method="close">
        <property name="driverClass" value="${oracle.jdbc.driver}"/>
        <property name="jdbcUrl" value="${oracle.jdbc.url}"/>
        <property name="user" value="${oracle.jdbc.user}"/>
        <property name="password" value="${oracle.jdbc.password}"/>
        <property name="minPoolSize" value="${oracle.jdbc.minPoolSize}"/>
        <property name="maxPoolSize" value="${oracle.jdbc.maxPoolSize}"/>
        <property name="initialPoolSize" value="${oracle.jdbc.initialPoolSize}"/>
        <property name="maxIdleTime" value="${oracle.jdbc.maxIdleTime}"/>
        <property name="acquireIncrement" value="${oracle.jdbc.acquireIncrement}"/>
        <property name="acquireRetryAttempts" value="${oracle.jdbc.acquireRetryAttempts}"/>
        <property name="acquireRetryDelay" value="${oracle.jdbc.acquireRetryDelay}"/>
        <property name="testConnectionOnCheckin" value="${oracle.jdbc.testConnectionOnCheckin}"/>
        <property name="automaticTestTable" value="${oracle.jdbc.automaticTestTable}"/>
        <property name="idleConnectionTestPeriod" value="${oracle.jdbc.idleConnectionTestPeriod}"/>
        <property name="checkoutTimeout" value="${oracle.jdbc.checkoutTimeout}"/>
    </bean>

    <!-- Mysql配置数据源-->
    <bean id="mysqlDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
          destroy-method="close">
        <property name="driverClass" value="${mysql.jdbc.driver}"/>
        <property name="jdbcUrl" value="${mysql.jdbc.url}"/>
        <property name="user" value="${mysql.jdbc.user}"/>
        <property name="password" value="${mysql.jdbc.password}"/>
        <property name="minPoolSize" value="${mysql.jdbc.minPoolSize}"/>
        <property name="maxPoolSize" value="${mysql.jdbc.maxPoolSize}"/>
        <property name="initialPoolSize" value="${mysql.jdbc.initialPoolSize}"/>
        <property name="maxIdleTime" value="${mysql.jdbc.maxIdleTime}"/>
        <property name="acquireIncrement" value="${mysql.jdbc.acquireIncrement}"/>
        <property name="acquireRetryAttempts" value="${mysql.jdbc.acquireRetryAttempts}"/>
        <property name="acquireRetryDelay" value="${mysql.jdbc.acquireRetryDelay}"/>
        <property name="testConnectionOnCheckin" value="${mysql.jdbc.testConnectionOnCheckin}"/>
        <property name="automaticTestTable" value="${mysql.jdbc.automaticTestTable}"/>
        <property name="idleConnectionTestPeriod" value="${mysql.jdbc.idleConnectionTestPeriod}"/>
        <property name="checkoutTimeout" value="${mysql.jdbc.checkoutTimeout}"/>
    </bean>

    <bean id="pgDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 基本属性 url、user、password -->
        <property name="driverClassName" value="${pg.db.drivername}"/>
        <property name="url" value="${pg.db.url}" />
        <property name="username" value="${pg.db.username}" />
        <property name="password" value="${pg.db.password}" />
        <property name="dbType" value="postgresql" />

        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="1" />
        <property name="minIdle" value="1" />
        <property name="maxActive" value="50" />

        <!-- 配置获取链接等待超时的时间 -->
        <property name="maxWait" value="60000" />

        <!-- 配置间隔多久才进行一次检测,检测须要关闭的空闲链接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000" />

        <!-- 配置一个链接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000" />

        <property name="validationQuery" value="SELECT 1" />
        <property name="testWhileIdle" value="true" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="true" />

        <!-- 打开PSCache,而且指定每一个链接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="true" />
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />

        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="stat" />
    </bean>

    <bean id="multipleDataSource" class="com.common.support.MultipleDataSource">
        <property name="defaultTargetDataSource" ref="mysqlDataSource"/>
        <property name="targetDataSources">
            <map>
                <entry key="mySqlDataSource" value-ref="mysqlDataSource"/>
                <entry key="passSqlDataSource" value-ref="passsqlDataSource"/>
                <entry key="oracleDataSource" value-ref="oracleDataSource"/>
                <entry key="greenplumDataSource" value-ref="greenplumDataSource"/>
            </map>
        </property>
    </bean>

    <!-- 配置sql会话工厂:SqlSessionFactoryBean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="multipleDataSource" />
        <!--<property name="dataSource" ref="oracleDataSource"/>-->
        <property name="plugins">
            <array>
                <bean class="com.common.support.CustomPageHelper" scope="singleton">
                    <property name="properties">
                        <value>dialect=mysql</value>
                    </property>
                </bean>
            </array>
        </property>
    </bean>

最后定义一个PageHelperHolder,在数据源切换时把当前使用的数据库类型放进来觉得Pagehelper动态切换数据源。spring

public final class PageHelperHolder {
//    public enum PagerType {
//        MySql, Oracle
//    }
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setPagerType(Dialect Dialect) {
        contextHolder.set(Dialect.name());
    }

    public static String getPagerType() {
        return contextHolder.get();
    }

    public static void clearPaerType() {
        contextHolder.remove();
    }
}

自定义注解,用来加到mapper上面,aop拦截mybatis mapper方法时根据注解来判断数据源类型从而决定用哪一个分页sql

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface DataSourceSelector {
    DataSourceType value();
}
public enum DataSourceType {
    MYSQL_DATASOURCE("mySqlDataSource", Dialect.mysql),
    ORACLE_DATASOURCE("oracleDataSource", Dialect.oracle),
    GREENPLUM_DATASOURCE("pgDataSource", Dialect.postgresql),

    private String type;
    private Dialect dialect;

    DataSourceType(String type, Dialect dialect) {
        this.type = type;
        this.dialect = dialect;
    }

    public String getType() {
        return type;
    }

    public Dialect getDialect() {
        return dialect;
    }

    public static DataSourceType getType(String type) {
        for(DataSourceType dataSourceType : DataSourceType.values()) {
            if(dataSourceType.type.equals(type)) {
                return dataSourceType;
            }
        }
        return null;
    }
}

下面是spring aop拦截mapper的重点代码片断数据库

@Aspect
public class MultipleDataSourceAspectAdvice {    
    @Around("execution(* com.mapper.*.*(..))")
    public Object doAround(ProceedingJoinPoint jp) throws Throwable {
        return DataSourceHolder.getDynaDataSource(jp);
    }
}
//作mapper使用数据源类型缓存,不用每次都要从注解中读取 
    private transient static Map<Class, String> dataSourceHolder = new HashMap<>();
    public static Object getDynaDataSource(ProceedingJoinPoint jp) throws Throwable {
        Object result = null;
        Class targetClass = jp.getThis().getClass();
        try {
            log.debug("MultipleDataSourceAspectAdvice: {}, {}", jp.getThis(), jp.getTarget());
            //获取咱们写的mapper类,以便读取到类上面的注解
            Class[] interfaces = targetClass.getInterfaces();
            if (interfaces != null && interfaces.length > 0) {
                targetClass = interfaces[0];
            }
            String targetDataSource = dataSourceHolder.get(targetClass);
            boolean isHold = false;
            if (StringUtils.isBlank(targetDataSource)) {
                //读取注解上声明要使用的数据源类型
                DataSourceSelector dss = (DataSourceSelector) targetClass.getAnnotation(DataSourceSelector.class);
                if (dss != null) {
                    targetDataSource = dss.value().getType();
                }
            } else {
                isHold = true;
            }

            if (StringUtils.isNotBlank(targetDataSource)) {
                DataSourceType dataSourceType = DataSourceType.getType(targetDataSource);
                if(dataSourceType != null) {
                    DataSourceHolder.setCustomerType(dataSourceType.getType());
                    PageHelperHolder.setPagerType(dataSourceType.getDialect());
                    if (!isHold) {
                        dataSourceHolder.put(targetClass, targetDataSource);
                    }
                } else {
                    log.warn("{}-{} not found available dataSourceType, use default.", targetClass, targetDataSource);
                }
            }
            log.debug("{}: {}", targetClass, dataSourceHolder.get(targetClass));
            result = jp.proceed();
        } catch (Exception ex) {
            log.error("deal with dynamic datasource error. datasource: {}", DataSourceHolder.getCustomerType());
            dataSourceHolder.remove(targetClass);
            throw ex;
        } finally {
            DataSourceHolder.clearCustomerType();
            PageHelperHolder.clearPaerType();
        }

        return result;
    }

mapper类缓存

@DataSourceSelector(DataSourceType.MYSQL_DATASOURCE)
public interface SysRoleMapper {
    ……
}

大致实现就是这样子了,要注意的是spring数据源的配置bean的id和DataSourceType中定义的名称了一致,要否则key不同会取不到哦。markdown