Title

springboot配置多数据源-Dynamic

导包

<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
	<version>3.1.0</version>
</dependency>

配置

spring:
  datasource:
    dynamic:
      druid: #以下是全局默认值,可以全局更改
        #监控统计拦截的filters
        filters: stat
        #配置初始化大小/最小/最大
        initial-size: 1
        min-idle: 1
        max-active: 20
        #获取连接等待超时时间
        max-wait: 60000
        #间隔多久进行一次检测,检测需要关闭的空闲连接
        time-between-eviction-runs-millis: 60000
        #一个连接在池中最小生存的时间
        min-evictable-idle-time-millis: 300000
        validation-query: SELECT 'x'
        test-while-idle: true
        test-on-borrow: false
        test-on-return: false
        #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
        pool-prepared-statements: false
        max-pool-prepared-statement-per-connection-size: 20
        stat:
          merge-sql: true
          log-slow-sql: true
          slow-sql-millis: 3000
      # 配置默认数据源
      primary: master
      # true:找不到数据源报错,false:找不到数据源则使用数据源
      strict: false
      datasource:
        # 数据源1,名为 master
        master:
          username: root
          password: 123456
          url: jdbc:mysql://localhost:3306/resource?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&failOverReadOnly=false&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
        # 数据源2,名为 readbase
        readbase:
          username: root
          password: 123456
          url: jdbc:mysql://localhost:3306/user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&failOverReadOnly=false&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
        # 数据源3,名为 writebase
        writebase:
          username: root
          password: 123456
          url: jdbc:mysql://localhost:3306/user_info?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&failOverReadOnly=false&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver

数据源名称最好不要包含下滑线,下滑线的数据源切换不了

primary: master配置后,全局默认master主数据源,只要没有@DS注解显式申明切换数据源,默认就是master主数据源。

使用

@DS可以注解在方法上、类上、接口、枚举,同时存在方法注解优先于类上注解。

@DS("readbase")
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)

建议在service类中使用,不建议在mapper上面使用

解决事务内不能切换数据源:可以在被调用方方法或者类上创建一个新事务 @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) 外部事务回滚,内部不会回滚,内部回滚,外部事务会回滚。

如果调用方与被调用方在同一个Service类中,propagation = Propagation.REQUIRES_NEW会被忽略掉,调用时不会再创建新事务。

注意的问题

一:涉及需要切换数据源时
      1.不能使用事务,否则数据源不会切换,使用的还是是第一次加载的数据源 。
       删除 操作多数据源的方法或者类、接口 上的 注解 @Transactional() 即可。
       
      2.第一次加载的数据源之后,第二次(第三次...)操作其它数据源,如果数据源不存在,使用的还是第一     
        次加载的数据源
        
      3.数据源名称最好不要包含下滑线,下滑线的数据源切换不了
 
二:其他
    1.接口中A、B两个方法,A无@Transactional标签,B有,上层通过A间接调用B,此时事务不生效。
 
    2.接口中异常(运行时异常)被捕获而没有被抛出。
      默认配置下,spring 只有在抛出的异常为运行时 unchecked 异常时才回滚该事务,
      也就是抛出的异常为RuntimeException 的子类(Errors也会导致事务回滚),
      而抛出 checked 异常则不会导致事务回滚 。可通过 @Transactional rollbackFor进行配置。
 
    3.多线程下事务管理因为线程不属于 spring 托管,故线程不能够默认使用 spring 的事务,
      也不能获取spring 注入的 bean 。
      在被 spring 声明式事务管理的方法内开启多线程,多线程内的方法不被事务控制。
      一个使用了@Transactional 的方法,如果方法内包含多线程的使用,方法内部出现异常,
      不会回滚线程中调用方法的事务。

类配置多数据源

yml 配置

