有一定开发经验的同学都知道,在业务发展到一定瓶颈的时候,单数据源是无法支撑应用正常的读操作以及写操作,这个时候我们就需要对应用进行改造,实现 读写 分离的方式来优化整个应用的架构,今天这篇文章就带大家了解mybatis如何实现读写分离的功能。

准备工作

各个组件的版本信息

SpringBoot 2.1.0.RELEASE

mybatis 2.1.3

mysql 5.7

目录结构

这里我们实现的是mybatis动态数据源加载,具体思路:

自定义SqlSession模板,继承mybatis中的SqlSessionTemplate并实现具体方法,这里自定义了CustomSqlSessionTemplate类用来设置具体加载的数据源。使用HashMap容器来存储相对应的数据源名称,比如设置 “写” 数据源,它的key可以对应为“write”,设置“读”数据源,它的key可以对应为“read”。在使用具体数据源的时候,可以使用当前线程的变量ThreadLocal获取相对应的数据源,然后进行CRUD操作。

我们先来看下具体目录结构:

上述文件中,我们只需要关心datasource目录下,动态数据源的实现。接下来我们来分析下具体文件内容

applcation.yml


spring: datasource: write: db: url: jdbc:mysql://127.0.0.1:3306/mybatis?autoReconnect=true&useSSL=false&connectTimeout=10000 username: root password: mengxi read: db: url: jdbc:mysql://127.0.0.1:3306/mybatis1?autoReconnect=true&useSSL=false&connectTimeout=10000 username: root password: mengxi server: port: 8001

上面的配置文件定义了两个数据源,一个 write、read。这里笔者将write数据源设置为主数据源,也就是在Spring加载配置文件的时候将write数据源设置为当前数据源。如果数据源较多的话,可以设置一个动态数据源表,在加载的时候首先遍历动态数据源表中的数据,然后对所有数据源进行加载。

mybatis数据源配置类

package com.mengxi.summary.multiple.mybatis.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.mengxi.summary.multiple.mybatis.config.datasource.CustomSqlSessionTemplate;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description mysql对应的mybatis配置信息
 * @Author mengxi
 * @date 2020-07-15 10:28
 */
@Configuration
@MapperScan(basePackages = {"com.mengxi.summary.multiple.mybatis.mapper"}, sqlSessionTemplateRef = "sqlSessionTemplate")
public class MysqlForMybatisConfiguration {

    @Value("${spring.datasource.write.db.url}")
    private String url;
    @Value("${spring.datasource.write.db.username}")
    private String username;
    @Value("${spring.datasource.write.db.password}")
    private String password;
    /**
     * mybatis mapper resource 路径
     */
    private final static String MAPPER_PATH = "classpath:mapper/*.xml";

    public static final Integer INIT_SIZE = 5;
    public static final Integer MIN_IDLE = 10;
    public static final Integer MAX_ACTIVE = 100;
    public static final Integer MAX_WAIT = 60000;

