动态数据源:Mybatis-plus、C3P0
- 示例项目概览
- 依赖库引入
- 配置
- mybatis配置
- 数据源连接属性配置
- Spring数据上下文配置
- Spring程序上下文配置
- 码代码
- 数据源选择关注点注解类
- 数据源名字枚举
- 数据源指定类
- 数据源路由类
- 数据源选择切面类
- 测试Controller
- 测试
在项目开发中我们可能会遇到要访问多个数据源的操作,就比如说联库查询、用一个库的数据更新另一个库。所以对于一个灵活通用的系统框架来说,一个健壮可靠的多数据源方案尤为重要。
Spring项目下多数据源方案有两类:静态多数据源和动态数据源。使用静态的方案,也就是为各个不同的数据源创建各自独立的配置在,各自的配置文件config中,让每个Mapper都有对应的config文件配置、每个数据源都有一套配置和SqlSessionFactory。在实际使用中要把每个源的DAO隔离开来,不然极容易造成系统崩溃。
动态数据源核心是使用AbstractRoutingDataSource来实现数据源的路由选择,可在运行时动态切换数据源,实现上是多个数据源,统一的配置管理。
静态多数据源特点是配置简单、实际使用僵硬死板。
动态数据源灵活、同一个DAO类可在运行时在多数据源之间切换。通过注解配置加AOP注入,使用起来十分方便。
如果在项目进行中手上没有动态数据源的配置和代码,而项目架构是临时短期的使用的话也能选择用静态配置。但是大部分时候还是推荐使用动态数据源配置。
示例项目概览
示例项目:
Spring 5.1.5
Mybatis-plus 3.2.0
C3P0 0.9.5.4
项目结构
表结构-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