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