文档章节

Spring+Mybatis读写分离

mahengyang
 mahengyang
发布于 2017/07/25 11:00
字数 1059
阅读 641
收藏 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
粉丝 52
博文 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
418
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
53
2
mybatis多条件查询的一个错误,求解!

框架是spring+mybatis。 dao层代码: public List findAll(String uuid,String portletid); 配置文件:

Catelyn
2013/11/17
39.9K
3

没有更多内容

加载失败,请刷新页面

加载更多

下一页

[雪峰磁针石博客]python3快速入门教程1 turtle绘图-2函数

菲波那契序列: >>> # Fibonacci series:... # the sum of two elements defines the next... a, b = 0, 1>>> while b < 10:... print(b)... a, b = b, a+b...112......

python测试开发人工智能安全
今天
0
0
java环境变量配置最正确的方式

原贴:https://blog.csdn.net/qq_40007997/article/details/79784711,十分详细,亲测有效

kitty1116
今天
0
0
49.Nginx防盗链 访问控制 解析php相关 代理服务器

12.13 Nginx防盗链 12.14 Nginx访问控制 12.15 Nginx解析php相关配置(502的问题) 12.16 Nginx代理 扩展 502问题汇总 http://ask.apelearn.com/question/9109 location优先级 http://blog....

王鑫linux
今天
1
0
Nginx防盗链、访问控制、解析php相关配置、Nginx代理

一、Nginx防盗链 1. 编辑虚拟主机配置文件 vim /usr/local/nginx/conf/vhost/test.com.conf 2. 在配置文件中添加如下的内容 { expires 7d; valid_referers none blocked server_names *.tes......

芬野de博客
今天
0
0
spring EL 和资源调用

资源调用 import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.PropertySource;import org.springframework.core.io.Resource;......

Canaan_
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部