如何一个项目配置多个数据库

2022年6月15日10:13:11

我们在项目中经常会遇到配置多个数据的情况,之前我有写过通过配置mapper.xml的不同地址来实现同一个项目连接到多个数据库。如果用这种方式可以参考,下面一片文章。

https://blog.csdn.net/fajing_feiyue/article/details/100604300

这篇文档与上面一种方式相比,是通过注解来实现和切面来配置实现数据库,这种方式需要能够更深刻来理解mybatis与spring的结合,这里将这种方式展示出来供大家参考。

1、引入maven依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.0.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>8.0.17</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
            <version>2.0.4.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>1.5.3.RELEASE</version>
        </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>

2、配置application.properties

server.port=8080


default.datasource.type=com.alibaba.druid.pool.DruidDataSource
default.datasource.url=jdbc:mysql://127.0.0.1:3306/pingyougou?serverTimezone=UTC
default.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
default.datasource.username=root
default.datasource.password=123

custom.datasource.names=custom1
custom.datasource.custom1.type=com.alibaba.druid.pool.DruidDataSource
custom.datasource.custom1.url=jdbc:mysql://127.0.0.1:3306/pinyougoudb?serverTimezone=UTC
custom.datasource.custom1.driver-class-name=com.mysql.cj.jdbc.Driver
custom.datasource.custom1.username=root
custom.datasource.custom1.password=123


#配置初始化大小,最小,最大
common.datasource.initialSize=10
common.datasource.minIdle=10
common.datasource.maxActive=50
#配置连接等待超时时间
common.datasource.maxWait=15000
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
common.datasource.timeBetweenEvictionRunsMillis=60000
#配置一个连接在池中最小生存时间,一个小时
common.datasource.minEvictableIdleTimeMillis=3600000
#连接空闲时是否进行有效性验证(即是否还和数据库连通的)
common.datasource.testWhileIdle=true
#取得连接时是否进行有效性验证(即是否还和数据库连通的)【防止取到连接不可用】
common.datasource.testOnBorrow=true
#回连接时是否进行有效性验证(即是否还和数据库连通的)
common.datasource.testOnReturn=false
#配置提交方式,默认是true,可以不用配置
common.datasource.defultAutoCommint=true


#logging.config=classpath:config/log4j2.xml

#配置.xml文件路径
#mybatis.config-locations=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.yin.dynamic_datasource.dto

3、通过实现ImportBeanDefinitionRegistrar动态注册DataSource,实现EnvironmentAware 来读取application.properties里面的内容。由自己定义DynamicDataSourceContextHolder来保存目前正在数据库的信息
和每个线程所使用的数据库。并且设置由自己定义的DynamicDataSource来切换数据库。

