博客信息

多数据源切换

发布时间:『 2020-09-18 05:42』  博客类别:解决方案  阅读(692)
先来说说业务场景
飞刀教育下属三个子项目(子公司),岳阳职院、娄底职院、常德职院
三个子公司分别部署了三个应用程序,也就是说一套代码部署到了三台服务器上,又因为各子公司的公司业务不同,所以数据库分别部署,也就是说岳阳职院、娄底职院、常德职院的子公司业务库是分开的,如图所示;
但是因为岳阳职院、娄底职院、常德职院是属于同一个集团公司,流媒体一般与业务数据是隔离的,所以有了中心库;
中心库:在设计上主要存放流媒体文件,所在的服务器是文件服务器,三个子公司的流媒体文件都会存放在这里;

小李飞刀_解决方案



后续因为中心库数据量过大,文件服务器服务器资源即将耗尽,所以对其进行了数据库层面的重构,重构后的架构图如下

小李飞刀_解决方案


随后因为需要打通各个子项目的业务,所以将业务库1、业务库2、业务库3合成一个
下面需求来了,飞刀教育的总部在岳阳,现在需要发送文件到子公司娄底和常德,那么就需求而言,就有频繁切换数据源的问题,具体流程如下图

小李飞刀_解决方案


step1:岳阳人员新建了文件相关事项,此时在业务库1中,有了事项相关的基本信息,某一字段字段1(字段值为 新数据源文件库1,附件表,附件唯一编号)关联了新库附件记录,在文件库1(新库)中有了附件记录
step2:岳阳人员将附件相关信息发送,附件从文件库1到中心库,此时业务库的另一字段字段2值发生改变,字段值为(旧数据源中心库,附件表,附件唯一编号)
step3.1:常德人员从业务库中签收相关事项,马上执行step4.1,读取中心库中的文件到文件库2中,并且删除中心库中的文件,此时字段2值发生改变,
字段值为(新数据源文件库2,附件表,附件唯一编号)
step3.2:娄底人员从业务库中签收相关事项,马上执行step4.2,读取中心库中的文件到文件库3中,并且删除中心库中的文件,此时字段3值发生改变,
字段值为(新数据源文件库3,附件表,附件唯一编号)
最终结果的结果是,岳阳看附件内容通过字段1,读的文件库1的数据,常德看附件内容通过字段2,读的是文件库2的数据,娄底看附件内容通过字段3,读的是文件库3的数据;
当不需要子项目文件流转的时候,各子项目就不需要和中心库打交道


业务说完了,接下来,我们来说重点,如何实现多数据源切换


第一步:准备三个数据源
我这里分别是本地数据源,docker 启动了两个mysql容器,端口分别是3308、3309;
容器创建参考下面命令
docker run -p 3309:3306 --name mysql3   -di \
-v /home/javaxl/data/mysql3/mysql.conf.d/:/etc/mysql/mysql.conf.d/ \
-v /home/javaxl/data/mysql3/data/:/var/lib/mysql \
-v /home/javaxl/data/mysql3/log/:/var/log -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7

以下为修改大小写配置,让mysql容器表名字段不区分大小写,两个容器都改
docker cp /home/javaxl/data/mysql3/mysql.conf.d/mysqld.cnf mysql2:/etc/mysql/mysql.conf.d/

更改mysqld.cnf的配置
lower_case_table_names=1

docker restart 容器名,然后mysql测试一下

小李飞刀_解决方案



第二步:多数据源类的编写

package com.javaxl.ssm.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @author 小李飞刀
 * @site www.javaxl.com
 */
public class DynamicDataSource  extends AbstractRoutingDataSource {
    public static final String DATA_SOURCE = "dataSource";
    public static final String DATA_SOURCE_ADMIN = "dataSourceAdmin";

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

    public static void setDataSource(String dbName) {
        contextHolder.set(DATA_SOURCE + dbName);
    }

    public static void setDataSourceAdmin() {
        contextHolder.set(DATA_SOURCE_ADMIN);
    }

    public static String getCustomerType() {
        return contextHolder.get();
    }

    public static void clearCustomerType() {
        contextHolder.remove();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return getCustomerType();
    }
}


第三步:更改spring-mybatis.xml的配置

将ssm的整合文件做稍微的修改,修改成下面这样即可

<bean id="dataSourceAdmin" class="org.apache.commons.dbcp2.BasicDataSource"
          destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <!--初始连接数-->
        <property name="initialSize" value="10"/>
        <!--最大活动连接数-->
        <property name="maxTotal" value="100"/>
        <!--最大空闲连接数-->
        <property name="maxIdle" value="50"/>
        <!--最小空闲连接数-->
        <property name="minIdle" value="10"/>
        <!--设置为-1时,如果没有可用连接,连接池会一直无限期等待,直到获取到连接为止。-->
        <!--如果设置为N(毫秒),则连接池会等待N毫秒,等待不到,则抛出异常-->
        <property name="maxWaitMillis" value="-1"/>
    </bean>
    <bean id="dataSource_3308" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="jdbc:mysql://47.100.191.44:3308/work?useUnicode=true&characterEncoding=UTF-8&useSSL=false"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="dataSource_3309" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="jdbc:mysql://47.100.191.44:3309/work?useUnicode=true&characterEncoding=UTF-8&useSSL=false"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="dataSource" class="com.javaxl.ssm.datasource.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String" >
                <entry key="dataSourceAdmin" value-ref="dataSourceAdmin"/>
                <entry key="dataSource_3308" value-ref="dataSource_3308"/>
                <entry key="dataSource_3309" value-ref="dataSource_3309"/>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="dataSourceAdmin"/>
    </bean>


