提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、配置数据源
- 二、定义一个AbstractRoutingDataSource子类
- 三、创建一个类管理存储访问数据库路由标识的ThreadLocal变量
- 四、自定义注解标识当前访问路由
- 五、AOP切面在数据库操作前获取实际使用数据库的标识
- 六、代码结构
前言
实际项目开发过程中,有可能需要在同一个项目中接入不同数据源,Spring AbstractRoutingDataSource
提供了这方面的支持,当然要实现动态切换数据源的功能还需要另外一些步骤,我们先整体了解下
- 需要配置实际的数据源
AbstractRoutingDataSource
只是一个路由功能 - 定义一个类继承
AbstractRoutingDataSource
类实现determineCurrentLookupKey
方法 - 需要有一个
ThreadLocal
变量,存储路由标识,表明当前应该具体使用哪个数据源 - 定义一个自定义注解(一般作用在dao层前面),标记当前操作的数据源
- 定义AOP切面在执行数据库操作前获具体使用的数据源标识(第4步注解的标识),并存入ThreadLocal线程本地变量
接下来看看具体怎么实现
一、配置数据源
- 配置AbstractRoutingDataSource数据源
这里的DynamicDataSource
继承了AbstractRoutingDataSource
targetDataSources执行了实际的数据源
<bean id="dataSource" class="com.dlh.util.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="dataSource1" key="db1"></entry>
<entry value-ref="dataSource2" key="db2"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource1" >
</property>
</bean>
数据源1
<bean id="dataSource1" class="com.zaxxer.hikari.HikariDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- Default settings -->
<!-- 控制自动提交行为 default:true -->
<property name="autoCommit" value="true"/>
<!--连接池获取的连接是否只读 default:false-->
<property name="readOnly" value="false"/>
<!--最大连接超时时间 default:30秒-->
<property name="connectionTimeout" value="30000"/>
<!--最大空闲超时时间 default:10分钟 -->
<property name="idleTimeout" value="600000"/>
<!--连接池中一个连接的最大生命周期 default:30分钟-->
<property name="maxLifetime" value="1800000 "/>
</bean>
数据源2
<bean id="dataSource2" class="com.zaxxer.hikari.HikariDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- Default settings -->
<!-- 控制自动提交行为 default:true -->
<property name="autoCommit" value="true"/>
<!--连接池获取的连接是否只读 default:false-->
<property name="readOnly" value="false"/>
<!--最大连接超时时间 default:30秒-->
<property name="connectionTimeout" value="30000"/>
<!--最大空闲超时时间 default:10分钟 -->
<property name="idleTimeout" value="600000"/>
<!--连接池中一个连接的最大生命周期 default:30分钟-->
<property name="maxLifetime" value="1800000 "/>
</bean>
我们用一个properties记录数据源的配置 用context:property-placeholder导入进来
<context:property-placeholder location="classpath*:jdbc.properties" />
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
jdbc.url1=jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=123456
我们dao层用Spring来托管MyBatis,Spring整合Mybatis的配置是这样的
<!-- 3. 将SqlSession对象的加载交给Spring托管 -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<!-- spring托管MyBatis,配置自动扫描数据源执行AbstractRoutingDataSource -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath*:com/dlh/dao/mapper/*.xml"></property>
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.dlh.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
二、定义一个AbstractRoutingDataSource子类
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DBRoute.getDbRoute();
}
}
这里重写determineCurrentLookupKey
方法的目的是判断具体应该路由到AbstractRoutingDataSource
的哪个数据源
这里需要简单了解下AbstractRoutingDataSource
这个类
实现了InitializingBean
接口,可以在Bean初始化的时候做点事情
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
} else {
this.resolvedDataSources = new HashMap(this.targetDataSources.size());
this.targetDataSources.forEach((key, value) -> {
Object lookupKey = this.resolveSpecifiedLookupKey(key);
DataSource dataSource = this.resolveSpecifiedDataSource(value);
this.resolvedDataSources.put(lookupKey, dataSource);
});
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
}
这里就是拿到targetDataSources配置将底层的数据源注入到AbstractRoutingDataSource的targetDataSources
map对象中
具体获取数据库连接时先判断路由到哪个数据源,这个就要子类去实现了
三、创建一个类管理存储访问数据库路由标识的ThreadLocal变量
public class DBRoute {
private final static ThreadLocal<String> dbRoute = new ThreadLocal<>();
public static void setDbRoute(String routeKey) {
dbRoute.set(routeKey);
}
public static String getDbRoute() {
return dbRoute.get();
}
public static void clearDbRoute() {
dbRoute.remove();
}
}
四、自定义注解标识当前访问路由
public @interface DBSwitch {
String value() default "";
}
@DBSwitch("db1")
这个注解的值配的就是动态数据源具体数据源的Key
五、AOP切面在数据库操作前获取实际使用数据库的标识
@Aspect
@Component
public class DBSwitchAspect {
@Pointcut("execution(* com.dlh.manager.impl.dbA.UserManagerImpl.queryAll())")
private void dbAPointCut() {}
@Pointcut("execution(* com.dlh.manager.impl.dbB.UserManagerImpl.queryAll())")
private void dbBPointCut() {}
/**
* 前置通知:
*/
@Before("dbAPointCut()|| dbBPointCut()")
public void before(JoinPoint jp){
System.out.println("前置通知 代理类"+jp.getTarget().getClass() + "代理方法 "+jp.getSignature().getName());
Annotation[] annotations = jp.getTarget().getClass().getDeclaredAnnotations();
DBSwitch dbSwitch = (DBSwitch) jp.getTarget().getClass().getAnnotation(DBSwitch.class);
if(dbSwitch != null){
DBRoute.setDbRoute( dbSwitch.value());
}
}
/**
* 后置通知
* @param jp 连接点的基本信息
*/
@Before("dbAPointCut()|| dbBPointCut()")
public void after(JoinPoint jp){
DBRoute.clearDbRoute();
}
}
这里在执行dao层前用AOP做了拦截获取到后面实际使用数据库的标识存入线程本地变量
六、代码结构
最后我们来运行下结果验证下
本地建了两个两个数据库来模拟多数据源
<entry value-ref="dataSource1" key="db1"></entry>
<entry value-ref="dataSource2" key="db2"></entry>
- 配置访问test1数据库
@Qualifier("userB")
@Autowired
private UserManager userManager;
@Override
public List<User> queryAll() {
return userManager.queryAll();
}
@Component("userB")
@DBSwitch("db2")
public class UserManagerImpl implements UserManager {
@Autowired
private UserDao userDao;
@Override
public List<User> queryAll() {
return userDao.queryAll();
}
}
刚好4条数据
- 配置访问test数据库
@Qualifier("userA")
@Autowired
private UserManager userManager;
@Override
public List<User> queryAll() {
return userManager.queryAll();
}
@Component("userA")
@DBSwitch("db1")
public class UserManagerImpl implements UserManager {
@Autowired
private UserDao userDao;
@Override
public List<User> queryAll() {
return userDao.queryAll();
}
}
刚好8条数据