轉載出處https://www.cnblogs.com/shamo89/p/10032117.html
一、定義我們自己的切庫注解類
自定義注解有幾點需要注意:
1)@Target 是作用的目標,接口、方法、類、字段、包等等,具體看:ElementType
2)@Retention 是注解存在的范圍,RUNTIME代表的是注解會在class字節碼文件中存在,在運行時可以通過反射獲取到,具體看:RetentionPolicy
3)允許的變量,通常都要給定默認值,比如我們使用一個service時,可以@Service,也可以
@Service("xxxx") @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD }) public @interface RoutingDataSource { String value() default DataSources.MASTER_DB; }
二、定義需要使用的數據庫及配置
1、數據庫配置:application.properties,這里要注意不同db的前綴區別
## datasource master # spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/master?characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=466420182 ## datasource slave # spring.datasourceSlave.type=com.alibaba.druid.pool.DruidDataSource spring.datasourceSlave.driver-class-name=com.mysql.jdbc.Driver spring.datasourceSlave.url=jdbc:mysql://localhost:3306/slave?characterEncoding=UTF-8 spring.datasourceSlave.username=root spring.datasourceSlave.password=466420182
2、定義支持的數據源id
public interface DataSources { String MASTER_DB = "masterDB"; String SLAVE_DB = "slaveDB"; }
3、定義數據庫實體類並配置為多數據源的形式
這里不要忽略了通過 MapperScan 指定需要掃描的mybatis的接口類
@Configuration public class DatasourceConfig { //destroy-method="close"的作用是當數據庫連接不使用的時候,就把該連接重新放到數據池中,方便下次使用調用. @Bean(destroyMethod = "close", name = DataSources.MASTER_DB) @ConfigurationProperties(prefix = "spring.datasource") public DataSource dataSource() { return DataSourceBuilder.create().type(DruidDataSource.class).build(); } @Bean(destroyMethod = "close", name = DataSources.SLAVE_DB) @ConfigurationProperties(prefix = "spring.datasourceSlave") public DataSource dataSourceSlave() { return DataSourceBuilder.create().type(DruidDataSource.class).build(); } }
4、配置成動態數據源
@Configuration @MapperScan(basePackages = {"com.xxx.dao"}) // 這里需要替換為實際的路徑 public class MybatisConfig { @Autowired @Qualifier(Datasources.MASTER_DB) private DataSource masterDB; @Autowired @Qualifier(DataSources.SLAVE_DB) private DataSource slaveDB; /** * 動態數據源 */ @Bean(name = "dynamicDataSource") public DataSource dynamicDataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); // 默認數據源 dynamicDataSource.setDefaultTargetDataSource(masterDB); // 配置多數據源 Map<Object, Object> dsMap = Maps.newHashMap(); dsMap.put(DataSources.MASTER_DB, masterDB); dsMap.put(DataSources.SLAVE_DB, slaveDB); dynamicDataSource.setTargetDataSources(dsMap); return dynamicDataSource; } @Bean @ConfigurationProperties(prefix = "mybatis") public SqlSessionFactoryBean sqlSessionFactoryBean() { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); // 配置數據源,此處配置為關鍵配置,如果沒有將 dynamicDataSource 作為數據源則不能實現切換 sqlSessionFactoryBean.setDataSource(dynamicDataSource()); return sqlSessionFactoryBean; } }
三、使用ThreadLocal安全的管理當前進程使用的數據源連接
@Slf4j public class DataSourceContextHolder { /** * 默認數據源 */ public static final String DEFAULT_DATASOURCE = DataSources.MASTER_DB; private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); // 設置數據源名 public static void setDB(String dbType) { log.debug("切換到{}數據源", dbType); contextHolder.set(dbType); } // 獲取數據源名 public static String getDB() { return (contextHolder.get()); } // 清除數據源名 public static void clearDB() { contextHolder.remove(); } }
四、通過編寫切面,對所有我們自定義切庫注解的方法進行攔截,動態的選擇數據源
這里是為下一步提供鋪墊,動態調整DataSourceContextHolder里存儲的值,使用threadLocal來管理是為了避免多線程之間互相影響。
自定義注解,核心的處理就是寫處理這個注解的邏輯,然后通過指定的攔截方案根據當前的數據做一些動態的處理。比如Spring提供的@Controller、@Service等注解,都是需要我們在配置文件里配置好需要掃描的路徑,然后項目啟動時,spring根據配置去指定路徑讀取這些配置,然后這些類才可以被spring進行管理。
這里不要忽略了默認數據源要選擇主庫,如果切庫出現什么問題,比如配置錯誤等,可以保證訪問主庫來得到正確的結果;另外,請求完了不要忘記調用提供的clearDB的操作,防止threadLocal誤用帶來的內存泄露。
@Aspect @Component @Slf4j public class DynamicDataSourceAspect { @Before("@annotation(RoutingDataSource)") public void beforeSwitchDS(JoinPoint point){ //獲得當前訪問的class Class<?> className = point.getTarget().getClass(); //獲得訪問的方法名 String methodName = point.getSignature().getName(); //得到方法的參數的類型 Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes(); String dataSource = DataSourceContextHolder.DEFAULT_DATASOURCE; try { // 得到訪問的方法對象 Method method = className.getMethod(methodName, argClass); // 判斷是否存在@DS注解 if (method.isAnnotationPresent(RoutingDataSource.class)) { RoutingDataSource annotation = method.getAnnotation(RoutingDataSource.class); // 取出注解中的數據源名 dataSource = annotation.value(); } } catch (Exception e) { log.error("routing datasource exception, " + methodName, e); } // 切換數據源 DataSourceContextHolder.setDB(dataSource); } @After("@annotation(RoutingDataSource)") public void afterSwitchDS(JoinPoint point){ DataSourceContextHolder.clearDB(); } }
五、動態的取出我們在切面里設置的數據源的字符串即可
@Slf4j public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { log.debug("數據源為{}", DataSourceContextHolder.getDB()); return DataSourceContextHolder.getDB(); } }
六、取消自動配置數據源,使用我們這里定義的數據源配置
在SpringBoot啟動類上通常直接使用@SpringBootApplication就可以了,這里需要調整為:
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
其實這個配置就是告訴springboot不使用默認的數據源配置。
七、使用
@Service public class DataSourceRoutingService { @Resource private SysUserMapper sysUserMapper; @RoutingDataSource(DataSources.MASTER_DB) // 這個注解這時是可以省略,因為默認就是訪問主庫 public SysUser test1(int id) { return sysUserMapper.selectByPrimaryKey(id); } @RoutingDataSource(DataSources.SLAVE_DB) public SysUser test2(int id) { return sysUserMapper.selectByPrimaryKey(id); } }
如此,數據庫切庫就OK了。如果你的系統已經有主庫、從庫之分了,那么趕緊在你的系統里利用起來吧。
八、擴展
這里呢,還可以支持多個擴展。比如現在一個主庫后面有多個從庫,在切面拿到需要切換從庫時,還可以選擇隨機選擇一個,或者根據類名、方法名或業務配置等選擇某一個從庫,這樣不但可以分擔每個從庫的壓力,也可以有針對性的讓指定的讀請求打到指定的從庫上。如果有多個主庫,也可以有更多的選擇~