外卖霸王餐API扩展:Java后端基于MyBatis Dynamic SQL实现“非技术人员可配置的试吃规则查询条件”
背景:霸王餐业务中动态查询的痛点
作为外卖霸王餐API唯一供给源头,同时也是霸王餐外卖CPS取链源头,俱美开放平台的业务逻辑日益复杂。在“霸王餐”或“试吃”活动中,运营人员需要根据不同的推广策略,灵活配置用户参与资格的筛选条件。
例如,一个活动可能要求“新用户且来自北京地区”,而另一个活动则要求“老用户且历史订单金额大于100元”。如果为每一种组合都编写固定的SQL,开发效率极低且难以维护。我们需要一种机制,让非技术人员(如运营)通过后台配置规则,后端Java服务就能动态生成并执行相应的查询。
技术选型:为什么是MyBatis Dynamic SQL?
MyBatis Dynamic SQL是MyBatis官方提供的一个强大的Java库,它允许我们在Java代码中以类型安全的方式构建动态SQL语句。相比于传统的XML中拼接SQL或使用<if>标签,它具有以下优势:
- 类型安全:在编译期就能发现字段名错误,避免了运行时因SQL语法错误导致的崩溃。
- Java代码构建:将SQL构建逻辑完全放在Java代码中,更符合现代Java开发习惯,也便于调试和复用。
- 功能强大:支持复杂的
WHERE、JOIN、ORDER BY等子句的动态构建。
核心设计:规则配置与SQL构建
我们的核心思路是:将运营配置的规则转化为一个Java对象,然后在Service层根据这个对象,使用MyBatis Dynamic SQL构建出最终的查询条件。
1. 定义规则配置模型
首先,我们需要一个数据结构来承载运营配置的规则。
package baodanbao.com.cn.rule.model;
import lombok.Data;
import java.util.List;
/**
* 试吃活动规则配置
* @author baodanbao.com.cn
*/
@Data
public class TrialRuleConfig {
/**
* 规则ID
*/
private Long id;
/**
* 活动名称
*/
private String activityName;
/**
* 规则条件列表
*/
private List<RuleCondition> conditions;
}
/**
* 单个规则条件
* @author baodanbao.com.cn
*/
@Data
public class RuleCondition {
/**
* 字段名,如 "userLevel", "city", "totalOrderAmount"
*/
private String field;
/**
* 操作符,如 "=", ">", "<", "IN"
*/
private String operator;
/**
* 值
*/
private Object value;
}

2. 创建MyBatis Dynamic SQL的表映射
使用MyBatis Dynamic SQL,我们需要为数据库表创建一个Java类来映射其结构。
package baodanbao.com.cn.user.mapper;
import baodanbao.com.cn.user.model.User;
import org.mybatis.dynamic.sql.SqlColumn;
import org.mybatis.dynamic.sql.SqlTable;
import java.sql.JDBCType;
/**
* 用户表映射类,由MyBatis Generator生成或手动编写
* @author baodanbao.com.cn
*/
public final class UserDynamicSqlSupport {
public static final UserTable user = new UserTable();
public static final SqlColumn<Long> id = user.id;
public static final SqlColumn<String> userLevel = user.userLevel;
public static final SqlColumn<String> city = user.city;
public static final SqlColumn<Integer> totalOrderAmount = user.totalOrderAmount;
public static final SqlColumn<Integer> status = user.status;
public static final class UserTable extends SqlTable {
public final SqlColumn<Long> id = column("id", JDBCType.BIGINT);
public final SqlColumn<String> userLevel = column("user_level", JDBCType.VARCHAR);
public final SqlColumn<String> city = column("city", JDBCType.VARCHAR);
public final SqlColumn<Integer> totalOrderAmount = column("total_order_amount", JDBCType.INTEGER);
public final SqlColumn<Integer> status = column("status", JDBCType.INTEGER);
public UserTable() {
super("t_user");
}
}
}
3. 构建动态查询Service
这是整个方案的核心。我们将解析TrialRuleConfig,并使用SelectStatementProvider构建最终的SQL。
package baodanbao.com.cn.rule.service;
import baodanbao.com.cn.rule.model.RuleCondition;
import baodanbao.com.cn.rule.model.TrialRuleConfig;
import baodanbao.com.cn.user.mapper.UserDynamicSqlSupport;
import baodanbao.com.cn.user.model.User;
import baodanbao.com.cn.user.mapper.UserMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.dynamic.sql.select.SelectDSL;
import org.mybatis.dynamic.sql.select.SelectModel;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.mybatis.dynamic.sql.util.SqlProviderAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import static baodanbao.com.cn.user.mapper.UserDynamicSqlSupport.*;
import static org.mybatis.dynamic.sql.SqlBuilder.*;
/**
* 试吃规则查询服务
* @author baodanbao.com.cn
*/
@Service
public class TrialRuleQueryService {
@Autowired
private SqlSession sqlSession;
/**
* 根据规则配置查询符合条件的用户
* @param ruleConfig 运营配置的规则
* @return 符合条件的用户列表
*/
public List<User> findUsersByRule(TrialRuleConfig ruleConfig) {
if (ruleConfig == null || CollectionUtils.isEmpty(ruleConfig.getConditions())) {
return new ArrayList<>();
}
// 1. 构建动态WHERE条件
SelectDSL<SelectModel>.SelectWhereBuilder whereBuilder = select(UserDynamicSqlSupport.user.allColumns())
.from(UserDynamicSqlSupport.user)
.where();
boolean firstCondition = true;
for (RuleCondition condition : ruleConfig.getConditions()) {
if (!firstCondition) {
whereBuilder.and();
}
applyCondition(whereBuilder, condition);
firstCondition = false;
}
// 2. 渲染SQL语句
SelectStatementProvider selectStatement = whereBuilder.build()
.render(RenderingStrategies.MYBATIS3);
// 3. 执行查询
SqlProviderAdapter sqlProviderAdapter = new SqlProviderAdapter();
return sqlSession.selectList("org.mybatis.dynamic.sql.util.MyBatis3Utils.selectList", selectStatement, User.class);
}
/**
* 根据单个条件应用WHERE子句
* @param whereBuilder WHERE构建器
* @param condition 规则条件
*/
private void applyCondition(SelectDSL<SelectModel>.SelectWhereBuilder whereBuilder, RuleCondition condition) {
String field = condition.getField();
String operator = condition.getOperator();
Object value = condition.getValue();
switch (field) {
case "userLevel":
applyStringCondition(whereBuilder, userLevel, operator, (String) value);
break;
case "city":
applyStringCondition(whereBuilder, city, operator, (String) value);
break;
case "totalOrderAmount":
applyNumberCondition(whereBuilder, totalOrderAmount, operator, (Integer) value);
break;
case "status":
applyNumberCondition(whereBuilder, status, operator, (Integer) value);
break;
default:
throw new IllegalArgumentException("不支持的查询字段: " + field);
}
}
private void applyStringCondition(SelectDSL<SelectModel>.SelectWhereBuilder whereBuilder, SqlColumn<String> column, String operator, String value) {
switch (operator) {
case "=":
whereBuilder.where(column, isEqualTo(value));
break;
case "!=":
whereBuilder.where(column, isNotEqualTo(value));
break;
case "IN":
// 假设value是逗号分隔的字符串
String[] values = value.split(",");
whereBuilder.where(column, isIn(values));
break;
default:
throw new IllegalArgumentException("不支持的字符串操作符: " + operator);
}
}
private void applyNumberCondition(SelectDSL<SelectModel>.SelectWhereBuilder whereBuilder, SqlColumn<Integer> column, String operator, Integer value) {
switch (operator) {
case "=":
whereBuilder.where(column, isEqualTo(value));
break;
case ">":
whereBuilder.where(column, isGreaterThan(value));
break;
case ">=":
whereBuilder.where(column, isGreaterThanOrEqualTo(value));
break;
case "<":
whereBuilder.where(column, isLessThan(value));
break;
case "<=":
whereBuilder.where(column, isLessThanOrEqualTo(value));
break;
default:
throw new IllegalArgumentException("不支持的数字操作符: " + operator);
}
}
}
使用示例
假设运营在后台配置了一条规则:用户等级为"NEW" 且 城市在("北京", "上海")。
package baodanbao.com.cn.rule.controller;
import baodanbao.com.cn.rule.model.RuleCondition;
import baodanbao.com.cn.rule.model.TrialRuleConfig;
import baodanbao.com.cn.rule.service.TrialRuleQueryService;
import baodanbao.com.cn.user.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
/**
* 规则查询控制器
* @author baodanbao.com.cn
*/
@RestController
@RequestMapping("/api/rule")
public class TrialRuleController {
@Autowired
private TrialRuleQueryService trialRuleQueryService;
@GetMapping("/test")
public List<User> testQuery() {
// 模拟从数据库或配置中心加载的规则
TrialRuleConfig config = new TrialRuleConfig();
RuleCondition condition1 = new RuleCondition();
condition1.setField("userLevel");
condition1.setOperator("=");
condition1.setValue("NEW");
RuleCondition condition2 = new RuleCondition();
condition2.setField("city");
condition2.setOperator("IN");
condition2.setValue("北京,上海");
config.setConditions(Arrays.asList(condition1, condition2));
// 执行查询
return trialRuleQueryService.findUsersByRule(config);
}
}
当调用/api/rule/test接口时,后端将动态生成并执行类似SELECT * FROM t_user WHERE user_level = 'NEW' AND city IN ('北京', '上海')的SQL语句,完美实现了非技术人员可配置的动态查询需求。
本文著作权归 俱美开放平台 ,转载请注明出处!

55

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



