Mybatis多数据源配置

1.背景

最近项目中碰到需要配置多个数据库的问题,其中两个或多个数据库是master-slave的关系,比如有mysql搭建一个 master-master,其后又带有多个slave;或者采用MHA搭建的master-slave复制;以下介绍两种配置方式,
1.使用Spring配置多个数据源;
2.基于 AbstractRoutingDataSource 和 AOP 的多数据源的配置实现。

2.实现方式

2.1. 使用Spring配置多个数据源

配置方式实现方式

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
">
    <context:component-scan base-package="com.seed.app" />
    <!-- 引入属性文件 -->
    <context:property-placeholder location="classpath:config/config.properties" />

    <!-- 配置数据源 -->
    <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc_url}" />
        <property name="username" value="${jdbc_username}" />
        <property name="password" value="${jdbc_password}" />
        <!-- 初始化连接大小 -->
        <property name="initialSize" value="0" />
        <!-- 连接池最大使用连接数量 -->
        <property name="maxActive" value="20" />
        <!-- 连接池最大空闲 -->
        <property name="maxIdle" value="20" />
        <!-- 连接池最小空闲 -->
        <property name="minIdle" value="0" />
        <!-- 获取连接最大等待时间 -->
        <property name="maxWait" value="60000" />
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:config/mybatis-config.xml" />
        <property name="mapperLocations" value="classpath*:config/mappers/**/*.xml" />
    </bean>

    <!-- Transaction manager for a single JDBC DataSource -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- 使用annotation定义事务 -->
    <tx:annotation-driven transaction-manager="transactionManager" />

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="net.aazj.mapper" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

    <!-- Enables the use of the @AspectJ style of Spring AOP -->
    <aop:aspectj-autoproxy/>

    <!-- ===============第二个数据源的配置=============== -->
    <bean name="dataSource_2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc_url_2}" />
        <property name="username" value="${jdbc_username_2}" />
        <property name="password" value="${jdbc_password_2}" />
        <!-- 初始化连接大小 -->
        <property name="initialSize" value="0" />
        <!-- 连接池最大使用连接数量 -->
        <property name="maxActive" value="20" />
        <!-- 连接池最大空闲 -->
        <property name="maxIdle" value="20" />
        <!-- 连接池最小空闲 -->
        <property name="minIdle" value="0" />
        <!-- 获取连接最大等待时间 -->
        <property name="maxWait" value="60000" />
    </bean>

    <bean id="sqlSessionFactory_slave" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource_2" />
        <property name="configLocation" value="classpath:config/mybatis-config-2.xml" />
        <property name="mapperLocations" value="classpath*:config/mappers2/**/*.xml" />
    </bean>

    <!-- Transaction manager for a single JDBC DataSource -->
    <bean id="transactionManager_2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource_2" />
    </bean>

    <!-- 使用annotation定义事务 -->
    <tx:annotation-driven transaction-manager="transactionManager_2" />

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="net.aazj.mapper2" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory_2"/>
    </bean>
    </beans>

以上配置方式配置了两个 dataSource,两个sqlSessionFactory,两个transactionManager,以及通过MapperScannerConfigurer 的配置——使用sqlSessionFactoryBeanName属性,注入不同的sqlSessionFactory的名称,这样的话,就为不同的数据库对应的 mapper 接口注入了对应的 sqlSessionFactory。

缺点:但是这样的配置情况下多个数据库的这种配置是不支持分布式事务的,也就是同一个事务中,不能操作多个数据库,不灵活。
优点:这种配置方式的优点是很简单。
结论:对于master-slave类型的多数据源配置而言不太适应,master-slave性的多数据源的配置,需要特别灵活,需要根据业务的类型进行细致的配置。比如对于一些耗时特别大的select语句,我们希望放到slave上执行,而对于update,delete等操作肯定是只能在master上执行的,另外对于一些实时性要求很高的select语句,我们也可能需要放到master上执行——比如有一个实际场景是客户去商城购买一件兵器,购买操作的很定是master,同时购买完成之后,需要重新查询出我所拥有的兵器和金币,那么这个查询可能也需要在master上执行,而不能放在slave上去执行,因为slave上可能存在延时,结果客户卖完之后发现找不到自己的大刀。

