微服务架构师封神之路09-Springboot多数据源,Hikari连接池和事务配置


以在应用中配置两个数据源为例。这两个数据源对应两个不同的MySQL数据库,它们有各自的连接池配置,以求达到应用运行的最佳状态。

application.yml

spring:
  datasource:
    master:
      driverClassName: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3306/demo-master?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
      username: root
      password: root
      initialization-mode: always
      hikari:
        minimum-idle: 25
        maximum-pool-size: 25
        connection-test-query: SELECT 1
        max-lifetime: 1800000
        connection-timeout: 30000
        pool-name: masterdb-connection-pool
        auto-commit: false
        data-source-properties:
          prepStmtCacheSize: 500
          prepStmtCacheSqlLimit: 20480
          cachePrepStmts: true
          useServerPrepStmts: false
          rewriteBatchedStatements: true
          cacheServerConfiguration: true
          cacheResultSetMetaData: true
          metadataCacheSize: 20480
          maintainTimeStats: false
          # 读取超大数据需要,默认false,一次读取全部
          # useCursorFetch: true
          # defaultFetchSize: 50
          # 压缩传输
          useCompression: true
    slave:
      driverClassName: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3306/demo-slave?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
      username: root
      password: root
      initialization-mode: always
      hikari:
        minimum-idle: 5
        maximum-pool-size: 5
        connection-test-query: SELECT 1
        max-lifetime: 1800000
        connection-timeout: 30000
        pool-name: slavedb-connection-pool
        auto-commit: false
        data-source-properties:
          prepStmtCacheSize: 500
          prepStmtCacheSqlLimit: 20480
          cachePrepStmts: true
          useServerPrepStmts: false
          rewriteBatchedStatements: true
          cacheServerConfiguration: true
          cacheResultSetMetaData: true
          metadataCacheSize: 20480
          maintainTimeStats: false
          # 读取超大数据需要,默认false,一次读取全部
          # useCursorFetch: true
          # defaultFetchSize: 50
          # 压缩传输
          useCompression: true

初始化DataSource

DataSourceConfig的两种写法

写法一

@Slf4j
@Configuration
public class DataSourceConfig {

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

    @Bean("slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource(){
        return DataSourceBuilder.create().build();
    }
    
    @Bean("masterJdbcTemplate")
    public JdbcTemplate masterJdbcTemplate(@Qualifier("masterDataSource") DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    @Bean("slaveJdbcTemplate")
    public JdbcTemplate slaveJdbcTemplate(@Qualifier("slaveDataSource") DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }
        
}

写法二

@Slf4j
@Configuration
public class DataSourceConfig {

    @Bean("masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource(DataSourceProperties dataSourceProperties){
        return DataSourceBuilder
                .create(dataSourceProperties.getClassLoader())
                .driverClassName(dataSourceProperties.getDriverClassName())
                .url(dataSourceProperties.getUrl())
                .username(dataSourceProperties.getUsername())
                .password(dataSourceProperties.getPassword())
                .build();
    }

    @Bean("slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource(DataSourceProperties dataSourceProperties){
        return DataSourceBuilder
                .create(dataSourceProperties.getClassLoader())
                .driverClassName(dataSourceProperties.getDriverClassName())
                .url(dataSourceProperties.getUrl())
                .username(dataSourceProperties.getUsername())
                .password(dataSourceProperties.getPassword())
                .build();
    }

    @Bean("masterJdbcTemplate")
    public JdbcTemplate masterJdbcTemplate(@Qualifier("masterDataSource") DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    @Bean("slaveJdbcTemplate")
    public JdbcTemplate slaveJdbcTemplate(@Qualifier("slaveDataSource") DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }
}

两种方法的区别

其实完全没有区别!一模一样。在DAO中我们直接就可以注入使用相应的JdbcTemplate。

    @Autowired
    @Qualifier("masterJdbcTemplate")
    private JdbcTemplate masterJdbcTemplate;

    @Autowired
    @Qualifier("slaveJdbcTemplate")
    private JdbcTemplate slaveJdbcTemplate;

如果你在项目中这样做了,问题很严重!!!

以上的代码其实是有缺陷的,完全不能在真实的项目中使用。这里面有两个很严峻的问题没有考虑:

