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);
}
相关推荐
阳冬园17 分钟前
mysql数据库 主从同步
数据库·主从同步
goTsHgo19 分钟前
在 Spring Boot 的 MVC 框架中 路径匹配的实现 详解
spring boot·后端·mvc
钱多多_qdd28 分钟前
spring cache源码解析(四)——从@EnableCaching开始来阅读源码
java·spring boot·spring
飞的肖39 分钟前
前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端
前端·spring boot·架构
Q_192849990641 分钟前
基于Spring Boot的摄影器材租赁回收系统
java·spring boot·后端
Mr.131 小时前
数据库的三范式是什么?
数据库
Cachel wood2 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
gb42152872 小时前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
Python之栈2 小时前
【无标题】
数据库·python·mysql
风_流沙2 小时前
java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
java·数据库·elasticsearch