spring数据库主从配置

Jacky Java 2021-11-22 16

配置文件

application.yml文件配置

spring:
  datasources:
    # 默认连接数据源配置
    default-type: slave
    # 主数据源参数配置
    master:
      # 数据库驱动
      driver-class-name: com.mysql.jdbc.Driver
      # 数据库连接地址
      url: jdbc:mysql://127.0.0.1:3306/blog
      # 数据库用户名
      username: root
      # 数据库密码
      password: root
    # 从数据源参数配置
    slave:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3307/blog
      username: root
      password: root

数据源标记

public enum DbType {

    /**
     * 主数据源标记
     */
    master,
    /**
     * 从数据源标记
     */
    slave

}

配置类

import lombok.Data;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@EnableConfigurationProperties
@ConfigurationProperties("spring.datasources")
public class Config {

    /**
     * 主数据源配置
     */
    DataSourceProperties master;
    /**
     * 从数据源配置
     */
    DataSourceProperties slave;
    /**
     * 默认连接数据源配置
     */
    DbType defaultType = DbType.slave;

}

数据源

由于在代码中库的实例需要动态选择,因此我们继承AbstractRoutingDataSource类来聚合多个数据源 通过实现determineCurrentLookupKey方法来筛选我们需要的数据源

import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 数据源
 */
@Slf4j
public class DbRouting extends AbstractRoutingDataSource {

    /**
     * 默认
     */
    private DbType def;

    public DbRouting(DbType def) {
        this.def = def;
    }

    @Override
    protected Object determineCurrentLookupKey() {
        DbType type = DbContextHolder.get().orElse(def);
        log.debug("连接的数据库是:{}", type);
        return type;
    }
}

数据库切换标记类

import java.lang.annotation.*;

/**
 * 切换数据库标记<br/>
 * 默认指向主库标记
 **/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface DB {
    DbType type() default DbType.master;
}

数据库切换控制类

使用面向切面编程的方式,通过声明注解 ** @DB **,来拦截选择master和slave库

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Optional;

/**
 * 数据库切换控制类
 */
@Slf4j
@Aspect
@Order(0)
@Component
public class DbContextHolder {

    private static final ThreadLocal<DbType> LOCAL = new ThreadLocal<>();

    /**
     * 定义切点
     */
    @Pointcut("@annotation(com.tanshuyi.db.DB)")
    public void pointcut() {
    }

    /**
     * 前置通知,做一些参数的预处理,不能阻止进入切点除非报错
     */
    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint) {
        DB db = getDB(joinPoint);
        if (db != null) {
            enableSlaveOrMaster(db.type());
            log.debug("切换到:{}", db.type());
        } else {
            log.debug("继续使用默认库");
        }
    }

    /**
     * 后置通知,切点执行后执行,就像 finally
     */
    @After("pointcut()")
    public void doAfter(JoinPoint joinPoint) {
        DB db = getDB(joinPoint);
        if (db != null) {
            closeSlaveOrMaster();
            log.debug("切换回默认库");
        }
    }

    public DB getDB(JoinPoint joinPoint) {
        try {
            Object target = joinPoint.getTarget();
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = target.getClass().getMethod(signature.getName(), signature.getParameterTypes());
            if (method.isAnnotationPresent(DB.class)) {
                return method.getAnnotation(DB.class);
            } else if (target.getClass().isAnnotationPresent(DB.class)) {
                return target.getClass().getAnnotation(DB.class);
            }
        } catch (Exception e) {
            log.error("获取注释失败:", e);
        }
        return null;
    }

    /**
     * 获取当前需要使用的库标记
     */
    public static Optional<DbType> get() {
        return Optional.ofNullable(LOCAL.get());
    }

    /**
     * 启用库
     */
    public void enableSlaveOrMaster(DbType dbType) {
        LOCAL.set(dbType);
    }

    /**
     * 关闭
     */
    public void closeSlaveOrMaster() {
        LOCAL.remove();
    }
}

数据源声明类

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Configuration
public class DbConfig {

    @Bean
    public DataSource dataSource(Config config) {

        log.debug("默认连接数据库:{}", config.getDefaultType());

        Assert.notNull(config.master, "必须配置主数据库");

        Map<Object, Object> targetDataSources = new HashMap<>(2);
        targetDataSources.put(DbType.master, getDataSource(config.master));
        if (config.slave != null) {
            targetDataSources.put(DbType.slave, getDataSource(config.slave));
        } else {
            targetDataSources.put(DbType.slave, targetDataSources.get(DbType.master));
        }
        DbRouting dbRouting = new DbRouting(config.getDefaultType());
        dbRouting.setTargetDataSources(targetDataSources);
        return dbRouting;
    }

    /**
     * 创建库的datasource
     **/
    private DataSource getDataSource(DataSourceProperties pp) {
        DataSourceBuilder<?> builder = DataSourceBuilder.create();
        builder.driverClassName(pp.getDriverClassName());
        builder.url(pp.getUrl());
        builder.username(pp.getUsername());
        builder.password(pp.getPassword());
        return builder.build();
    }
}

应用实例


@Service
public class TypeServiceImpl{

    @Resource
    private TypeMapper typeMapper;

...

    @DB
    public int updateType(Type type) {
        return typeMapper.updateById(type);
    }

    public Type getType(Long id) {
        return typeMapper.selectById(id);
    }
...

}