乐趣区

关于java:Spring-Boot-实现读写分离还有谁不会

起源:www.liaoxuefeng.com

第一步:配置多数据源

Spring Boot 根底就不介绍了,举荐下这个实战教程:
https://github.com/javastacks…

首先,咱们在 SpringBoot 中配置两个数据源,其中第二个数据源是ro-datasource

spring:
  datasource:
    jdbc-url: jdbc:mysql://localhost/test
    username: rw
    password: rw_password
    driver-class-name: com.mysql.jdbc.Driver
    hikari:
      pool-name: HikariCP
      auto-commit: false
      ...
  ro-datasource:
    jdbc-url: jdbc:mysql://localhost/test
    username: ro
    password: ro_password
    driver-class-name: com.mysql.jdbc.Driver
    hikari:
      pool-name: HikariCP
      auto-commit: false
      ...

在开发环境下,没有必要配置主从数据库。只须要给数据库设置两个用户,一个 rw 具备读写权限,一个 ro 只有 SELECT 权限,这样就模仿了生产环境下对主从数据库的读写拆散。

在 SpringBoot 的配置代码中,咱们初始化两个数据源:

@SpringBootApplication
public class MySpringBootApplication {
    /**
     * Master data source.
     */
    @Bean("masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource")
    DataSource masterDataSource() {logger.info("create master datasource...");
        return DataSourceBuilder.create().build();
    }

    /**
     * Slave (read only) data source.
     */
    @Bean("slaveDataSource")
    @ConfigurationProperties(prefix = "spring.ro-datasource")
    DataSource slaveDataSource() {logger.info("create slave datasource...");
        return DataSourceBuilder.create().build();
    }

    ...
}

第二步:编写 RoutingDataSource

而后,咱们用 Spring 内置的 RoutingDataSource,把两个实在的数据源代理为一个动静数据源:

public class RoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {return "masterDataSource";}
}

对这个RoutingDataSource,须要在 SpringBoot 中配置好并设置为主数据源:

@SpringBootApplication
public class MySpringBootApplication {
    @Bean
    @Primary
    DataSource primaryDataSource(@Autowired @Qualifier("masterDataSource") DataSource masterDataSource,
            @Autowired @Qualifier("slaveDataSource") DataSource slaveDataSource
    ) {logger.info("create routing datasource...");
        Map<Object, Object> map = new HashMap<>();
        map.put("masterDataSource", masterDataSource);
        map.put("slaveDataSource", slaveDataSource);
        RoutingDataSource routing = new RoutingDataSource();
        routing.setTargetDataSources(map);
        routing.setDefaultTargetDataSource(masterDataSource);
        return routing;
    }
    ...
}

当初,RoutingDataSource 配置好了,然而,路由的抉择是写死的,即永远返回"masterDataSource"

当初问题来了:如何存储动静抉择的 key 以及在哪设置 key?

在 Servlet 的线程模型中,应用 ThreadLocal 存储 key 最合适,因而,咱们编写一个 RoutingDataSourceContext,来设置并动静存储 key:

public class RoutingDataSourceContext implements AutoCloseable {

    // holds data source key in thread local:
    static final ThreadLocal<String> threadLocalDataSourceKey = new ThreadLocal<>();

    public static String getDataSourceRoutingKey() {String key = threadLocalDataSourceKey.get();
        return key == null ? "masterDataSource" : key;
    }

    public RoutingDataSourceContext(String key) {threadLocalDataSourceKey.set(key);
    }

    public void close() {threadLocalDataSourceKey.remove();
    }
}

而后,批改 RoutingDataSource,获取 key 的代码如下:

public class RoutingDataSource extends AbstractRoutingDataSource {protected Object determineCurrentLookupKey() {return RoutingDataSourceContext.getDataSourceRoutingKey();
    }
}

这样,在某个中央,例如一个 Controller 的办法外部,就能够动静设置 DataSource 的 Key:

@Controller
public class MyController {@Get("/")
    public String index() {
        String key = "slaveDataSource";
        try (RoutingDataSourceContext ctx = new RoutingDataSourceContext(key)) {
            // TODO:
            return "html... www.liaoxuefeng.com";
        }
    }
}

到此为止,咱们曾经胜利实现了数据库的动静路由拜访。

这个办法是可行的,然而,须要读从数据库的中央,就须要加上一大段 try (RoutingDataSourceContext ctx = ...) {} 代码,应用起来非常不便。有没有办法能够简化呢?

有!

咱们认真想想,Spring 提供的申明式事务管理,就只须要一个 @Transactional() 注解,放在某个 Java 办法上,这个办法就主动具备了事务。

咱们也能够编写一个相似的 @RoutingWith("slaveDataSource") 注解,放到某个 Controller 的办法上,这个办法外部就主动抉择了对应的数据源。代码看起来应该像这样:

@Controller
public class MyController {@Get("/")
    @RoutingWith("slaveDataSource")
    public String index() {return "html... www.liaoxuefeng.com";}
}

这样,齐全不批改应用程序的逻辑,只在必要的中央加上注解,主动实现动静数据源切换,这个办法是最简略的。

想要在应用程序中少写代码,咱们就得多做一点底层工作:必须应用相似 Spring 实现申明式事务的机制,即用 AOP 实现动静数据源切换。

实现这个性能也非常简单,编写一个 RoutingAspect,利用 AspectJ 实现一个Around 拦挡:

@Aspect
@Component
public class RoutingAspect {@Around("@annotation(routingWith)")
    public Object routingWithDataSource(ProceedingJoinPoint joinPoint, RoutingWith routingWith) throws Throwable {String key = routingWith.value();
        try (RoutingDataSourceContext ctx = new RoutingDataSourceContext(key)) {return joinPoint.proceed();
        }
    }
}

留神办法的第二个参数 RoutingWith 是 Spring 传入的注解实例,咱们依据注解的 value() 获取配置的 key。编译前须要增加一个 Maven 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

到此为止,咱们就实现了用注解动静抉择数据源的性能。最初一步重构是用字符串常量替换散落在各处的 "masterDataSource""slaveDataSource"

应用限度

受 Servlet 线程模型的局限,动静数据源不能在一个申请内设定后再批改,也就是 @RoutingWith 不能嵌套。此外,@RoutingWith@Transactional 混用时,要设定 AOP 的优先级。

本文代码须要 SpringBoot 反对,JDK 1.8 编译并关上 -parameters 编译参数。

近期热文举荐:

1.1,000+ 道 Java 面试题及答案整顿(2021 最新版)

2. 劲爆!Java 协程要来了。。。

3. 玩大了!Log4j 2.x 再爆雷。。。

4.Spring Boot 2.6 正式公布,一大波新个性。。

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

退出移动版