springboot主從數據庫

是從springmvc的思路上來作的,主要就是配置主、從DataSource,
再繼承AbstractRoutingDataSource,重寫determineCurrentLookupKey
方法,經過Context結合 aop 進行數據主、從庫的切換。

 

上代碼:java

 

路由,即實現多數據庫的切換源web

/*
*    重寫的函數決定了最後選擇的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();
    }

}

註解,即用以標識選擇主仍是從數據庫spring

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String value();
}

常規配置項,具體主從繼承並經過sql

@ConfigurationProperties(prefix = "master.datasource") 進行配置讀取數據庫

public class BaseDataSourceConfig {

    private String url;
    private String username;
    private String password;
    private String driverClassName;
    // 添加上getter、setter方法
}

多數據源設置springboot

@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,用以保持當前線程的數據源選擇。session

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+註解實現數據源切換啦mvc

@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;
    }
}

那用法就是以下了:ide

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

相關文章
相關標籤/搜索