读写分离

原理图

image.png
  1. 一个连接池只能操作一个数据库,现在我们项目有多个数据库,就必须为每一个数据库配置对应的连接池
  2. 在执行SQL语句的使用,根据执行的操作来选择使用哪个连接池进行操作,如:执行DML就必须是选择
    MasterDataSource来操作,执行DQL操作就选择SlaveDataSource来操作
  3. 此时应当再有一个对象,该对象拥有管理所有连接池和选择连接池的来使用的功能,该对象就是具有路由功能的连
    接池
  4. 当调用者需要进行数据库操作时,再告诉路由连接池我需要你帮我选择哪个连接池进行操作数据库即可

实现

1. 自定义一个工具类,用于标记当选择连接池操作,绑定到当前线程中

//路由工具类
 public abstract class RoutingUtil { 
//帮助我们在线程中绑定变量的对象
 private static ThreadLocal<String> local = new ThreadLocal<>(); 
public static void setMaster() {
local.set("master");
 System.err.println("设置连接池:master"); 
}
public static void setSlave() { 
local.set("slave");
 System.err.println("设置连接池:slave");
 }
public static String get() { return local.get(); }

2. 自定义一个路由连接池,继承Spring提供的AbstractRoutingDataSource

//自定义的路由连接池
 public class RBACRoutingDataSource extends AbstractRoutingDataSource {
 //返回想要的连接池的key
 protected Object determineCurrentLookupKey() { 
String routingKey = RouteUtil.get();
 System.err.println("获取连接池:"+routingKey); 
return routingKey; 
} 
}

配置对象DataSourceConfig配置连接池

//配置连接池
 @Configuration
 public class DataSourceConfig { 
@Bean
 public DataSource master() {
 DruidDataSource master = new DruidDataSource();
 //填写你自己的4要素信息
 master.setUrl("jdbc:mysql://xx.xx.xx.xx/rbac"); master.setUsername("root");
 master.setPassword("admin");
 return master;
 }
@Bean
 public DataSource slave() {
 DruidDataSource slave = new DruidDataSource();
 //填写你自己的4要素信息
 slave.setUrl("jdbc:mysql://xx.xx.xx.xx/rbac");
 slave.setUsername("root"); 
slave.setPassword("admin"); 
return slave;
 }
@Bean
 public DataSource routingDataSource(DataSource master, DataSource slave) {
 Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("master", master);
 targetDataSources.put("slave", slave); 
RoutingDataSource routingDataSource = new RoutingDataSource(); routingDataSource.setDefaultTargetDataSource(master); routingDataSource.setTargetDataSources(targetDataSources);
 return routingDataSource;
 } 
}

4. 配置MyBatis核心对象

@Configuration 
@MapperScan("cn.wolfcode.readwriter.mapper")
@EnableTransactionManagement
public class MyBatisConfig {
 @Autowired
 private DataSource routingDataSource; 
@Bean 
public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); 
//具有路由功能的连接池 
bean.setDataSource(routingDataSource);
bean.setTypeAliasesPackage("cn.wolfcode.readwriter.domain");
Resource[] resources = new PathMatchingResourcePatternResolver() .getResources("classpath:cn/wolfcode/readwriter/mapper/*Mapper.xml");
bean.setMapperLocations(resources);
 return bean.getObject(); 
}
@Bean
 public PlatformTransactionManager transactionManager() {
 return new DataSourceTransactionManager(routingDataSource);
 } 
}

5. 使用AOP的方式对业务层中选择路由的逻辑解耦

@Order(1) //设置优先级,数字越大越优先级越高 
@Aspect 
@Component public class RoutingOptionAspect {
@Pointcut("execution(*cn.wolfcode.readwriter.service.impl.*ServiceImpl.*(..))") 
public void pc() {
 }
 @Before("pc()") 
public void setRoutingOption(JoinPoint point) { 
//获取当前方法的名称 
String methodName = point.getSignature().getName();
 if (isSalve(methodName)) { 
RoutingUtil.setSlave();
} else { 
RoutingUtil.setMaster(); 
} 
}
//判断是否查询操作
 private boolean isSalve(String methodName) { 
return methodName.startsWith("get") || methodName.startsWith("list") || methodName.startsWith("query");
 }
 }

6. 配置SpringBoot的applicationContext.properties开启MyBatis日志

logging.level.cn.wolfcode.readwrite.mapper=trace

7. 测试

@RunWith(SpringRunner.class) 
@SpringBootTest
public class ReadWriterApplicationTests {
 @Autowired
 private IDepartmentService departmentService; 
/*往Master中插入一条数据,检查Slave中是否也有该数据 */ 
@Test public void testSave() { 
departmentService.save(new Department(null, "小卖部", "SALE")); 
}
/* 故意手动在Slave中添加一条数据,然后再做查询,看是否能查询到 */
@Test 
public void testGet() {
 System.out.println(departmentService.get(7L));
 } 
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容