其他

SpringBoot + MybatisPlus 实现多数据源事务

https://zhuanlan.zhihu.com/p/612825647?utm_id=0

#### 用Dynamic Datasource配置多数据源
dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器

https://jiuaidu.com/it/1930027/
springboot多数据源原理 https://blog.51cto.com/u_16099226/7026534

概述

多数据源一般有两种解决方案:
1、通过显示模式声明多数据源,应用切换。优点是简单,缺点是不易于拓展,强耦合。

动态多数据源的场景

应用不拆,数据库拆
读写分离

SpringBoot集成Mybatis实现多数据源配置

application.yml中配置多数据源 master,second

指定数据源操作指定目录XML文件

该种方式需要操作的数据库的Mapper层和Dao层分别建立一个文件夹,分包放置。

结构图

SpringBoot集成内存数据库Sqlite 动态数据源 springboot集成多数据源_bc

Application.java

取消服务启动时对于数据源配置的自动导入。后面我们会手动配置多个数据源。如果有需要,也可把事务管理与mybatis的自动导入去掉。

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

application.yml

spring:
  datasource:
    master:
      driver-class-name: com.mysql.jdbc.Driver
      jdbc-url: jdbc:mysql://120.70.10.40:3306/wxtong-dev?useUnicode=true&characterEncoding=utf8
      username: root
      password: root
      hikari:
        minimum-idle: 5
        idle-timeout: 180000
        maximum-pool-size: 200
        # 此属性控制池中连接的最长生命周期
        max-lifetime: 1800000
        connection-timeout: 30000
    second:
      driver-class-name: com.mysql.jdbc.Driver
      jdbc-url: jdbc:mysql://120.70.10.40:3306/wxt_staff?useUnicode=true&characterEncoding=utf8
      username: root
      password: root
      hikari:
        minimum-idle: 5
        idle-timeout: 180000
        maximum-pool-size: 200
        max-lifetime: 1800000
        connection-timeout: 30000
      #mybatis: 
        #mapperLocations: classpath:mapper/second/*.xml

DataSourceConfig

@Configuration
public class DataSourceConfig
{
    @Primary
    @Bean(name="master")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource(){
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name="second")
    @ConfigurationProperties(prefix = "spring.datasource.second")
    public DataSource secondDataSource(){
        return DataSourceBuilder.create().build();
    }
}

master

DBMasterConfig.java

@Configuration
@MapperScan(basePackages = DBMasterConfig.PACKAGE, sqlSessionFactoryRef = "masterSqlSessionFactory")
public class DBMasterConfig
{
    static final String PACKAGE = "cn.db.dao.master";
    static final String MAPPER_LOCATION = "classpath:mapper/master/*.xml";

    @Autowired
    @Qualifier("master")
    private DataSource masterDataSource;

    @Bean(name="masterSqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory()throws Exception{
        SqlSessionFactoryBean masterBean = new SqlSessionFactoryBean();
        masterBean.setDataSource(masterDataSource);
        masterBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources(MAPPER_LOCATION));
        return masterBean.getObject();
    }
}

second

DBSecondConfig.java

@Configuration
@MapperScan(basePackages = DBSecondConfig.PACKAGE, sqlSessionFactoryRef = "secondSqlSessionFactory")
public class DBSecondConfig
{
    static final String PACKAGE = "cn.db.dao.second";
    static final String MAPPER_LOCATION = "classpath:mapper/second/*.xml";
    
    @Bean(name = "secondSqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("second") DataSource secondDataSource)throws Exception
    {
        SqlSessionFactoryBean secondBean = new SqlSessionFactoryBean();
        secondBean.setDataSource(secondDataSource);
        secondBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources(MAPPER_LOCATION));
        return secondBean.getObject();
    }
    
    //开启事务
    @Bean
    public DataSourceTransactionManager secondDataSourceTransactionManager() throws Exception{
        return new DataSourceTransactionManager(secondDataSource);
    }
}

SpringBoot 多模块、多数据源项目中Mybatis找不到子模块Mapper的解决办法

其他

Mybatis问题

解决 BindingException:Invalid bound statement (not found)