2.2.基于 AbstractRoutingDataSource 和 AOP 的多数据源的配置实现

手动切换实现代码
1. 定义一个类继承 AbstractRoutingDataSource

重写determineCurrentLookupKey()方法来实现数据源切换功能
* 1.自定义一个类扩展AbstractRoutingDataSource抽象类
* 2.相当于数据源DataSourcer的路由中介,可以实现在项目运行时根据相应key值切换到对应的数据源DataSource上
* 3.AbstractRoutingDataSource的getConnection()方法调用了determineTargetDataSource()
* 4.该方法里使用到了determineCurrentLookupKey()方法,它是AbstractRoutingDataSource类的抽象方法,也是实现数据源切换要扩展的方法,该方法的返回值就是项目中所要用的DataSource的key值
* 5.拿到该key后就可以在resolvedDataSource中取出对应的DataSource,如果key找不到对应的DataSource就使用默认的数据源

public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        System.out.println(" 当前数据源为: " + DataSourceTypeManager.get());
        return DataSourceTypeManager.get();
    }

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

    public static void setDataSourceKey(String dataSource) {
        dataSourceKey.set(dataSource);
    }
}
/**
 * Created by zhoudl on 2018/6/23.
 * 数据源操作类
 */
public class DataSourceTypeManager {


    private static final ThreadLocal<DataSources> dataSourceTypes = new ThreadLocal<DataSources>(){
        @Override
        protected DataSources initialValue(){
            return DataSources.MASTER;
        }
    };
    /**
     * 获取当前线程的数据源路由的key
     */
    public static DataSources get() {
        return dataSourceTypes.get();
    }

    /**
     * 绑定当前线程数据源路由的key
     * 使用完成后必须调用removeRouteKey()方法删除
     */
    public static void set(DataSources dataSourceType) {
        dataSourceTypes.set(dataSourceType);
    }

    /**
     * 删除与当前线程绑定的数据源路由的key
     */
    public static void removeRouteKey()    {
        dataSourceTypes.remove();
    }

}

2.定义一个枚举类,用于标识数据库名称

/**
 * 枚举类
 * 标识数据源的类别:master/slave
 * master 主库
 * slave 副库
 * Created by zhoudl on 2018/6/23.
 */
public enum DataSources {
    MASTER, SLAVE
}

3.为了方便测试,再定义一张表(如何定义请随意,只要保证和mapper接口一致即可)

CREATE TABLE `apply_person_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(18) DEFAULT NULL,
  `telphone` varchar(13) DEFAULT NULL,
  `telphone_md5` varchar(32) DEFAULT NULL,
  `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8

4.映射对应的Mapper接口

/**
 * Created by zhoudl on 2018/6/23.
 */
public interface UserDao {

    String selectUserNameById(String id) throws SQLException;

    String queryUserNameByUid(int uid)throws SQLException;

}

5.对应接口的XML文件 UserInfoMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.seed.app.dao.UserDao">

    <!-- test 库 slave数据源 -->
     <select id="selectUserNameById" parameterType="java.lang.String" resultType="java.lang.String">
        SELECT username FROM t_user where userid = #{uid}
     </select>

    <!-- extension_pro_qrcode 库 master 数据源 -->
    <select id="queryUserNameByUid" parameterType="java.lang.Integer" resultType="java.lang.String">
        SELECT * FROM apply_person_info WHERE id = #{uid}
    </select>
</mapper>

