实现SpringBoot的多数据源配置

【场景】

  1. 当业务数据量达到了一定程度,DBA 需要合理配置数据库资源。即配置主库的机器高配置,把核心高频的数据放在主库上;把次要的数据放在从库,低配置。
    –(引自 https://www.cnblogs.com/Alandre/p/6611813.html 泥瓦匠BYSocket 大神博客)
  2. 实现读写分离(详见

https://www.cnblogs.com/surge/p/3582248.html


【实现步骤(以renren-security为例)】

  1. 在yml中配置多数据源
    在这里插入图片描述注意此处的 first 和 second,后续配置数据源的名称要与此处名称一致。
    2.配置多数据源的标志注解@DataSource,其使用方法是在具体进行业务编码的时候,通过@DataSource(name = “first”)来实现数据源的切换,此处的 first 是在 yml 文件中定义的数据库名称。
    在这里插入图片描述
    这里重点介绍下SpringBoot的3个注解:
    Java注解之 @Target、@Retention、@Documented简介
    另外补充2个注解:

@Inherited注解 功能:允许子类继承父类中的注解。
@interface意思是声明一个注解,方法名对应参数名,返回值类型对应参数类型。

3.编写DynamicDataSource类,该类需要继承AbstractRoutingDataSource,在Spring容器加载的时候,就注册(设置)数据源,其中,AbstractRoutingDataSource重写了determineCurrentLookupKey()方法,该方法从ThreadLocal(当前线程)中获取数据源,同时也避免了多线程操作数据源的时候互相干扰。(其中,各DataSource类的关系是: AbstractRoutingDataSource继承了AbstractDataSource ,而AbstractDataSource 又是DataSource 的子类。DataSource 是javax.sql 的数据源接口。详见

https://www.cnblogs.com/surge/p/3582248.html

在这里插入图片描述在getDataSource方法中,使用了ThreadLocal类的get()方法以获取当前线程,该方法遍历Map的方式十分优雅,MARK一下。当然,getDataSource()方法是public static修饰的,会在类加载的时候就执行。该类实现功能的路径是:获取当前线程–>获取当前线程中的数据源–>设置默认数据源、设置所有可用数据源、执行初始化。
在这里插入图片描述
4. 使用@Aspect编写DataSourceAspect切面,环绕立体织入数据源调配功能。此处@Aspect和@Component方法必须同时使用,否则切面会不起作用。

/*@AspectJ可以使用切点函数定义切点*/
@Aspect
/*和sping整合的时候必须要这个注解,否则sping容器解析不到该切面导致切面不能工作*/
@Component
public class DataSourceAspect implements Ordered {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * @Pointcut 注解以及切面类方法对切点进行命名,配置spring的配置拦截规则
     * 当前执行方法上持有注解 io.renren.datasources.annotation.DataSource将被匹配
     */
    @Pointcut("@annotation(io.renren.datasources.annotation.DataSource)")
    public void dataSourcePointCut() {/*切面名称是 dataSourcePointCut*/

    }

    /**
     * 环绕 dataSourcePointCut()方法,在使用 @DataSource 注解的时候,会触发数据源设定方法
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("dataSourcePointCut()")
    /**
     * ProceedingJoinPoint:aspectJ切面通过ProceedingJoinPoint获取当前执行的方法,并且使用proceed()方法来执行目标方法:
     */
    public Object around(ProceedingJoinPoint point) throws Throwable {
        /*
        通过pjp对象获取Signature对象,该对象封装了连接点的信息。
        比如通过getDeclaringType获取连接点所在类的  class对象*/
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        /*获取 DataSource 中的注解*/
        DataSource ds = method.getAnnotation(DataSource.class);
        if(ds == null){
            /*设置默认数据源为DataSourceNames.FIRST*/
            DynamicDataSource.setDataSource(DataSourceNames.FIRST);
            logger.debug("set datasource is " + DataSourceNames.FIRST);
        }else {
            DynamicDataSource.setDataSource(ds.name());
            logger.debug("set datasource is " + ds.name());
        }

        try {
            //使用proceed()方法来执行目标方法
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
            logger.debug("clean datasource");
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

其中,这一块代码很关键,在没有考虑调用clearDataSource()方法之前,在测试中发现,当涉及事务处理的时候,比如,数据源A执行了查询操作,紧接着数据源B执行写入操作,会发现,错了,数据被写入了A数据源。那么,怎么解决呢。这就需要调用一次clearDataSource()方法,因为ThreadLocal存在内存泄漏的问题。所谓内存泄漏,就是Tomcat在加载的时候会开启线程池,第一个涉及数据库的操作结束后,如果没有及时将ThreadLocal中的DataSource清掉,那么,在下一次的事务操作中,当前的DataSource仍然存在,就会导致混乱。在这里插入图片描述
5.配置数据源,即在Spring容器中装配数据源信息。

@Configuration
public class DynamicDataSourceConfig {
    /**
     * @ConfigurationProperties主要作用:就是绑定application.properties中的属性
     */
    @Bean(name = "firstDataSource")
    @ConfigurationProperties("spring.datasource.druid.first")
    public DataSource firstDataSource(){
        return DruidDataSourceBuilder.create().build();
    }


    @Bean(name = "secondDataSource")
    @ConfigurationProperties("spring.datasource.druid.second")
    public DataSource secondDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "firstDataSourceTransactionManager")
    public DataSourceTransactionManager firstDataSourceTransactionManager(@Qualifier("firstDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "secondDataSourceTransactionManager")
    public DataSourceTransactionManager secondDataSourceTransactionManager(@Qualifier("secondDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    @Primary
    public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
        targetDataSources.put(DataSourceNames.SECOND, secondDataSource);
        return new DynamicDataSource(firstDataSource, targetDataSources);
    }
}

6.测试一下

@RunWith(SpringRunner.class)
@SpringBootTest
public class DynamicDataSourceTest {
    @Autowired
    private DataSourceTestService dataSourceTestService;

    @Test
    public void test(){
        //数据源1
        SysUserEntity user1 = dataSourceTestService.queryUser(1L);
        System.out.println(ToStringBuilder.reflectionToString(user1));

        //数据源2
        SysUserEntity user2 = dataSourceTestService.queryUser2(1L);
        System.out.println(ToStringBuilder.reflectionToString(user2));

        //数据源1
        SysUserEntity user3 = dataSourceTestService.queryUser(1L);
        System.out.println(ToStringBuilder.reflectionToString(user3));

    }

}

Service层调用@DataSource(name=“DataSourceNames.SECOND”)
在这里插入图片描述
至此,SpringBoot的多数据源配置就完成了。

完整相关代码请见

https://gitee.com/renrenio/renren-security

猜你喜欢

转载自blog.csdn.net/qq_34749144/article/details/84030833