  • 数据库连接池使用的是默认设置。如果是单一数据源配置,application.yml中spring.datasource.hikari的设置是会起作用的,但是在多数据源的情况下,hikari连接池不会自动加载application.yml中的设置。对应数据源的连接池初始化使用的是默认的参数,一旦这种情况发生数据库的读写性能会很糟糕;
  • 不同的数据源应该有自己独立的事务对象。Hikari数据源默认设置是自动提交。如果不是自动提交,又没有配置事务,对数据库的操作全白做了。

让Hikari连接池、DataSource和事务按照我们配置的那样运行

DataSourceSettings.java

用来从application.yml中读取数据源配置项。它由两个class构成。
DataSourceHikariSettings.java

@Data
public class DataSourceHikariSettings {

    private String driverClassName;
    private String url;
    private String username;
    private String password;
    private int minimumIdle;
    private int maximumPoolSize;
    private String connectionTestQuery;
    private int maxLifetime;
    private int connectionTimeout;
    private String poolName;
    private boolean autoCommit;

    private Properties dataSourceProperties = new Properties();

    public int getPrepStmtCacheSize(){
        return Integer.valueOf((String)dataSourceProperties.get("prepStmtCacheSize"));
    }

    public int getPrepStmtCacheSqlLimit(){
        return Integer.valueOf((String)dataSourceProperties.get("prepStmtCacheSqlLimit"));
    }

    public boolean getCachePrepStmts(){
        return Boolean.valueOf((String)dataSourceProperties.get("cachePrepStmts"));
    }

    public boolean getUseServerPrepStmts(){
        return Boolean.valueOf((String)dataSourceProperties.get("useServerPrepStmts"));
    }

    public boolean getRewriteBatchedStatements(){
        return Boolean.valueOf((String)dataSourceProperties.get("rewriteBatchedStatements"));
    }

    public boolean getCacheServerConfiguration(){
        return Boolean.valueOf((String)dataSourceProperties.get("cacheServerConfiguration"));
    }

    public boolean getCacheResultSetMetaData(){
        return Boolean.valueOf((String)dataSourceProperties.get("cacheResultSetMetaData"));
    }

    public int getMetadataCacheSize(){
        return Integer.valueOf((String)dataSourceProperties.get("metadataCacheSize"));
    }

    public boolean getMaintainTimeStats(){
        return Boolean.valueOf((String)dataSourceProperties.get("maintainTimeStats"));
    }

    public boolean getUseCursorFetch(){
        return Boolean.valueOf((String)dataSourceProperties.get("useCursorFetch"));
    }

    public int getDefaultFetchSize(){
        return Integer.valueOf((String)dataSourceProperties.get("defaultFetchSize"));
    }

    public boolean getUseCompression(){
        return Boolean.valueOf((String)dataSourceProperties.get("useCompression"));
    }

}

DataSourceSettings.java

@Data
public class DataSourceSettings {
    private String driverClassName;
    private String jdbcUrl;
    private String username;
    private String password;
    private String initializationMode;
    private DataSourceHikariSettings hikari;
}

DataSourceConfig.java按配置文件初始化Hikari连接池、数据源和事务

真正的做到Hikari连接池和数据源按照我们预想的那样运行。

@Slf4j
@Configuration
public class DataSourceConfig {

    @Bean("masterDataSourceSettings")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSourceSettings masterDataSourceSettings(){
        return new DataSourceSettings();
    }

    @Bean("slaveDataSourceSettings")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSourceSettings slaveDataSourceSettings(){
        return new DataSourceSettings();
    }

    public HikariDataSource createHikariDataSource(DataSourceSettings dataSourceSettings){
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName(dataSourceSettings.getDriverClassName());
        hikariConfig.setJdbcUrl(dataSourceSettings.getJdbcUrl());
        hikariConfig.setUsername(dataSourceSettings.getUsername());
        hikariConfig.setPassword(dataSourceSettings.getPassword());
        hikariConfig.setPoolName(dataSourceSettings.getHikari().getPoolName());
        hikariConfig.setMinimumIdle(dataSourceSettings.getHikari().getMinimumIdle());
        hikariConfig.setMaximumPoolSize(dataSourceSettings.getHikari().getMaximumPoolSize());
        hikariConfig.setConnectionTestQuery(dataSourceSettings.getHikari().getConnectionTestQuery());
        hikariConfig.setMaxLifetime(dataSourceSettings.getHikari().getMaxLifetime());
        hikariConfig.setConnectionTimeout(dataSourceSettings.getHikari().getConnectionTimeout());
        hikariConfig.setAutoCommit(dataSourceSettings.getHikari().isAutoCommit());
        hikariConfig.setDataSourceProperties(dataSourceSettings.getHikari().getDataSourceProperties());
        return new HikariDataSource(hikariConfig);
    }

    @Bean("masterDataSource")
    public DataSource masterDataSource(@Qualifier("masterDataSourceSettings") DataSourceSettings dataSourceSettings){
        return createHikariDataSource(dataSourceSettings);
    }

