Springboot integrates mybatis and configures multiple data sources

principle

Determine which data source is needed through the annotations on the spring AOP acquisition method, bind the key of the data source to ThreadLocal, the dynamic data source class DynamicDataSource inherits AbstractRoutingDataSource, and configure all data sources that need to be routed in the construction method, in the form of key-value, And rewrite the routing method, the routing rule is to obtain the key of the data source according to ThreadLocal to determine which data source to choose

Code

Package structure
Insert picture description here
pom

  <dependencies>
        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>
        <!-- druid依赖的日志包 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
          <!--aop 配置动态数据源使用-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
        </dependency>
        <!--aop实现-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

marl

spring:
  datasource:
    local:
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3306/law?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8
    remote:
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3306/law_file?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8

mybatis:
  mapper-locations: classpath:mybatis/mapper/*.xml
  config-location: classpath:/config/mybatis-config.xml

Startup class
Exclude the automatic configuration class of the data source to avoid an error at startup (dependency cycle injection)

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SpringbootMybatisDatadourceApplication {

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

}

Insert picture description here

The mybatis global configuration file mybatis-config.xml
has aliases configured here, mainly because mapper.xml reports red, although it does not affect the operation

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "mybatis-3-config.dtd" >
<configuration>
   <typeAliases>
       <package name="com.example.domain"/>
   </typeAliases>
</configuration>

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.dao.UserDao">

    <select id="selectAll" resultType="user">
        select * from user
    </select>
</mapper>

UserDao
recommends to write two annotations
@Mapper to inject the implementation class into spring. You can write @MapperScan on the startup class instead of
@Repository. You don’t need to write it, mainly because other classes injected into dao will report red

@Mapper
@Repository
public interface UserDao {
    List<User> selectAll();
}

Dynamic data source
Create a new config package and put all the following classes in it

public enum DataSourceType {
    REMOTE,
    LOCAL


}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    /**
     * 切换数据源名称
     */
    DataSourceType value() default DataSourceType.REMOTE;
}
@Aspect
@Order(1)
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.example.config.DataSource)")
    public void dsPointCut() {

    }

    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource dataSource = method.getAnnotation(DataSource.class);
        if (dataSource != null) {
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
        }
        try {
            return point.proceed();
        } finally {
            // 销毁数据源 在执行方法之后
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }
}
@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.remote")
    public DataSource remoteDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.local")
    public DataSource localDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    @Bean(name = "dynamicDataSource")
    @Primary //代表默认选项
    public DynamicDataSource dataSource(DataSource remoteDataSource, DataSource localDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.REMOTE.name(), remoteDataSource);
        targetDataSources.put(DataSourceType.LOCAL.name(), localDataSource);
        return new DynamicDataSource(remoteDataSource, targetDataSources);
    }
}

Dynamic data source class, configure routing rules, the above code DataSourceConfig has been injected

public class DynamicDataSource extends AbstractRoutingDataSource {

    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        // afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的
        super.afterPropertiesSet();
    }

    /**
     * 根据Key获取数据源的信息
     *
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

threadLocal类

public class DynamicDataSourceContextHolder {

    /**
     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     *  所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
     * 设置数据源变量
     * @param dataSourceType
     */
    public static void setDataSourceType(String dataSourceType){
        System.out.printf("切换到{%s}数据源", dataSourceType);
        CONTEXT_HOLDER.set(dataSourceType);
    }

    /**
     * 获取数据源变量
     * @return
     */
    public static String getDataSourceType(){
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清空数据源变量
     */
    public static void clearDataSourceType(){
        CONTEXT_HOLDER.remove();
    }
}

mybatis configuration class

/**
 * mybatis configuration(建议使用配置类的方式重新声明相关bean)
 **/
@Configuration
@Slf4j
public class MybatisConfig {


    @Value("${mybatis.mapper-locations}")
    private String mapperLocations;

    @Value("${mybatis.config-location}")
    private String configLocation;

    @Autowired
    private DynamicDataSource dynamicDataSource;

    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dynamicDataSource);
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        Resource[] mapperResources = resourcePatternResolver.getResources(mapperLocations);
        sqlSessionFactory.setMapperLocations(mapperResources);
        sqlSessionFactory.setConfigLocation(resourcePatternResolver.getResource(configLocation));
        log.info("config location: {},mapper locations: {}", configLocation, mapperLocations);
        return sqlSessionFactory.getObject();
    }
}

Business code
user

@Data
public class User {
    private String id;
    private String password;
    private String username;
}

controller

@RestController
public class UserController {
    @Autowired
    private UserDao userDao;
    @GetMapping("/law")
    @DataSource(DataSourceType.LOCAL)
    public List<User> findAll(){
        return userDao.selectAll();
    }

    @GetMapping("/lawfile")
    @DataSource(DataSourceType.REMOTE)
    public List<User> findAll1(){
        return userDao.selectAll();
    }
}

Guess you like

Origin blog.csdn.net/weixin_44892460/article/details/108165933