# elt **Repository Path**: dufafei/elt ## Basic Information - **Project Name**: elt - **Description**: 数据集成子项目 - 字符串模版引擎 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-02 - **Last Updated**: 2026-07-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # **字符串模板引擎** ## 概述 本项目为数据集成子项目,专为 SQL处理插件等 设计,可根据需求进行扩展。 基于 TagNode 节点模型与 EL 表达式引擎,实现字符串的动态拼接。 ## 功能 ### 1. 支持SQL解析 通过解析文本得到 静态SQL源 / 动态SQL源 。 #### 1.1 静态SQL源 不包含 ${} 和 动态标签 的静态SQL。 相比动态SQL: * 1. 只需要解析一次 * 2. 可以使用预编译 因此getBoundSql每次获取的BoundSql对象都是相同的。 ```java public class StaticSqlSource extends AbstractSqlSource { private final BoundSql boundSql; public StaticSqlSource(TextNode node) { PlaceholderParser pl = new PlaceholderParser("#{", "}", placeholder -> new Placeholder(placeholder, "?")); String sql = pl.parse(node.getText()); List mappings = pl.getPlaceholders().stream().map(Placeholder::getPlaceholder).collect(Collectors.toList()); this.boundSql = new BoundSql(sql, mappings); } @Override public BoundSql getBoundSql(Schema schema, Row row) { return this.boundSql; } } ``` #### 1.2 动态SQL源 包含 ${} 或 动态标签 的动态sql。 因此getBoundSql每次获取的BoundSql对象都是重新生成的。 ```java public class DynamicSqlSource extends AbstractSqlSource { private final CompositeNode root; public DynamicSqlSource(CompositeNode root) { this.root = root; } public CompositeNode getCompositeELTNode() { return this.root; } @Override public BoundSql getBoundSql(Schema schema, Row row) { StringBuilder builder = new StringBuilder(); this.root.eval(schema, row, builder); PlaceholderParser pl = new PlaceholderParser("#{", "}", placeholder -> new Placeholder(placeholder, "?")); String sql = pl.parse(builder.toString()); List mappings = pl.getPlaceholders().stream().map(Placeholder::getPlaceholder).collect(Collectors.toList()); return new BoundSql(sql, mappings); } } ``` ### 2. 支持表达式计算 通过引入函数执行引擎实现。 ### 3. 支持表达式缓存 支持以SQL模版为维度的表达式的缓存。 在创建SQL源的时候,会将该SQL源 与 对应解析的Tag节点完成绑定。 ```java /** * 获取 SQL 源对象。 * 如果 SQL 中不包含任何变量,返回静态 SQL 源;否则返回动态 SQL 源。 */ static SqlSource getSqlSource(Schema schema, String sql) { CompositeNode root = ELTParser.parse(sql); List children = root.getChildren(); final AtomicBoolean hasVariables = new AtomicBoolean(false); PlaceholderParser pl = new PlaceholderParser("${", "}", placeholder -> { hasVariables.set(true); int index = schema.indexOf(placeholder); return new Placeholder(placeholder, "${" + index + "}"); }); // 将sql模版中的变量名称替换其下标位置 replace(root, pl); Node firstNode = children.get(0); if (children.size() == 1 && firstNode instanceof TextNode && !hasVariables.get()) { return new StaticSqlSource((TextNode) firstNode); } DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(root); // 将该动态数据源和解析生成的Tag节点实现绑定 init(root, dynamicSqlSource, schema); return dynamicSqlSource; } static void replace(Node node, PlaceholderParser pl) { if (node == null) { return; } if (node instanceof TextNode) { TextNode textNode = (TextNode) node; String replaceText = pl.parse(textNode.getText()); textNode.setText(replaceText); } List children = node.getChildren(); for (Node child : children) { replace(child, pl); } } static void init(Node node, ELCache cache, Schema schema) { if (node == null) { return; } if (node instanceof TagNode) { // 初始化缓存一次表达式 TagNode tagNode = (TagNode) node; tagNode.setCache(cache); tagNode.init(schema); } List children = node.getChildren(); for (Node child : children) { init(child, cache, schema); } } ``` 在标签解析器里面,根据key取出缓存的表达式并计算。 ```java public class If extends AbstractTag { @Override public String getId() { return "if"; } @Override public boolean eval(ELCache cache, Map attributes, Schema schema, Row row, StringBuilder builder, List children) { String test = attributes.get("test"); ELInterface el = cache.getEL(test); if (el.eval(row) == BooleanValue.TRUE) { for (Node child: children) { child.eval(schema, row, builder); } } return true; } @Override public List getCacheKeys() { return Collections.singletonList("test"); } } ``` ### 4. 内置Mybaits语法 ## 使用 示例如下: ```java /** * 测试静态 SQL 与动态 SQL 的自动识别与解析。 */ @Test public void testStaticSql() { // 1. 准备 Schema 和 Row Schema schema = new Schema(); LongField field1 = new LongField(); field1.setName("id"); schema.addField(field1); Row row = new Row(1); row.setValue(0, LongValue.fromInt(1)); // 2. 测试纯静态 SQL String staticTpl = "SELECT * FROM user"; SqlSource staticSqlSource = SqlSource.getSqlSource(schema, staticTpl); System.out.println(staticSqlSource.getClass().getSimpleName()); // 预期: StaticSqlSource // 3. 测试包含 #{} 的动态 SQL String dynamicTpl1 = "SELECT * FROM user WHERE id = #{id}"; SqlSource dynamicSqlSource1 = SqlSource.getSqlSource(schema, dynamicTpl1); System.out.println(dynamicSqlSource1.getClass().getSimpleName()); // 预期: StaticSqlSource // 4. 测试包含 ${} 的动态 SQL String dynamicTpl2 = "SELECT * FROM user WHERE id = ${id}"; SqlSource dynamicSqlSource2 = SqlSource.getSqlSource(schema, dynamicTpl2); System.out.println(dynamicSqlSource2.getClass().getSimpleName()); // 预期: DynamicSqlSource } /** * 测试 标签的条件判断。 */ @Test public void testIf() { // 准备 Schema 和 Row Schema schema = new Schema(); LongField field1 = new LongField(); field1.setName("id"); StringField field2 = new StringField(); field2.setName("name"); schema.addField(field1); schema.addField(field2); Row row = new Row(2); row.setValue(0, LongValue.fromInt(1)); row.setValue(1, StringValue.fromString("dff")); // 执行解析 String tpl = "SELECT * FROM users WHERE 1=1 AND name = ${name}"; SqlSource sqlSource = SqlSource.getSqlSource(schema, tpl); System.out.println(sqlSource.getBoundSql(schema, row).getSql()); // 预期输出: SELECT * FROM users WHERE 1=1 AND name = dff } /** * 测试 标签的自定义前后缀及覆盖逻辑。 */ @Test public void testTrim() { // 准备 Schema 和 Row Schema schema = new Schema(); StringField field1 = new StringField(); field1.setName("id"); StringField field2 = new StringField(); field2.setName("name"); schema.addField(field1); schema.addField(field2); Row row = new Row(2); row.setValue(0, LongValue.fromInt(1)); row.setValue(1, StringValue.fromString("dff")); // 执行解析 String tpl = "SELECT * FROM users AND id = #{id} OR name = #{name}"; SqlSource sqlSource = SqlSource.getSqlSource(schema, tpl); System.out.println(sqlSource.getBoundSql(schema, row).getSql()); // 预期输出: SELECT * FROM users WHERE id = ? OR name = ? // 验证点:成功加上了 WHERE,并剔除了开头的 AND } /** * 测试 标签在 UPDATE 语句中的逗号处理。 */ @Test public void testSet() { // 准备 Schema 和 Row Schema schema = new Schema(); StringField field1 = new StringField(); field1.setName("id"); StringField field2 = new StringField(); field2.setName("name"); schema.addField(field1); schema.addField(field2); Row row = new Row(2); row.setValue(0, LongValue.fromInt(1)); row.setValue(1, StringValue.fromString("dff")); // 执行解析 String tpl = "UPDATE users id = #{id},name = #{name}, WHERE id = #{id}"; SqlSource sqlSource = SqlSource.getSqlSource(schema, tpl); System.out.println(sqlSource.getBoundSql(schema, row).getSql()); // 预期输出: UPDATE users SET id = ?,name = ? WHERE id = ? } /** * 测试 标签自动添加 WHERE 及去除多余的 AND/OR。 */ @Test public void testWhere() { // 准备 Schema 和 Row Schema schema = new Schema(); StringField field1 = new StringField(); field1.setName("id"); StringField field2 = new StringField(); field2.setName("name"); schema.addField(field1); schema.addField(field2); Row row = new Row(2); row.setValue(0, LongValue.fromInt(1)); row.setValue(1, StringValue.fromString("dff")); // 执行解析 String tpl = "SELECT * FROM users AND username = #{name} 18\">AND age > #{id}"; SqlSource sqlSource = SqlSource.getSqlSource(schema, tpl); System.out.println(sqlSource.getBoundSql(schema, row).getSql()); // 预期输出: SELECT * FROM users WHERE username = ? } /** * 测试 , , 标签的分支选择逻辑。 */ @Test public void testChoose() { // 准备 Schema Schema schema = new Schema(); StringField field1 = new StringField(); field1.setName("username"); StringField field2 = new StringField(); field2.setName("email"); schema.addField(field1); schema.addField(field2); // 构建多行 SQL 模板 String tpl = "SELECT * FROM users WHERE 1=1 \n" + "\n" + " \n" + " AND username = #{username}\n" + " \n" + " \n" + " AND email = #{email}\n" + " \n" + " \n" + " AND status = 'ACTIVE'\n" + " \n" + ""; SqlSource sqlSource = SqlSource.getSqlSource(schema, tpl); // 场景 1: username 不为空 Row row1 = new Row(2); row1.setValue(0, StringValue.fromString("Tom")); row1.setValue(1, StringValue.fromString("test@test.com")); System.out.println(sqlSource.getBoundSql(schema, row1).getSql()); // 预期输出: SELECT * FROM users WHERE 1=1 AND username = ? // 场景 2: username 为空,email 不为空 Row row2 = new Row(2); row2.setValue(0, null); row2.setValue(1, StringValue.fromString("test@test.com")); System.out.println(sqlSource.getBoundSql(schema, row2).getSql()); // 预期输出: SELECT * FROM users WHERE 1=1 AND email = ? // 场景 3: 均为空,走 otherwise 分支 Row row3 = new Row(2); row3.setValue(0, null); row3.setValue(1, null); System.out.println(sqlSource.getBoundSql(schema, row3).getSql()); // 预期输出: SELECT * FROM users WHERE 1=1 AND status = 'ACTIVE' } ``` ## 声明 本项目代码受版权保护。除明确授权外,**保留所有权利(All Rights Reserved)**。 - **授权范围**:本项目仅供内部学习、测试及非商业性质的个人研究使用。 - **禁止事项**:未经授权,严禁任何形式的二次开源、分发或作为衍生作品发布于公共代码托管平台。