文档章节

Spring+Mybatis读写分离

mahengyang
 mahengyang
发布于 2017/07/25 11:00
字数 1059
阅读 738
收藏 30

说明

mybatis插件+扩展Spring数据源是最省力的方式,不需要改动任何原代码,如果是数据库事务,则插件不生效,不会对数据库事务造成影响

spring配置

<bean id="abstractDataSource" abstract="true" class="com.alibaba.druid.pool.DruidDataSource" 
    init-method="init" destroy-method="close">
</bean>

<bean id="write_db" parent="abstractDataSource" >
    <property name="url" value="${write.jdbc.url}" />
    <property name="username" value="${write.jdbc.username}" />
    <property name="password" value="${write.jdbc.password}" />
</bean>

<bean id="read_db" parent="abstractDataSource">
    <property name="url" value="${read.jdbc.url}" />
    <property name="username" value="${read.jdbc.username}" />
    <property name="password" value="${read.jdbc.password}" />
</bean>

<!-- 数据源切换 -->
<bean id="routingDataSource" class="com.main.common.mybatis.MyRoutingDataSource">
    <property name="read" ref="read_db" />
    <property name="write" ref="write_db" />
</bean>

<!-- MyBatis配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="routingDataSource" />
    <!-- 自动扫描domain目录, 省掉Configuration.xml里的手工配置 -->
    <property name="typeAliasesPackage" value="com.main.common.domain" />
    <property name="mapperLocations" value="classpath:/mybatis/*Mapper.xml" />
    <property name="configLocation" value="classpath:mybatis-config.xml" />
</bean>

<!-- mybatis增强Mapper 通用CRUD -->
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    <property name="basePackage" value="com.main.common.repository"/>
    <property name="markerInterface" value="tk.mybatis.mapper.common.Mapper"/>
</bean>

<!-- 使用自定义的事务管理器 -->
<bean id="transactionManager" class="com.main.common.mybatis.MyDynamicDataSourceTransactionManager" >
    <property name="dataSource" ref="routingDataSource" />
</bean>

<tx:annotation-driven transaction-manager="transactionManager" order="1"/>

在mybatis配置文件中配置插件

<configuration>
    <plugins>
        <!-- 分页插件 -->
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <property name="helperDialect" value="mysql"/>
            <property name="reasonable" value="true"/>
            <property name="supportMethodsArguments" value="true"/>
            <property name="autoRuntimeDialect" value="true"/>
        </plugin>

        <!-- 读写分离插件 -->
        <plugin interceptor="com.main.common.mybatis.MyDynamicDataSourcePlugin">
        </plugin>
    </plugins>
</configuration>

mybatis读写分离插件源码MyDynamicDataSourcePlugin.java