第四步:测试代码的编写,maven逆向生成代码,关键代码如下


xml文件

 select 
    <include refid="Base_Column_List" />
    from t_dynamicdatasource
    where id = #{id,jdbcType=INTEGER}


service

package com.javaxl.ssm.service.impl;

import com.javaxl.ssm.datasource.DynamicDataSource;
import com.javaxl.ssm.entity.DynamicDatasourceEntity;
import com.javaxl.ssm.mapper.DynamicDatasourceEntityMapper;
import com.javaxl.ssm.service.DynamicDatasourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author 小李飞刀
 * @site www.javaxl.com
 */
@Service
public class DynamicDatasourceServiceImpl implements DynamicDatasourceService {
    @Autowired
    private DynamicDatasourceEntityMapper dynamicDatasourceEntityMapper;
    @Override
    public DynamicDatasourceEntity selectByPrimaryKey(Integer id) {
        return dynamicDatasourceEntityMapper.selectByPrimaryKey(id);
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public DynamicDatasourceEntity getDataBy3309(Integer id) {
        DynamicDatasourceEntity dynamicDatasourceEntity = null;
        try {
            DynamicDataSource.setDataSource("_3309");
            dynamicDatasourceEntity = dynamicDatasourceEntityMapper.selectByPrimaryKey(id);
        }finally {
            DynamicDataSource.clearCustomerType();
            DynamicDataSource.setDataSourceAdmin();
        }
        return dynamicDatasourceEntity;
    }
}


controller

package com.javaxl.ssm.controller;

import com.javaxl.ssm.datasource.DynamicDataSource;
import com.javaxl.ssm.entity.DynamicDatasourceEntity;
import com.javaxl.ssm.service.DynamicDatasourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 小李飞刀
 * @site www.javaxl.com
 */
@RestController
@RequestMapping("/dynamicDatasource")
public class DynamicDatasourceController {
    @Autowired
    private DynamicDatasourceService dynamicDatasourceService;

    /**
     * 从主数据源获取数据
     * @param id
     * @return
     */
    @GetMapping("/getDataByAdmin/{id}")
    public DynamicDatasourceEntity getDataByAdmin(@PathVariable(value = "id") Integer id ){
        return dynamicDatasourceService.selectByPrimaryKey(id);
    }


    /**
     * 从从数据源jdbc:mysql://47.100.191.44:3308/work获取数据
     * @param id
     * @return
     */
    @GetMapping("/getDataBy3308/{id}")
    public DynamicDatasourceEntity getDataBy3308(@PathVariable(value = "id") Integer id ){
        DynamicDatasourceEntity dynamicDatasourceEntity = null;
        try {
            DynamicDataSource.setDataSource("_3308");
            dynamicDatasourceEntity = dynamicDatasourceService.selectByPrimaryKey(id);
        }finally {
            DynamicDataSource.setDataSourceAdmin();
        }
        return dynamicDatasourceEntity;
    }

    /**
     * 从从数据源jdbc:mysql://47.100.191.44:3309/work获取数据
     * @param id
     * @return
     */
    @GetMapping("/getDataBy3309/{id}")
    public DynamicDatasourceEntity getDataBy3309(@PathVariable(value = "id") Integer id ){
        return dynamicDatasourceService.getDataBy3309(id);
    }
}


注意:三个数据源都有t_DynamicDataSource这张表


第五步:测试

小李飞刀_解决方案


附录:

docker 容器 mysql5.7 忘记root密码 重置密码

# 进入容器
docker exec -it mysql bash
# 设置跳过权限表的加载 
# 警告:这就意味着任何用户都能登录进来,并进行任何操作,相当不安全。
echo "skip-grant-tables" >> /etc/mysql/conf.d/docker.cnf
# 退出容器
exit
# 重启容器
docker restart mysql
# 再次进入容器
docker exec -it mysql bash
# 登录 mysql(无需密码)
mysql -uroot 
# 更新权限
flush privileges;
# 修改密码
alter user 'root'@'localhost' identified by '123456';
# 退出mysql
exit
# 替换掉刚才加的跳过权限表的加载参数
sed -i "s/skip-grant-tables/ /" /etc/mysql/conf.d/docker.cnf
# 退出容器
exit
# 重启容器
docker restart mysql


设置完可以用新设置的密码“123456”进行登录


如果登录出错,可以再进入mysql容器刷新权限

flush privileges;


docker启动mysql8容器设置不区分大小写

 docker run --name mysq.db -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d -v /home/mysql/:/var/lib/mysql mysql --lower_case_table_names=1


注:lower_case_table_names=1 只能在初始化时配置


over ......


关键字:     解决方案       ssm       aop       多数据源  

备案号:湘ICP备19000029号

Copyright © 2018-2019 javaxl晓码阁 版权所有