    @Primary
    @Bean(name = "basicDataSource")
    public DataSource basicDataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);

        // 配置初始化大小、最小、最大
        druidDataSource.setInitialSize(INIT_SIZE);
        druidDataSource.setMinIdle(MIN_IDLE);
        druidDataSource.setMaxActive(MAX_ACTIVE);

        druidDataSource.setPoolPreparedStatements(false);

        // 配置获取连接等待超时的时间
        druidDataSource.setMaxWait(MAX_WAIT);

        // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        druidDataSource.setTimeBetweenEvictionRunsMillis(2000);

        // 配置一个连接在池中最小生存的时间,单位是毫秒
        druidDataSource.setMinEvictableIdleTimeMillis(600000);
        druidDataSource.setMaxEvictableIdleTimeMillis(900000);

        druidDataSource.setTestWhileIdle(true);
        druidDataSource.setTestOnBorrow(true);
        druidDataSource.setTestOnReturn(false);

        druidDataSource.setKeepAlive(true);

        // 配置监控统计拦截的filters
        druidDataSource.setFilters("stat");
        druidDataSource.init();

        return druidDataSource;
    }

    @Bean(name = "mysqlTransactionManager")
    public DataSourceTransactionManager transactionManager() throws SQLException {
        return new DataSourceTransactionManager(basicDataSource());
    }

    @Bean(name = "mysqlSessionFactory")
    public SqlSessionFactory sqlSessionFactoryBean(@Qualifier("basicDataSource") DataSource dataSource)
        throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        //添加mapper 扫描路径
        PathMatchingResourcePatternResolver
            pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(pathMatchingResourcePatternResolver.getResources(MAPPER_PATH));
        //设置datasource
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * 数据源模板
     *
     * @param factorySystem
     * @return
     * @throws Exception
     */
    @Bean(name = "sqlSessionTemplate")
    public CustomSqlSessionTemplate sqlSessionTemplate(@Qualifier("mysqlSessionFactory")
                                                           SqlSessionFactory factorySystem) throws Exception {
        Map<Object, SqlSessionFactory> sqlSessionFactoryMap = new HashMap<>();
        sqlSessionFactoryMap.put("basic", factorySystem);
        CustomSqlSessionTemplate customSqlSessionTemplate = new CustomSqlSessionTemplate(factorySystem);
        customSqlSessionTemplate.setTargetSqlSessionFactorys(sqlSessionFactoryMap);
        return customSqlSessionTemplate;
    }

    /**
     * 获取数据源
     *
     * @param
     * @return
     */
    public DataSource getDataSource(String url, String username, String password) {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }

    public org.apache.ibatis.session.Configuration configuration() {
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);
        return configuration;
    }

    /**
     * 创建数据源
     *
     * @param dataSource
     * @return
     */
    public SqlSessionFactory createSqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setVfs(SpringBootVFS.class);
        bean.setTypeAliasesPackage("com.mengxi.summary.multiple.mybatis.domain");
        bean.setMapperLocations(
            new PathMatchingResourcePatternResolver().getResources(MAPPER_PATH));
        bean.setConfiguration(configuration());
        return bean.getObject();
    }

}

MysqlForMybatisConfiguration配置类,设置主数据源,设置对应的mapper类以及配置类,创建数据源,获取数据源等方法。

自定义SqlSeesion模板 CustomSqlSessionTemplate

package com.mengxi.summary.multiple.mybatis.config.datasource;

import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.session.*;
import org.mybatis.spring.MyBatisExceptionTranslator;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.util.Assert;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.List;
import java.util.Map;

import static java.lang.reflect.Proxy.newProxyInstance;
import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable;
import static org.mybatis.spring.SqlSessionUtils.*;

/**
 * 数据源模板
 *
 * @author mengxi
 * @date 2020年07月15日17:51:39
 */
public class CustomSqlSessionTemplate extends SqlSessionTemplate {

    private final SqlSessionFactory sqlSessionFactory;
    private final ExecutorType executorType;
    private final SqlSession sqlSessionProxy;
    private final PersistenceExceptionTranslator exceptionTranslator;

    private Map<Object, SqlSessionFactory> targetSqlSessionFactorys;
    private SqlSessionFactory defaultTargetSqlSessionFactory;