6.修改SpringXML文件配置两个数据源 Spring-mybtis.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
">
    <!--开启AOP-->
    <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

    <!--扫描切面-->
    <context:component-scan base-package="com.seed.app.annotion" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
    </context:component-scan>


     <!-- 配置数据源Master -->
    <bean name="dataSourceMaster" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc_url}" />
        <property name="username" value="${jdbc_username}" />
        <property name="password" value="${jdbc_password}" />
        <!-- 初始化连接大小 -->
        <property name="initialSize" value="0" />
        <!-- 连接池最大使用连接数量 -->
        <property name="maxActive" value="20" />
        <!-- 连接池最小空闲 -->
        <property name="minIdle" value="0" />
        <!-- 获取连接最大等待时间 -->
        <property name="maxWait" value="60000" />
    </bean>    
    <!-- 配置数据源Slave -->
    <bean name="dataSourceSlave" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc_url_slave}" />
        <property name="username" value="${jdbc_username_slave}" />
        <property name="password" value="${jdbc_password_slave}" />
        <!-- 初始化连接大小 -->
        <property name="initialSize" value="0" />
        <!-- 连接池最大使用连接数量 -->
        <property name="maxActive" value="20" />
        <!-- 连接池最小空闲 -->
        <property name="minIdle" value="0" />
        <!-- 获取连接最大等待时间 -->
        <property name="maxWait" value="60000" />
    </bean>    
    <bean id="dataSource" class="com.seed.app.datasources.ThreadLocalRountingDataSource">
        <property name="defaultTargetDataSource" ref="dataSourceMaster" />
        <property name="targetDataSources">
            <map key-type="com.seed.app.datasources.DataSources">
                <entry key="MASTER" value-ref="dataSourceMaster"/>
                <entry key="SLAVE" value-ref="dataSourceSlave"/>
                <!-- 这里还可以加多个dataSource -->
            </map>
        </property>
    </bean>    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSource" />
      <!-- <property name="configLocation" value="classpath:config/mybatis-config.xml" />
      <property name="mapperLocations" value="classpath*:config/mappers/**/*.xml" /> -->
      <property name="mapperLocations" value="classpath*:com/seed/app/dao/mapper/**/*.xml" />
    </bean>    
    <!-- Transaction manager for a single JDBC DataSource -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>    
    <!-- 使用annotation定义事务 -->
    <tx:annotation-driven transaction-manager="transactionManager" />
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
      <property name="basePackage" value="com.seed.app.dao" />
    </bean>
    </beans>

7.对应Service UserService.java 及UserServiceImpl.xml

/**
 * Created by zhoudl on 2018/6/23.
 */
public interface UserService {
    /** slave 数据源 */
    void selectUserNameById(String string);

    /** master 数据源 */
    void queryUserNameByUid(int i);

    /**
     * 使用注解指定数据源
     */
    //@DataSourceKey("MASTER")
    void queryUserNameByUidToAnnotion(int userId);

    /**
     * 使用注解指定数据源
     */
    //@DataSourceKey("SLAVE")
    void selectUserNameByIdToAnnotion(String string);

}

8.创建测试类 测试手动切换数据源 ChangeDbSource.java

/**
 * Created by zhoudl on 2018/6/23.
 * 手动切换数据源
 */
public class ChangeDbSource {
    static ApplicationContext app = null;

    static {
        app = new ClassPathXmlApplicationContext(new String[]{"classpath:spring/spring.xml", "classpath:spring/spring-mybatis.xml"});
    }

    UserService userService = app.getBean(UserService.class);

    public static void main(String[] args) {
        new ChangeDbSource().run();
    }

    private void run() {
        //slave库
        DataSourceTypeManager.set(DataSources.SLAVE);
        userService.selectUserNameById("1");
        //master库
        // 使用reset方法快速切换
        //DataSourceTypeManager.reset();
        // 调用set方法切换数据源
        DataSourceTypeManager.set(DataSources.MASTER);
        userService.queryUserNameByUid(27);
    }
}

