快速开始
本指南介绍如何使用 foggy-dataset 模块进行 SQL 查询,重点讲解如何使用 FSScript 安全地构建动态 SQL。
1. 添加依赖
xml
<dependency>
<groupId>com.foggyframework</groupId>
<artifactId>foggy-dataset</artifactId>
<version>${foggy.version}</version>
</dependency>2. 使用 FSScript 构建动态 SQL(核心)
在实际业务中,查询条件往往是动态的。传统的字符串拼接方式存在 SQL 注入风险:
javascript
// ❌ 不安全的做法
let sql = `SELECT * FROM users WHERE name = '${userName}'`;
// 如果 userName = "admin' OR '1'='1",将导致 SQL 注入foggy-dataset 提供 SQL 辅助函数,通过 PreparedStatement 参数化查询 解决这个问题。
3.1 核心函数
| 函数 | 功能 | 用途 |
|---|---|---|
sqlExp(value, sql,force) | 条件参数 | 单值条件查询 |
sqlInExp(array, sql,force) | IN 查询 | 多值 IN 查询 |
toLikeStr(str) | 两端模糊 | LIKE '%value%' |
toLikeStrL(str) | 左侧模糊 | LIKE '%value' |
toLikeStrR(str) | 右侧模糊 | LIKE 'value%' |
iif(cond, true, false) | 条件表达式 | 复杂条件拼接 |
3.2 sqlExp - 条件参数
签名: sqlExp(value, sqlFragment[, force])
当 value 不为空时,将其添加到参数列表,并返回 SQL 片段;否则返回空字符串。
javascript
// user_query.fsscript
export const sql = `
SELECT id, name, email, status, create_time
FROM users
WHERE 1=1
${sqlExp(form.param.teamId, 'AND team_id = ?')}
${sqlExp(form.param.status, 'AND status = ?',true)}
${sqlExp(form.param.startTime, 'AND create_time >= ?')}
${sqlExp(form.param.endTime, 'AND create_time < ?')}
ORDER BY create_time DESC
`;执行结果:
- 如果
form.param.teamId = "T001",生成:AND team_id = ?,参数列表添加"T001" - 如果
form.param.teamId = null,生成:空字符串(条件被忽略) - 如果
form.param.status = null或'',生成:AND status = ?,参数列表添加null或''
3.3 sqlInExp - IN 查询
签名: sqlInExp(array, sqlPrefix[, force])
处理 IN 查询,自动生成多个占位符:
javascript
export const sql = `
SELECT * FROM orders
WHERE 1=1
${sqlInExp(form.param.statusList, 'AND status IN ')}
${sqlInExp(form.param.productIds, 'AND product_id IN ')}
`;执行结果:
- 如果
statusList = [10, 20, 30],生成:AND status IN (?,?,?),添加 3 个参数 - 如果
statusList = []或null,生成:空字符串
3.4 模糊查询
javascript
export const sql = `
SELECT * FROM users
WHERE 1=1
${sqlExp(toLikeStr(form.param.name), 'AND name LIKE ?')}
${sqlExp(toLikeStrR(form.param.mobile), 'AND mobile LIKE ?')}
${sqlExp(toLikeStrL(form.param.email), 'AND email LIKE ?')}
`;执行结果:
toLikeStr("张三")→"%张三%"(匹配包含"张三"的记录)toLikeStrR("138")→"138%"(匹配以"138"开头的手机号)toLikeStrL("gmail.com")→"%gmail.com"(匹配以"gmail.com"结尾的邮箱)
3.5 条件表达式 iif
签名: iif(condition, trueValue, falseValue)
用于复杂的条件拼接:
javascript
//导入构建权限的函数
import {buildAuth} from './auth-utils.fsscript';
// import {buildAuth} from '@authHelper';
export const sql = `
SELECT * FROM orders
WHERE 1=1 ${buildAuth()}
${iif(
form.param.includeDeleted,
'',
'AND deleted = 0'
)}
${iif(
form.param.channel == 'OFFLINE',
"AND (channel = 'OFFLINE' OR channel = 'INNER' OR channel IS NULL)",
sqlExp(form.param.channel, 'AND channel = ?')
)}
${iif(
form.param.priceRange?.length == 2,
sqlExp(form.param.priceRange[0], 'AND price >= ?') +
sqlExp(form.param.priceRange[1], 'AND price <= ?'),
''
)}
`;4. 完整示例
4.1 FSScript 查询脚本
javascript
// order_query.fsscript
/**
* 订单查询
* 支持:团队筛选、状态筛选、关键字搜索、日期范围、金额范围
*/
export const sql = `
SELECT
o.order_id,
o.order_no,
o.amount,
o.status,
o.create_time,
c.customer_name,
t.team_name
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.id
LEFT JOIN teams t ON o.team_id = t.id
WHERE o.deleted = 0
${sqlExp(form.param.teamId, 'AND o.team_id = ?')}
${sqlInExp(form.param.statusList, 'AND o.status IN ')}
${sqlExp(toLikeStr(form.param.orderNo), 'AND o.order_no LIKE ?')}
${sqlExp(toLikeStr(form.param.customerName), 'AND c.customer_name LIKE ?')}
${sqlExp(form.param.startTime, 'AND o.create_time >= ?')}
${sqlExp(form.param.endTime, 'AND o.create_time < ?')}
${iif(
form.param.amountRange?.length == 2,
sqlExp(form.param.amountRange[0], 'AND o.amount >= ?') +
sqlExp(form.param.amountRange[1], 'AND o.amount <= ?'),
''
)}
ORDER BY o.create_time DESC
`;4.2 Java 调用代码
java
@Service
public class OrderQueryService {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private FileFsscriptLoader fsscriptLoader;
public List<Map<String, Object>> queryOrders(Map<String, Object> params) {
// 1. 加载 FSScript
Fsscript script = fsscriptLoader.findLoadFsscript(
getClass().getResource("order_query.fsscript")
);
// 2. 创建 QueryExpEvaluator
QueryExpEvaluator evaluator = QueryExpEvaluator.newInstance(applicationContext);
// 3. 设置参数
Map<String, Object> form = new HashMap<>();
form.put("param", params);
evaluator.setVar("form", form);
// 4. 执行脚本,生成 SQL
ExpEvaluator ee = script.newInstance(applicationContext, evaluator);
script.eval(ee);
String sql = (String) ee.getExportMap().get("sql");
// 5. 获取参数列表
List<Object> args = evaluator.getArgs();
// 6. 执行查询
return jdbcTemplate.queryForList(sql, args.toArray());
}
}4.3 带分页的查询
java
public PageResult<Order> queryOrdersWithPaging(QueryParams params) {
// 加载和执行脚本...
String sql = ...;
List<Object> args = evaluator.getArgs();
// 查询总数
String countSql = "SELECT COUNT(*) FROM (" + sql + ") tmp";
Long total = jdbcTemplate.queryForObject(countSql, Long.class, args.toArray());
// 获取数据库方言,生成分页 SQL
FDialect dialect = DbUtils.getDialect(dataSource);
String pageSql = dialect.generatePagingSql(sql, params.getStart(), params.getLimit());
// 执行分页查询
List<Order> data = jdbcTemplate.query(
pageSql,
new BeanPropertyRowMapper<>(Order.class),
args.toArray()
);
return new PageResult<>(data, total, params.getStart(), params.getLimit());
}5. 工作原理
FSScript 模板字符串
↓
QueryExpEvaluator 执行
↓
SQL 辅助函数收集参数
↓
生成 SQL + 参数列表
↓
PreparedStatement 执行- FSScript 脚本执行时,使用
QueryExpEvaluator而非普通的DefaultExpEvaluator - SQL 辅助函数(如
sqlExp)将参数值添加到QueryExpEvaluator的参数列表 - 返回包含
?占位符的 SQL 片段 - 最终生成的 SQL 和参数列表传递给 JDBC PreparedStatement
6. 最佳实践
6.1 安全性
javascript
// ✅ 正确:使用 sqlExp
${sqlExp(form.param.userId, 'AND user_id = ?')}
// ❌ 错误:直接拼接变量(SQL 注入风险)
AND user_id = '${form.param.userId}'6.2 性能优化
javascript
// ✅ 使用前缀匹配(可以用到索引)
${sqlExp(toLikeStrR(form.param.mobile), 'AND mobile LIKE ?')}
// ⚠️ 左侧模糊匹配无法使用索引
${sqlExp(toLikeStrL(form.param.email), 'AND email LIKE ?')}6.3 可读性
javascript
// ✅ 提取复杂条件为变量
let hasDateRange = form.param.startDate && form.param.endDate;
let hasPriceRange = form.param.priceRange?.length == 2;
export const sql = `
SELECT * FROM products
WHERE 1=1
${iif(hasDateRange,
sqlExp(form.param.startDate, 'AND create_time >= ?') +
sqlExp(form.param.endDate, 'AND create_time <= ?'),
''
)}
${iif(hasPriceRange,
sqlExp(form.param.priceRange[0], 'AND price >= ?') +
sqlExp(form.param.priceRange[1], 'AND price <= ?'),
''
)}
`;下一步
- SQL 辅助函数详解 - 完整的函数参考
- 多数据库支持 - MySQL、PostgreSQL、SQL Server、SQLite 适配
- API 参考 - DatasetTemplate、QueryExpEvaluator API