    @Bean("slaveDataSource")
    public DataSource slaveDataSource(@Qualifier("slaveDataSourceSettings") DataSourceSettings dataSourceSettings){
        return createHikariDataSource(dataSourceSettings);
    }

    @Bean("masterJdbcTemplate")
    public JdbcTemplate masterJdbcTemplate(@Qualifier("masterDataSource") DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    @Bean("slaveJdbcTemplate")
    public JdbcTemplate slaveJdbcTemplate(@Qualifier("slaveDataSource") DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    @Bean(name = "masterDataSourceTransactionManager")
    public PlatformTransactionManager masterDataSourceTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "slaveDataSourceTransactionManager")
    public PlatformTransactionManager slaveDataSourceTransactionManager(@Qualifier("slaveDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

}

在DAO中使用这些数据源和事务处理

    @Autowired
    @Qualifier("masterJdbcTemplate")
    private JdbcTemplate masterJdbcTemplate;

    @Autowired
    @Qualifier("slaveJdbcTemplate")
    private JdbcTemplate slaveJdbcTemplate;

    @Transactional(value="masterDataSourceTransactionManager")
    public void createUserInMasterDB(User user){
        ...
    }

    @Transactional(value="slaveDataSourceTransactionManager")
    public void createUserInSlaveDB(User user){
        ...
    }

参考

Ref: Hikari DataSource Initialization.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot 中,可以通过配置多个数据源来实现多数据源的功能。如果要在启动时创建多个数据源的连接池,可以使用 Spring Boot 提供的 `@Configuration` 注解和 `@Bean` 注解来实现。 首先,需要在应用程序中定义多个数据源的配置信息,例如数据库连接 URL、用户名、密码等。然后,可以将这些配置信息封装为一个 `DataSourceProperties` 类,如下所示: ``` @Configuration public class DataSourceConfig { @Bean @ConfigurationProperties("datasource.primary") public DataSourceProperties primaryDataSourceProperties() { return new DataSourceProperties(); } @Bean @ConfigurationProperties("datasource.secondary") public DataSourceProperties secondaryDataSourceProperties() { return new DataSourceProperties(); } // ... } ``` 这里定义了两个数据源,分别为 `primaryDataSourceProperties` 和 `secondaryDataSourceProperties`。这些数据源的配置信息可以通过 `application.yml` 或 `application.properties` 文件进行配置。 接下来,可以使用这些数据源配置信息创建真正的数据源对象。可以使用 `DataSourceBuilder` 来创建数据源对象,如下所示: ``` @Configuration public class DataSourceConfig { // ... @Bean @Primary public DataSource primaryDataSource( @Qualifier("primaryDataSourceProperties") DataSourceProperties properties) { return properties.initializeDataSourceBuilder().build(); } @Bean public DataSource secondaryDataSource( @Qualifier("secondaryDataSourceProperties") DataSourceProperties properties) { return properties.initializeDataSourceBuilder().build(); } // ... } ``` 这里使用 `@Primary` 注解标注了 `primaryDataSource` 方法,表示这是主数据源。然后,使用 `DataSourceProperties` 对象创建 `DataSource` 对象,并将其注入到 `primaryDataSource` 和 `secondaryDataSource` 方法中。 最后,可以将这些数据源对象放入一个 `DataSourceMap` 中,方便在应用程序中使用。可以使用 `@PostConstruct` 注解标注一个方法,该方法会在应用程序启动时执行,如下所示: ``` @Configuration public class DataSourceConfig { // ... @Bean public DataSourceMap dataSourceMap( DataSource primaryDataSource, DataSource secondaryDataSource) { DataSourceMap dataSourceMap = new DataSourceMap(); dataSourceMap.put("primary", primaryDataSource); dataSourceMap.put("secondary", secondaryDataSource); return dataSourceMap; } @PostConstruct public void init() { // 初始化连接池 dataSourceMap.values().forEach(dataSource -> { HikariDataSource hikariDataSource = (HikariDataSource) dataSource; hikariDataSource.setMaximumPoolSize(10); hikariDataSource.setMinimumIdle(1); hikariDataSource.setIdleTimeout(600000); hikariDataSource.setConnectionTimeout(30000); }); } // ... } ``` 在 `init` 方法中,将创建的数据源对象放入一个 `DataSourceMap` 中,并对每个数据源对象进行初始化,例如设置最大连接数、最小连接数、空闲超时时间、连接超时时间等。 这样,在应用程序启动时,就会创建多个数据源对象,并对这些对象进行初始化,从而实现多数据源的功能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值