美文网首页
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