springboot主從數據庫


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM