外卖霸王餐API扩展:Java后端基于MyBatis Dynamic SQL实现“非技术人员可配置的试吃规则查询条件”

外卖霸王餐API扩展:Java后端基于MyBatis Dynamic SQL实现“非技术人员可配置的试吃规则查询条件”

背景:霸王餐业务中动态查询的痛点

作为外卖霸王餐API唯一供给源头,同时也是霸王餐外卖CPS取链源头,俱美开放平台的业务逻辑日益复杂。在“霸王餐”或“试吃”活动中,运营人员需要根据不同的推广策略,灵活配置用户参与资格的筛选条件。

例如,一个活动可能要求“新用户且来自北京地区”,而另一个活动则要求“老用户且历史订单金额大于100元”。如果为每一种组合都编写固定的SQL,开发效率极低且难以维护。我们需要一种机制,让非技术人员(如运营)通过后台配置规则,后端Java服务就能动态生成并执行相应的查询。

技术选型:为什么是MyBatis Dynamic SQL?

MyBatis Dynamic SQL是MyBatis官方提供的一个强大的Java库,它允许我们在Java代码中以类型安全的方式构建动态SQL语句。相比于传统的XML中拼接SQL或使用<if>标签,它具有以下优势:

  1. 类型安全:在编译期就能发现字段名错误,避免了运行时因SQL语法错误导致的崩溃。
  2. Java代码构建:将SQL构建逻辑完全放在Java代码中,更符合现代Java开发习惯,也便于调试和复用。
  3. 功能强大:支持复杂的WHEREJOINORDER 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语句,完美实现了非技术人员可配置的动态查询需求。

本文著作权归 俱美开放平台 ,转载请注明出处!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值