问题描述:
做合同到期自动发送消息的需求时,需要定时扫描所有的数据源信息,涉及到数据源切换的实现。当时一直无法解决的原因是不理解通用代码中远程调用feign指定数据源的原理,也没有尝试去解读源码,一直在做表面分析,导致找不到解决方法,现整理思路如下:
原因分析:
原始的参考代码如下:
// 远程调用feign指定数据源.
MockHttpServletRequest request = new MockHttpServletRequest();
request.setAttribute(DynamicConstant.SOURCES_ID, DynamicDataSourceContextHolder.peek());
request.setAttribute(DynamicConstant.GATEWAY_MASTER, DynamicConstant.DYNAMIC_CLEAR);
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
分析:理解MockHttpServletRequest 是用于模拟HTTP请求的一个方法类
向请求中设置参数key为"sources_id",value为DynamicDataSourceContextHolder.peek()即动态数据源队列中的sourceId值,因此这里在使用这个方法时应该传入对应的数据源的sources_id值。并设置主网关为清空状态,体现在项目中即从主数据源切换到指定数据源
public static final String DYNAMIC_CLEAR = “clear”;
public static final String GATEWAY_MASTER = “gateway_master”;
这里远程调用feign指定数据源就可以封装成一个方法(参数为sourceId),供后面的代码中远程调用服务去使用
使用方式:setSourcesId(sourcesId);在远程调用代码执行之前进行设置,
注意:当需要再次远程调用时需要重新设置,每次都要指定远程数据源
注意:由于切换数据源的内部拼接sql需要去查询主数据源的数据源配置表,因此在执行此方法之前必须清除当前数据源信息(使线程处于主数据源)
private void setSourcesId(String sourcesId) {
MockHttpServletRequest request = new MockHttpServletRequest();
// System.out.println(DynamicDataSourceContextHolder.peek());
request.setAttribute(DynamicConstant.SOURCES_ID, sourcesId);
request.setAttribute(DynamicConstant.GATEWAY_MASTER, DynamicConstant.DYNAMIC_CLEAR);
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
}
当定时任务被触发时,首先清除当前的数据源信息
DynamicDataSourceContextHolder.clear();
在主数据源下查询数据源配置表,遍历(循环中首先清除数据源),指定数据源,处理业务逻辑(业务逻辑方法参数传入指定数据源的sourceId,用于远程调用服务前)
分析:指定数据源的实现思路
public boolean swatchByMark(String mark) {
if (StrUtil.isEmpty(mark)) {
return false;
}
String executeSql = StrUtil.format(DynamicConstant.DEFAULT_DB_TENANT, mark);
Triple<String, String, String> dbInfo = getDbInfo(executeSql);
if (dbInfo.getLeft() == null || dbInfo.getMiddle() == null || dbInfo.getRight() == null) {
return false;
}
// 切换数据源
return swatch(dbInfo.getRight());
}
public static final String DEFAULT_DB_TENANT = “SELECT id,mark,sources_id FROM sys_tenant where mark=‘{}’ limit 1”;
这里写了一个拼接的sql,调用getDbInfo方法获取到三个查询结果,将第三个结果即sourceId传入,swatch()方法的作用是校验数据源是否存在并进行切换,最终调用的还是DynamicDataSourceContextHolder.push(sourceId);对数据源进行设置
public Triple<String, String, String> getDbInfo(String sql) {
Map<String, Object> result = jdbcTemplate.queryForMap(sql);
if (ObjectUtil.isEmpty(result) || result.isEmpty()) {
return Triple.of(null, null, null);
}
String id = String.valueOf(result.get("id"));
String mark = String.valueOf(result.get("mark"));
String sourceId = String.valueOf(result.get("sources_id"));
return Triple.of(id, mark, sourceId);
}
拓展知识点:
对于在循环中可能会出现异常的代码行,可以采用try-catch的方式捕获异常信息,并continue,这样可以结束本次循环,执行下一循环
// 查询表Employee合同到期的雇员
List<EmployeeContractVO> employeeContractVOs;
; try {
employeeContractVOs = employeeService.selectByContract();
}catch (Exception e){
e.printStackTrace();
log.info("查询数据库合同信息出现错误,重试下一个");
continue;
}