美文网首页
SpringBoot多数据源配置

SpringBoot多数据源配置

作者: 上善若泪 | 来源:发表于2022-03-27 14:54 被阅读0次

    最近项目用到了spring多数据源配置,恰好看到这篇好文章,特地分享下

    1 SpringBoot分库配置

    主要介绍两种整合方式,分别是 springboot+mybatis 使用分包方式整合,和 springboot+druid+mybatisplus 使用注解方式整合

    1.1 准备数据

    在本地新建两个数据库,名称分别为db1db2,新建一张user表,表结构如下

    image.png
    CREATE TABLE `user` (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
      `name` varchar(25) NOT NULL COMMENT '姓名',
      `age` int(2) DEFAULT NULL COMMENT '年龄',
      `sex` tinyint(1) NOT NULL DEFAULT '0' COMMENT '性别:0-男,1-女',
      `addr` varchar(100) DEFAULT NULL COMMENT '地址',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='测试用户表'
    

    1.2 springboot+mybatis使用分包方式整合

    1.2.1 pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.9.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>multipledatasource</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>multipledatasource</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
       
        <dependencies>
            <!-- spring 依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
             <!-- mysql 依赖 -->
            <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>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    

    1.2.2 application.yml 配置文件

    server:
      port: 8080 # 启动端口
    spring:
      datasource: 
        db1: # 数据源1
          jdbc-url: jdbc:mysql://localhost:3306/db1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
        db2: # 数据源2
          jdbc-url: jdbc:mysql://localhost:3306/db2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
    

    注意事项:
    各个版本的 springboot 配置 datasource 时参数有所变化,例如低版本配置数据库 url时使用 url 属性,高版本使用 jdbc-url 属性,请注意区分

    1.2.3 连接数据源配置文件

    1.2.3.1 连接源配置一

    @Configuration
    @MapperScan(basePackages = "com.example.multipledatasource.mapper.db1", sqlSessionFactoryRef = "db1SqlSessionFactory")
    public class DataSourceConfig1 {
    
        @Primary // 表示这个数据源是默认数据源, 这个注解必须要加,因为不加的话spring将分不清楚那个为主数据源(默认数据源)
        @Bean("db1DataSource")
        @ConfigurationProperties(prefix = "spring.datasource.db1") //读取application.yml中的配置参数映射成为一个对象
        public DataSource getDb1DataSource(){
            return DataSourceBuilder.create().build();
        }
    
        @Primary
        @Bean("db1SqlSessionFactory")
        public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dataSource);
            // mapper的xml形式文件位置必须要配置,不然将报错:no statement (这种错误也可能是mapper的xml中,namespace与项目的路径不一致导致)
            bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/db1/*.xml"));
            return bean.getObject();
        }
    
        @Primary
        @Bean("db1SqlSessionTemplate")
        public SqlSessionTemplate db1SqlSessionTemplate(@Qualifier("db1SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }
    

    1.2.3.2 连接源配置二

    @Configuration
    @MapperScan(basePackages = "com.example.multipledatasource.mapper.db2", sqlSessionFactoryRef = "db2SqlSessionFactory")
    public class DataSourceConfig2 {
    
        @Bean("db2DataSource")
        @ConfigurationProperties(prefix = "spring.datasource.db2")
        public DataSource getDb1DataSource(){
            return DataSourceBuilder.create().build();
        }
    
        @Bean("db2SqlSessionFactory")
        public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db2DataSource") DataSource dataSource) throws Exception {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dataSource);
            bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/db2/*.xml"));
            return bean.getObject();
        }
    
        @Bean("db2SqlSessionTemplate")
        public SqlSessionTemplate db1SqlSessionTemplate(@Qualifier("db2SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }
    

    1.2.4 项目结构

    image.png

    注意事项:
    service 层中根据不同的业务注入不同的 dao
    如果是主从复制- -读写分离:比如 db1 中负责增删改,db2 中负责查询。但是需要注意的是负责增删改的数据库必须是主库(master)

    1.3 springboot+druid+mybatisplus使用注解整合

    1.3.1 pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
       <modelVersion>4.0.0</modelVersion>
       <parent>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-parent</artifactId>
           <version>2.1.9.RELEASE</version>
           <relativePath/> <!-- lookup parent from repository -->
       </parent>
       <groupId>com.example</groupId>
       <artifactId>mutipledatasource2</artifactId>
       <version>0.0.1-SNAPSHOT</version>
       <name>mutipledatasource2</name>
       <description>Demo project for Spring Boot</description>
    
       <properties>
           <java.version>1.8</java.version>
       </properties>
    
       <dependencies>
           <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-web</artifactId>
           </dependency>
           <dependency>
               <groupId>com.baomidou</groupId>
               <artifactId>mybatis-plus-boot-starter</artifactId>
               <version>3.2.0</version>
           </dependency>
           <dependency>
               <groupId>com.baomidou</groupId>
               <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
               <version>2.5.6</version>
           </dependency>
           <dependency>
               <groupId>mysql</groupId>
               <artifactId>mysql-connector-java</artifactId>
               <scope>runtime</scope>
           </dependency>
           <dependency>
               <groupId>com.alibaba</groupId>
               <artifactId>druid-spring-boot-starter</artifactId>
               <version>1.1.20</version>
           </dependency>
           <dependency>
               <groupId>org.projectlombok</groupId>
               <artifactId>lombok</artifactId>
               <optional>true</optional>
           </dependency>
           <dependency>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-test</artifactId>
               <scope>test</scope>
           </dependency>
       </dependencies>
    
       <build>
           <plugins>
               <plugin>
                   <groupId>org.springframework.boot</groupId>
                   <artifactId>spring-boot-maven-plugin</artifactId>
               </plugin>
           </plugins>
       </build>
    
       <profiles>
           <profile>
               <id>local1</id>
               <properties>
                   <profileActive>local1</profileActive>
               </properties>
               <activation>
                   <activeByDefault>true</activeByDefault>
               </activation>
           </profile>
           <profile>
               <id>local2</id>
               <properties>
                   <profileActive>local2</profileActive>
               </properties>
           </profile>
       </profiles>
    </project>
    

    1.3.2 application.yml 配置文件

    server:
      port: 8080
    spring:
      datasource:
        dynamic:
          primary: db1 # 配置默认数据库
          datasource:
            db1: # 数据源1配置
              url: jdbc:mysql://localhost:3306/db1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
              username: root
              password: root
              driver-class-name: com.mysql.cj.jdbc.Driver
            db2: # 数据源2配置
              url: jdbc:mysql://localhost:3306/db2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
              username: root
              password: root
              driver-class-name: com.mysql.cj.jdbc.Driver
          durid:
            initial-size: 1
            max-active: 20
            min-idle: 1
            max-wait: 60000
      autoconfigure:
        exclude:  com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 去除druid配置
    

    DruidDataSourceAutoConfigure会注入一个DataSourceWrapper,其会在原生的spring.datasource下找 url, username, password 等。动态数据源 URL 等配置是在 dynamic 下,因此需要排除,否则会报错。排除方式有两种,一种是上述配置文件排除,还有一种可以在项目启动类排除

    @SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
    public class Application {
      public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
      }
    }
    

    1.3.3 使用@DS区分数据源

    给使用非默认数据源添加注解@DS
    @DS可以注解在 方法 上和 上,同时存在方法注解优先于类上注解。
    注解在 service 实现或 mapper 接口方法上,不要同时在 servicemapper 注解

    mapper上使用

    @DS("db2") 
    public interface UserMapper extends BaseMapper<User> {
    }
    

    service上使用

    @Service
    @DS("db2")
    public class ModelServiceImpl extends ServiceImpl<ModelMapper, Model> implements IModelService {}
    

    方法上使用

    @Select("SELECT * FROM user")
    @DS("db2")
    List<User> selectAll();
    

    转载于:https://www.cnblogs.com/aizen-sousuke/p/11756279.html

    1.4 读写分离库

    读写分离库使用AbstractRoutingDataSource
    AbstractRoutingDataSource类和aop结合,还可以用来作为读写分离库

    1.4.1 AbstractRoutingDataSource原理

    AbstractRoutingDataSource原理:

    1. Map<Object, Object> targetDataSources 保存了所有可能的数据源,key为数据库的keyvalueDataSource对象或字符串形式的连接信息
    2. Object defaultTargetDataSource 保存了默认的数据源,用于找不到具体的数据源时使用
    3. afterPropertiesSet() 方法
      解析targetDataSources数据源信息成<key,DataSource>的形式,保存在Map<Object, DataSource> resolvedDataSources
      defaultTargetDataSource中的默认数据源信息解析成 DataSource对象保存在 DataSource resolvedDefaultDataSource
    4. determineCurrentLookupKey() 提供给子类重写,指定当前线程使用的具体的数据源的key
    5. determineTargetDataSource() 根据 determineCurrentLookupKey()方法返回的key 返回数据源DataSouce对象,若没有,则使用默认数据源对象
    6. getConnection() 根据determineTargetDataSource()返回的数据源,与其建立连接

    1.4.2 数据源分离配置application.properties

    server.port=8081
    spring.datasource.jdbcUrl=jdbc:mysql://127.0.0.1:3306/spring-db-1?useUnicode=true&characterEncoding=utf8
    spring.datasource.username=root
    spring.datasource.password=*****
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    
    y2021.spring.datasource.jdbcUrl=jdbc:mysql://127.0.0.1:3306/spring-db-2?useUnicode=true&characterEncoding=utf8
    y2021.spring.datasource.username=root
    y2021.spring.datasource.password=*****
    y2021.spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    
    logging.level.com.ijianghu.dynamic.web=debug
    

    1.4.3 配置数据源和事务管理

    @Configuration
    public class BaseDataSourceConfig {
    
        /**
         * 配置默认数据源,并设置高优先级
         * @return
         */
        @Primary
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource")
        public DataSource dataSource(){
            return DataSourceBuilder.create().build();
        }
    
        /**
         * 配置多数据源
         * @return
         */
        @Bean("y2021DataSource")
        @ConfigurationProperties(prefix = "y2021.spring.datasource")
        public DataSource y2021DataSource(){
            return DataSourceBuilder.create().build();
        }
    
        /**
         * 设置多数据源,配置动态路由数据源
         * @return
         */
        @Bean("dynamincDataSource")
        public DataSource dynamincDataSource(){
            DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
            HashMap<Object, Object> dataSourceMap = new HashMap<>();
            dataSourceMap.put("dataSource",dataSource());
            dataSourceMap.put("y2021DataSource",y2021DataSource());
            //配置默认目标数据源
            dynamicRoutingDataSource.setDefaultTargetDataSource(dataSource());
            //设置目标数据源集合,供路由选择
            dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
            return dynamicRoutingDataSource;
        }
    
        /**
         * 设置会话工厂
         * @return
         * @throws Exception
         */
        @Bean(name="sqlSessionFactory")
        public SqlSessionFactory sqlSessionFactory() throws Exception {
            SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
            //配置数据源为多数据源
            factoryBean.setDataSource(dynamincDataSource());
            //设置mybaits的xml文件路径
            factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                    .getResources("classpath:mapper/*.xml"));
            return factoryBean.getObject();
        }
    
        /**
         * 配置事务管理器
         * @return
         */
        @Bean(name="transactionManager")
        public PlatformTransactionManager transactionManager(){
            return new DataSourceTransactionManager(dynamincDataSource());
        }
    
    }
    

    1.4.4 继承AbstractRoutingDataSource类,实现选择目标数据源

    /**
     * Multiple DataSource Configurer
     */
    public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
        private final Logger logger = LoggerFactory.getLogger(getClass());
    
        @Override
        protected Object determineCurrentLookupKey() {           
            logger.info("Current DataSource is {}",DynamicDataSourceContextHolder.getDataSourceKey());
            //从动态数据源上下文持有者里面获取
            return DynamicDataSourceContextHolder.getDataSourceKey();
        }
    }
    

    1.4.5 配置多数据源上下文持有者

    public class DynamicDataSourceContextHolder {
        private final Logger logger = LoggerFactory.getLogger(getClass());
        /**
         * Maintain variable for every thread, to avoid effect other thread
         */
        private static ThreadLocal<String> CONTEXT_HOLDER = ThreadLocal.withInitial(DataSourceKey.dataSource::name);;
        /**
         * All DataSource List
         */
        public static List<Object> dataSourceKeys = new ArrayList<>();
        /**
         * Get current DataSource
         * @return data source key
         */
        public static String getDataSourceKey() {
            return CONTEXT_HOLDER.get();
        }
        public static void setDataSourceKey(String key) {
            CONTEXT_HOLDER.set(key);
        }
    
        public static void clearDataSourceKey() {
            CONTEXT_HOLDER.remove();
        }
    
      
        public static boolean containDataSourceKey(String key) {
            return dataSourceKeys.contains(key);
        }
    
        public static void useSlaveDataSource(String ds) {
            DataSourceKey dataSourceKey = DataSourceKey.valueOf(ds);
            //if there is no suitable enum,then use default dataSource
            if(dataSourceKey == null){
                setDataSourceKey(DataSourceKey.dataSource.dataSourceName);
            }
            setDataSourceKey(dataSourceKey.dataSourceName);
        }
    
        public static void useMasterDataSource() {
            CONTEXT_HOLDER.set(DataSourceKey.dataSource.dataSourceName);
        }
    
    }
    

    DataSourceKey类

    public enum DataSourceKey {
        dataSource("dataSource","dataSource"),
        y2021("key","y2021DataSource");
    
        public String key;
        public String dataSourceName;
        DataSourceKey(String key, String dataSourceName){
            this.key = key;
            this.dataSourceName = dataSourceName;
        }
    }
    

    1.4.6 配置AOP切面

    @Aspect
    @Order(-1)//Order中的数字代表启动优先级,-1是比0还有更高的优先级
    @Component
    public class DynamicDataSourceAspect {
        private final Logger logger = LoggerFactory.getLogger(getClass());
        //@annotation用于匹配当前执行方法持有指定注解的方法
        @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)||" +
                "@annotation(org.springframework.web.bind.annotation.GetMapping)||" +
                "@annotation(org.springframework.web.bind.annotation.RequestMapping)")
        public void daoAspect(){ }
    
    
        @Before("daoAspect()")
        public void switchDataSource(JoinPoint point){
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String ds = request.getHeader("ds");
            if(ds != null){
                DynamicDataSourceContextHolder.useSlaveDataSource(ds);
            }else{
                DynamicDataSourceContextHolder.useMasterDataSource();
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:SpringBoot多数据源配置

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