Spring+Mybatis多数据源切换

1、为什么要使用多数据源?

在做项目事很多情况下都要涉及到多数据库,二开公司成熟的产品,给客户实施产品时,对接客户的众多系统(比如客户的系统都是外包的,其他系统的供应商比较忙,不是很配合时,并且得到客户的同意,直接连到该系统数据进行操作);

2、使用步骤

2.1、修改jdbc.properties文件

#oracle
jdbc.driverClassName=oracle.jdbc.OracleDriver
jdbc.databaseUrl=jdbc:oracle:thin:@192.168.187.25:1521:oratest
jdbc.username=mdm_fssc
jdbc.password=123456
#ERPoracle
erp.jdbc.driverClassName=oracle.jdbc.OracleDriver
erp.jdbc.databaseUrl=jdbc:oracle:thin:@192.168.187.27:1531/UAT
erp.jdbc.username=apps
erp.jdbc.password=CLONE
复制代码

2.2、修改mybatis.xml文件

<!-- 数据源配置1 -->
<bean name="dataSourceMdm" class="com.alibaba.druid.pool.DruidDataSource"
      init-method="init" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.databaseUrl}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <!-- 初始化连接大小 -->
    <property name="initialSize" value="50"/>
    <!-- 连接池最大使用连接数量 -->
    <property name="maxActive" value="200"/>
    <!-- 连接池最大空闲 -->
    <!-- <property name="maxIdle" value="20" /> -->
    <!-- 连接池最小空闲 -->
    <property name="minIdle" value="5"/>
    <!-- 获取连接最大等待时间 -->
    <property name="maxWait" value="6000"/>
    <property name="poolPreparedStatements" value="true"/>
    <property name="maxPoolPreparedStatementPerConnectionSize"
              value="100"/>
    <property name="validationQuery" value="SELECT 1 FROM DUAL"/>
    <property name="testOnBorrow" value="false"/>
    <property name="testOnReturn" value="false"/>
    <property name="testWhileIdle" value="true"/>
    <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="60000"/>
    <!-- 配置一个连接在池中最小生存的时间,单位:毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="25200000"/>
    <!-- 打开removeAbandoned功能 -->
    <property name="removeAbandoned" value="true"/>
    <!-- 1800 单位:秒 -->
    <property name="removeAbandonedTimeout" value="1800"/>
    <!-- 关闭abanded连接时输出错误日志 -->
    <property name="logAbandoned" value="true"/>
    <!-- 监控数据库 -->
    <property name="proxyFilters">
        <list>
            <ref bean="stat-filter" />
            <ref bean="wall-filter"/>
        </list>
    </property>
</bean>
<bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter"/>
<bean id="wall-filter" class="com.alibaba.druid.wall.WallFilter">
    <property name="config" ref="wall-config" />
</bean>
<bean id="wall-config" class="com.alibaba.druid.wall.WallConfig">
    <property name="multiStatementAllow" value="true" />
</bean>
<!-- 数据源配置2 -->
<bean name="dataSourceErp" class="com.alibaba.druid.pool.DruidDataSource"
      init-method="init" destroy-method="close">
    <property name="driverClassName" value="${erp.jdbc.driverClassName}"/>
    <property name="url" value="${erp.jdbc.databaseUrl}"/>
    <property name="username" value="${erp.jdbc.username}"/>
    <property name="password" value="${erp.jdbc.password}"/>
    <!-- 初始化连接大小 -->
    <property name="initialSize" value="50"/>
    <!-- 连接池最大使用连接数量 -->
    <property name="maxActive" value="200"/>
    <!-- 连接池最大空闲 -->
    <!-- <property name="maxIdle" value="20" /> -->
    <!-- 连接池最小空闲 -->
    <property name="minIdle" value="5"/>
    <!-- 获取连接最大等待时间 -->
    <property name="maxWait" value="6000"/>
    <property name="poolPreparedStatements" value="true"/>
    <property name="maxPoolPreparedStatementPerConnectionSize"
              value="100"/>
    <property name="validationQuery" value="SELECT 1 FROM DUAL"/>
    <property name="testOnBorrow" value="false"/>
    <property name="testOnReturn" value="false"/>
    <property name="testWhileIdle" value="true"/>
    <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="60000"/>
    <!-- 配置一个连接在池中最小生存的时间,单位:毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="25200000"/>
    <!-- 打开removeAbandoned功能 -->
    <property name="removeAbandoned" value="true"/>
    <!-- 1800 单位:秒 -->
    <property name="removeAbandonedTimeout" value="1800"/>
    <!-- 关闭abanded连接时输出错误日志 -->
    <property name="logAbandoned" value="true"/>
    <!-- 监控数据库 -->
    <property name="proxyFilters">
        <list>
            <ref bean="stat-filter" />
            <ref bean="wall-filter"/>
        </list>
    </property>
