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