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
相关产品推荐
相关产品推荐

