JFinal 读取 H2 数据库长文本乱码问题的终极解决方案

📅 2026-05-29 📁 技术分享 👁 31 次阅读

问题背景

在使用 JFinal + H2 开发个人博客Oily Blog系统时,遇到了一个棘手的问题:文章内容在保存后,前端显示不完整,出现乱码或内容被截断的现象。

问题复现

现象

  • 数据库中的 CONTENT 字段完整存储(长度正确)
  • 控制台输出长度正确,但内容显示不完整
  • 前端页面显示的内容被截断

问题代码

// 直接使用 JFinal 的 Model 读取
Article article = Article.dao.findFirst("SELECT * FROM ARTICLE WHERE SLUG = ?", slug);
String content = article.getStr("CONTENT");  // 这里读取长文本的内容末尾不能正常显示

解决方案

方案一:重写 Model 的 getStr 方法

AppConfig 中保存 DruidPlugin 实例:

public class AppConfig extends JFinalConfig {
    private static DruidPlugin dp;
    
    @Override
    public void configPlugin(Plugins me) {
        dp = new DruidPlugin(jdbcUrl, user, password);
        // ... 其他配置
    }
    
    public static DruidPlugin getDruidPlugin() {
        return dp;
    }
}

Article 模型中重写 getStr 方法:

@Override
public String getStr(String attr) {
    if ("CONTENT".equals(attr)) {
        try (Connection conn = AppConfig.getDruidPlugin().getDataSource().getConnection();
             PreparedStatement pst = conn.prepareStatement("SELECT CONTENT FROM ARTICLE WHERE ID = ?")) {
            pst.setInt(1, this.getInt("ID"));
            ResultSet rs = pst.executeQuery();
            if (rs.next()) {
                return rs.getString("CONTENT");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
    return super.getStr(attr);
}

方案二:使用 Db.execute() 读取(推荐)

这是最简洁优雅的解决方案:

public void index() {
    String slug = getPara(0);
    
    // 查询基本信息(不包含 CONTENT)
    Article article = Article.dao.findFirst(
        "SELECT ID, TITLE, SLUG FROM ARTICLE WHERE SLUG = ? AND STATUS = 1", slug
    );
    
    if (article == null) {
        redirect("/");
        return;
    }
    
    // 单独读取 CONTENT 字段
    String sql = "SELECT CONTENT FROM ARTICLE WHERE ID = " + article.getInt("ID");
    String content = (String)Db.execute(conn -> {
        PreparedStatement ps = conn.prepareStatement(sql);
        ResultSet rs = ps.executeQuery();
        return rs.next() ? rs.getString("CONTENT") : null;
    });
    
    // 处理文章内容
    if (content != null && !content.trim().isEmpty()) {
        String html = MarkdownUtil.toHtml(content);
        article.set("CONTENT_HTML", html);
    }
    
    setAttr("article", article);
    render("article.html");
}

方案三:封装工具方法

public class ArticleUtil {
    public static String getContentById(int id) {
        String sql = "SELECT CONTENT FROM ARTICLE WHERE ID = " + id;
        return (String)Db.execute(conn -> {
            PreparedStatement ps = conn.prepareStatement(sql);
            ResultSet rs = ps.executeQuery();
            return rs.next() ? rs.getString("CONTENT") : null;
        });
    }
}

方案对比

方案 优点 缺点 推荐度
重写 getStr 一次修改,全局生效 需要修改 Model ⭐⭐⭐⭐
Db.execute() 简洁、灵活、无侵入 比getStr复杂 ⭐⭐⭐⭐⭐
封装工具类 复用性好 需要额外维护 ⭐⭐⭐⭐

验证方法

// 验证读取是否完整
String content = (String)Db.execute(conn -> {
    PreparedStatement ps = conn.prepareStatement("SELECT CONTENT FROM ARTICLE WHERE ID = 1");
    ResultSet rs = ps.executeQuery();
    return rs.next() ? rs.getString("CONTENT") : null;
});

System.out.println("长度:" + content.length());
System.out.println("结尾:" + content.substring(Math.max(0, content.length() - 100)));

注意

  • 使用JFinal 框架,其他数据库(MySQL、PostgreSQL、SQLite)是否存在此类问题?期待验证。