实现原理:继承spring-jdbc 的抽象类org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,重写其lookup数据源的逻辑
先上spring配置:
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.szmcob.pis.runners.cache"/>
<aop:aspectj-autoproxy/>
<bean id="ds_szkdx" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:mysql://10.249.91.70:3306/szkdx?useUnicode=true&characterEncoding=utf8&useSSL=false"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="lasttrain"/>
<property name="password" value="Szmcob@123"/>
</bean>
<!--pis-->
<bean id="ds_pis_data" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:mysql://10.249.35.2:3306/pis_data?useUnicode=true&characterEncoding=utf8&useSSL=false"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="yongjidu"/>
<property name="password" value="www.szmcob.com"/>
</bean>
<!--当日计划运行图同步库-->
<bean id="ds_szmcob_stg" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:postgresql://10.249.35.2:25308/szmcob_stg"/>
<property name="driverClassName" value="org.postgresql.Driver"/>
<property name="username" value="last"/>
<property name="password" value="last@06360"/>
</bean>
<bean id="dataSource" class="com.szmcob.pis.runners.cache.support.DynamicDataSource">
<property name="dataSourceLookup" ref="dataSourceLookup"/>
<property name="targetDataSources">
<map key-type="java.lang.String" value-type="javax.sql.DataSource">
<entry key="ds_szkdx" value-ref="ds_szkdx"/>
<entry key="ds_pis_data" value-ref="ds_pis_data"/>
<entry key="ds_szmcob_stg" value-ref="ds_szmcob_stg"/>
</map>
</property>
<property name="defaultTargetDataSource" value="ds_szkdx"/>
</bean>
<bean id="dataSourceLookup" class="com.szmcob.pis.runners.cache.support.DynamicDataSourceLookup"/>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.szmcob.pis.runners.cache.model"/>
</bean>
<bean id="mapperScan" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.szmcob.pis.runners.cache.mapper"/>
</bean>
<bean id="jedisClusterClient" class="redis.clients.jedis.JedisCluster">
<constructor-arg name="nodes">
<set>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="10.249.35.5"/>
<constructor-arg name="port" value="22400"/>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="10.249.35.6"/>
<constructor-arg name="port" value="22400"/>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="10.249.35.7"/>
<constructor-arg name="port" value="22400"/>
</bean>
</set>
</constructor-arg>
<constructor-arg name="timeout" value="15000"/>
</bean>
</beans>
以上配置了3个数据源,然后自定义一个数据源继承自AbstractRoutingDataSource类和一个dataSourceLookup
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> activeDataSource = new ThreadLocal<>();
private DynamicDataSourceLookup lookup;
private Map<Object, Object> dataSources;
@Override
protected Object determineCurrentLookupKey() {
return activeDataSource.get();
}
public static void setActive(String dataSourceId){
//System.out.println("设置数据源" + dataSourceId);
activeDataSource.set(dataSourceId);
}
@Override
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
System.out.println("设置setTargetDataSources");
super.setTargetDataSources(targetDataSources);
//this.dataSources = targetDataSources;
lookup.setDataSources(targetDataSources);
}
@Override
public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
System.out.println("设置setDataSourceLookup");
lookup = (DynamicDataSourceLookup)dataSourceLookup;
super.setDataSourceLookup(lookup);
}
public static void clean(){
activeDataSource.remove();
}
}
重写了determineCurrentLookupKey、setTargetDataSources和setDataSourceLookup方法,重写setTargetDataSources是为了将targetDataSources列表设置到lookup实例,否则lookup的时候是无法根据数据源id来获取到数据源实例的。
public class DynamicDataSourceLookup implements DataSourceLookup {
private Map<Object, Object> dataSources;
@Override
public DataSource getDataSource(String dataSource) throws DataSourceLookupFailureException {
System.out.println("查找数据源" + dataSource);
return (DataSource) dataSources.get(dataSource);
}
public Map<Object, Object> getDataSources() {
return dataSources;
}
public void setDataSources(Map<Object, Object> dataSources) {
this.dataSources = dataSources;
}
}
这里重写的getDataSource方法就需要用到DynamicDataSource实例中set进来的数据源列表了。当日这个列表不一定要维护在looup里边,还有其他实现方式,大同小异吧。
动态数据源设置好后,需要有个方式去触发或者去切换数据源,这里就使用注解的方式来做了。
首先定义注解:
value就是数据源的ID了,和文件开头的spring配置中的数据源ID一致
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface DataSource {
DataSourceEntry value() default DataSourceEntry.SZKDX;
}
接下来我们希望将切换数据源的注解加到mybatis mapper接口上来控制数据源切换:
@Mapper
@DataSource(DataSourceEntry.SZKDX)
public interface StationIInfoMapper {
@Select("SELECT * FROM stationinfo WHERE lineNo=#{lineNo} ORDER BY stationorder+0 ASC")
List<StationInfo> selectByLineNo(String lineNo);
}
@Mapper
@DataSource(DataSourceEntry.PIS_DATA)
public interface PlatformsMapper {
@Select("SELECT * FROM platforms WHERE lineid=#{lineId}")
List<PlatformInfo> selectByLineId(int lineId);
}
要能够完成切换,最后还需要用到spring aop。因为spring配置中我们启动了注解配置,所以这里就使用注解来注册一个aop切面来干这个事了。
@Aspect
@EnableAspectJAutoProxy
@Component
public class DynamicDataSourceAspect {
private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Pointcut("@annotation(com.szmcob.pis.runners.cache.support.DataSource)")
public void pointCut(){
}
@Pointcut("execution(* com.szmcob.pis.runners.cache.mapper.*.*(..))")
public void pointCut2(){
}
@Around(value = "pointCut2()")
public Object setDataSource(ProceedingJoinPoint joinPoint){
DataSource annotation = (DataSource)joinPoint.getSignature().getDeclaringType().getAnnotation(DataSource.class);
DataSourceEntry entry = annotation.value();
DynamicDataSource.setActive(entry.getDataSourceId());
logger.info("数据源已切换至" + entry.getDataSourceId());
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
上面定义的基本意思就是当满足pointCut()条件的时候/类,执行@Around配置的方法。这里的逻辑就只是对DynamicDataSource设置当前的数据源。
pointCut定义切面切入的点,execution()表达式指定什么包,什么类, @annotation()指定什么注解的类
测试:
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-mysql2redis.xml");
PlatformsMapper platformsMapper = ctx.getBean(PlatformsMapper.class);
//方法调用...
StationIInfoMapper stationInfoMapper = ctx.getBean(StationIInfoMapper.class);
//方法调用...