文档章节

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

aduan
 aduan
发布于 2016/09/21 15:02
字数 1182
阅读 1104
收藏 2

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

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

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


@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动态切换数据源。

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方法时根据注解来判断数据源类型从而决定用哪个分页

@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不一样会取不到哦。

© 著作权归作者所有

aduan
粉丝 4
博文 25
码字总数 13186
作品 0
海口
架构师
私信 提问
加载中

评论(12)

aduan
aduan

引用来自“王大涛”的评论

AOP 起作用了,控制台能输出,也能进入不同的数据库,只要引用pageHelper的业务都没有起作用.

PageHelper.startPage(pageNum, pageSize);
return fangMapper.queryIndexLand();
mybatis配置了plugins吗,要把pagehelper plugin加进去








dialect=mysql




王大涛
王大涛
王海涛
aduan
aduan

引用来自“王大涛”的评论

AOP 起作用了,控制台能输出,也能进入不同的数据库,只要引用pageHelper的业务都没有起作用.

PageHelper.startPage(pageNum, pageSize);
return fangMapper.queryIndexLand();
另外mybatis配置了pagehelper plugin吗?
FactoryBean">






dialect=mysql




aduan
aduan

引用来自“王大涛”的评论

358981721 我的QQ 能方便帮我看一下吗? 把你的这个例子发给我邮箱也行 谢谢你
有真实姓名验证哦
文件在我项目里,发给你也可以,但只能发几个零散的文件给你。其实上面的代码已经差不多是实现全部了
aduan
aduan

引用来自“王大涛”的评论

358981721 我的QQ 能方便帮我看一下吗? 把你的这个例子发给我邮箱也行 谢谢你
我加你QQ
aduan
aduan

引用来自“王大涛”的评论

AOP 起作用了,控制台能输出,也能进入不同的数据库,只要引用pageHelper的业务都没有起作用.

PageHelper.startPage(pageNum, pageSize);
return fangMapper.queryIndexLand();
切换数据源的时候也要切换pagehelper
//切换数据源
DataSourceHolder.setCustomerType(dataSourceType.getType());
//切换pagehelper
PageHelperHolder.setPagerType(dataSourceType.getDialect());

最后pagehelper
private PageHelper determinePageHelper() {
String pageType = PageHelperHolder.getPagerType();
PageHelper pageHelper = targetPageHelper.get(pageType);
if (pageHelper != null) {
return pageHelper;
} else {
return defaultPageHelper;
}
}
王大涛
王大涛
358981721 我的QQ 能方便帮我看一下吗? 把你的这个例子发给我邮箱也行 谢谢你
王大涛
王大涛
AOP 起作用了,控制台能输出,也能进入不同的数据库,只要引用pageHelper的业务都没有起作用.

PageHelper.startPage(pageNum, pageSize);
return fangMapper.queryIndexLand();
王大涛
王大涛

引用来自“王大涛”的评论

我添加了多数据源 使用AOP判断要使用的Mapper文件名来控制数据源 运行后发现pageHelper都没有起作用.能把你的这个项目发给我研究吗?非常感谢.

引用来自“aduan”的评论

应该是你的AOP没有起作用吧,把你AOP的代码贴出来看看
public class MultipleDataSource extends AbstractRoutingDataSource {

private static final ThreadLocal dataSourceKey = new InheritableThreadLocal();

public static void setDataSourceKey(String dataSource) {
dataSourceKey.set(dataSource);
}

@Override
protected Object determineCurrentLookupKey() {
return dataSourceKey.get();
}
}
王大涛
王大涛

引用来自“王大涛”的评论

我添加了多数据源 使用AOP判断要使用的Mapper文件名来控制数据源 运行后发现pageHelper都没有起作用.能把你的这个项目发给我研究吗?非常感谢.

引用来自“aduan”的评论