出现这个错误时,按以下步骤检查一般就会解决问题:
1:检查xml文件所在package名称是否和Mapper interface所在的包名一一对应;
2:检查xml的namespace是否和xml文件的package名称一一对应;
3:检查方法名称与xml的id是否对应;
4:去除xml文件中的中文注释;
5:随意在xml文件中加一个空格或者空行然后保存。

HikariPool-1 - Failed to validate connection


Mybatis-plus多数据源配置

<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>

main

@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, MybatisPlusAutoConfiguration.class})
@SpringBootApplication

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

配置文件

spring:
  datasource:
    primary:
      url: jdbc:mysql://192.168.3.202:3306/test1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    second:
      url: jdbc:mysql://192.168.3.202:3306/test2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  mapper-locations:
    - classpath*:mapper/master/*.xml # *.xml的具体路径

mybatis-plus-second:
  mapper-locations:
    - classpath*:mapper/second/*.xml # *.xml的具体路径
@Configuration
public class MybatisPlusConfig {

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

    @Primary
    @Bean(name = "primaryMybatisPlusProperties")
    @ConfigurationProperties("mybatis-plus")
    public MybatisPlusProperties primaryMybatisPlusProperties(){
        return new MybatisPlusProperties();
    }

    @Primary
    @Bean(name = "secondMybatisPlusProperties")
    @ConfigurationProperties("mybatis-plus-second")
    public MybatisPlusProperties secondMybatisPlusProperties(){
        return new MybatisPlusProperties();
    }
}
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandler;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.List;

@Configuration
@MapperScan(basePackages = SecondDataSourceConfig.PACKAGE, sqlSessionTemplateRef  = "secondSqlSessionTemplate")
public class SecondDataSourceConfig extends MybatisPlusAutoConfiguration {
    static final String PACKAGE = "org.example.phdatagather.mapper.second";
    static final String MAPPER_LOCATION = "classpath*:mapper/second/*.xml";

    @Value("${spring.datasource.second.url}")
    private String url;

    @Value("${spring.datasource.second.username}")
    private String user;

    @Value("${spring.datasource.second.password}")
    private String password;

    @Value("${spring.datasource.second.driver-class-name}")
    private String driverClass;

    public SecondDataSourceConfig(@Autowired @Qualifier("secondMybatisPlusProperties") MybatisPlusProperties secondMybatisPlusProperties, ObjectProvider<Interceptor[]> interceptorsProvider, ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider, ObjectProvider<List<MybatisPlusPropertiesCustomizer>> mybatisPlusPropertiesCustomizerProvider, ApplicationContext applicationContext) {
        super(secondMybatisPlusProperties, interceptorsProvider, typeHandlersProvider, languageDriversProvider, resourceLoader, databaseIdProvider, configurationCustomizersProvider, mybatisPlusPropertiesCustomizerProvider, applicationContext);
    }

    @Primary
    @Bean(name = "secondDataSource")
    public DataSource secondDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClass);
        dataSource.setUrl(url);
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean(name = "secondTransactionManager")
    @Primary
    public DataSourceTransactionManager secondTransactionManager() {
        return new DataSourceTransactionManager(secondDataSource());
    }

    @Bean(name = "secondSqlSessionFactory")
    @Primary
    public SqlSessionFactory secondSqlSessionFactory(@Qualifier("secondDataSource") DataSource dataSource) throws Exception{
        return super.sqlSessionFactory(dataSource);
        
        // MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
        // sessionFactory.setDataSource(dataSource);
        // sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
        // return sessionFactory.getObject();
    }

    @Bean(name = "secondSqlSessionTemplate")
    @Primary
    public SqlSessionTemplate secondSqlSessionTemplate(@Qualifier("secondSqlSessionFactory") SqlSessionFactory sessionFactory) throws Exception{
        return  new SqlSessionTemplate(sessionFactory);
    }
}

说明

在方案一中,我们针对每个DataSourece都创建对应的DataSourceTransactionManager实现,也可以看出DataSourceTransactionManager就是管理我们整体的事务的,当我们配置了事物管理器以及拦截Service中的方法后,每次执行Service中方法前会开启一个事务,并且同时会缓存DataSource、SqlSessionFactory、Connection,因为DataSource、Conneciton都是从缓存中拿的,因此我们怎么切换数据源也没用,因此就出现表不存在的报错,具体源码可参考下面截图部分: