ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Spring Boot +Mybatis plus多数据源实践

2022-08-24 12:32:13  阅读:171  来源: 互联网

标签:return 数据源 Boot dataSource DataSource Spring public datasource


  随着业务及客户的不断壮大,单数据库已经不足以支撑程序业务的完美运行(响应快、高吞吐),所以数据库往往都会进行分表分库/读写分离,那么问题来了,分库后程序如何从不同URL数据库中读取数据呢?

  这篇文章只讲如何配置/使用多数据源,不讲分表分库/读写分离,也不讲主键生成策略及读取策略。

  如何实现多数据源呢?原理很简单:Spring的AOP.只需要mybatis plus及spring boot的基础依赖,不需要引入其他依赖

  说明:多数据源不仅指同类不同地址的数据源,也可以是异构关系型数据库

  自定义注解

@Documented
@Inherited
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
    DataSourceType value() default DataSourceType.MESH;

    enum DataSourceType {
        /**
         * 数据源类型
         **/
        MASTER,
        SLAVE,
    }
}

yml文件

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  datasource:
    driver-class-name: org.postgresql.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 5
      maximum-pool-size: 10
    db:
      conn:
        str: useUnicode=true&characterEncoding=UTF-8
    master:
      jdbc-url: jdbc:postgresql://127.0.01:54320/master?${spring.datasource.db.conn.str}
      username: postgres
      password: 123456
    salve:
      jdbc-url: jdbc:postgresql://127.0.0.1:54321/salve?${spring.datasource.db.conn.str}
      username: postgres
      password: 123456

动态数据源上下文

@Slf4j
public class DynamicDataSourceContextHolder {

    /**
     * 数据源标识,保存在线程变量中,避免多线程操作数据源时互相干扰
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 设置数据源
     *
     * @param dataSource 数据源名称
     */
    public static void setDataSource(String dataSource) {
        log.info("切换到{}数据源", Assert.isEmpty(dataSource) ? DataSource.DataSourceType.MASTER.name() : dataSource);
        CONTEXT_HOLDER.set(Assert.isEmpty(dataSource) ? DataSource.DataSourceType.MASTER.name() : dataSource);
    }

    /**
     * 获取数据源
     *
     * @return java.lang.String
     * @author Jackpot
     * @date 2021/4/30 4:38 下午
     */
    public static String getDataSource() {
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清除数据源
     */
    public static void clearDataSource() {
        CONTEXT_HOLDER.remove();
    }

}

动态数据源

继承AbstractRoutingDataSource类,该类是能够实现数据源切换的关键所在。实现determineCurrentLookupKey(),返回数据源的key值。

@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {

    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = DynamicDataSourceContextHolder.getDataSource();
        log.info("当前数据源:{}", Assert.isEmpty(dataSource) ?
                com.**.config.datasource.DataSource.DataSourceType.MASTE.name() : dataSource);
        return dataSource;
    }

}

多数据源配置

@Slf4j
@Configuration
public class DataSourceConfig {

    final MybatisPlusInterceptor interceptor;

    public DataSourceConfig(MybatisPlusInterceptor interceptor) {
        this.interceptor = interceptor;
    }

    /**
     * 主数据源配置
     * Primary 表示当前数据源为主数据源
     *
     * @return javax.sql.DataSource
     * @author Jackpot
     * @date 2021/5/12 9:32 上午
     */
    @Primary
    @Bean("master")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 从数据源配置
     *
     * @return javax.sql.DataSource
     * @author Jackpot
     * @date 2021/5/12 9:32 上午
     */
    @Bean("salve")
    @ConfigurationProperties(prefix = "spring.datasource.salve")
    public DataSource salveDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 多数据源定义
     *
     * @param masterDataSource  主数据源
     * @param salveDataSource 从数据源
     * @return com.**.config.datasource.DynamicDataSource
     * @author Jackpot
     * @date 2021/5/12 9:33 上午
     */
    @Bean(name = "dynamicDataSource")
    public DynamicDataSource dataSource(@Qualifier("master") DataSource meshDataSource, @Qualifier("salve") DataSource walleDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>(4);
        targetDataSources.put(com.**.config.datasource.DataSource.DataSourceType.MASTER.name(), masterDataSource);
        targetDataSources.put(com.**.config.datasource.DataSource.DataSourceType.SALVE.name(), salveDataSource);
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }

