springboot 实现数据库的读写分离

springboot + mybatis +druid 主从数据库,所有select语句都走从数据库

在 Spring Boot 应用中,MyRoutingDataSource 可以与 DataSource 配置结合使用,通过 @Configuration 类来配置数据源和事务管理器,并使用 @Primary 注解来指定主数据源

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</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.2.20</version> <!-- 根据需要选择合适的版本 -->
</dependency>

配置文件

spring:
  datasource:
    master:
      jdbc-url: jdbc:mysql://localhost:3306/master_db?useSSL=false&serverTimezone=UTC
      username: master_user
      password: master_password
      driver-class-name: com.mysql.cj.jdbc.Driver
    slave1:
      jdbc-url: jdbc:mysql://localhost:3306/slave_db1?useSSL=false&serverTimezone=UTC
      username: slave_user
      password: slave_password
      driver-class-name: com.mysql.cj.jdbc.Driver
    slave2:
      jdbc-url: jdbc:mysql://localhost:3306/slave_db2?useSSL=false&serverTimezone=UTC
      username: slave_user
      password: slave_password
      driver-class-name: com.mysql.cj.jdbc.Driver

  # MyBatis配置
  mybatis:
    mapper-locations: classpath:/mapper/*.xml
    type-aliases-package: com.example.demo.model
    configuration:
      map-underscore-to-camel-case: true

# MyBatis配置,可以指定config-location来加载mybatis-config.xml配置文件
# mybatis.config-location: classpath:mybatis-config.xml

或者

# 主数据源配置
spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/master_db?useSSL=false&serverTimezone=UTC
spring.datasource.master.username=master_user
spring.datasource.master.password=master_password
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver

# 从数据源配置
spring.datasource.slave1.jdbc-url=jdbc:mysql://localhost:3306/slave_db1?useSSL=false&serverTimezone=UTC
spring.datasource.slave1.username=slave_user
spring.datasource.slave1.password=slave_password
spring.datasource.slave1.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.slave2.jdbc-url=jdbc:mysql://localhost:3306/slave_db2?useSSL=false&serverTimezone=UTC
spring.datasource.slave2.username=slave_user
spring.datasource.slave2.password=slave_password
spring.datasource.slave2.driver-class-name=com.mysql.cj.jdbc.Driver

# MyBatis配置
mybatis.mapper-locations=classpath:/mapper/*.xml
mybatis.type-aliases-package=com.example.demo.model
mybatis.configuration.map-underscore-to-camel-case=true

# MyBatis配置,可以指定config-location来加载mybatis-config.xml配置文件
# mybatis.config-location=classpath:mybatis-config.xml

druid配置

druid:
  initial-size: 5
  min-idle: 5
  max-active: 20
  max-wait: 60000

配置类

@Configuration
public class DataSourceConfig {

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

    @Bean(name = "slave1DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave1")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().type(DruidDataSource.class).build();
    }
    @Bean(name = "slave2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave2")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().type(DruidDataSource.class).build();
    }
}

定义数据源上下文持有者 (DbContextHolder):

这是一个用于存储和检索当前请求所选择的数据源类型的类,通常使用 ThreadLocal 来保证线程安全。

public class DbContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDbType(String dbType) {
        contextHolder.set(dbType);
    }

    public static String getDbType() {
        return contextHolder.get();
    }

    public static void clearDbType() {
        contextHolder.remove();
    }
}

自定义一个数据源路由类MyRoutingDataSource 类,并且继承 AbstractRoutingDataSource,并且重写了 determineCurrentLookupKey() 方法,用于确定当前请求应该使用哪个数据源。这个方法的实现通常基于一些业务逻辑或者执行上下文来决定数据源的选择。

实现 determineCurrentLookupKey() 方法 : 在这个方法中,你可以通过调用 DbContextHolder.getDbType() 来获取当前请求的数据源类型。这个类型可以是一个简单的字符串、枚举值或其他可以唯一标识数据源的类型。

public class MyRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        // 根据业务逻辑获取数据源类型
        String dbType = DbContextHolder.getDbType();
        if (dbType == null) {
            // 如果没有设置,可以使用默认的数据源类型
            dbType = "defaultDataSource";
        }
        if(dbType.equals("select")){
            dbType = "slaveDataSource"
        }
        return  dbType;
    }
}

配置路由

@Configuration
public class DataSourceConfig {

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

    @Bean(name = "slave1DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave1")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().type(DruidDataSource.class).build();
    }
    @Bean(name = "slave2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave2")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().type(DruidDataSource.class).build();
    }

    @Bean
    public MyRoutingDataSource dataSource() {
        MyRoutingDataSource dataSource = new MyRoutingDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("masterDataSource", masterDataSource());
        targetDataSources.put("slave1DataSource", slaveDataSource());
        targetDataSources.put("slave2DataSource", slaveDataSource());
        dataSource.setTargetDataSources(targetDataSources);
        dataSource.setDefaultTargetDataSource(masterDataSource()); // 设置默认数据源
        return dataSource;
    }

}

使用 AOP 拦截 SQL 语句

@Aspect
@Component
public class DataSourceAspect {

    @Before("execution(* org.springframework.jdbc.core.JdbcTemplate.query(..))")
    public void setReadDataSource() {
        // 所有查询操作前设置使用从数据库
        DbContextHolder.setDbType("select");
    }

    @After("execution(* org.springframework.jdbc.core.JdbcTemplate.query(..))")
    public void clearReadDataSource() {
        // 查询操作后清除设置
        DbContextHolder.clearDbType();
    }

    // 可以添加更多的切点来处理其他类型的数据操作
}

配置事务管理器

@Configuration
public class DataSourceConfig {

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

    @Bean(name = "slave1DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave1")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().type(DruidDataSource.class).build();
    }
    @Bean(name = "slave2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave2")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().type(DruidDataSource.class).build();
    }

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

@Bean(name = "slaveTransactionManager1")
public PlatformTransactionManager slaveTransactionManager1(@Qualifier("slave2DataSource") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "slaveTransactionManager2")
public PlatformTransactionManager slaveTransactionManager1(@Qualifier("slave2DataSource") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

    @Bean
    public MyRoutingDataSource dataSource() {
        MyRoutingDataSource dataSource = new MyRoutingDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("masterDataSource", masterDataSource());
        targetDataSources.put("slave1DataSource", slaveDataSource());
        targetDataSources.put("slave2DataSource", slaveDataSource());
        dataSource.setTargetDataSources(targetDataSources);
        dataSource.setDefaultTargetDataSource(masterDataSource()); // 设置默认数据源
        return dataSource;
    }

}

以上就配置好了

另外如果你对性能和可扩展性要求比较高,还可以为每个数据源创建不同的MyBatis会话工厂。可以为每个数据源配置SqlSessionFactory Bean,并注入对应的数据源。

@Bean
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource masterDataSource) throws Exception {
    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    sessionFactory.setDataSource(masterDataSource);
    sessionFactory.setMapperLocations(mapperLocations());
    // 其他MyBatis配置...
    return sessionFactory.getObject();
}

@Bean
public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource slaveDataSource) throws Exception {
    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    sessionFactory.setDataSource(slaveDataSource);
    sessionFactory.setMapperLocations(slaveMapperLocations());
    // 其他MyBatis配置...
    return sessionFactory.getObject();
}

配置MyBatis模板 :为每个会话工厂配置SqlSessionTemplate

@Bean
public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory masterSqlSessionFactory) {
    return new SqlSessionTemplate(masterSqlSessionFactory);
}

@Bean
public SqlSessionTemplate slaveSqlSessionTemplate(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory slaveSqlSessionFactory) {
    return new SqlSessionTemplate(slaveSqlSessionFactory);
}
相关推荐
lwprain19 分钟前
springboot 2.7.6 security mysql redis jwt配置例子
spring boot·redis·mysql
vcshcn42 分钟前
DBASE DBF数据库文件解析
数据库·dbase
AIGC大时代2 小时前
对比DeepSeek、ChatGPT和Kimi的学术写作撰写引言能力
数据库·论文阅读·人工智能·chatgpt·数据分析·prompt
如风暖阳2 小时前
Redis背景介绍
数据库·redis·缓存
酷爱码3 小时前
springboot 动态配置定时任务
java·spring boot·后端
计算机-秋大田3 小时前
基于SpringBoot的美食烹饪互动平台的设计与实现(源码+SQL脚本+LW+部署讲解等)
vue.js·spring boot·后端·课程设计·美食
lingllllove3 小时前
Redis脑裂问题详解及解决方案
数据库·redis·缓存
字节全栈_BjO4 小时前
mysql死锁排查_mysql 死锁问题排查
android·数据库·mysql
微光守望者4 小时前
Redis常见命令
数据库·redis·缓存
martian6655 小时前
第六篇:事务与并发控制
数据库