You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache FreeMarker自定义模板使用求助:ResultSet数据渲染问题

在FreeMarker中渲染JDBC ResultSet的最佳实践

嘿,你找对方向了!用自定义模板模型来适配ResultSet确实是解决这个问题的核心思路,我来一步步帮你把这套流程跑通,包括怎么把你的自定义模型和FreeMarker模板关联起来:

1. 先把你的自定义ResultSet模板模型补全

你提到已经写了极简的自定义类,那大概率是要实现FreeMarker的TemplateSequenceModel接口(因为ResultSet本质是行的序列,适合用序列模型来处理)。这里给你一个实用的实现示例,兼顾了行数据的读取和索引访问:

import freemarker.template.*;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

public class ResultSetTemplateModel implements TemplateSequenceModel {
    private final ResultSet resultSet;
    private final ResultSetMetaData metaData;

    public ResultSetTemplateModel(ResultSet resultSet) throws SQLException {
        this.resultSet = resultSet;
        this.metaData = resultSet.getMetaData();
    }

    @Override
    public TemplateModel get(int index) throws TemplateModelException {
        try {
            // ResultSet的行索引从1开始,所以要+1转换
            if (resultSet.absolute(index + 1)) {
                Map<String, Object> rowData = new HashMap<>();
                int columnCount = metaData.getColumnCount();
                for (int i = 1; i <= columnCount; i++) {
                    // 用列名作为key,把当前行的列值存入map
                    rowData.put(metaData.getColumnName(i), resultSet.getObject(i));
                }
                // 把map转成FreeMarker能识别的Hash模型
                return new SimpleHash(rowData, Configuration.getDefaultConfiguration());
            }
            return null;
        } catch (SQLException e) {
            throw new TemplateModelException("读取ResultSet行数据失败", e);
        }
    }

    @Override
    public int size() throws TemplateModelException {
        try {
            // 注意:调用last()会移动游标,之后要移回初始位置避免影响后续读取
            resultSet.last();
            int totalRows = resultSet.getRow();
            resultSet.beforeFirst();
            return totalRows;
        } catch (SQLException e) {
            throw new TemplateModelException("获取ResultSet行数失败", e);
        }
    }
}

2. 关键步骤:把自定义模型注入FreeMarker上下文

你困惑的“指定使用自定义模板”其实是指把包装好的ResultSet模型传递给FreeMarker的数据上下文,让模板能访问到它。整个流程分为这几步:

示例代码(完整流程)

import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;

public class FreeMarkerResultSetDemo {
    public static void main(String[] args) throws Exception {
        // 1. 初始化FreeMarker配置(你已经掌握模板加载,这里快速带过)
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
        cfg.setClassForTemplateLoading(FreeMarkerResultSetDemo.class, "/templates"); // 模板所在路径
        cfg.setDefaultEncoding("UTF-8");

        // 2. 获取数据库ResultSet(替换成你的数据库连接逻辑)
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/your_db", "username", "password");
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("SELECT id, name, email FROM users");

        try {
            // 3. 用你的自定义模型包装ResultSet
            ResultSetTemplateModel rsTemplateModel = new ResultSetTemplateModel(rs);

            // 4. 构建FreeMarker的根数据模型
            Map<String, Object> rootModel = new HashMap<>();
            rootModel.put("userList", rsTemplateModel); // 给模型起个名字,模板里就用这个名字访问

            // 5. 加载你的模板文件
            Template template = cfg.getTemplate("user_table.ftl");

            // 6. 渲染模板(这里输出到控制台,你也可以输出到文件或HTTP响应流)
            Writer output = new OutputStreamWriter(System.out);
            template.process(rootModel, output);
            output.flush();
        } finally {
            // 务必关闭数据库资源,避免泄漏
            rs.close();
            stmt.close();
            conn.close();
        }
    }
}

3. 对应的模板写法(示例:user_table.ftl)

现在模板里就可以像遍历普通列表一样处理ResultSet的数据了,完全符合FreeMarker的语法习惯:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>用户列表</title>
</head>
<body>
    <h2>用户信息表</h2>
    <table border="1" cellpadding="8">
        <tr>
            <th>用户ID</th>
            <th>姓名</th>
            <th>邮箱</th>
        </tr>
        <!-- 遍历我们注入的userList模型 -->
        <#list userList as user>
        <tr>
            <td>${user.id}</td>
            <td>${user.name}</td>
            <td>${user.email}</td>
        </tr>
        </#list>
    </table>
</body>
</html>

额外的优化建议

  • 优先缓存ResultSet到内存:直接操作ResultSet会保持数据库连接处于打开状态,容易导致资源泄漏。更稳妥的方式是先把ResultSet的数据读取到List<Map<String, Object>>中,再用FreeMarker的SimpleSequence包装,这样可以尽早释放数据库连接:
// 提前把ResultSet数据读入内存
List<Map<String, Object>> userData = new ArrayList<>();
ResultSetMetaData meta = rs.getMetaData();
int colCount = meta.getColumnCount();
while (rs.next()) {
    Map<String, Object> row = new HashMap<>();
    for (int i = 1; i <= colCount; i++) {
        row.put(meta.getColumnName(i), rs.getObject(i));
    }
    userData.add(row);
}
// 用FreeMarker内置的序列模型包装
TemplateModel sequenceModel = new SimpleSequence(userData, cfg);
rootModel.put("userList", sequenceModel);
  • 处理特殊类型:如果ResultSet里有Date、Blob这类特殊类型,你可以在包装时自定义转换逻辑,或者在模板中用?string("yyyy-MM-dd")这类指令格式化。

内容的提问来源于stack exchange,提问作者OraFan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:55:33