spring: 
  datasource:
	# 通过bean加载,层级任意
    datasource:
      readbase:
        url: jdbc:postgresql://192.168.11.20:5432/test1?stringtype=unspecified&currentSchema=public
        username: postgres
        password: xxx@abcd
        driver-class-name: org.postgresql.Driver
        minimum-idle: 5
        maximum-pool-size: 40
        auto-commit: true
        idle-timeout: 60000
        max-lifetime: 1800000
        connection-timeout: 60000
        connection-test-query: SELECT 1
      writebase:
        url: jdbc:postgresql://192.168.11.21:5432/test2?stringtype=unspecified&currentSchema=public
        username: postgres
        password: xxx@abcd
        driver-class-name: org.postgresql.Driver
        minimum-idle: 5
        maximum-pool-size: 40
        auto-commit: true
        idle-timeout: 60000
        max-lifetime: 1800000
        connection-timeout: 60000
        connection-test-query: SELECT 1
# Mybatis-plus相关配置
mybatis-plus:
  # xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
  mapper-locations: classpath:/mapper/**/*.xml
  configuration:
    # 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
    map-underscore-to-camel-case: true
    # 返回map时true:当查询数据为空时字段返回为null,false:不加这个查询数据为空时,字段将被隐藏
    call-setters-on-nulls: true
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

数据源配置

package com.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {
    @Primary
    @Bean(name = "db1DataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.datasource.readbase")
    public DataSourceProperties geocodeDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Primary
    @Bean(name = "db1DataSource")
    public DataSource geocodeDataSource(@Qualifier("db1DataSourceProperties") DataSourceProperties dataSourceProperties) {
        return dataSourceProperties.initializeDataSourceBuilder().build();
    }

    @Bean(name = "db2DataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.datasource.writebase")
    public DataSourceProperties geocode1DataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean(name = "db2DataSource")
    public DataSource geocode1DataSource(@Qualifier("db2DataSourceProperties") DataSourceProperties dataSourceProperties) {
        return dataSourceProperties.initializeDataSourceBuilder().build();
    }
}

db1配置

package com.config;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.xx.xx.mapper.db1", sqlSessionTemplateRef = "db1SqlSessionTemplate")
public class Db1DataSourceConfig {

