Spring动态数据源:Mybatis-plus、C3P0

在项目开发中我们可能会遇到要访问多个数据源的操作,就比如说联库查询、用一个库的数据更新另一个库。所以对于一个灵活通用的系统框架来说,一个健壮可靠的多数据源方案尤为重要。
Spring项目下多数据源方案有两类:静态多数据源和动态数据源。使用静态的方案,也就是为各个不同的数据源创建各自独立的配置在,各自的配置文件config中,让每个Mapper都有对应的config文件配置、每个数据源都有一套配置和SqlSessionFactory。在实际使用中要把每个源的DAO隔离开来,不然极容易造成系统崩溃。
动态数据源核心是使用AbstractRoutingDataSource来实现数据源的路由选择,可在运行时动态切换数据源,实现上是多个数据源,统一的配置管理。
静态多数据源特点是配置简单、实际使用僵硬死板。
动态数据源灵活、同一个DAO类可在运行时在多数据源之间切换。通过注解配置加AOP注入,使用起来十分方便。
如果在项目进行中手上没有动态数据源的配置和代码,而项目架构是临时短期的使用的话也能选择用静态配置。但是大部分时候还是推荐使用动态数据源配置。

示例项目概览

示例项目:
Spring 5.1.5
Mybatis-plus 3.2.0
C3P0 0.9.5.4

示例项目代码仓库》SSMExampleProject

项目结构
在这里插入图片描述

表结构-DDL
tabel-DDL-sql

依赖库引入

<!--    c3p0连接池  依赖库-->
        <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.4</version>
        </dependency>
        <!--        mybatis-plus 依赖库 -->
        <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>3.2.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>

配置

mybatis配置

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--<!DOCTYPE configuration PUBLIC >-->

<configuration>
    <settings>
        <setting name="cacheEnabled" value="false"/>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
</configuration>

数据源连接属性配置

database.properties

###################################### 数据库配置 ################################################
#mysql database setting
# 主数据源配置
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/exampledb
jdbc.username=root
jdbc.password=123456

# 第 2 数据源属性 mysql
jdbc.driver2=com.mysql.jdbc.Driver
jdbc.url2=jdbc:mysql://127.0.0.1:3306/test
jdbc.username2=root
jdbc.password2=123456

# 第 3 数据源属性 mysql
jdbc.driver3=com.mysql.jdbc.Driver
jdbc.url3=jdbc:mysql://127.0.0.1:3306/world
jdbc.username3=root
jdbc.password3=123456

Spring数据上下文配置

applicationContext-MyBatis-DataResource.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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
      					http://www.springframework.org/schema/aop
      					http://www.springframework.org/schema/aop/spring-aop.xsd
      					http://www.springframework.org/schema/context
      					https://www.springframework.org/schema/context/spring-context.xsd
      					http://www.springframework.org/schema/jee
      					http://www.springframework.org/schema/jee/spring-jee.xsd
                        http://www.springframework.org/schema/tx
                        http://www.springframework.org/schema/tx/spring-tx.xsd">


    <!--1 引入属性文件,在配置中占位使用 -->
    <context:property-placeholder location="classpath:database.properties" />


    <!--2 配置C3P0数据源 -->
    <bean id="PrimaryDatasource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <!--    <bean id="datasource" class="cn.CommonUtils.MybatisC3p0DatabaseSourcesFactory" destroy-method="close">-->
        <!--驱动类名 -->
        <property name="driverClass" value="${jdbc.driver}" />
        <!-- url -->
        <property name="jdbcUrl" value="${jdbc.url}" />
        <!-- 用户名 -->
        <property name="user" value="${jdbc.username}" />
        <!-- 密码 -->
        <property name="password" value="${jdbc.password}" />
        <!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数  -->
        <property name="acquireIncrement" value="8"></property>
        <!-- 初始连接池大小 -->
        <property name="initialPoolSize" value="8"></property>
        <!-- 连接池中连接最小个数 -->
        <property name="minPoolSize" value="8"></property>
        <!-- 连接池中连接最大个数 -->
        <property name="maxPoolSize" value="32"></property>
    </bean>


    <!--2 配置C3P0数据源 -->
    <bean id="MinorDatasource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <!--    <bean id="datasource" class="cn.CommonUtils.MybatisC3p0DatabaseSourcesFactory" destroy-method="close">-->
        <!--驱动类名 -->
        <property name="driverClass" value="${jdbc.driver2}" />
        <!-- url -->
        <property name="jdbcUrl" value="${jdbc.url2}" />
        <!-- 用户名 -->
        <property name="user" value="${jdbc.username2}" />
        <!-- 密码 -->
        <property name="password" value="${jdbc.password2}" />
        <!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数  -->
        <property name="acquireIncrement" value="8"></property>
        <!-- 初始连接池大小 -->
        <property name="initialPoolSize" value="8"></property>
        <!-- 连接池中连接最小个数 -->
        <property name="minPoolSize" value="8"></property>
        <!-- 连接池中连接最大个数 -->
        <property name="maxPoolSize" value="32"></property>
    </bean>


    <!--2 配置C3P0数据源 -->