</bean>
<!-- 数据源切换 -->
<bean id="dataSource" class="com.epoch.customproject.sinohttpinterface.dao.DynamicDataSource">
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <!-- 指定lookupKey和与之对应的数据源 -->
            <entry key="dataSourceMdm" value-ref="dataSourceMdm"></entry>
            <entry key="dataSourceErp" value-ref="dataSourceErp"></entry>
        </map>
    </property>
    <!-- 这里可以指定默认的数据源 -->
    <property name="defaultTargetDataSource" ref="dataSourceMdm" />
</bean>
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <!-- Mybatis XML映射文件路径 -->
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations">
        <array>
        <!-- 扫描Mapper.xml映射文件 -->
            <value>classpath*:com/epoch/**/dao/**/*DAO.xml</value>
        </array>
    </property>
    <property name="typeHandlers">
        <list>
            <bean class="com.epoch.infrastructure.util.service.typehandler.BooleanTypeHandler" />
            <bean class="com.epoch.infrastructure.util.service.typehandler.StringArrayTypeHandler" />
            <bean class="com.epoch.infrastructure.util.service.typehandler.StringListTypeHandler" />
            <bean class="com.epoch.infrastructure.util.service.typehandler.I18nStringHandler"/>
        </list>
    </property>
    <property name="typeAliasesPackage" value="com.epoch.**.model"/>
</bean>
<!-- 配置mybatis mapper接口 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.epoch.**.dao"/>
    <property name="sqlSessionFactoryBeanName" value="sessionFactory"/>
</bean>
复制代码

2.3、创建DynamicDataSource.java类 继承AbstractRoutingDataSource

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.logging.Logger;
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDbType();
    }
    @Override
    public Logger getParentLogger() {
        return null;
    }
}
复制代码

2.4、创建DynamicDataSourceHolder.java类

public class DynamicDataSourceHolder {
    private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>();
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    public static void setDbType(String dbType) {
        contextHolder.set(dbType);
    }
    public static String getDbType() {
        return ((String) contextHolder.get());
    }
    public static void clearDbType() {
        contextHolder.remove();
    }
}
复制代码

2.5、使用方法

调用DynamicDataSourceHolder.setDbType()方法切换数据源

DynamicDataSourceHolder.setDbType("dataSourceErp");   //这里最好使用枚举 降低出错率
复制代码

3、可能会出现的问题

//启动时报错java.sql.SQLRecoverableException:IO 错误:Got minus one from a read call
复制代码

3.1、原因说明:

该数据库实例的最大连接数查超出了

3.2、解决办法:

1.调整数据库最大连接数(公司的话当然联系由DBA调整) 2.修改mybatis.xml文件中的初始化连接

4、自定义注解

4.1、创建@interface 注解

package com.epoch.customproject.utils;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DataSource {
    String value() default "dataSourceMdm";
}
复制代码

4.2、创建AOP切面类

package com.epoch.customproject.utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
@Order(1)
@Aspect
public class DataSourceAspect {
    //使用DS注解动态切换
    @Before("@annotation(DataSource)")
    public void beforeSwitchDS(JoinPoint point){
        //获得当前访问的class
        Class<?> className = point.getTarget().getClass();
        //获得访问的方法名
        String methodName = point.getSignature().getName();
        //得到方法的参数的类型
        Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
        String dataSource = "";
        try {
            // 得到访问的方法对象
            Method method = className.getMethod(methodName, argClass);
            // 判断是否存在@DS注解
            if (method.isAnnotationPresent(DataSource.class)) {
                DataSource annotation = method.getAnnotation(DataSource.class);
                // 取出注解中的数据源名
                dataSource = annotation.value();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 切换数据源
        DynamicDataSourceHolder.setDbType(dataSource);
    }
    //释放资源时
    @After("@annotation(DataSource)")
    public void after(JoinPoint point) {
        //使用完记得清空
        DynamicDataSourceHolder.setDbType("dataSourceMdm");
    }
}
复制代码

4.3、修改applicationContext.xml文件

注入AOP

<!-- 指定AOP切面类 -->
<bean id="dataSourceAspect" class="com.epoch.customproject.utils.DataSourceAspect" />
<aop:config>
    <aop:aspect id="dataAspect" ref="dataSourceAspect">
        <!-- ‘expression属性’ 扫描service中的方法 -->
        <aop:pointcut id="tx" expression="execution(* com.epoch.*.service(..))"/>
        <!-- ‘method属性’ AOP切面类中的方法名 -->
        <aop:before pointcut-ref="tx" method="beforeSwitchDS"/>
    </aop:aspect>
</aop:config>
复制代码

4.4、使用方法

使用时 写在service实现类方法上

@DataSource(value = "数据源的key")
复制代码

这样就实现了数据库切换的功能了!

5、测试

package com.example.thread.example;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @Description
 * @Author: makejava
 * @Date: 2021/3/20 14:12
 */
@Controller
@ResponseBody
public class JiSuanQi{
    
        @DataSource(value = "${datasource.key1}")
        public void test(){
            TestService testService = new TestService();
            User user=new User();
            user.setUsername("数据源切换");
            user.setPassword("123");
            testService.insert(user);
        }

}   
复制代码

这样就实现动态切换,就新增成功了!!!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享