一、常用場景
讀寫分離:寫走主庫,讀走從庫
分庫分表:通常有兩種路由算法,范圍或hash。
二、讀寫分離實(shí)現(xiàn)方法
1.實(shí)現(xiàn)動態(tài)數(shù)據(jù)源
spring提供了抽象類AbstractRoutingDataSource,里面有兩個重要的參數(shù),
targetDataSources代表提供的數(shù)據(jù)源。
defaultTargetDataSource代表默認(rèn)數(shù)據(jù)源。
public void setTargetDataSources(Map targetDataSources) { this.targetDataSources = targetDataSources;}public void setDefaultTargetDataSource(Object defaultTargetDataSource) { this.defaultTargetDataSource = defaultTargetDataSource;}
還有一個抽象方法
@Nullableprotected abstract Object determineCurrentLookupKey();
我們可以繼承AbstractRoutingDataSource,實(shí)現(xiàn)determineCurrentLookupKey方法進(jìn)行動態(tài)路由數(shù)據(jù)源。實(shí)現(xiàn)如下:
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(); } //不用重寫改方法,這里是為了打印數(shù)據(jù)源信息 @Override public Connection getConnection() throws SQLException { DataSource dataSource = this.determineTargetDataSource(); logger.info(dataSource); return dataSource.getConnection(); }}
2.基于注解的方式,實(shí)現(xiàn)動態(tài)切換數(shù)據(jù)源
@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Master {}@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @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.配置數(shù)據(jù)源
@Bean@Primarypublic 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.實(shí)現(xiàn)一個測試Service
@Service@Slf4jpublic class MasterSlaveService { @Slave @Transactional public void slaveTest() { } @Master @Transactional public void masterTest() { }}
5.測試如下
@SpringBootApplication@EnableTransactionManagementpublic 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(); }}
我們可以看到數(shù)據(jù)源的切換信息
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)
三、其他
如果業(yè)務(wù)簡單,我們可以自己實(shí)現(xiàn)數(shù)據(jù)源的切換,如果復(fù)雜的話,建議使用ShardingSphere框架,ShardingSphere是基于更底層的jdbc代理實(shí)現(xiàn)。