<!--    第三数据源-->
    <bean id="ThirdDatasource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <!--    <bean id="datasource" class="cn.CommonUtils.MybatisC3p0DatabaseSourcesFactory" destroy-method="close">-->
        <!--驱动类名 -->
        <property name="driverClass" value="${jdbc.driver3}" />
        <!-- url -->
        <property name="jdbcUrl" value="${jdbc.url3}" />
        <!-- 用户名 -->
        <property name="user" value="${jdbc.username3}" />
        <!-- 密码 -->
        <property name="password" value="${jdbc.password3}" />
        <!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数  -->
        <property name="acquireIncrement" value="8"></property>
        <!-- 初始连接池大小 -->
        <property name="initialPoolSize" value="8"></property>
        <!-- 连接池中连接最小个数 -->
        <property name="minPoolSize" value="8"></property>
        <!-- 连接池中连接最大个数 -->
        <property name="maxPoolSize" value="32"></property>
    </bean>



    <!--    定义动态数据源-->
    <bean id="dynamicDataSource" class="cn.DynamicDataSource.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry value-ref="PrimaryDatasource" key="PrimaryDatasource"></entry>
                <entry value-ref="MinorDatasource" key="MinorDatasource"></entry>
                <entry value-ref="ThirdDatasource" key="ThirdDatasource"></entry>

            </map>
        </property>
        <property name="defaultTargetDataSource" ref="PrimaryDatasource">
        </property>
    </bean>
    
    <!--3 会话工厂bean sqlSessionFactoryBean -->
    <!--    del 2019年11月2日-->
    <!--    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">-->
    <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">

        <!-- 数据源 -->
<!--        del by 2019年11月3日-->
<!--        <property name="dataSource" ref="datasource"></property>-->

        <property name="dataSource" ref="dynamicDataSource"></property>
        <!-- 别名 -->
        <!--        <property name="typeAliasesPackage" value="cn.Dao **"></property>-->
        <!-- sql映射文件路径 -->
        <property name="mapperLocations" value="classpath:cn/Dao/*Dao.xml"></property>
        <!--    mybatis配置文件 -->
        <!--        <property name="configLocation" value="classpath:mybatis-config.xml"></property>-->
    </bean>

    <!-- 4 自动扫描对象关系映射 -->
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.Dao"></property>
    </bean>

    <!--5 声明式事务管理 -->
    <!--定义事物管理器,由spring管理事务 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--        del by 2019年11月3日-->
<!--        <property name="dataSource" ref="datasource"></property>-->
        <property name="dataSource" ref="dynamicDataSource"></property>
    </bean>
    <!--支持注解驱动的事务管理,指定事务管理器 -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
    
</beans>

Spring程序上下文配置

applicationContext.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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
      					http://www.springframework.org/schema/aop
      					http://www.springframework.org/schema/aop/spring-aop.xsd
      					http://www.springframework.org/schema/context
      					https://www.springframework.org/schema/context/spring-context.xsd
      					http://www.springframework.org/schema/jee
      					http://www.springframework.org/schema/jee/spring-jee.xsd
                        http://www.springframework.org/schema/tx
                        http://www.springframework.org/schema/tx/spring-tx.xsd">

<!--    开启注解 -->
    <context:annotation-config />
<!--    添加包扫描路径  -->
    <context:component-scan base-package="cn.Dao"></context:component-scan>
    <context:component-scan base-package="cn.Service"></context:component-scan>


    <import resource="applicationContext-Cache.xml"></import>

<!--    add by 2019年11月3日-->
    <import resource="applicationContext-MyBatis-DataResource.xml"></import>
    
</beans>

码代码

数据源选择关注点注解类

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChooseDataSource {
    /**
     * 数据库名称
     */
//    String dataSourceName() default DataSourceName.PRIMARYDATASOURCE.value;
    String dataSourceName() default "PrimaryDatasource";
}

数据源名字枚举

public enum DataSourceName{
    PRIMARYDATASOURCE("PrimaryDatasource" , "主数据源"),
    MINORDATASOURCE("MinorDatasource"     , "次数据源"),
    THIRDDATASOURCE("ThirdDatasource"     , "第三数据源");

    private final String  value;
    private final String  description;

    DataSourceName(String value, String description) {
        this.value = value;
        this.description = description;
    }

    public String getValue() {
        return value;
    }

    public String getDescription() {
        return description;
    }
}

数据源指定类

public class DynamicDataSourceHolder {

