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