public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
    private static final Object DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";
    private DataSource defaultDataSource;
    private Map<String, DataSource> customDataSources = new HashMap();
    private static String DB_NAME = "names";
    private static String DB_DEFAULT_VALUE = "default.datasource";
    private static String DB_CUSTOM_VALUE = "custom.datasource";
    private static String DB_COMMON_VALUE = "common.datasource";


    public DynamicDataSourceRegister() {
    }



    @Override
    public void setEnvironment(Environment env) {
        initDefaultDataSource(env);
        initCustomDataSources(env);

    }

    private void initCustomDataSources(Environment env) {
        String dsPrefixs = env.getProperty(DB_CUSTOM_VALUE + "." + DB_NAME);
        String[] var3 = dsPrefixs.split(",");
        int var4 = var3.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String dsPrefix = var3[var5];
            Map<String, Object> dsMap = new HashMap();
            dsMap.put("type", env.getProperty(DB_CUSTOM_VALUE + "." + dsPrefix + ".type"));
            dsMap.put("driver-class-name", env.getProperty(DB_CUSTOM_VALUE + "." + dsPrefix + ".driver-class-name"));
            dsMap.put("url", env.getProperty(DB_CUSTOM_VALUE + "." + dsPrefix + ".url"));
            dsMap.put("username", env.getProperty(DB_CUSTOM_VALUE + "." + dsPrefix + ".username"));
            dsMap.put("password", env.getProperty(DB_CUSTOM_VALUE + "." + dsPrefix + ".password"));
            DataSource ds = this.buildDataSource(dsMap,env);
            this.customDataSources.put(dsPrefix, ds);
        }
    }

    private void initDefaultDataSource(Environment env) {

        HashMap<String, Object> dsMap = new HashMap<>();
        dsMap.put("type", env.getProperty(DB_DEFAULT_VALUE + ".type"));
        dsMap.put("driver-class-name", env.getProperty(DB_DEFAULT_VALUE + ".driver-class-name"));
        dsMap.put("url", env.getProperty(DB_DEFAULT_VALUE + ".url"));
        dsMap.put("username", env.getProperty(DB_DEFAULT_VALUE + ".username"));
        dsMap.put("password", env.getProperty(DB_DEFAULT_VALUE + ".password"));
        //创建数据源
        defaultDataSource = buildDataSource(dsMap, env);
    }

    private DataSource buildDataSource(Map<String, Object> dsMap,Environment env) {
        Object type = dsMap.get("type");
        if (type == null) {
            type = DATASOURCE_TYPE_DEFAULT;
        }
        Class<? extends DataSource> dataSourceType;
        try {
            dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
            String driverClassName = dsMap.get("driver-class-name").toString();
            String url = dsMap.get("url").toString();
            String username = dsMap.get("username").toString();
            String password = dsMap.get("password").toString();

            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setUrl(url);
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            dataSource.setDriverClassName(driverClassName);

            dataSource.setInitialSize(Integer.valueOf(env.getProperty(DB_COMMON_VALUE + ".initialSize")));
            dataSource.setMinIdle(Integer.valueOf(env.getProperty(DB_COMMON_VALUE + ".minIdle")));
            dataSource.setMaxActive(Integer.valueOf(env.getProperty(DB_COMMON_VALUE + ".maxActive")));
            dataSource.setMaxWait(Integer.valueOf(env.getProperty(DB_COMMON_VALUE + ".maxWait")));
            dataSource.setTimeBetweenEvictionRunsMillis(Integer.valueOf(env.getProperty(DB_COMMON_VALUE + ".timeBetweenEvictionRunsMillis")));
            dataSource.setMinEvictableIdleTimeMillis(Long.valueOf(env.getProperty(DB_COMMON_VALUE + ".minEvictableIdleTimeMillis")));
            dataSource.setTestWhileIdle(Boolean.valueOf(env.getProperty(DB_COMMON_VALUE + ".testWhileIdle")));
            dataSource.setTestOnBorrow(Boolean.valueOf(env.getProperty(DB_COMMON_VALUE + ".testOnBorrow")));
            dataSource.setTestOnReturn(Boolean.valueOf(env.getProperty(DB_COMMON_VALUE + ".testOnReturn")));
            dataSource.setDefaultAutoCommit(Boolean.valueOf(env.getProperty(DB_COMMON_VALUE + ".defultAutoCommint")));
            dataSource.setValidationQuery("SELECT 1");
            return dataSource;


        } catch (ClassNotFoundException ex) {
            logger.error(ex.getMessage(), ex);
        }
        return null;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
        Map<Object, Object> targetDataSources = new HashMap();
        targetDataSources.put("dataSource", this.defaultDataSource);
        DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
        targetDataSources.putAll(this.customDataSources);
        Iterator var4 = this.customDataSources.keySet().iterator();

        while(var4.hasNext()) {
            String key = (String)var4.next();
            DynamicDataSourceContextHolder.dataSourceIds.add(key);
        }

        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        mpv.addPropertyValue("defaultTargetDataSource", this.defaultDataSource);
        mpv.addPropertyValue("targetDataSources", targetDataSources);
        registry.registerBeanDefinition("dataSource", beanDefinition);
        logger.info("Dynamic DataSource Registry");
    }
}

4、将DynamicDataSourceRegister 注册类交由启动类加载

@SpringBootApplication
@Import(DynamicDataSourceRegister.class)
public class DynamicDatasourceApplication {

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

}

5、定义DynamicDataSourceContextHolder类来保存所有用到数据库和每个线程所使用到数据库

public class DynamicDataSourceContextHolder {
    /**
     * 保存每个线程对应数据信息
     */
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
    /**
     * 保存所有数据库的信息
     */
    public static List<String> dataSourceIds = new CopyOnWriteArrayList<>();

