spring多数据源实现读写分离

一、常用场景

读写分离:写走主库,读走从库

分库分表:通常有两种路由算法,范围或hash。

二、读写分离实现方法

1.实现动态数据源

spring提供了抽象类AbstractRoutingDataSource,里面有两个重要的参数,

targetDataSources代表提供的数据源。

defaultTargetDataSource代表默认数据源。

public void setTargetDataSources(Map targetDataSources) {
    this.targetDataSources = targetDataSources;
}

public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
    this.defaultTargetDataSource = defaultTargetDataSource;
}

还有一个抽象方法

@Nullable
protected abstract Object determineCurrentLookupKey();

我们可以继承AbstractRoutingDataSource,实现determineCurrentLookupKey方法进行动态路由数据源。实现如下:

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
    private static ThreadLocal ROUTING_KEY = new ThreadLocal<>();

    @Override
    protected Object determineCurrentLookupKey() {
        return ROUTING_KEY.get();
    }

    public static void setRoutingKey(String routingKey) {
        ROUTING_KEY.set(routingKey);
    }

    public static void removeRoutingKey() {
        ROUTING_KEY.remove();
    }
	
    //不用重写改方法,这里是为了打印数据源信息
    @Override
    public Connection getConnection() throws SQLException {
        DataSource dataSource = this.determineTargetDataSource();
        logger.info(dataSource);
        return dataSource.getConnection();
    }
}

2.基于注解的方式,实现动态切换数据源

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Master {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Slave {
}
@Aspect
@Component
@Order(-99)
public class MasterAop {

    @Pointcut("@annotation(com.example.demo.aop.Master)")
    public void recordAspect() {}

    @Around("recordAspect()")
    public Object recordAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        try {
            DynamicRoutingDataSource.setRoutingKey("master");
            return proceedingJoinPoint.proceed();
        } finally {
            DynamicRoutingDataSource.removeRoutingKey();
        }
    }
}
@Aspect
@Component
@Order(-99)
public class SlaveAop {

    @Pointcut("@annotation(com.example.demo.aop.Slave)")
    public void recordAspect() {
    }

    @Around("recordAspect()")
    public Object recordAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        try {
            DynamicRoutingDataSource.setRoutingKey("slave");
            return proceedingJoinPoint.proceed();
        } finally {
            DynamicRoutingDataSource.removeRoutingKey();
        }
    }
}

3.配置数据源

@Bean
@Primary
public DataSource dataSource() {
    DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
    HikariConfig masterHikariConfig = new HikariConfig();
    masterHikariConfig.setPassword("xx");
    masterHikariConfig.setUsername("xx");
    masterHikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver");
    masterHikariConfig.setJdbcUrl("jdbc:mysql://xxxx?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true");
    masterHikariConfig.setPoolName("masterPool");

    HikariDataSource masterDataSource = new HikariDataSource(masterHikariConfig);


    HikariConfig slaveHikariConfig = new HikariConfig();
    slaveHikariConfig.setPassword("xx");
    slaveHikariConfig.setUsername("xx");
    slaveHikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver");
    slaveHikariConfig.setJdbcUrl("jdbc:mysql://xxxx?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true");
    slaveHikariConfig.setPoolName("slavePool");
    HikariDataSource slaveDataSource = new HikariDataSource(slaveHikariConfig);

    HashMap targetDataSources = new HashMap<>();
    targetDataSources.put("master", masterDataSource);
    targetDataSources.put("slave", slaveDataSource);
    dynamicRoutingDataSource.setTargetDataSources(targetDataSources);

    return dynamicRoutingDataSource;
}

4.实现一个测试Service

@Service
@Slf4j
public class MasterSlaveService {

    @Slave
    @Transactional
    public void slaveTest() {

    }

    @Master
    @Transactional
    public void masterTest() {

    }
}

5.测试如下

@SpringBootApplication
@EnableTransactionManagement
public class RoutingDataSourceDemoApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(RoutingDataSourceDemoApplication.class, args);
        MasterSlaveService bean = run.getBean(MasterSlaveService.class);
        bean.masterTest();
        bean.slaveTest();
    }
}

我们可以看到数据源的切换信息

com.zaxxer.hikari.HikariDataSource       : masterPool - Starting...
com.zaxxer.hikari.HikariDataSource       : masterPool - Start completed.
com.zaxxer.hikari.HikariDataSource       : slavePool - Starting...
com.zaxxer.hikari.HikariDataSource       : slavePool - Start completed.
c.e.d.RoutingDataSourceDemoApplication   : Started RoutingDataSourceDemoApplication in 2.054 seconds (JVM running for 2.682)
c.e.d.d.DynamicRoutingDataSource         : HikariDataSource (masterPool)
c.e.d.d.DynamicRoutingDataSource         : HikariDataSource (slavePool)

三、其他

如果业务简单,我们可以自己实现数据源的切换,如果复杂的话,建议使用ShardingSphere框架,ShardingSphere是基于更底层的jdbc代理实现。

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章