文档章节

Spring+Mybatis读写分离

mahengyang
 mahengyang
发布于 2017/07/25 11:00
字数 1059
阅读 694
收藏 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
粉丝 54
博文 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

没有更多内容

加载失败,请刷新页面

加载更多

崩溃bug日志总结1

目录介绍 1.1 java.lang.UnsatisfiedLinkError找不到so库异常 1.2 java.lang.IllegalStateException非法状态异常 1.3 android.content.res.Resources$NotFoundException 1.4 java.lang.Ille......

潇湘剑雨
55分钟前
0
0
学习大数据为什么要先学Java?

计算机编程语言有很多,目前用的多一点的就是Java,C++,Python等等。目前大多数学习大数据的人都是选择学习Java,那Java到底好在哪呢?为什么学大数据之前要先学Java呢?我们今天就来分析一...

董黎明
今天
1
0
php删除服务器所有session

php删除服务器所有session踢掉所有在线用户linux 注意:如果要删除服务器上所有session,重启php服务是解决不了问题的,php的session是持久化的。 有效解决办法: 删除 /tmp 下的所有文件(默...

妖尾巴
今天
0
0
Ubuntu18.04 安装最新版WPS

1.手动卸载libreoffice:sudo apt-get remove --purge libreoffice* 2.官网下载WPS和字体: WPS:http://wps-community.org/download.html 字体:http://wps-community.org/download.html?vl......

AI_SKI
今天
4
0
数据结构(算法)-图(深度优先搜索 DFS)

#include <iostream>using namespace std;#define MaxVex 30typedef char VertexType;typedef struct vexNode adjList[MaxVex];struct edgeNode{int adjvex;//邻接点......

ashuo
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部