当前位置:首页 > 问答 > 正文

Spring里头咋整两个数据库连接,配置和用法那些事儿分享下

行,那咱们就直接唠唠在Spring项目里怎么整两个数据库连接这事儿,这事儿说白了,就是让你的应用能同时跟两个不同的数据库“对话”,你可能需要从一个老系统(数据库A)里读点基础数据,然后运算完了再把结果写到另一个新库(数据库B)里去。

为啥要整两个连接?

最常见的就是“读写分离”,为了减轻主数据库的压力,通常会把写操作(增删改)都给主库,而把查询的活儿交给另一个专门负责读的从库,再比如,你的应用可能需要连接一个自己业务的核心数据库,同时还得从公司另一个独立的项目数据库里拉取数据,这时候也得配俩连接。

怎么配?核心就是两套配置

Spring Boot让这个事变得简单了不少,核心思路就是为每个数据库定义一套独立的配置“班子”:数据源(DataSource)、负责数据库会话的SqlSessionFactory、事务管理器(TransactionManager)等等,你得让Spring清楚,哪些Bean是伺候第一个库的,哪些是伺候第二个库的,别让它们搞混了。

下面我以经典的MyBatis框架为例,说说最常用的配置方法。

在application.yml里把两个数据库的信息都写上

得在配置文件(比如application.yml)里把两个数据库的连接信息都声明了,这里不能用默认的spring.datasource了,得给它们起个名区分开。

Spring里头咋整两个数据库连接,配置和用法那些事儿分享下

# 第一个数据库,我们管它叫 primary-db,是主库
primary:
  datasource:
    url: jdbc:mysql://localhost:3306/db_primary?useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
# 第二个数据库,我们管它叫 secondary-db,是从库或另一个业务库
secondary:
  datasource:
    url: jdbc:mysql://localhost:3306/db_secondary?useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

用Java代码来配置每个数据库的“工作小组”

光有连接信息还不够,我们需要用Java配置类来创建每个数据库需要的各种工具Bean。

  • 配置主数据库(primary-db)

我们创建一个配置类,比如叫PrimaryDataSourceConfig,在这个类头上,我们用@Configuration告诉Spring这是个配置类,再用@MapperScan指定扫描哪个包下面的Mapper接口是归这个数据库管的,同时要指明用哪个SqlSessionFactory和事务管理器。

@Configuration
// 重点:指定扫描mapper接口的包,并指定这个库专用的sqlSessionFactory和transactionManager
@MapperScan(basePackages = "com.example.mapper.primary",
            sqlSessionFactoryRef = "primarySqlSessionFactory",
            transactionManagerRef = "primaryTransactionManager")
public class PrimaryDataSourceConfig {
    // 读取配置文件中 primary.datasource 开头的属性,构造第一个数据源
    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "primary.datasource")
    public DataSource primaryDataSource() {
        // 这里用HikariCP连接池,Spring Boot 2.x默认就是这个,性能好
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
    // 创建第一个数据库专用的SqlSessionFactory
    @Bean(name = "primarySqlSessionFactory")
    public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        // 如果你的MyBatis映射文件XML和这个配置类不在默认位置,可以在这里设置路径
        // sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/primary/*.xml"));
        return sessionFactoryBean.getObject();
    }
    // 创建第一个数据库专用的事务管理器
    @Bean(name = "primaryTransactionManager")
    public DataSourceTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
  • 配置第二个数据库(secondary-db)

第二个库的配置类写法几乎一模一样,就是改个名字,指向的配置前缀和扫描的包路径要换掉。

Spring里头咋整两个数据库连接,配置和用法那些事儿分享下

@Configuration
// 重点:扫描的包换成 secondary 的,引用的Bean名字也全换成 secondary 开头的
@MapperScan(basePackages = "com.example.mapper.secondary",
            sqlSessionFactoryRef = "secondarySqlSessionFactory",
            transactionManagerRef = "secondaryTransactionManager")
public class SecondaryDataSourceConfig {
    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "secondary.datasource")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
    @Bean(name = "secondarySqlSessionFactory")
    public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        return sessionFactoryBean.getObject();
    }
    @Bean(name = "secondaryTransactionManager")
    public DataSourceTransactionManager secondaryTransactionManager(@Qualifier("secondaryDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

具体怎么用?

配置好了,用法就简单了。

  1. 分包管理:最清晰的用法是把不同数据库的Mapper接口和对应的XML映射文件也分开放,操作主库的Mapper接口都放在com.example.mapper.primary包下,操作第二个库的都放在com.example.mapper.secondary包下,这样,上面配置里的@MapperScan就能自动把它们分别关联到正确的数据库上。
  2. 在Service里注入使用:在你的业务Service里,你想用哪个库的Mapper,就直接@Autowired注入哪个,Spring会根据包路径和我们的配置,自动把正确的Mapper实例给你注入进来。
@Service
public class SomeService {
    @Autowired
    private PrimaryMapper primaryMapper; // 这个接口在 primary 包下,会自动用主库连接
    @Autowired
    private SecondaryMapper secondaryMapper; // 这个接口在 secondary 包下,会自动用第二个库连接
    public void someBusinessMethod() {
        // 从第二个库读数据
        SomeData dataFromSecondary = secondaryMapper.selectSomeData();
        // 经过一些业务处理...
        // 把结果写入主库
        primaryMapper.insertResult(dataFromSecondary);
    }
}

事务问题要注意

如果一个Service方法需要同时操作两个数据库,并且要保证一致性(要么都成功,要么都失败),这就涉及到分布式事务了,比较复杂,Spring自带的事务管理器(@Transactional)默认只能管一个数据源,在你上面的方法上如果只写@Transactional,它默认只会管理那个被标记为@Primary的数据源的事务。

对于这种跨库的事务,简单的办法可能是在代码里做补偿(一个成功了,另一个失败了,就手动回滚第一个),如果对一致性要求极高,就需要引入像JTA这样的分布式事务解决方案,或者使用Seata这类中间件,但那配置起来就麻烦多了,通常在这种多数据源场景下,会尽量设计业务,避免跨库的分布式事务。

总结一下:整两个数据库连接,关键就是“分家”——配置分开、Bean名字分开、Mapper接口的包分开,配的时候细心点,别让Bean打架,用的时候根据包路径注入,就基本没啥大问题了,事务那块儿是难点,需要根据实际业务需求来权衡处理方案。