    public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys) {
        this.targetSqlSessionFactorys = targetSqlSessionFactorys;
    }

    public Map<Object, SqlSessionFactory> getTargetSqlSessionFactorys() {
        return targetSqlSessionFactorys;
    }

    public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {
        this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;
    }

    public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
    }

    public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
        this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration()
            .getEnvironment().getDataSource(), true));
    }

    public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
                                    PersistenceExceptionTranslator exceptionTranslator) {

        super(sqlSessionFactory, executorType, exceptionTranslator);

        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;

        this.sqlSessionProxy = (SqlSession)newProxyInstance(
            SqlSessionFactory.class.getClassLoader(),
            new Class[] {SqlSession.class},
            new SqlSessionInterceptor());

        this.defaultTargetSqlSessionFactory = sqlSessionFactory;
    }

    @Override
    public SqlSessionFactory getSqlSessionFactory() {
        SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(
            DataSourceContextHolder.getDatasourceType());
        if (targetSqlSessionFactory != null) {
            return targetSqlSessionFactory;
        } else if (defaultTargetSqlSessionFactory != null) {
            return defaultTargetSqlSessionFactory;
        } else {
            Assert.notNull(targetSqlSessionFactorys,
                "Property 'targetSqlSessionFactorys' or 'defaultTargetSqlSessionFactory' are required");
            Assert.notNull(defaultTargetSqlSessionFactory,
                "Property 'defaultTargetSqlSessionFactory' or 'targetSqlSessionFactorys' are required");
        }
        return this.sqlSessionFactory;
    }

    @Override
    public Configuration getConfiguration() {
        return this.getSqlSessionFactory().getConfiguration();
    }

    @Override
    public ExecutorType getExecutorType() {
        return this.executorType;
    }

    @Override
    public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
        return this.exceptionTranslator;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T selectOne(String statement) {
        return this.sqlSessionProxy.<T>selectOne(statement);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T selectOne(String statement, Object parameter) {
        return this.sqlSessionProxy.<T>selectOne(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
        return this.sqlSessionProxy.<K, V>selectMap(statement, mapKey);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
        return this.sqlSessionProxy.<K, V>selectMap(statement, parameter, mapKey);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
        return this.sqlSessionProxy.<K, V>selectMap(statement, parameter, mapKey, rowBounds);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <E> List<E> selectList(String statement) {
        return this.sqlSessionProxy.<E>selectList(statement);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <E> List<E> selectList(String statement, Object parameter) {
        return this.sqlSessionProxy.<E>selectList(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        return this.sqlSessionProxy.<E>selectList(statement, parameter, rowBounds);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void select(String statement, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, handler);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void select(String statement, Object parameter, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, parameter, handler);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int insert(String statement) {
        return this.sqlSessionProxy.insert(statement);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int insert(String statement, Object parameter) {
        return this.sqlSessionProxy.insert(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int update(String statement) {
        return this.sqlSessionProxy.update(statement);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int update(String statement, Object parameter) {
        return this.sqlSessionProxy.update(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int delete(String statement) {
        return this.sqlSessionProxy.delete(statement);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int delete(String statement, Object parameter) {
        return this.sqlSessionProxy.delete(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T getMapper(Class<T> type) {
        return getConfiguration().getMapper(type, this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void commit() {
        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void commit(boolean force) {
        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void rollback() {
        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void rollback(boolean force) {
        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close() {
        throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void clearCache() {
        this.sqlSessionProxy.clearCache();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Connection getConnection() {
        return this.sqlSessionProxy.getConnection();
    }

    /**
     * {@inheritDoc}
     *
     * @since 1.0.2
     */
    @Override
    public List<BatchResult> flushStatements() {
        return this.sqlSessionProxy.flushStatements();
    }

    /**
     * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
     * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to
     * the {@code PersistenceExceptionTranslator}.
     */
    private class SqlSessionInterceptor implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            final SqlSession sqlSession = getSqlSession(
                CustomSqlSessionTemplate.this.getSqlSessionFactory(),
                CustomSqlSessionTemplate.this.executorType,
                CustomSqlSessionTemplate.this.exceptionTranslator);
            try {
                Object result = method.invoke(sqlSession, args);
                if (!isSqlSessionTransactional(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory())) {
                    // force commit even on non-dirty sessions because some databases require
                    // a commit/rollback before calling close()
                    sqlSession.commit(true);
                }
                return result;
            } catch (Throwable t) {
                Throwable unwrapped = unwrapThrowable(t);
                if (CustomSqlSessionTemplate.this.exceptionTranslator != null
                    && unwrapped instanceof PersistenceException) {
                    Throwable translated = CustomSqlSessionTemplate.this.exceptionTranslator
                        .translateExceptionIfPossible((PersistenceException)unwrapped);
                    if (translated != null) {
                        unwrapped = translated;
                    }
                }
                throw unwrapped;
            } finally {
                closeSqlSession(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory());
            }
        }
    }

}

设置自定义SqlSeesion模板,这个类中我们只需要关注 setTargetSqlSessionFactorysgetSqlSessionFactory 方法。

setTargetSqlSessionFactorys

在自定义模板中,设置变量 targetSqlSessionFactorys,也就是上述说的 HashMap 容器,在load数据源的时候设置对应的数据源。

getSqlSessionFactory

SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(
            DataSourceContextHolder.getDatasourceType());

实现动态数据源其实就是上面这段代码,在获取 目标数据源工厂的时候,获取当前线程设置的数据源类型即可。

数据库上下文切换类 DataSourceContextHolder

package com.mengxi.summary.multiple.mybatis.config.datasource;

import java.util.ArrayList;
import java.util.List;

/**
 * 数据库上下文切换
 *
 * @author mengxi
 * @date 2020年07月15日17:52:12
 */
public class DataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static List<String> dataSourceKeys = new ArrayList<>();

    /**
     * 设置当前数据库。
     *
     * @param dbType
     */
    public static void setDatasourceType(String dbType) {
        contextHolder.set(dbType);
    }

    public static void setDefaultDataSource() {
    }

    /**
     * 取得当前数据源。
     *
     * @return
     */
    public static String getDatasourceType() {
        String str = contextHolder.get();
        return str;
    }

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

    /**
     * @param dataSourceId
     * @return
     */
    public static boolean containsDataSource(String dataSourceId) {
        return dataSourceKeys.contains(dataSourceId);
    }

}

在进行业务库操作时,通过调用setDatasourceType设置当前数据源类型,是读库还是写库

数据源管理类 DataSourceManager

package com.mengxi.summary.multiple.mybatis.config.datasource;

import com.mengxi.summary.multiple.mybatis.config.MysqlForMybatisConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 数据源管理
 *
 * @author mengxi
 * @date 2020年07月15日18:00:55
 */
@Slf4j
@Component
public class DataSourceManager {

    @Value("${spring.datasource.read.db.url}")
    private String url;
    @Value("${spring.datasource.read.db.username}")
    private String username;
    @Value("${spring.datasource.read.db.password}")
    private String password;

    @Resource
    private CustomSqlSessionTemplate sqlSessionTemplate;
    @Resource
    private MysqlForMybatisConfiguration mysqlForMybatisConfiguration;

    /**
     * 初始或者重载入数据源
     *
     * @throws Exception
     */
    @PostConstruct
    public void loadDataSource() throws Exception {
        //后续数据源可以设置为数据库中保存,此处可以循环加载数据源
        Map<Object, SqlSessionFactory> newSqlSessionFactoryMap = new HashMap<>(16);
        Map<Object, SqlSessionFactory> sqlSessionFactoryMap = sqlSessionTemplate.getTargetSqlSessionFactorys();
        //如果存在多集合 可使用 for() 循环来配置数据源
        SqlSessionFactory sqlSessionFactory = mysqlForMybatisConfiguration.createSqlSessionFactory(
            mysqlForMybatisConfiguration.getDataSource(url, username, password));
        newSqlSessionFactoryMap.put("read", sqlSessionFactory);
        newSqlSessionFactoryMap.putAll(sqlSessionFactoryMap);
        this.sqlSessionTemplate.setTargetSqlSessionFactorys(newSqlSessionFactoryMap);
    }

}

数据源管理类设置读库的数据源加载内容,在Spring加载的时候对当前读库数据源进行初始化,@PostConstruct注解的作用就是初始化实例。从loadDataSource方法注释可以看出,可以设置“动态数据源表”,在初始化的时候读取所有数据源信息进行加载,然后利用for循环,将加载好的数据源信息容器设置到 自定义SqlSeesion模板CustomSqlSessionTemplate中,在使用的时候,通过当前线程变量直接get出所需要使用的数据源类型即可。

下面我们来看看如何在业务代码中具体使用动态数据源。

用户实现类 UserServiceImpl

package com.mengxi.summary.multiple.mybatis.serivce.impl;

import com.mengxi.summary.multiple.mybatis.config.datasource.DataSourceContextHolder;
import com.mengxi.summary.multiple.mybatis.domain.User;
import com.mengxi.summary.multiple.mybatis.mapper.UserMapper;
import com.mengxi.summary.multiple.mybatis.serivce.UserService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @Description 用户实现类
 * @Author mengxi
 * @date 2020-07-16 17:12
 * @Copyright 2019 Alibaba.com All right reserved.
 */
@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public User selectUser() {
        DataSourceContextHolder.setDatasourceType("read");
        User user = userMapper.selectByPrimaryKey(1L);
        return user;
    }

    @Override
    public void insert(User user) {
        DataSourceContextHolder.setDatasourceType("basic");
        userMapper.insert(user);
    }
}

笔者在这里举了两个简单的例子
一个是 查询所有用户信息,使用的是 读库。
一个是 增加用户信息,使用的是 写库(这里笔者设置类型是 “basic”)

现场演示 UserServiceTest

package com.mengxi.summary.multiple.mybatis.serviceTest;

import com.mengxi.summary.multiple.mybatis.SummaryMultipleMybatisApplication;
import com.mengxi.summary.multiple.mybatis.domain.User;
import com.mengxi.summary.multiple.mybatis.serivce.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

/**
 * @Description 用户测试类
 * @Author mengxi
 * @date 2020-07-16 17:18
 * @Copyright 2019 Alibaba.com All right reserved.
 */
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {SummaryMultipleMybatisApplication.class})
public class UserServiceTest {
    @Resource
    private UserService userService;

    @Test
    public void userServiceTest() {
        log.info("测试数据源read,读取read库 mybatis1 user 信息..");
        User user = userService.selectUser();
        log.info("user info -----> " + user.toString());
    }

    @Test
    public void userServiceInsertTest() {
        log.info("测试数据源basic,增加baisc数据库 mybatis user 信息..");
        User user = new User();
        user.setName("add datasource basic , my database name is mybatis,add info by userServiceInsertTest");
        user.setAge("15");
        userService.insert(user);
        log.info("user id -----> " + user.getId());

    }
}

演示的方法很简单,一个读库,一个写库。

mysql数据库

先来运行写库的测试方法。

写库中存在两条内容,运行测试类,设置age为 “15”。运行日志如下:

10:06:48.579 default [main] INFO  c.m.s.m.m.s.UserServiceTest - 测试数据源basic,增加baisc数据库 mybatis user 信息..
10:06:48.956 default [main] INFO  c.m.s.m.m.s.UserServiceTest - user id -----> 4

再来运行读库的测试方法。

读库中只存在一条信息,运行测试类。运行日志如下:

10:09:33.347 default [main] INFO  c.m.s.m.m.s.UserServiceTest - 测试数据源read,读取read库 mybatis1 user 信息..
10:09:33.395 default [main] INFO  c.alibaba.druid.pool.DruidDataSource - {dataSource-2} inited
10:09:33.548 default [main] INFO  c.m.s.m.m.s.UserServiceTest - user info -----> User(id=1, name=我是read数据源,我的数据库名称是 mybatis1, age=10)

可以看到,运行读库后,将mybatis1中的数据罗列出来。

大家可以根据自己的数据源进行动态设置,可以设置一主多从等模式。

源码地址:mybaits动态数据源