在同一个类的不同方法中使用 @DS
注解来切换数据源时,需要注意一些潜在的问题。具体来说,如果一个方法调用另一个方法,并且这两个方法上都有不同的 @DS
注解,那么数据源的切换可能不会按预期工作。这是因为注解通常通过 AOP(面向切面编程)机制来实现,在方法调用链中,内部方法的数据源切换可能不会生效。
为了更好地理解这个问题,我们可以通过一个具体的例子来说明。假设你有一个服务类 YourService
,其中有两个方法 list
和 listDict
,分别使用 @DS("slave")
和 @DS("master")
注解:
import java.util.List;
public class YourService {
private SlaveMapper slaveMapper;
private DictMapper dictMapper;
// 构造函数或者通过依赖注入初始化 mapper
public YourService(SlaveMapper slaveMapper, DictMapper dictMapper) {
this.slaveMapper = slaveMapper;
this.dictMapper = dictMapper;
}
@DS("slave")
public List<KeyValueVo> list(String areaCode) {
List<KeyValueVo> list = slaveMapper.list(areaCode);
List<Dict> educations = listDict("education");
// ...后续操作...
return list; // 假设需要返回这个列表
}
@DS("master")
public List<Dict> listDict(String dictType) {
List<Dict> list = dictMapper.list(dictType);
return list;
}
}
在这个例子中,当 list
方法调用 listDict
方法时,listDict
方法上的 @DS("master")
注解可能不会生效。原因在于,AOP 代理机制通常只会在外部调用时生效,而内部方法调用不会触发 AOP 切面。
解决方案
方案一:提取公共逻辑到单独的服务类
将 listDict
方法移到一个新的服务类中,这样可以确保每个方法都可以被独立地拦截和处理数据源切换。
@Service
public class DictService {
private DictMapper dictMapper;
// 构造函数或者通过依赖注入初始化 mapper
public DictService(DictMapper dictMapper) {
this.dictMapper = dictMapper;
}
@DS("master")
public List<Dict> listDict(String dictType) {
List<Dict> list = dictMapper.list(dictType);
return list;
}
}
@Service
public class YourService {
private SlaveMapper slaveMapper;
private DictService dictService;
// 构造函数或者通过依赖注入初始化 mapper 和 service
public YourService(SlaveMapper slaveMapper, DictService dictService) {
this.slaveMapper = slaveMapper;
this.dictService = dictService;
}
@DS("slave")
public List<KeyValueVo> list(String areaCode) {
List<KeyValueVo> list = slaveMapper.list(areaCode);
List<Dict> educations = dictService.listDict("education");
// ...后续操作...
return list; // 假设需要返回这个列表
}
}
方案二:手动设置数据源
如果你不想将 listDict
方法移到新的服务类中,可以在 list
方法中手动设置数据源。不过这种方法比较复杂,通常不推荐。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
public static String getDataSourceKey() {
return contextHolder.get();
}
public static void clearDataSourceKey() {
contextHolder.remove();
}
}
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceKey();
}
}
@Service
public class YourService {
private SlaveMapper slaveMapper;
private DictMapper dictMapper;
// 构造函数或者通过依赖注入初始化 mapper
public YourService(SlaveMapper slaveMapper, DictMapper dictMapper) {
this.slaveMapper = slaveMapper;
this.dictMapper = dictMapper;
}
@DS("slave")
public List<KeyValueVo> list(String areaCode) {
List<KeyValueVo> list = slaveMapper.list(areaCode);
try {
DataSourceContextHolder.setDataSourceKey("master");
List<Dict> educations = dictMapper.list("education");
// ...后续操作...
} finally {
DataSourceContextHolder.clearDataSourceKey();
}
return list; // 假设需要返回这个列表
}
}
总结
最简单和推荐的方法是将不同数据源的操作放在不同的服务类中,这样可以确保每个方法都能被独立地拦截和处理数据源切换。这种方式不仅符合面向对象的设计原则,还能避免复杂的内部方法调用导致的数据源切换问题。