Mybatis+Spring实现Mysql读写分离

2018-12-26

使用spring AbstractRoutingDatasource实现多数据源

public class DynamicDataSource extends AbstractRoutingDataSource {
    //写数据源
    private Object writeDataSource;
    //读数据源
    private Object readDataSource;


    @Override
    public void afterPropertiesSet() {
        Assert.isNull(writeDataSource, "Property 'writeDataSource' is required");
        setDefaultTargetDataSource(writeDataSource);
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        targetDataSources.put(DBTypeEnum.WRITE.name(), writeDataSource);
        if (null != readDataSource)
            targetDataSources.put(DBTypeEnum.READ.name(), readDataSource);
        setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    /**
     * 获取当前使用那个数据源
     */
    @Override
    protected Object determineCurrentLookupKey() {
        DBTypeEnum dataSource = DbContextHolder.getDbType();
        if (null == dataSource || dataSource == DBTypeEnum.WRITE)
            return DBTypeEnum.WRITE.name();
        return DBTypeEnum.READ.name();
    }

    public Object getWriteDataSource() {
        return writeDataSource;
    }

    public void setWriteDataSource(Object writeDataSource) {
        this.writeDataSource = writeDataSource;
    }

    public Object getReadDataSource() {
        return readDataSource;
    }

    public void setReadDataSource(Object readDataSource) {
        this.readDataSource = readDataSource;
    }
}


读写数据库类型

public enum DBTypeEnum {
    WRITE, READ;
}

当前数据库配置上下文

public class DbContextHolder {
    private static final ThreadLocal<DBTypeEnum> DATASOURCES = new ThreadLocal<DBTypeEnum>();

    private DbContextHolder() {
    }

    /**
     * 设置数据源
     *
     * @param dbType
     */
    public static void setDbType(DBTypeEnum dbType) {
        DATASOURCES.set(dbType.WRITE);
    }

    /*
    * 获取当前数据源
    * @return DBTypeEnum
    * */
    public static DBTypeEnum getDbType() {
        return DATASOURCES.get();
    }

    /**
     * 清除上下文数据
     */
    public static void clearDbType() {
        DATASOURCES.remove();
    }
}


自定义事务管理器

public class DynamicDataSourceTransactionManager extends DataSourceTransactionManager {
    /**
     * 设置数据源
     */
    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        boolean readOnly = definition.isReadOnly();
        if (readOnly)
            DbContextHolder.setDbType(DBTypeEnum.READ);
        DbContextHolder.setDbType(DBTypeEnum.WRITE);
        super.doBegin(transaction, definition);
    }

    /**
     * 清理本地线程数据源
     */
    @Override
    protected void doCleanupAfterCompletion(Object transaction) {
        super.doCleanupAfterCompletion(transaction);
        DbContextHolder.clearDbType();
    }
}

mybatis插件(拦截器)

@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 DynamicPlugin implements Interceptor {
    private static final Logger _log = LoggerFactory.getLogger(DynamicPlugin.class);
    private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";
    private static final Map<String, DBTypeEnum> cachaMap = new HashMap<String, DBTypeEnum>();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        boolean isSynchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
        if (!isSynchronizationActive) {
            MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
            DBTypeEnum dbTypeEnum = null;
            if ((dbTypeEnum = cachaMap.get(ms.getId())) == null) {
                //读方法
                if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
                    //!selectKeu为自增id查询主键(SELECT LAST_INSERT_ID)方法,使用主库
                    if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX))
                        dbTypeEnum = DBTypeEnum.WRITE;
                    else {
                        BoundSql boundSql = ms.getSqlSource().getBoundSql(invocation.getArgs()[1]);
                        String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
                        if (sql.matches(REGEX))
                            dbTypeEnum = DBTypeEnum.WRITE;
                        dbTypeEnum = DBTypeEnum.READ;
                    }
                } else {
                    dbTypeEnum = DBTypeEnum.WRITE;
                }
                _log.warn("设置方法[{}] use [{}] Strategy, SqlCommandType [{}]..", ms.getId(), dbTypeEnum.name(), ms.getSqlCommandType().name());
                cachaMap.put(ms.getId(), dbTypeEnum);
            }
            DbContextHolder.setDbType(dbTypeEnum);
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

下载地址

百度网盘
密码: