是从springmvc的思路上来做的,主要就是配置主、从DataSource,
再继承AbstractRoutingDataSource,重写determineCurrentLookupKey
方法,通过Context结合 aop 进行数据主、从库的切换。
上代码:
路由,即实现多数据库的切换源
/*
* 重写的函数决定了最后选择的DataSource
* 因为AbstractRoutingDataSource中获取连接方法为:
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
*/
public class MultiRouteDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
注解,即用以标识选择主还是从数据库
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value();
}
常规配置项,具体主从继承并通过
@ConfigurationProperties(prefix = "master.datasource") 进行配置读取
public class BaseDataSourceConfig {
private String url;
private String username;
private String password;
private String driverClassName;
// 添加上getter、setter方法
}
多数据源设置
@Configuration
public class DataSourceComponent {
@Resource
MasterDataSourceConfig masterDataSourceConfig;
@Resource
FirstDataSourceConfig firstDataSourceConfig;
@Resource
SecondDataSourceConfig secondDataSourceConfig;
/*
* 一开始以为springboot的自动配置还是会生效,直接加了@Resource DataSource dataSource;
* 显示是不work的,会报create bean 错误
*/
public DataSource masterDataSource() {
DataSource dataSource = new DataSource();
dataSource.setUrl(masterDataSourceConfig.getUrl());
dataSource.setUsername(masterDataSourceConfig.getUsername());
dataSource.setPassword(masterDataSourceConfig.getPassword());
dataSource.setDriverClassName(masterDataSourceConfig.getDriverClassName());
return dataSource;
}
/*
* 一开始在这里加了@Bean的注解,当然secondDataSource()也加了
* 会导致springboot识别的时候,发现有多个
* 所以,其实都不要加@Bean,最终有效的的DataSource就只需要一个multiDataSource即可
*/
public DataSource firstDataSource() {
DataSource dataSource = new DataSource();
dataSource.setUrl(firstDataSourceConfig.getUrl());
dataSource.setUsername(firstDataSourceConfig.getUsername());
dataSource.setPassword(firstDataSourceConfig.getPassword());
dataSource.setDriverClassName(firstDataSourceConfig.getDriverClassName());
return dataSource;
}
public DataSource secondDataSource() {
DataSource dataSource = new DataSource();
dataSource.setUrl(secondDataSourceConfig.getUrl());
dataSource.setUsername(secondDataSourceConfig.getUsername());
dataSource.setPassword(secondDataSourceConfig.getPassword());
dataSource.setDriverClassName(secondDataSourceConfig.getDriverClassName());
return dataSource;
}
@Bean(name = "multiDataSource")
public MultiRouteDataSource exampleRouteDataSource() {
MultiRouteDataSource multiDataSource = new MultiRouteDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
targetDataSources.put("first", firstDataSource());
targetDataSources.put("second", secondDataSource());
multiDataSource.setTargetDataSources(targetDataSources);
multiDataSource.setDefaultTargetDataSource(masterDataSource());
return multiDataSource;
}
@Bean(name = "transactionManager")
public DataSourceTransactionManager dataSourceTransactionManager() {
DataSourceTransactionManager manager = new DataSourceTransactionManager();
manager.setDataSource(exampleRouteDataSource());
return manager;
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactoryBean sqlSessionFactory() {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(exampleRouteDataSource());
return sessionFactoryBean;
}
}
当然少不了DataSourceContextHolder,用以保持当前线程的数据源选择。
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSource(String value) {
contextHolder.set(value);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
最后,自然就是AOP+注解实现数据源切换啦
@Aspect
@Component
public class DynamicDataSourceAspect {
@Around("execution(public * com.wdm.example.service..*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method targetMethod = methodSignature.getMethod();
if(targetMethod.isAnnotationPresent(TargetDataSource.class)){
String targetDataSource = targetMethod.getAnnotation(TargetDataSource.class).value() ;
DataSourceContextHolder.setDataSource(targetDataSource);
}
Object result = pjp.proceed();
DataSourceContextHolder.clearDataSource();
return result;
}
}
那用法就是如下了:
package com.wdm.example.service;
import java.util.Date;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.wdm.example.dao.UserDao;
import com.wdm.example.datasource.TargetDataSource;
import com.wdm.example.model.User;
import com.wdm.example.service.UserService;
/*
* @author wdmyong
* 20170416
*/
@Service
public class UserService {
@Resource
UserDao userDao;
public User getById(Integer id) {
return userDao.getById(id);
}
@TargetDataSource("master")
public User getById0(Integer id) {
return userDao.getById(id);
}
@TargetDataSource("first")
public User getById1(Integer id) {
return userDao.getById(id);
}
@TargetDataSource("second")
public User getById2(Integer id) {
return userDao.getById(id);
}
public void insert(User user) {
Date now = new Date();
user.setCreateTime(now);
user.setModifyTime(now);
userDao.insert(user);
}
public void update(User user) {
user.setModifyTime(new Date());
userDao.update(user);
}
}
自己在网上找的时候不是全的,包括上文注释中提到的出现的问题,也是根据错误提示多个DataSource目标,以及没设置就是没有DataSource了。
PS:其实之前一直以为DataSource听起来挺悬乎,没去细想,当然主要由于自己是半路出家的Java、web开发,本身也没那么熟悉,所以没理解哈,
现在想想DataSource其实就是保存了些配置,说白了是url和账号密码,就是连接数据库的,相当于你用命令行连接了数据库进行了操作一样,各种
数据库DataSource的实现高功能多半应该是做了些连接池的管理,以及连接的打开关闭之类,其实实质上我觉得应该就是说最后用的就是那个url加
上账号密码就能连接并操作了。这样的话,多数据源的切换就好理解了,结合 aop 在函数入口之前设置好当前线程数据源,以及根据路由数据库类
AbstractRoutingDataSource将选择数据源留给子类实现的方法
determineCurrentLookupKey,从而在service方法入口设置数据源,在使用时取到数据源。
大PS:这应该算是我写的最全的一次Java的博客了!!!