运行结果
这里写图片描述

使用AOP自动切换数据源
1.定义一个切面类 MultipleDataSourceAspectAdvice.java

/**
 * Created by zhoudl on 2018/6/23.
 * 通过增加一个切面去拦截servcie层在调用mybatis生成的接口时,来切换数据源
 * 从而省去每次调用mybatis生成的接口时都要手动注明数据源
 * 此处使用的方式:将调用不同数据源的service配置在不同的包中 然后拦截包找到的对应应该执行的数据源
 * 也可每次切换数据源时手动切换(dataSourceTypes.set(dataSourceType))或者使用自定义注解方式切换
 * @Order(0) 执行sql前就切换数据源
 */
@Aspect
@Component
@Order(0)
public class MultipleDataSourceAspectAdvice {

    @Pointcut("execution(public * com.seed.app.service.master..*.*(..))")
    public void invanyMethod() {}

    @Pointcut("execution(public * com.seed.app.service.slave..*.*(..))")
    public void galaxyanyMethod() {}

    @Before("invanyMethod()")
    public void beforeinv(JoinPoint jp) {
        Object[] args = jp.getArgs();
        // 如果获取切面失败 默认使用 master 数据源
        if(args==null){
            System.out.println("获取切面失败,默认切换数据源到 + " + DataSources.MASTER);
            DataSourceTypeManager.set(DataSources.MASTER);
        }
        System.out.println("切换数据源到 + " + DataSources.MASTER);
        DataSourceTypeManager.set(DataSources.MASTER);
    }

    @Before("galaxyanyMethod()")
    public void beforegalaxy(JoinPoint jp) {
        System.out.println("切换数据源到 + " + DataSources.SLAVE);
        DataSourceTypeManager.set(DataSources.SLAVE);
    }
}

此时为了实现自动需要把调用MASTER和SLAVE库的业务代码放在不同的包里,AOP拦截的时候通过判断不同的包名去选择使用哪个SqlSessionFactory
2.此处从业务上区分主从库,所以在service对调用不同库的代码做了区分,新建两个包
package com.seed.app.service.master;
package com.seed.app.service.slave;

3.UserServiceMaster .java代码如下

public interface UserServiceMaster {

    /** master 数据源 */
    void queryUserNameByUid(int i);

}

4.UserServiceSlave.java代码如下

/**
 * Created by zhoudl on 2018/6/23.
 */
public interface UserService {
    /** slave 数据源 */
    void selectUserNameById(String string);

}

5.测试类 DynamicChangeDbSource.java

这里写代码片

/**
* Created by zhoudl on 2018/6/23.
* 使用AOP自动切换数据源
*/
public class DynamicChangeDbSource {

static ApplicationContext applicationContext = null;

static {
    applicationContext = new ClassPathXmlApplicationContext(new String[]{"classpath:spring/spring.xml", "classpath:spring/spring-mybatis.xml"});
}

public static void main(String[] args) {
    new DynamicChangeDbSource().run();
}

private void run() {
    UserServiceMaster userServiceMaster = applicationContext.getBean(UserServiceMaster.class);

    // 通过AOP自动切换到master库
    userServiceMaster.queryUserNameByUid(27);

    UserServiceSlave userServiceSlave = applicationContext.getBean(UserServiceSlave.class);

    // 通过AOP自动切换到slave库
    userServiceSlave.selectUserNameById("1");
}

}
测试结果
这里写图片描述

3 可以改进的地方

方式二中切换方式也可以通过自定义注解的方式,通过在业务代码上加注解,假设该注解设计为可以接受一个数据库名称当做key ,则我们可以在对应的业务方法上使用注解设置不同的key进行数据库切换。
至于说使用注解还是不同的包名或者其他方式切换数据源,仁者见仁智者见智,再次便不再赘述,如果你感兴趣的话可以自己实现。
所谓java注解,不外乎是对java类的另一种调用方式,有的人对java注解趋之若鹜,认为java注解很高级。所有的技术都有两面性,从我的理解来看注解的最终意义也就是方便程序员而已,注解本质上通过反射来实现的,我们都知道,反射是一种程序的自省机制,其实反射是破坏封装的一种方式,反射的效率很低的,对程序本身访问会造成很多的额外开销。