    //解决线程安全问题
    private static final ThreadLocal<String> holder = new ThreadLocal<String>();

    public static void putDataSourceName(String dataName) {
        holder.set(dataName);
    }

    public static String getDataSourceName() {
        return holder.get();
    }

    public static class DataSourceName {
        public final static String dataSource = cn.DynamicDataSource.DataSourceName.PRIMARYDATASOURCE.getValue();
    }
}

数据源路由类

import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource{

    @Override
    protected Object determineCurrentLookupKey() {
//        return DatabaseContextHolder.getCustomerType();
        String dataSourceName = DynamicDataSourceHolder.getDataSourceName();
        if (dataSourceName == null) {
//            dataSourceName = "PrimaryDatasource";
            dataSourceName = DataSourceName.PRIMARYDATASOURCE.getValue();
        }
        log.info("当前选择的数据源是:" + dataSourceName);
        return dataSourceName;
    }
}

数据源选择切面类

/**
 * 数据源路由选择切面类
 */
@Aspect
@Component
public class ChooseDataSourceAop {


    /**
     * 注解切入点
     */
    @Pointcut("@annotation(cn.MetaData.Annotation.ChooseDataSource) && args(..)")
    private void ChooseDatasourcePointcutByAnnotation() {
    }


    /**
     * 动态数据源选择切面
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("ChooseDatasourcePointcutByAnnotation()")
    public Object permission(ProceedingJoinPoint joinPoint) throws Throwable {
        Object target = joinPoint.getTarget();
        Object[] args = joinPoint.getArgs();
        Method method = getMethod(joinPoint, args);
        //获取数据库名称参数
        ChooseDataSource chooseDataSource = method.getAnnotation(ChooseDataSource.class);
        if (chooseDataSource != null) {
            String dataSourceName = chooseDataSource.dataSourceName();
            //检查数据库名称是否存在
            if (! "".equals(dataSourceName)) {
                DynamicDataSourceHolder.putDataSourceName(dataSourceName);
            } else {
//                DynamicDataSourceHolder.putDataSourceName("PrimaryDatasource");
                DynamicDataSourceHolder.putDataSourceName(DataSourceName.PRIMARYDATASOURCE.getValue());
            }
        }
        return joinPoint.proceed();
    }


    /**
     * 获取关注点 方法信息
     * @param joinPoint
     * @param args
     * @return
     * @throws NoSuchMethodException
     */
    private Method getMethod(ProceedingJoinPoint joinPoint, Object[] args) throws NoSuchMethodException {
        String methodName = joinPoint.getSignature().getName();
        Class clazz = joinPoint.getTarget().getClass();
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            if (methodName.equals(method.getName())) {
                return method;
            }
        }
        return null;
    }
}

测试Controller


@Slf4j
@Controller
public class FourthController {
    @Autowired
    private EmployeeDao employeeDao;

    @Autowired
    private ApplicationConfigurationDao applicationConfigurationDao;

    @Autowired
    private JobDao jobDao;

    /**
     * 默认数据源
     * @return
     */
    @RequestMapping("/fourth/get1")
    @ResponseBody
    public JSONObject testProc3(){
        JSONObject result = new JSONObject();
        result.put("name", "TOM");

        List<Employee> employeeList = employeeDao.findAll();

        employeeList.forEach(i->{
            log.info(i.toString());
        });

        return result;
    }

    /**
     * 次数据源
     * @return
     */
    @RequestMapping("/fourth/get2")
    @ResponseBody
    @ChooseDataSource(dataSourceName = "MinorDatasource")
    public JSONObject testProc4(){
        JSONObject result = new JSONObject();
        result.put("name", "Jay");
        applicationConfigurationDao.findAll().forEach(i->{
            log.info(i.toString());
        });
        return result;
    }

    /**
     * 主数据源
     * @return
     */
    @RequestMapping("/fourth/get3")
    @ResponseBody
    @ChooseDataSource()
    public JSONObject testProc5(){
        JSONObject result = new JSONObject();
        result.put("name", "Jay");
        applicationConfigurationDao.findAll().forEach(i->{
            log.info(i.toString());
        });
        return result;
    }

    /**
     * 第三数据源
     */

    @RequestMapping("/fourth/get4")
    @ResponseBody
    @ChooseDataSource(dataSourceName = "ThirdDatasource")
    public JSONObject testProc6(){
        JSONObject result = new JSONObject();
        result.put("name", "Jay");
        jobDao.findAll().forEach(i->{
            log.info(i.toString());
        });
        return result;
    }

}

测试

默认数据源
在这里插入图片描述
在这里插入图片描述
第二数据源
fourth/get2
在这里插入图片描述

第三数据源
fourth/get4

在这里插入图片描述

发布了48 篇原创文章 · 获赞 8 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/wangxudongx/article/details/103106596
今日推荐