原理图
- 一个连接池只能操作一个数据库,现在我们项目有多个数据库,就必须为每一个数据库配置对应的连接池
- 在执行SQL语句的使用,根据执行的操作来选择使用哪个连接池进行操作,如:执行DML就必须是选择
MasterDataSource来操作,执行DQL操作就选择SlaveDataSource来操作
- 此时应当再有一个对象,该对象拥有管理所有连接池和选择连接池的来使用的功能,该对象就是具有路由功能的连
接池
- 当调用者需要进行数据库操作时,再告诉路由连接池我需要你帮我选择哪个连接池进行操作数据库即可
实现
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));
}
}