springboot结合druid使用多数据源,动态切换
前言
有时候为了减少数据库的压力,就要实现数据库的读写分离,这种情况往往是读多写少的情况,例如电商平台。既然数据库读写分离了,那么代码层也就需要读写不同的数据库了。实现方法应该有不少,我知道有插件实现,判断写请求还是读请求来请求不同的数据库,还有代码实现,不同的SQL访问不同的数据源,也就是接下来要说的多数据源。
一、基础介绍
代码层多数据源的实现方法也有很多,例如不同的包拥有不同的数据源、AOP实现动态切换数据源等等。目前我使用的方法为AOP动态切换数据源方法,这种方法可以自定义注解,注解在controller层,根据不同的方法访问不同的数据源。主要实现方法就是先实例化一批数据源实例Map集合,key为实例名称,value为示例,AOP以自定义的注解为切点,根据注解的value读取是哪个数据库的数据源实例key,来获取指定的数据源。
二、配置多个数据源
首先,定义一个枚举来说明一下当前数据源实例key有哪些。
public enum DataSourceKey {
MASTER("master"),
SLAVE("slave");
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private DataSourceKey(String name) {
this.name = name;
}
}
其次,使用ThreadLocal存储当前使用数据源实例的key。ThreadLocal实例化的时候给一个master的默认值,也就是默认数据源是master数据源。
public class DynamicDataSourceContextHolder {
private static ThreadLocal<Object> CONTEXT_HOLDER = ThreadLocal.withInitial(() -> DataSourceKey.MASTER.getName());
public static List<Object> dataSourceKeys = new ArrayList<Object>();
public static void setDataSourceKey(String key){
CONTEXT_HOLDER.set(key);
}
public static Object getDataSourceKey(){
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceKey(){
CONTEXT_HOLDER.remove();
}
public static Boolean containDataSourceKey(String key){
return dataSourceKeys.contains(key);
}
}
重写AbstractRoutingDataSource的determineCurrentLookupKey方法,在访问数据库时会调用该类的 determineCurrentLookupKey() 方法获取数据库实例的 key。
/**
* 该类继承自 AbstractRoutingDataSource 类,
* 在访问数据库时会调用该类的 determineCurrentLookupKey() 方法获取数据库实例的 key
*/
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
private static final Logger logger = LoggerFactory.getLogger(DynamicRoutingDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
logger.info("current datasource is : {}",DynamicDataSourceContextHolder.getDataSourceKey());
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
然后,需要实例化多个DataSource的Bean放在ApplicationContext中,这里我们使用druid的实现,定义好每个Bean的名字,名字要和枚举的name一致,
@primary的意思就是默认使用这个Bean
dynamicDataSource需要一个存储数据库实例的Map集合
sqlSessionFactoryBean可以配置mybatis的相关配置
transactionManager是事务配置
/**
* 数据源配置类
* 在该类中生成多个数据源实例并将其注入到 ApplicationContext 中
* 2019年6月4日 上午10:06:54
*/
@Configuration
public class DataSourceConfigurer {
/**
* 配置数据源
* @return
*/
@Bean(name = "master")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.druid.master")
public DataSource master() {
return DruidDataSourceBuilder.create().build();
}
/**
* 配置数据源
* @return
*/
@Bean(name = "slave")
@ConfigurationProperties(prefix = "spring.datasource.druid.slave")
public DataSource slave() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource(){
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<Object, Object>(2);
dataSourceMap.put(DataSourceKey.MASTER.getName(),master());
dataSourceMap.put(DataSourceKey.SLAVE.getName(),slave());
dynamicRoutingDataSource.setDefaultTargetDataSource(master());
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet());
return dynamicRoutingDataSource;
}
@Bean
@ConfigurationProperties(prefix = "mybatis")
public SqlSessionFactoryBean sqlSessionFactoryBean(){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true); //下划线转骆驼
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
sqlSessionFactoryBean.setConfiguration(configuration);
return sqlSessionFactoryBean;
}
/**
* 事务
* @return
*/
@Bean
public PlatformTransactionManager transactionManager(){
return new DataSourceTransactionManager(dynamicDataSource());
}
}
三、自定义注解
为了使用AOP代理特定的方法,我们就可以自定义注解。这个注解很简单,也没啥太大含义。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
DataSourceKey value() default DataSourceKey.MASTER;
}
四、AOP动态切换数据源
使用AOP,以自定义注解注解在的方法为切点,动态切换数据源
@Aspect
@Component
public class DynamicDataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Before("@annotation(targetDataSource))")
public void switchDataSource(JoinPoint joinPoint,TargetDataSource targetDataSource){
if ( !DynamicDataSourceContextHolder.containDataSourceKey( targetDataSource.value().getName() ) ) {
logger.error("DataSource [{}] doesn't exist, use default DataSource [{}]", targetDataSource.value());
} else {
DynamicDataSourceContextHolder.setDataSourceKey( targetDataSource.value().getName() );
logger.info("Switch DataSource to [{}] in Method [{}]",
DynamicDataSourceContextHolder.getDataSourceKey(), joinPoint.getSignature());
}
}
@After("@annotation(targetDataSource))")
public void restoreDataSource(JoinPoint joinPoint,TargetDataSource targetDataSource){
DynamicDataSourceContextHolder.clearDataSourceKey();
logger.info("Restore DataSource to [{}] in Method [{}]",
DynamicDataSourceContextHolder.getDataSourceKey(), joinPoint.getSignature());
}
}
五、使用
在controller层使用,在不同的方法上添加自定义注解来使用不同的数据源。MASTER是默认数据源,可以不使用注解。
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user")
@TargetDataSource(DataSourceKey.MASTER)
public ResponseEntity<Object> listAll(){
List<JSONObject> result = userService.listAll();
return ResponseEntity.ok(result);
}
@PostMapping("/user")
@TargetDataSource(DataSourceKey.SLAVE)
public ResponseEntity<Object> listAlla(){
List<JSONObject> result = userService.listAll();
return ResponseEntity.ok(result);
}
}
写在最后的话
数据库主从同步可以参考网上的实现,我暂时还没实现,这个后面再说。代码的多数据源我自己测试过了。我也是看了别人写的,然后自己敲了一遍,记录一下。如果我有幸被你搜到了这篇博客,可以留言讨论。
更多推荐
所有评论(0)