应该是你的AOP没有起作用吧,把你AOP的代码贴出来看看
@Component
@Aspect
@Order(0)
public class MultipleDataSourceAspectAdvice {

@Around("execution(* com.greentown.datav.mapper.*.*(..))")
public Object doAround(ProceedingJoinPoint jp) throws Throwable {
if (jp.getTarget() instanceof UserMapper || jp.getTarget() instanceof DigestAuthMapper) {
System.out.println("进入了DateT数据库 ||||||||||||||||||||||||||||||||||||||||||||");
MultipleDataSource.setDataSourceKey("dataTSource");
} else {
System.out.println("进入了DateT数据库 ++++++++++++++++++++++++++++++++++++++++++++");

}
System.out.println(jp.getTarget().getClass().getName());
MultipleDataSource.setDataSourceKey("dataVSource");
return jp.proceed();
}

--------------------------------------------------------------------------
public class MultipleDataSource extends AbstractRoutingDataSource {

private static final ThreadLocal dataSourceKey = new InheritableThreadLocal();

public st
SelectPage v2.18,下拉选择的增强和扩展方案

下拉分页选择器 SelectPage v2.18发布了,更新内容: v2.18: 修复使用 formatItem 自定义列表时,提示文本也是 html 代码的问题(使用 formatItem 自定义格式化内容时,不将内容设置到 titl...

TerryZ
2018/03/05
1K
4
Mybatis 分页插件 4.1.1 发布

如果你也在用Mybatis,建议尝试该分页插件,这一定是最方便使用的分页插件。 分页插件支持任何复杂的单表、多表分页,部分特殊情况请看重要提示。 想要使用分页插件?请看如何使用分页插件 ...

Liuzh_533
2016/01/05
1K
1
简洁而强大的下拉分页选择器 - v-selectpage

v-selectpage 简洁而强大的下拉分页选择器 基于 Vue2 强大的选择器, 可分页的列表或表格展现形式, 使用标签形式的多选模式, 国际化 i18n 和服务端数据源支持 文档、实例 请浏览 English site...

TerryZ
2018/07/02
0
0
简洁而强大的下拉分页选择器 v-selectpage

v-selectpage 简洁而强大的下拉分页选择器 基于 Vue2 强大的选择器, 可分页的列表或表格展现形式, 使用标签形式的多选模式, 国际化 i18n 和服务端数据源支持 文档、实例 请浏览 English site...

孟飞阳
2018/07/27
0
0
Mybatis 分页插件 4.1.2 发布

如果你也在用Mybatis,建议尝试该分页插件,这一定是最方便使用的分页插件。 分页插件支持任何复杂的单表、多表分页,部分特殊情况请看重要提示。 想要使用分页插件?请看如何使用分页插件 ...

Liuzh_533
2016/03/07
4.1K
4

没有更多内容

加载失败,请刷新页面

加载更多

“旧城改造”的背后——银泰新零售阿里云解决方案(上)

相关免费课程《银泰新零售上云解决方案精讲》上线中 立足实战 讲透经典案例 助你快速理解新零售 第一节学习地址 第二节学习地址 传统线下商业体上云的案例 与其说银泰上云,倒不如说银泰“旧...

阿里云官方博客
5分钟前
0
0
记一次升级Oracle驱动引发的死锁

问题描述 近期项目需要从虚拟机环境迁移到容器环境,其中有一个项目在迁移到容器环境之后的两天之内出现了2次“死锁(deadlock)”的问题,部分关键日志如下: Found one Java-level deadlock:...

ksfzhaohui
7分钟前
0
0
MySQL 中的 information_schema 数据库

欢迎查看原文 - 本博客仅记录 https://blog.csdn.net/kikajack/article/details/80065753 -- 是否开启bin_log日志: off为关闭-- show variables like 'log_%'; show variables like '......

莫库什勒
15分钟前
0
0
Random在高并发下的缺陷以及JUC对其的优化

Random可以说是每个开发都知道,而且都用的很6的类,如果你说,你没有用过Random,也不知道Random是什么鬼,那么你也不会来到这个技术类型的社区,也看不到我的博客了。但并不是每个人都知道...

编程SHA
19分钟前
0
0
T5大牛带你解析:如何实现分布式技术

1.分布式事务 2. 分布式锁 Java 原生 API 虽然有并发锁,但并没有提供分布式锁的能力,所以针对分布式场景中的锁需要解决的方案。 分布式锁的解决方案大致有以下几种: 基于数据库实现 基于缓...

李红欧巴
31分钟前
31
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部