是从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的博客了!!!