    @Primary
    @Bean("ds1SqlSessionFactory")
    public MybatisSqlSessionFactoryBean ds1SqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception {
        MybatisConfiguration configuration = new MybatisConfiguration();
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().
            getResources("classpath*:mapper/db1/*.xml"));
        configuration.addInterceptor(new MybatisPlusInterceptor());
        // 设置数据库大小写读取不敏感
        configuration.setMapUnderscoreToCamelCase(true);
        return bean;
    }

    @Primary
    @Bean("db1SqlSessionTemplate")
    public SqlSessionTemplate db1SqlSessionTemplate(
        @Qualifier("ds1SqlSessionFactory") SqlSessionFactory sessionFactory) {
        return new SqlSessionTemplate(sessionFactory);
    }

    @Primary
    @Bean("db1TransactionManager")
    public DataSourceTransactionManager ds1TransactionManager(@Qualifier("db1DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

db2配置

package com.config;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.xx.xx.mapper.db2", sqlSessionTemplateRef = "db2SqlSessionTemplate")
public class Db2DataSourceConfig {

    @Bean("ds2SqlSessionFactory")
    public MybatisSqlSessionFactoryBean ds2SqlSessionFactory(@Qualifier("db2DataSource") DataSource dataSource) throws Exception {
        MybatisConfiguration configuration = new MybatisConfiguration();
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().
            getResources("classpath*:mapper/db2/*.xml"));
        configuration.addInterceptor(new MybatisPlusInterceptor());
        // 设置数据库大小写读取不敏感
        configuration.setMapUnderscoreToCamelCase(true);
        return bean;
    }

    @Bean("db2SqlSessionTemplate")
    public SqlSessionTemplate db1SqlSessionTemplate(
        @Qualifier("ds2SqlSessionFactory") SqlSessionFactory sessionFactory) {
        return new SqlSessionTemplate(sessionFactory);
    }

    @Bean("db2TransactionManager")
    public PlatformTransactionManager db1TransactionManager(@Qualifier("db2DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

mybatis与mybatis-plus 配置

// mybatis
@Primary
@Bean("db1SqlSessionFactory")
public SqlSessionFactory ds1SqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception {
    SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
    sqlSessionFactory.setDataSource(dataSource);
    sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().
            getResources("classpath*:mapper/db1/*.xml"));

    sqlSessionFactory.setConfigLocation(new PathMatchingResourcePatternResolver().
            getResource("classpath:/mapper/mybatis-config.xml"));
    return sqlSessionFactory.getObject();
}

// mybatis-plus
@Bean("ds2SqlSessionFactory")
public MybatisSqlSessionFactoryBean ds2SqlSessionFactory(@Qualifier("db2DataSource") DataSource dataSource) throws Exception {
    MybatisConfiguration configuration = new MybatisConfiguration();
    MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
    bean.setDataSource(dataSource);
    bean.setMapperLocations(new PathMatchingResourcePatternResolver().
        getResources("classpath*:mapper/db2/*.xml"));

    bean.setConfigLocation(new PathMatchingResourcePatternResolver().
        getResource("classpath:/mapper/mybatis-config.xml"));
    configuration.addInterceptor(new MybatisPlusInterceptor());
    // 设置数据库大小写读取不敏感
    configuration.setMapUnderscoreToCamelCase(true);
    return bean;
}

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <!--show sql in control-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <setting name="callSettersOnNulls" value="true"/>

    </settings>

    <!--page info-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--            <property name="param1" value="value1"/>-->
            <property name="reasonable" value="true"/>
        </plugin>
    </plugins>
</configuration>

数据源为db1

package com.xx.xx.mapper.db1;
/**
 Mapper 接口
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

启动类

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

springboot执行sql脚本文件

创建脚本

在项目的resource 目录下新建一个sql目录, 用于存放建表语句
然后在sql 目录下创建 xxx-schema.sql, xxx-data.sql 脚本. 需要注意区分每个后缀代表的含义:

schema: 代表存放的是DDL(数据库定义语言): 对表结构的增删改在这里

data: 代表存放的是DML(数据库操作语言): 对表中数据的操作在这里

配置

  • .properties

    # 需要加上这句,否则不会自动执行sql文件
    spring.datasource.initialization-mode=always
    # schema.sql中一般存放的是建表语句DDL
    spring.datasource.schema = classpath:/sql/xxx-schema.sql
    # data.sql中一般存放的是需要插入更新等sql语句DML
    spring.datasource.data =  classpath:/sql/xxx-data.sql
    # 遇到错误继续执行
    spring.datasource.data.continue-on-error: true
    
  • .yml

spring:
  datasource:
    initialization-mode: always
    schema:
        - classpath:/sql/xxx-schema.sql
    data:
        - classpath:/sql/xxx-data.sql
    continue-on-error: true

因为在没有加上这个配置之前, 每次初始化都会执行一遍配置的SQL脚本内的SQL语句.
如果在第一次启动并建表成功后再次重启就会因项目在启动时执行SQL脚本并出现表已存在的错误导致项目启动失败
添加该属性之后, 则会忽略错误, 让项目初始化成功! 这样, 也符合我们想要在项目初始化的时候自动执行SQL脚本的思想

sql脚本

USE `user_info`;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

CREATE TABLE IF NOT EXISTS `t_user`  (
  `id` varcher(32) NOT NULL COMMENT '主键',
  `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
  `del_flag` int(1) NOT NULL DEFAULT 0 COMMENT '删除标志',
  `username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic;

参考链接:
https://blog.csdn.net/LittleBlackyoyoyo/article/details/109379791
https://blog.csdn.net/qq_43371556/article/details/122976456

posted @ 2022-06-20 17:23  快乐小洋人  阅读(1547)  评论(0编辑  收藏  举报