前言
在开发过程中可能需要用到多个数据源,比如一个项目(MySQL)就是和(SQL Server)混合使用,就需要使用多数据源;如果业务场景比较复炸,可以使用动态数据源,灵活切换,典型的应用就是读写分离。下面分两个模块来配置数据源,大家可以根据自己实际情况配置。
多数据源
禁用DataSourceAutoConfiguration
如果DataSourceAutoConfiguration不禁用的话,就会报错,多个数据源,无法装配哪一个。在springBoot的主程序入口的注解
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class
})
配置application.properties
#datasource 这是自动装配的默认配置禁止,这里不能使用
#spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#spring.datasource.url=jdbc:mysql://127.0.0.1:3306/api_resources?#autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
#spring.datasource.username=api
#spring.datasource.password=api
#db1
spring.datasource.db1.url=jdbc:mysql://127.0.0.1:3306/api_resources?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
spring.datasource.db1.username=api
spring.datasource.db1.password=api
spring.datasource.db1.driver-class-name=com.mysql.jdbc.Driver
#db2
spring.datasource.db2.url=jdbc:mysql://127.0.0.1:3306/ssm?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
spring.datasource.db2.username=api
spring.datasource.db2.password=api
spring.datasource.db2.driver-class-name=com.mysql.jdbc.Driver
配置dataSource
配置两个数据源,分别为db1,和db2
/**
* Author: Starry.Teng
* Email: tengxing7452@163.com
* Date: 17-11-1
* Time: 下午9:14
* Describe: DataSource Config
*/
@Configuration
public class DataSourceConfig {
@Autowired
Environment env;
@Bean(name = "ds1")
public DataSource dataSource1() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(env.getProperty("spring.datasource.db1.url"));
dataSource.setUsername(env.getProperty("spring.datasource.db1.username"));
dataSource.setPassword(env.getProperty("spring.datasource.db1.password"));
dataSource.setDriverClassName(env.getProperty("spring.datasource.db1.driver-class-name"));
return dataSource;
}
@Bean(name = "ds2")
public DataSource dataSource2() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(env.getProperty("spring.datasource.db2.url"));
dataSource.setUsername(env.getProperty("spring.datasource.db2.username"));
dataSource.setPassword(env.getProperty("spring.datasource.db2.password"));
dataSource.setDriverClassName(env.getProperty("spring.datasource.db2.driver-class-name"));
return dataSource;
}
}
Mybatis配置
为两个数据源分贝配置一个MybatisDb1Config类和MybatisDb2Config类,对数据源进行管理,注意他们所管理的包是不同的。
/**
* Author: Starry.Teng
* Email: tengxing7452@163.com
* Date: 17-11-1
* Time: 下午9:15
* Describe: MybatisDb1 Config
*/
@Configuration
@MapperScan(basePackages = {"cn.yjxxclub.demo.datasource.dao.db1"}, sqlSessionFactoryRef = "sqlSessionFactory1")
public class MybatisDb1Config {
@Qualifier("ds1")
@Autowired
DataSource dataSource;
@Bean(name = "sqlSessionFactory1")
public SqlSessionFactory sqlSessionFactory1() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factoryBean.setMapperLocations(resolver.getResources("classpath:mappers/db1/*.xml"));
factoryBean.setTypeAliasesPackage("cn.yjxxclub.demo.datasource.model");
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate1() throws Exception {
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory1()); // 使用上面配置的Factory
return template;
}
}
/**
* Author: Starry.Teng
* Email: tengxing7452@163.com
* Date: 17-11-1
* Time: 下午9:26
* Describe: MybatisDb2 Config
*/
@Configuration
@MapperScan(basePackages = {"cn.yjxxclub.demo.datasource.dao.db2"}, sqlSessionFactoryRef = "sqlSessionFactory2")
public class MybatisDb2Config {
@Qualifier("ds2")
@Autowired
DataSource dataSource;
@Bean(name = "sqlSessionFactory2")
public SqlSessionFactory sqlSessionFactory2() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factoryBean.setMapperLocations(resolver.getResources("classpath:mappers/db2/*.xml"));
factoryBean.setTypeAliasesPackage("cn.yjxxclub.demo.datasource.model");
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory2());
return template;
}
}
编写Dao和Mapper
Mapper.java和mapper.xml在basePackages和classpath对应放置即可,和springboot整合Mybtis一样,这里就不说了,然后正常启动即可。代码在此:https://github.com/tengxing/Multiple-dataSources
数据源动态切换
数据源注册
/**
* Author: Starry.Teng
* Email: tengxing7452@163.com
* Date: 17-11-1
* Time: 下午9:14
* Describe: DataSource Config
*/
@Configuration
public class DataSourceConfig {
@Autowired
Environment env;
@Bean(name = "ds1")
public DataSource dataSource1() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(env.getProperty("spring.datasource.db1.url"));
dataSource.setUsername(env.getProperty("spring.datasource.db1.username"));
dataSource.setPassword(env.getProperty("spring.datasource.db1.password"));
dataSource.setDriverClassName(env.getProperty("spring.datasource.db1.driver-class-name"));
return dataSource;
}
@Bean(name = "ds2")
public DataSource dataSource2() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(env.getProperty("spring.datasource.db2.url"));
dataSource.setUsername(env.getProperty("spring.datasource.db2.username"));
dataSource.setPassword(env.getProperty("spring.datasource.db2.password"));
dataSource.setDriverClassName(env.getProperty("spring.datasource.db2.driver-class-name"));
return dataSource;
}
@Bean(name = "dynamicDS1")//注意这个bean是mybatis的sqlSessionFacatory所管理的dataSource
public DataSource dataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(dataSource1());
// 配置多数据源
Map<Object, Object> dsMap = new HashMap(5);
dsMap.put("ds1", dataSource1());
dsMap.put("ds2", dataSource2());
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
}
动态数据源编写
需要继承AbstractRoutingDataSource类
/**
* Author: http://blog.csdn.net/neosmith/article/details/61202084
* Date: 17-11-2
* Time: 下午2:56
* Describe: 动态数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
log.debug("数据源为{}", DataSourceContextHolder.getDB());
return DataSourceContextHolder.getDB();
}
}
编写DataSourceContextHolder
/**
* Author: http://blog.csdn.net/neosmith/article/details/61202084
* Date: 17-11-2
* Time: 下午2:56
* Describe: DataSource ContextHolder
*/
public class DataSourceContextHolder {
public static final Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);
/**
* 默认数据源
*/
public static final String DEFAULT_DS = "ds1";
/**
* 获取当前线程
*/
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
// 设置数据源名
public static void setDB(String dbType) {
log.debug("切换到{}数据源", dbType);
contextHolder.set(dbType);
}
// 获取数据源名
public static String getDB() {
return (contextHolder.get());
}
// 清除数据源名
public static void clearDB() {
contextHolder.remove();
}
}
咋们到时候只要调用setDB()方法即可实现数据源的切换,下面使用Aop的方式进行动态切换。
编写代理类
/**
* Author: http://blog.csdn.net/neosmith/article/details/61202084
* Date: 17-11-2
* Time: 下午3:01
* Describe: 动态数据源代理类
* 逻辑:对方法@DB注解的方法进行切面换数据源操作
*/
@Aspect
@Component
@Order(value=-1) //保证该AOP在@Transactional之前执行
public class DynamicDataSourceAspect {
@Before("@annotation(DB)")
public void beforeSwitchDS(JoinPoint point){
//获得当前访问的class
Class<?> className = point.getTarget().getClass();
//获得访问的方法名
String methodName = point.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass);
// 判断是否存在@DS注解
if (method.isAnnotationPresent(DB.class)) {
DB annotation = method.getAnnotation(DB.class);
// 取出注解中的数据源名
dataSource = annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
// 切换数据源
DataSourceContextHolder.setDB(dataSource);
}
@After("@annotation(DB)")
public void afterSwitchDS(JoinPoint point){
DataSourceContextHolder.clearDB();
}
}
自定义注解
/**
* Author: Starry.Teng
* Email: tengxing7452@163.com
* Date: 17-11-2
* Time: 下午3:00
* Describe:
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD
})
public @interface DB {
String value() default "ds1";
}
serviceImpl
@DB("ds1")
public Object getDs1(){
Singer singer = singerMapper.findByName("陈奕迅");
logger.info("/n"+singer);
return null;
}
@DB("ds2")
public Object getDs2(){
Book book = bookMapper.findById(bookMapper.list().get(0).getId());
logger.info(book+"/n");
return null;
}
最后调用吧!!!项目在此:https://github.com/tengxing/DynamicDataSource
两者比较
类别 | 多数据源 | 动态数据源 |
---|---|---|
作用 | 配置多个数据源,分模块开发 | 通过灵活的手段对数据库进行灵活切库 |
原理 | 实例化多个SsqlSessionFactory | Spring+AOP |
作用框架 | Mybatis | Spring |
应用场景 | 解耦比较大的项目,模块化开发 | 主从分布,读写分离 |
知识点
上面的两种方式都达到了”换数据源”目的,只是实现的原理和方式不一样而已,都是优虐,还是那句话,没有最好的方法,只有在特定的环境下最适合的解决方案。
参考文章
http://blog.csdn.net/neosmith/article/details/61202084
http://www.cnblogs.com/hdwang/p/7041096.html
http://blog.csdn.net/catoop/article/details/50575038
https://github.com/zeq9069/koala/tree/master/src/main/java/org/kyrin/koala