擴展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

相關文章
相關標籤/搜索