如何在程序中做读写分离?
1.直接买阿里云的服务,让它自动帮我们做读写分离,我们客户端还是一个连接的url
2.客户端配置多数据源(主从数据库),然后在程序端通过动态数据源切换来切换数据源,基于ThreadLocal来实现。
关键代码
1.自定义注解(配置要使用哪个数据源)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
/**
* 数据源key值
* @return
*/
String value();
}
2.threadLocal工具类保存数据源的key
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(){
/**
* 将 master 数据源的 key作为默认数据源的 key
* @return
*/
@Override
protected String initialValue() {
return "master";
}
};
/**
* 数据源的 key集合, 用于切换时判断数据源是否存在
*/
public static List<Object> dataSourceKeys = new ArrayList<>();
/**
* 切换数据源
* @param key
*/
public static void setDataSourceKey(String key){
contextHolder.set(key);
}
/**
* 获取数据源
* @return
*/
public static String getDataSourceKey(){
return contextHolder.get();
}
/**
* 重置数据源
*/
public static void clearDataSourceKey(){
contextHolder.remove();
}
/**
* 判断是否包含数据源
* @param key
* @return
*/
public static boolean containDataSourceKey(String key){
return dataSourceKeys.contains(key);
}
/**
* 添加数据源keys
* @param keys
* @return
*/
public static boolean addDataSourceKeys(Collection<? extends Object> keys){
return dataSourceKeys.addAll(keys);
}
}
3.动态数据源配置类
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected DataSource determineTargetDataSource() {
return super.determineTargetDataSource();
}
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
public void setDefaultDataSource(Object defaultDataSource){
super.setDefaultTargetDataSource(defaultDataSource);
}
public void setDataSources(Map<Object, Object> dataSources){
super.setTargetDataSources(dataSources);
DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet());
}
}
4.mybatis配置
@Configuration
@MapperScan(basePackages = {"com.example.demo.mapper"})
public class MybatisConfig {
@Bean("master")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource master(){
return DataSourceBuilder.create().build();
}
@Bean("slave")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slave(){
return DataSourceBuilder.create().build();
}
@Bean("dynamicDataSource")
public DataSource dynamicDataSource(){
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put("master", master());
dataSourceMap.put("slave", slave());
dynamicDataSource.setDefaultDataSource(master());
dynamicDataSource.setDataSources(dataSourceMap);
return dynamicDataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean() throws Exception{
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dynamicDataSource());
sessionFactory.setTypeAliasesPackage("com.example.demo.entity");
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sessionFactory.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
return sessionFactory;
}
@Bean
public PlatformTransactionManager transactionManager(){
return new DataSourceTransactionManager(dynamicDataSource());
}
}
5.切面拦截设置datasource的key到threadLocal
@Aspect
@Order(-1)
@Component
public class DynamicDataSourceAspect {
@Before("@annotation(dataSource)")
public void switchDataSource(JoinPoint point, DataSource dataSource){
if(!DynamicDataSourceContextHolder.containDataSourceKey(dataSource.value())){
System.out.println("DataSource [{}] doesn't exist, use default DataSource [{}] " + dataSource.value());
}else {
DynamicDataSourceContextHolder.setDataSourceKey(dataSource.value());
System.out.println("Switch DataSource to [" + DynamicDataSourceContextHolder.getDataSourceKey()
+ "] in Method [" + point.getSignature() + "]");
}
}
@After("@annotation(dataSource)")
public void restoreDataSource(JoinPoint point, DataSource dataSource){
DynamicDataSourceContextHolder.clearDataSourceKey();
System.out.println("Restore DataSource to [" + DynamicDataSourceContextHolder.getDataSourceKey()
+ "] in Method [" + point.getSignature() + "]");
}
}