在日常开发中,非JDBC数据源(如OData、REST API等)的查询往往需要重复编写HTTP请求、参数拼接、数据解析逻辑,适配成本高且代码冗余。本文将介绍一款参考MyBatis-Plus设计的Lambda查询器,通过“最少3个类”即可快速扩展非JDBC数据源查询能力,以SAP BW4的OData接口为例,带你体验“一行代码查数据”的快捷性。
一、Lambda查询器核心设计思路
Lambda查询器的核心是通过抽象封装+开放接口,实现“数据源适配与查询逻辑解耦”,整体架构分为三层:
- BaseDao抽象层:定义通用查询方法(list、getFirst、page等),屏蔽查询逻辑差异;
- DaoFactory工厂层:管理数据源配置(如地址、认证信息),统一创建Dao实例;
- QueryBuilder构建层:根据Lambda表达式生成数据源对应的查询语句(如OData的$filter)。
只需针对特定数据源实现上述三层的具体逻辑,即可接入Lambda查询能力,无需重复编写通用代码。
二、SAP BW4 OData接口适配实现(仅需3个类)
本节将完整展示如何通过3个核心类,实现SAP BW4 OData接口的Lambda查询适配,全程无需编写冗余的HTTP请求与解析代码。
1. 实现Dao层:SapBw4Dao
继承BaseDao抽象类,实现SAP BW4 OData接口的具体查询逻辑(HTTP请求发送、响应解析、异常处理)。核心是重写buildQuery(生成OData查询语句)和execSapBw4(执行HTTP请求)方法。
@Slf4j
public class SapBw4Dao<T> extends BaseDao<T, String> {
private String url; // BW4 OData接口地址
private String username; // 认证用户名
private String pwd; // 认证密码
// 构造方法:传入实体类、数据源配置
public SapBw4Dao(Class<T> entityCls, String url, String username, String pwd) {
super(entityCls);
this.url = url;
this.username = username;
this.pwd = pwd;
}
// 生成OData的$filter查询语句(委托给SapBw4QueryBuilder)
@Override
public String buildQuery() {
QueryInfo queryInfo = baseWhereQuery.buildQueryInfo();
return SapBw4QueryBuilder.build(entityCls, queryInfo);
}
// 核心查询方法:执行HTTP请求并解析结果
@Override
public List<T> list() {
String filter = buildQuery();
return execSapBw4(filter, null, null);
}
// 分页查询实现(适配OData的$skip/$top参数)
@Override
public IJPage<T> page(IJPage<T> page) {
int top = page.getPageSize();
int skip = (page.getPageNo() - 1) * page.getPageSize();
String query = buildQuery();
List<T> list = execSapBw4(query, skip, top);
page.setData(list);
return page;
}
// 内部方法:发送HTTP请求到BW4 OData接口
private List<T> execSapBw4(String filter, Integer skip, Integer top) {
// 1. 构建HTTP请求(基础认证+JSON格式)
HttpRequest request = HttpUtil.createGet(url)
.basicAuth(username, pwd);
request.form("$format", "json");
// 2. 拼接查询参数($filter/$skip/$top)
if (StrUtil.isNotBlank(filter)) request.form("$filter", filter);
if (skip != null) request.form("$skip", skip);
if (top != null) request.form("$top", top);
// 3. 执行请求并解析JSON结果
try (HttpResponse httpResponse = request.execute()) {
log.info("BW4 OData请求地址:{}", request.getUrl());
if (httpResponse.getStatus() == 200) {
String body = httpResponse.body();
JSONObject jsonObject = JSON.parseObject(body);
JSONArray results = jsonObject.getJSONObject("d").getJSONArray("results");
log.info("BW4查询结果数量:{}", results.size());
return results.toJavaList(entityCls); // 直接转为实体类列表
} else {
throw new RRException("BW4请求失败,状态码:" + httpResponse.getStatus());
}
} catch (Exception e) {
log.error("BW4查询异常", e);
throw new RRException("BW4查询异常:" + e.getMessage());
}
}
// 未实现的方法(根据业务需求选择性实现)
@Override public T getFirst() { /* 已实现,见上文 */ }
@Override public Long count() { throw new UnsupportedOperationException(); }
@Override public T findById(Serializable id) { throw new UnsupportedOperationException(); }
// 其他增删改方法按需实现...
}
2. 实现工厂层:SapBw4DaoFactory
管理SAP BW4的数据源认证信息(按实体类隔离),统一创建SapBw4Dao实例,避免硬编码配置。
public class SapBw4DaoFactory extends DaoFactory {
// 存储“实体类→BW4账号”的映射(线程安全)
private Map<Class<?>, Bw4Account> accountMap = new ConcurrentHashMap<>();
// 构造方法:指定Dao实现类为SapBw4Dao
public SapBw4DaoFactory() {
super(SapBw4Dao.class);
}
// 外部调用:添加实体类对应的BW4认证信息
public void addAccount(Class<?> entity, Bw4Account bw4Account) {
accountMap.put(entity, bw4Account);
}
// 重写:创建SapBw4Dao实例(自动注入认证信息)
@Override
public <T> BaseDao<T, ?> createDao(Class<T> entityCls) {
Bw4Account account = accountMap.get(entityCls);
if (account == null) {
throw new RRException("实体类[" + entityCls.getName() + "]未配置BW4认证信息");
}
// 传入实体类、OData地址、用户名、密码,创建Dao实例
return new SapBw4Dao<>(entityCls, account.getPath(), account.getUserName(), account.getPassword());
}
}
其中Bw4Account为简单的实体类,用于封装BW4的认证信息(路径、用户名、密码),此处省略代码。
3. 实现构建层:SapBw4QueryBuilder
将Lambda表达式转化为SAP BW4 OData接口支持的$filter查询语句(如KURST eq 'E' and FCURR eq 'CNY')。
public class SapBw4QueryBuilder {
// 缓存“实体类→字段映射”(避免重复反射)
static ConcurrentHashMap<Class<?>, Map<String, String>> entityMap = new ConcurrentHashMap<>();
// 核心方法:生成OData的$filter语句
public static String build(Class<?> entityCls, QueryInfo queryInfo) {
StringBuilder sb = new StringBuilder();
// 1. 处理AND条件(拼接多个条件为“a and b and c”)
String andConditions = queryInfo.getAnd().stream()
.map(it -> buildFilterItem(entityCls, it))
.filter(Objects::nonNull)
.collect(Collectors.joining(" and "));
sb.append(andConditions);
// 2. OR条件(按需实现,本文暂不支持)
if (CollUtil.isNotEmpty(queryInfo.getOr())) {
throw new UnsupportedOperationException("BW4 OData暂未实现OR查询");
}
return sb.toString();
}
// 构建单个条件(如“KURST eq 'E'”)
private static String buildFilterItem(Class<?> entityCls, QueryItem queryItem) {
// 1. 将实体类字段名转为BW4 OData字段名(如getType()→KURST)
String odataField = buildField(entityCls, queryItem.getField());
// 2. 处理运算符(eq/ne/gt等,适配OData语法)
for (Map.Entry<WhereOperation, Object> entry : queryItem.getVal().entrySet()) {
switch (entry.getKey()) {
case eq: return StrUtil.format("{} eq '{}'", odataField, entry.getValue());
case ne: return StrUtil.format("{} ne '{}'", odataField, entry.getValue());
case gt: return StrUtil.format("{} gt '{}'", odataField, entry.getValue());
case lt: return StrUtil.format("{} lt '{}'", odataField, entry.getValue());
case gte: return StrUtil.format("{} ge '{}'", odataField, entry.getValue());
case lte: return StrUtil.format("{} le '{}'", odataField, entry.getValue());
default: throw new UnsupportedOperationException("不支持的运算符:" + entry.getKey());
}
}
return null;
}
// 映射实体类字段到OData字段(基于@JSONField注解)
public static String buildField(Class<?> entityCls, FieldInfo fieldInfo) {
Map<String, String> fieldMap = entityMap.computeIfAbsent(entityCls, k -> {
Map<String, String> map = new ConcurrentHashMap<>();
// 反射获取实体类字段,解析@JSONField注解(如@JSONField(name="KURST"))
Field[] fields = ReflectUtil.getFields(entityCls);
for (Field field : fields) {
JSONField jsonField = AnnotationUtil.getAnnotation(field, JSONField.class);
if (jsonField != null && StrUtil.isNotBlank(jsonField.name())) {
map.put(field.getName(), jsonField.name());
} else {
map.put(field.getName(), field.getName()); // 无注解则用字段名本身
}
}
return map;
});
// 返回OData字段名(支持一级字段,暂不支持嵌套)
String odataField = fieldMap.getOrDefault(fieldInfo.getField(), StrUtil.toUnderlineCase(fieldInfo.getField()));
if (fieldInfo.getSubList().size() > 0) {
throw new LambdaQueryException("BW4 OData暂不支持嵌套字段查询");
}
return odataField;
}
}
三、实战使用:3步实现BW4数据查询
完成上述3个类的适配后,只需3步即可通过Lambda表达式查询SAP BW4数据,代码简洁且直观。
1. 定义查询实体类
通过@JSONField注解映射BW4 OData的字段名,@DaoSelect指定使用SapBw4Dao。
@NoArgsConstructor
@Data
@DaoSelect(daoCls = SapBw4Dao.class) // 指定该实体类使用的Dao
public class BwRmbUsd {
/** 汇率类型(BW4字段:KURST) */
@JSONField(name = "KURST")
private String type;
/** 源货币(BW4字段:FCURR) */
@JSONField(name = "FCURR")
private String source;
/** 目标货币(BW4字段:TCURR) */
@JSONField(name = "TCURR")
private String target;
/** 有效期(BW4字段:GDATU) */
@JSONField(name = "GDATU")
private String expires;
/** 汇率(BW4字段:UKURS) */
@JSONField(name = "UKURS")
private String rate;
// 其他字段...
}
2. 配置BW4认证信息
在项目初始化时,通过SapBw4DaoFactory注入实体类对应的BW4 OData地址和认证信息(避免硬编码)。
@Component
public class Bw4Config {
@Autowired
@Lazy // 延迟加载,避免循环依赖
private SapBw4DaoFactory sapBw4DaoFactory;
// 项目启动时初始化认证信息
@PostConstruct
private void initBw4Account() {
// 1. 构建BW4认证信息(实际项目中建议从配置文件读取)
Bw4Account rmbUsdAccount = new Bw4Account();
rmbUsdAccount.setPath("https://xxx.sap.com/odata/bw/rmb_usd"); // BW4 OData地址
rmbUsdAccount.setUserName("bw_user"); // 用户名
rmbUsdAccount.setPassword("bw_pwd"); // 密码
// 2. 绑定“实体类→认证信息”
sapBw4DaoFactory.addAccount(BwRmbUsd.class, rmbUsdAccount);
}
}
3. 执行Lambda查询
通过JQuerys.lambdaQuery()方法,结合Lambda表达式编写查询条件,一行代码即可获取结果,无需关注HTTP请求细节。
示例1:查询所有数据
// 查询BwRmbUsd的所有数据
List<BwRmbUsd> allList = JQuerys.lambdaQuery(BwRmbUsd.class).list();
示例2:多条件精准查询
// 查询“汇率类型=E、源货币=CNY、目标货币=USD”的数据
List<BwRmbUsd> rateList = JQuerys.lambdaQuery(BwRmbUsd.class)
.eq(BwRmbUsd::getType, "E") // 汇率类型=E
.eq(BwRmbUsd::getSource, "CNY") // 源货币=CNY
.eq(BwRmbUsd::getTarget, "USD") // 目标货币=USD
.list(); // 执行查询并返回列表
示例3:分页查询
// 分页查询:第1页,每页10条数据
IJPage<BwRmbUsd> page = JQuerys.lambdaQuery(BwRmbUsd.class)
.eq(BwRmbUsd::getType, "E")
.page(new IJPage<>(1, 10)); // 页码从1开始,每页10条
List<BwRmbUsd> pageData = page.getData(); // 分页数据
long total = page.getTotal(); // 总条数(需BW4支持$count,本文暂未实现)
四、快捷性核心体现
相比传统的非JDBC查询开发,Lambda查询器的快捷性主要体现在3个方面:
- 最少代码扩展:新增数据源仅需实现3个核心类,无需重复编写HTTP请求、参数拼接、数据解析逻辑;
- Lambda语法直观:查询条件通过Lambda表达式编写(如
BwRmbUsd::getType),避免字符串硬编码(如"KURST eq 'E'"),降低拼写错误风险; - 统一查询风格:无论适配OData、REST API还是其他协议,查询调用方式完全一致(如
lambdaQuery().eq().list()),降低跨数据源开发的学习成本。
五、总结与扩展
本文以SAP BW4 OData接口为例,展示了Lambda查询器如何快速实现非JDBC数据源的通用查询能力。其核心优势在于“解耦数据源适配与查询逻辑”,通过抽象封装让开发者聚焦业务查询条件,而非底层技术细节。
后续可基于该框架扩展更多数据源,如:
- 适配Elasticsearch:实现
EsDao、EsDaoFactory、EsQueryBuilder; - 适配REST API:实现
RestDao,支持自定义请求头、参数格式; - 扩展查询能力:增加
like、in、orderBy等条件支持。
通过Lambda查询器,可大幅提升非JDBC数据源的开发效率,让“一行代码查数据”成为常态。
2233

被折叠的 条评论
为什么被折叠?



