美文网首页
Spring Boot + Mybatis 实现动态数据源

Spring Boot + Mybatis 实现动态数据源

作者: 务简 | 来源:发表于2022-03-11 17:40 被阅读0次

    多租户的场景,系统登录时需要根据用户信息切换到用户对应的数据库,怎么搞,直接来个最简单的实现。

    1. pom.xml 所需依赖

    <dependencies>
               <!-- spring boot -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <!-- spring aop -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <!-- mybatis -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.spring.version}</version>
            </dependency>
            <!-- mysql -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
    </dependencies>
    

    2. application.yml 配置文件

    spring:
      datasource:
        master:
          driver-class-name: com.mysql.jdbc.Driver
          type: com.zaxxer.hikari.HikariDataSource
          jdbcUrl: jdbc:mysql://127.0.0.1:3306/master?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8
          username: root
          password: 123
        slave:
          driver-class-name: com.mysql.jdbc.Driver
          type: com.zaxxer.hikari.HikariDataSource
          jdbcUrl: jdbc:mysql://127.0.0.1:3306/slave?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8
          username: root
          password: 123
    

    3. 主要几个java

    3.1 枚举类

    public enum DBTypeEnum {
        MASTER, SLAVE;
    }
    

    3.2 线程切换数据源

    @Slf4j
    public class DynamicHelper {
    
        private static final ThreadLocal<DBTypeEnum> dynamicDadaLocal = new ThreadLocal<>();
    
        /**
         * 主库
         */
        public static void master() {
            set(DBTypeEnum.MASTER);
        }
    
        public static void set(DBTypeEnum dbType) {
            log.info("--------------------> 切换数据源:{}", dbType.name());
            dynamicDadaLocal.set(dbType);
        }
    
        public static DBTypeEnum get() {
            return dynamicDadaLocal.get();
        }
    
        public static void remove() {
            dynamicDadaLocal.remove();
        }
    }
    

    3.3 覆写切换数据源类

    public class RoutingDataSource extends AbstractRoutingDataSource {
    
        /**
         * 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
         * 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可
         */
        @Override
        protected DataSource determineTargetDataSource() {
            return super.determineTargetDataSource();
        }
    
        /**
         * 如果希望所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据,定制这个方法
         */
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicHelper.get();
        }
    }
    

    3.4 mybatis 配置

    @ComponentScan(basePackages = {"com.xx.xx.entity"})
    @MapperScan({"com.xx.xx.mapper"})
    @Configuration
    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
        public DataSource routingDataSource() {
            Map<Object, Object> targetDataSources = new HashMap<>(2);
            targetDataSources.put(DBTypeEnum.MASTER, master());
            targetDataSources.put(DBTypeEnum.SLAVE, slave());
            RoutingDataSource routingDataSource = new RoutingDataSource();
            routingDataSource.setDefaultTargetDataSource(master());
            routingDataSource.setTargetDataSources(targetDataSources);
            return routingDataSource;
        }
    
        @Bean
        public SqlSessionFactoryBean sqlSessionFactoryBean() {
            SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
            // 配置数据源,此处配置为关键配置,如果没有将 dynamicDataSource作为数据源则不能实现切换
            sessionFactory.setDataSource(routingDataSource());
    //        sessionFactory.setTypeAliasesPackage("com.xx.**.entity");
    //        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    //        sessionFactory.setMapperLocations(resolver.getResources("classpath*:**/mapper/*.xml"));
            return sessionFactory;
        }
    
        @Bean
        public PlatformTransactionManager transactionManager() {
            // 配置事务管理, 使用事务时在方法头部添加@Transactional注解即可
            return new DataSourceTransactionManager(routingDataSource());
        }
    }
    

    4 单元测试(打完收工)

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = xxApplication.class)
    @Slf4j
    public class TestDynamicData {
    
        @Autowired
        private MasterMapper masterMapper;
    
        @Autowired
        private SlaverMapper slaveMapper;
    
        @Test
        public void testDynamic() {
            //
            DynamicHelper.master();
            final Integer masterCount = masterMapper.countxx();
            DynamicHelper.set(DBTypeEnum.SLAVE);
            final Integer slaveCount = slaveMapper.countXX();
            log.info("测试动态数据源。masterCount :{},slaveCount :{}", masterCount , slaveCount );
        }
    }
    

    5 aop 增强使用

    5.1 定义注解

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Documented
    public @interface DBType {
    
        DBTypeEnum type();
    }
    

    5.2 aop拦截

    @Aspect
    @Component
    public class DataSourceAop {
        /**
         * 通过切面实现数据源的切换
         *
         * @param jp
         */
        @Around("@annotation(com.xx.xx.DBType)")
        public Object datasourceSwitch(ProceedingJoinPoint jp) throws Throwable {
            Class<?> clazz = jp.getTarget().getClass();
            Method method = ((MethodSignature) jp.getSignature()).getMethod();
            if (method.isAnnotationPresent(DBType.class)) {
                //使用在方法上
                DynamicHelper.set(method.getAnnotation(DBType.class).type());
            } else if (clazz.isAnnotationPresent(DBType.class)) {
                //使用来类上
                DynamicHelper.set(clazz.getAnnotation(DBType.class).type());
            } else {
                //默认数据源
                DynamicHelper.master();
            }
            final Object proceed = jp.proceed();
            //清除
            DynamicHelper.remove();
            return proceed;
        }
    }
    

    相关文章

      网友评论

          本文标题:Spring Boot + Mybatis 实现动态数据源

          本文链接:https://www.haomeiwen.com/subject/otzxlltx.html