比如你采用Spring注解,@resource标识在一个类上面,那么程序会通过反射一遍遍的调用,首先通过class得到类对象,然后调取其中的getAnnotations()方法遍历类上的注解,一遍扫描和寻找注解,这其中就会有减慢效率,这不过是一种语法糖。

java注解的使用也有优缺点:
优点:

1、节省配置,减少配置文件大小

2、编译时即可查看正确与否,提高效率

3、保存在 class 文件中,降低维护成本

4、无需工具支持,无需解析 

5、提升开发效率。

缺点:

1、增加了程序的耦合性,因为注解保存在class文件中,而且比较分散

2、若要对配置进行修改需要重新编译

当类有继承接口问题时,尽量采用xml方式,当更改实现类时,可以方便修改xml实现扩展功能,而无非修改代码,当然XML方式也有自己的优缺点(来源自网络,参考 [https://blog.csdn.net/lengxingxing_/article/details/65441337]):

XML
目前web应用中几乎都使用xml作为配置项,例如我们常用的框架Struts、Spring、Hibernate、IBatis等等都采用xml作为配置。
xml之所以这么流行,是因为它的很多优点是其它技术的配置所无法替代的。
  1、xml作为可扩展标记语言最大的优势在于开发者能够为软件量身定制适用的标记,使代码更加通俗易懂。
  2、利用xml配置能使软件更具扩展性。例如Spring将class间的依赖配置在xml中,最大限度地提升应用的可扩展性。
  3、具有成熟的验证机制确保程序正确性。利用Schema或DTD可以对xml的正确性进行验证,避免了非法的配置导致应用程序出错。
  4、修改配置而无需变动现有程序。
  虽然有如此多的好处,但毕竟没有什么万能的东西,xml也有自身的缺点。
  1、需要解析工具或类库的支持。
  2、解析xml势必会影响应用程序性能,占用系统资源。
  3、配置文件过多导致管理变得困难。
  4、编译期无法对其配置项的正确性进行验证,或要查错只能在运行期。
  5、IDE无法验证配置项的正确性无能为力。
  6、查错变得困难。往往配置的一个手误导致莫名其妙的错误。
  7、开发人员不得不同时维护代码和配置文件,开发效率变得低下。
  8、配置项与代码间存在潜规则。改变了任何一方都有可能影响另外一方。

完整demo源码一上传GitHub,需要的请移步。点击这里获取代码

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用Mybatis配置多数据源时,可以按照以下步骤进行配置。 1. 在配置文件中指定多个数据源的相关信息。比如,可以在配置文件中定义mysql数据源和Oracle数据源。 2. 创建一个配置类,用于整合Mybatis和多个数据源。在配置类中,可以使用注解@MapperScan指定Mybatis的mapper接口的扫描路径,并使用@Primary注解标注主数据源。 3. 在配置类中,可以通过注入数据源和SqlSessionFactory的方式,为每个数据源配置对应的SqlSessionFactory。同时,还可以配置多个事务管理器,并使用注解@Transactional注解标注需要事务管理的方法。 4. 在mapper接口的mapper.xml文件中,可以使用namespace指定对应的数据源,以便在使用时能够正确选择数据源。 通过以上步骤,可以实现Mybatis多数据源配置,使得项目能够同时操作多个数据库。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Mybatis配置多数据源](https://blog.csdn.net/weixin_44385360/article/details/128121969)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [mybatis多数据源配置](https://blog.csdn.net/wwwwww12432/article/details/102637703)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值