    public static void setContextHolder(String dataSource){
        contextHolder.set(dataSource);
    }


    public static String getContextHolder(){
        return contextHolder.get();
    }

    public static  boolean isContainsDatasource(String dataSourceId){
        return dataSourceIds.contains(dataSourceId);
    }
//这里在使用完数据库进行.remove()防止内存泄漏从而导致内存溢出
    public static void clearDataSourceType(){
        contextHolder.remove();
    }


}

6、定义DynamicDataSource来切换数据库

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getContextHolder();
    }
}

7、定义注解TargetDataSource 来实现,对数据库指定

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String name();
}

8、定义切面对使用到这个注解的方法前后对用DynamicDataSourceContextHolder 保存和移除每个线程所使用到的数据库信息。之所以要用完移除主要是防止内存泄漏从而导致的内存溢出。

@Aspect
//保证该aop在@Transaction之前执行
@Order(-1)
@Component
public class DynamicDataSourceAspect {
    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

    public DynamicDataSourceAspect() {
    }

    @Before("@annotation(ds)")
    public void changeDataSource(JoinPoint point, TargetDataSource ds) throws Throwable {
        String dsId = ds.name();
        if (!DynamicDataSourceContextHolder.isContainsDatasource(dsId)) {
            logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature());
        } else {
            logger.debug("Use DataSource : {} > {}", dsId, point.getSignature());
            DynamicDataSourceContextHolder.setContextHolder(dsId);
        }

    }

    @After("@annotation(ds)")
    public void restoreDataSource(JoinPoint point, TargetDataSource ds) {
        logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());
        DynamicDataSourceContextHolder.clearDataSourceType();
    }
}

9、测试(以下为测试内容)

package com.yin.dynamic_datasource.controller;

import com.yin.dynamic_datasource.dto.AreaDto;
import com.yin.dynamic_datasource.service.DynamicService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author yin
 * @Date 2020/1/28 10:35
 * @Method
 */
@RestController
@RequestMapping("/dynamic/test")

public class DynamicController {
    @Autowired
    private DynamicService dynamicService;

    @GetMapping("/insert/default")
    public AreaDto insertDefault(){
        AreaDto areaDto = new AreaDto();
        areaDto.setArea("defaultArea1");
        areaDto.setAreaId("defaultId1");
        areaDto.setCityid("defaultCityId1");
        dynamicService.insertDefaultArea(areaDto);
        return areaDto;
    }

    @GetMapping("/insert/custom1")
    public AreaDto insertCustom1(){
        AreaDto areaDto = new AreaDto();
        areaDto.setArea("custom1Area1");
        areaDto.setAreaId("custom1Id1");
        areaDto.setCityid("custom1CityId1");
        dynamicService.insertCustom1Area(areaDto);
        return areaDto;
    }

}

service

public interface DynamicService {
    void insertArea(AreaDto areaDto);

    void insertDefaultArea(AreaDto areaDto);

    void insertCustom1Area(AreaDto areaDto);
}

serviceImpl

@Service
public class DynamicServiceImpl implements DynamicService {
    @Autowired
    private DynamicMapper dynamicMapper;

    @Override
    public void insertArea(AreaDto areaDto) {
        dynamicMapper.insertArea(areaDto);
    }

    @Override
    public void insertDefaultArea(AreaDto areaDto) {
        dynamicMapper.insertArea(areaDto);
    }

    @Override
    @TargetDataSource(name = "custom1")
    public void insertCustom1Area(AreaDto areaDto) {
        dynamicMapper.insertArea(areaDto);
    }
}

mapper.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.yin.dynamic_datasource.dao.DynamicMapper">

    <!-- 插入记录-->
    <insert id="insertArea" useGeneratedKeys="true" keyProperty="id"
            parameterType="com.yin.dynamic_datasource.dto.AreaDto">

		  INSERT INTO tb_areas(areaid, area, cityid) values (#{areaId}, #{area}, #{cityid})

    </insert>


</mapper>
  • 作者:筏镜
  • 原文链接:https://blog.csdn.net/fajing_feiyue/article/details/104099233
    更新时间:2022年6月15日10:13:11 ,共 12237 字。