package com.main.common.mybatis;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * mybatis插件,根据sql类型判断应该使用哪种数据源(读/写),如果是数据库事务,则不处理
 * Created by mahengyang on 2017/7/21.
 */
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MyDynamicDataSourcePlugin implements Interceptor {
    protected static final Logger log = LoggerFactory.getLogger(MyDynamicDataSourcePlugin.class);
    private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";

    private static final Map<String, MyDynamicDataSourceHolder.MyDataSource> cacheMap = new ConcurrentHashMap<>();

    [@Override](https://my.oschina.net/u/1162528)
    public Object intercept(Invocation invocation) throws Throwable {
        // 判断是否是数据库事务
        boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
        log.debug("动态数据源插件 是否是事务:{}", synchronizationActive);
        if (!synchronizationActive) {
            Object[] objects = invocation.getArgs();
            MappedStatement ms = (MappedStatement) objects[0];
            String statementId = ms.getId();
            MyDynamicDataSourceHolder.MyDataSource dataSource = cacheMap.get(statementId);
            log.debug("动态数据源插件 非事务 数据源:{}", dataSource);
            if (dataSource == null) {
                //读方法
                if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
                    //!selectKey 为自增id查询主键(SELECT LAST_INSERT_ID() )方法,使用主库
                    if (statementId.contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
                        dataSource = MyDynamicDataSourceHolder.MyDataSource.WRITE;
                    } else {
                        BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
                        String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
                        if (sql.matches(REGEX)) {
                            dataSource = MyDynamicDataSourceHolder.MyDataSource.WRITE;
                        } else {
                            dataSource = MyDynamicDataSourceHolder.MyDataSource.READ;
                        }
                    }
                } else {
                    // 写方法
                    dataSource = MyDynamicDataSourceHolder.MyDataSource.WRITE;
                }
                log.debug("读写分离插件 设置方法:{} 使用:{} 数据源 SqlCommandType [{}]", statementId, dataSource.name(), ms.getSqlCommandType().name());
                cacheMap.put(statementId, dataSource);
            }
            MyDynamicDataSourceHolder.putDataSource(dataSource);
        }
        return invocation.proceed();
    }

    [@Override](https://my.oschina.net/u/1162528)
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

自定义数据源切换类MyRoutingDataSource.java

package com.main.common.mybatis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.util.HashMap;
import java.util.Map;

/**
 * 根据MyDynamicDataSourceHolder中的线程局部变量,选择数据源(读/写)
 * Created by mahengyang on 2017/7/21.
 */
public class MyRoutingDataSource extends AbstractRoutingDataSource {
    protected static final Logger log = LoggerFactory.getLogger(MyRoutingDataSource.class);

    private Object read;
    private Object write;

    @Override
    protected Object determineCurrentLookupKey() {
        MyDynamicDataSourceHolder.MyDataSource dataSource = MyDynamicDataSourceHolder.getDataSource();
        if (dataSource == null || dataSource == MyDynamicDataSourceHolder.MyDataSource.WRITE) {
            log.debug("动态路由数据源 本次选择使用 写 数据源");
            return MyDynamicDataSourceHolder.MyDataSource.WRITE.name();
        }
        log.debug("动态路由数据源 本次选择使用 读 数据源");
        return MyDynamicDataSourceHolder.MyDataSource.READ.name();
    }

    /**
     * 把数据源配置到spring的管理器中,因为spring的数据源是用Map<Object,Object>存储的,所以这里以枚举的名称作为key,数据源作为value
     */
    @Override
    public void afterPropertiesSet() {
        if (this.write == null) {
            throw new IllegalArgumentException("Property 'write' is required");
        }
        setDefaultTargetDataSource(write);
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(MyDynamicDataSourceHolder.MyDataSource.WRITE.name(), write);
        if (read != null) {
            targetDataSources.put(MyDynamicDataSourceHolder.MyDataSource.READ.name(), read);
        }
        setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    public void setRead(Object read) {
        this.read = read;
    }

    public void setWrite(Object write) {
        this.write = write;
    }
}

自定义数据库事务处理类MyDynamicDataSourceTransactionManager

package com.main.common.mybatis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;

/**
 * 自定义事务管理器,继承自spring的事务管理器,只做了数据源的切换功能,其他全部继承父类
 * Created by mahengyang on 2017/7/24.
 */
public class MyDynamicDataSourceTransactionManager extends DataSourceTransactionManager {
    protected static final Logger log = LoggerFactory.getLogger(MyDynamicDataSourceTransactionManager.class);

    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        boolean readOnly = definition.isReadOnly();
        if (readOnly) {
            log.debug("数据库事务管理器 读");
            MyDynamicDataSourceHolder.putDataSource(MyDynamicDataSourceHolder.MyDataSource.READ);
        } else {
            log.debug("数据库事务管理器 写");
            MyDynamicDataSourceHolder.putDataSource(MyDynamicDataSourceHolder.MyDataSource.WRITE);
        }
        super.doBegin(transaction, definition);
    }

    @Override
    protected void doCleanupAfterCompletion(Object transaction) {
        super.doCleanupAfterCompletion(transaction);
        MyDynamicDataSourceHolder.clearDataSource();
    }
}

自定义数据源(读/写)

package com.main.common.mybatis;

/**
 * 把数据源作为线程局部变量,线程使用完清除
 * Created by mahengyang on 2017/7/24.
 */
public class MyDynamicDataSourceHolder {
    private static final ThreadLocal<MyDataSource> holder = new ThreadLocal<MyDataSource>();

    private MyDynamicDataSourceHolder() {
    }

    public static void putDataSource(MyDataSource dataSource){
        holder.set(dataSource);
    }

    public static MyDataSource getDataSource(){
        return holder.get();
    }

    public static void clearDataSource() {
        holder.remove();
    }

    public enum MyDataSource {
        READ, WRITE;
    }
}

© 著作权归作者所有

共有 人打赏支持
mahengyang
粉丝 56
博文 46
码字总数 32090
作品 0
苏州
程序员
私信 提问
spring+mybatis读写分离配置

<!-- 数据源的配置 --><bean id="masterHikariConfig" class="com.zaxxer.hikari.HikariConfig"> </bean> <bean id="slave01HikariConfig" class="com.zaxxer.hikari.HikariConfig"> </bean......

吕兵阳
2016/12/03
223
0
JFinal简化代码的机理

想请教一下,JFinal去掉了getter,setter这些东西,同spring+mybatis架构相比有何损失?经过千锤百炼的经典架构(spring+mybatis)有重大问题吗?...

hyhdl888
2015/04/14
461
5
was6到was8迁移应用

场景:was6项目迁移到was8 最近公司升级web容器版本,用的是web application server8,为什么我把was6的项目迁移到was8就报错了。应用使用spring+mybatis开发的,搜索引擎用的solr,solr在部...

luhf
2013/05/17
2.2K
0
javaservice将jar包制作成windows服务,可以安装,但是无法启动

最近有个需求,就是写个windows服务部署到内网,每天不断的对数据库查询对哪些满足条件的用户发送短信。我使用的是spring+mybatis,使用maven管理jar包。我实现了第一步:将maven项目打成jar...

在我的歌声里
2017/09/08
72
2
数据库同时分库和读写分离?

分库是把数据分散到多个数据库,此时每个数据库的访问压力就会平摊,但是还是有并发读写。读写分离后,数据库读压力会分散到多个从数据库,而且读写分离了,性能也更好会好。 之前看到好像介绍...

铂金小虫
2013/07/14
816
3

没有更多内容

加载失败,请刷新页面

加载更多

mysql 时间格式化

DATE_FORMAT

1713716445
5分钟前
0
0
聊聊flink的PartitionableListState

序 本文主要研究一下flink的PartitionableListState PartitionableListState flink-runtime_2.11-1.7.0-sources.jar!/org/apache/flink/runtime/state/DefaultOperatorStateBackend.java /*......

go4it
9分钟前
0
0
Micropython教程之TPYBoard开发板制作电子时钟(萝卜学科编程教育)

1.实验目的 1. 学习在PC机系统中扩展简单I/O?接口的方法。 2. 什么是SPI接口。 3. 学习TPYBoard I2C接口的用法。 4. 学习LCD5110接线方法。 5. 设定时钟并将当前时间显示在LCD5110上。 2.所需...

bodasisiter
10分钟前
0
0
js 闭包

闭包,是 Javascript 比较重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是 ECMAScript 规范给的定义,如果没有实战经验,很难从定义去理解它。因此,本文不会对闭包的概念...

MrBoyce
14分钟前
0
0
Java B2B2C o2o多用户商城 springcloud架-企业云架构common-service代码结构分析

当前的分布式微服务云架构平台使用Maven构建,所以common-service的通用服务按照maven构建独立的系统服务,结构如下: particle-commonservice: spring cloud 系统服务根项目,所有服务项目...

itcloud
20分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部