    /**
     * 将动态数据源注入到SqlSessionFactory
     * 同时解决分页失效
     *
     * @param dynamicDataSource 多数据源
     * @return org.apache.ibatis.session.SqlSessionFactory
     * @author Jackpot
     * @date 2021/4/30 4:30 下午
     */
    @Bean("sqlSessionFactory")
    public SqlSessionFactory getSqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource)
            throws Exception {
        final MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dynamicDataSource);
        //分页插件
        bean.setPlugins(interceptor);
        bean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*Mapper.xml"));
        return bean.getObject();
    }

    /**
     * sqlSessionTemplate定义
     *
     * @param sessionFactory session工厂
     * @return org.mybatis.spring.SqlSessionTemplate
     * @author Jackpot
     * @date 2021/5/12 9:34 上午
     */
    @Bean("sqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sessionFactory) {
        return new SqlSessionTemplate(sessionFactory);
    }

    /**
     * 多数据源事务管理
     * 防止事务绑定主数据源无法对数据源进行切换及事务失效
     *
     * @param dataSource 多数据源
     * @return org.springframework.jdbc.datasource.DataSourceTransactionManager
     * @author Jackpot
     * @date 2021/5/12 9:39 上午
     */
    @Bean("dataSourceTransactionManager")
    public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("dynamicDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

}

AOP切面实现。

注:@annotation和@within怎么区分?通俗点说就是@annotation作用与方法(method)之上,@within作用于controller/service上,也就是对象

@Slf4j
@Order(0)
@Aspect
@Component
public class DataSourceAspect {

    /**
     * 通过自定义注解@DataSource定义切点
     */
    @Pointcut("@annotation(com.**.config.datasource.DataSource)" + "|| @within(com.**.config.datasource.DataSource)")
    public void dsPointCut() {
    }

    /**
     * 切点环绕
     *
     * @param point 切入点
     * @return java.lang.Object
     * @author Jackpot
     * @date 2021/4/30 4:34 下午
     */
    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        DataSource dataSource = getDataSource(point);
        if (dataSource != null) {
            DynamicDataSourceContextHolder.setDataSource(dataSource.value().name());
        }
        try {
            return point.proceed();
        } finally {
            DynamicDataSourceContextHolder.clearDataSource();
        }
    }

    /**
     * 获取需要切换的数据源
     *
     * @param point 切入点
     * @return com.iot.mesh.common.config.datasource.DataSource
     * @author Jackpot
     * @date 2021/4/30 4:34 下午
     */
    public DataSource getDataSource(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Class<?> targetClass = point.getTarget().getClass();
        DataSource targetDataSource = targetClass.getAnnotation(DataSource.class);
        if (targetDataSource != null) {
            return targetDataSource;
        } else {
            Method method = signature.getMethod();
            return method.getAnnotation(DataSource.class);
        }
    }
}

启动类,需要剔除spring boot的自动数据源配置

@EnableScheduling
@EnableTransactionManagement
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class ApiApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiApplication.class, args);
    }


}

使用:粗粒度使用在service类对象之上,细粒度作用于方法之上,同时存在方法注解优先于类上注解。推荐使用第二种方式。

注:同一个方法里面如果涉及到多个数据源操作,事务会失效。

 

 

 附:其实mybatis plus提供了多数据的依赖

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

 

 

 想偷懒的同学可以直接使用,上面主要讲的是核心内容,dynamic-datasource-spring-boot-starter实现动态数据源的思想也是AOP,把上面的代码弄懂了,dynamic-datasource的源码看起来也就很简单了.

  mybatis plus的多数据源的使用也很简单,对象或方法上使用@DS(**)即可。

  注:使用mybatis plus动态数据源,yml文件格式和上面有点区别,我这里也把它贴出来。启动类上也不需要将DataSourceAutoConfiguration.class剔除

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    locale: zh_CN
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
          url: jdbc:postgresql://127.0.0.1:54320/master?useUnicode=true&characterEncoding=utf-8&reWriteBatchedInserts=true
          username: postgres
          password: 123456
        salve:
          url: jdbc:postgresql://127.0.0.1:54321/salve?useUnicode=true&characterEncoding=utf-8&reWriteBatchedInserts=true
          username: postgres
          password: 123456

 

标签:return,数据源,Boot,dataSource,DataSource,Spring,public,datasource
来源: https://www.cnblogs.com/JackpotHan/p/16619424.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有