jfinal操作pgsql的jsonb类型(方案二)

之前那个是操作map,满足不了jsonobj或者jsonarray,修改一下:

package com.jfinal.admin.common.pgsql;

import com.jfinal.plugin.activerecord.dialect.Dialect;
import com.jfinal.plugin.activerecord.generator.MetaBuilder;
import com.jfinal.plugin.activerecord.generator.ColumnMeta;
import com.jfinal.plugin.activerecord.generator.TableMeta;

import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

public class JsonbMetaBuilder extends MetaBuilder {

    private Dialect dialect;

    public JsonbMetaBuilder(DataSource dataSource) {
        super(dataSource);
    }

    // 添加设置方言的方法
    public void setDialect(Dialect dialect) {
        this.dialect = dialect;
        super.setDialect(dialect); // 同时设置父类的方言
    }

    @Override
    protected void buildColumnMetas(TableMeta tableMeta) throws SQLException {
        // 先调用父类方法构建基础列元数据
        super.buildColumnMetas(tableMeta);

        // 特殊处理 JSONB 列
        processJsonbColumns(tableMeta);
    }

    private void processJsonbColumns(TableMeta tableMeta) throws SQLException {
        String sql = dialect.forTableBuilderDoBuild(tableMeta.name);
        try (Statement stm = conn.createStatement();
             ResultSet rs = stm.executeQuery(sql)) {

            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();

            for (int i = 1; i <= columnCount; i++) {
                String columnName = rsmd.getColumnName(i);
                String typeName = getColumnTypeNameSafe(rsmd, i);

                // 检测是否为 JSONB 列
                if (isJsonbColumn(typeName)) {
                    // 找到对应的列元数据并更新
                    for (ColumnMeta cm : tableMeta.columnMetas) {
                        if (columnName.equals(cm.name)) {
                            updateColumnMetaForJsonb(cm, rsmd, i);
                            break;
                        }
                    }
                }
            }
        }
    }

    private String getColumnTypeNameSafe(ResultSetMetaData rsmd, int columnIndex) {
        try {
            return rsmd.getColumnTypeName(columnIndex);
        } catch (SQLException e) {
            return "UNKNOWN";
        }
    }

    private boolean isJsonbColumn(String typeName) {
        if (typeName == null) return false;
        String lowerType = typeName.toLowerCase();
        return "jsonb".equals(lowerType) || "json".equals(lowerType);
    }

    private void updateColumnMetaForJsonb(ColumnMeta cm, ResultSetMetaData rsmd, int columnIndex) throws SQLException {
        // 使用 java.lang.Object 类型,兼容JSONObject和JSONArray
        cm.javaType = Object.class.getName();
        if (generateRemarks) {
            cm.remarks = "JSONB 数据 (自动转换为 JSON 对象/数组)";
        }
    }
}
package com.jfinal.admin.common.pgsql;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.jfinal.kit.JsonKit;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.plugin.activerecord.dialect.PostgreSqlDialect;
import com.jfinal.plugin.activerecord.*;
import org.postgresql.util.PGobject;
import com.alibaba.fastjson2.JSON;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class JsonbPostgreSqlDialect extends PostgreSqlDialect {

    // 在 JsonbPostgreSqlDialect.java 中修改 fillStatement 方法
    @Override
    public void fillStatement(PreparedStatement pst, List<Object> paras) throws SQLException {
        for (int i = 0; i < paras.size(); i++) {
            Object value = paras.get(i);

            if (value instanceof JSONArray ||
                    value instanceof JSONObject ||
                    value instanceof Map ||
                    value instanceof List) {

                pst.setObject(i + 1, createJsonbObject(value), Types.OTHER);

            } else if (value instanceof java.util.Date) {
                // 处理 Date 类型 - 转换为 Timestamp
                java.util.Date date = (java.util.Date) value;
                pst.setTimestamp(i + 1, new Timestamp(date.getTime()));

            } else if (value instanceof java.sql.Date) {
                // 处理 SQL Date 类型
                pst.setDate(i + 1, (java.sql.Date) value);

            } else if (value instanceof java.sql.Time) {
                // 处理 Time 类型
                pst.setTime(i + 1, (java.sql.Time) value);

            } else {
                // 其他类型直接设置
                pst.setObject(i + 1, value);
            }
        }
    }

    // 对 fillStatement(Object... paras) 做同样的修改
    @Override
    public void fillStatement(PreparedStatement pst, Object... paras) throws SQLException {
        for (int i = 0; i < paras.length; i++) {
            Object value = paras[i];

            if (value instanceof JSONArray ||
                    value instanceof JSONObject ||
                    value instanceof Map ||
                    value instanceof List) {

                pst.setObject(i + 1, createJsonbObject(value), Types.OTHER);

            } else if (value instanceof java.util.Date) {
                java.util.Date date = (java.util.Date) value;
                pst.setTimestamp(i + 1, new Timestamp(date.getTime()));

            } else if (value instanceof java.sql.Date) {
                pst.setDate(i + 1, (java.sql.Date) value);

            } else if (value instanceof java.sql.Time) {
                pst.setTime(i + 1, (java.sql.Time) value);

            } else {
                pst.setObject(i + 1, value);
            }
        }
    }


    // 统一序列化方法
    private PGobject createJsonbObject(Object value) {
        try {
            PGobject pgObj = new PGobject();
            pgObj.setType("jsonb");

            if (value instanceof JSONObject || value instanceof JSONArray) {
                pgObj.setValue(value.toString());
            } else if (value instanceof Map || value instanceof List) {
                pgObj.setValue(JSON.toJSONString(value)); // 使用 fastjson2 序列化
            } else if (value instanceof String) {
                pgObj.setValue((String) value);
            } else {
                throw new IllegalArgumentException("Unsupported JSONB type: " +
                        (value != null ? value.getClass().getName() : "null"));
            }
            return pgObj;
        } catch (SQLException e) {
            throw new RuntimeException("Failed to create JSONB object", e);
        }
    }

    @Override
    public void forDbSave(String tableName, String[] pKeys, Record record, StringBuilder sql, List<Object> paras) {
        // 遍历字段,将 Map 转为 PGobject
        record.getColumns().forEach((key, value) -> {
            if (value instanceof Map) {
                PGobject pgObj = new PGobject();
                pgObj.setType("jsonb");
                try {
                    pgObj.setValue(JsonKit.toJson(value));
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
                record.set(key, pgObj);
            }
        });
        super.forDbSave(tableName, pKeys, record, sql, paras);
    }

    public JsonbPostgreSqlDialect() {
        this.recordBuilder = new JsonbRecordBuilder(); // 注入自定义RecordBuilder
    }
    

    @Override
    public void forDbUpdate(String tableName, String[] pKeys, Object[] ids, 
                           Record record, StringBuilder sql, List<Object> paras) {
        // 序列化JSONB字段
        try {
            processJsonbFields(record);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        super.forDbUpdate(tableName, pKeys, ids, record, sql, paras);
    }

    // 扩展字段处理逻辑
    private void processJsonbFields(Record record) throws SQLException {
        Map<String, Object> columns = record.getColumns();
        Set<String> modifyFlag = CPI.getModifyFlag(record);

        for (Map.Entry<String, Object> entry : columns.entrySet()) {
            Object value = entry.getValue();
            if (value instanceof JSONObject ||
                    value instanceof JSONArray ||
                    (value instanceof String && isJsonString((String) value))) {
                PGobject pgObj = createJsonbObject(value);
                columns.put(entry.getKey(), pgObj);
                modifyFlag.add(entry.getKey());
            }
        }
    }

    // 简单检查字符串是否是JSON格式
    private boolean isJsonString(String str) {
        if (str == null) return false;
        String trimmed = str.trim();
        return trimmed.startsWith("{") && trimmed.endsWith("}");
    }
}
package com.jfinal.admin.common.pgsql;

import com.jfinal.plugin.activerecord.*;
import com.jfinal.plugin.activerecord.Record;
import org.postgresql.util.PGobject;
import com.alibaba.fastjson2.JSON;
import java.sql.*;
import java.util.*;
import java.util.function.Function;

public class JsonbRecordBuilder extends RecordBuilder {
    
    @Override
    public List<Record> build(Config config, ResultSet rs, Function<Record, Boolean> func) 
        throws SQLException {
        
        List<Record> result = new ArrayList<>();
        ResultSetMetaData rsmd = rs.getMetaData();
        int columnCount = rsmd.getColumnCount();
        String[] labelNames = new String[columnCount + 1];
        int[] types = new int[columnCount + 1];
        buildLabelNamesAndTypes(rsmd, labelNames, types);

        while (rs.next()) {
            Record record = new Record();
            // ✅ 使用公共API设置配置
            record.setContainerFactoryByConfigName(config.getName());
            
            Map<String, Object> columns = record.getColumns();
            for (int i = 1; i <= columnCount; i++) {
                Object value = getColumnValue(rs, i, types[i]);
                
                // JSONB 类型识别与转换
                if (value instanceof PGobject) {
                    PGobject pgObj = (PGobject) value;
                    if ("jsonb".equals(pgObj.getType()) || "json".equals(pgObj.getType())) {
                        String jsonStr = pgObj.getValue();
                        if (jsonStr != null) {
                            jsonStr = jsonStr.trim();
                            try {
                                // 自动识别对象或数组
                                if (jsonStr.startsWith("{")) {
                                    value = JSON.parseObject(jsonStr);
                                } else if (jsonStr.startsWith("[")) {
                                    value = JSON.parseArray(jsonStr);
                                }
                            } catch (Exception e) {
                                // 解析失败保持原始字符串
                            }
                        }
                    }
                }
                
                columns.put(labelNames[i], value);
            }
            
            if (func == null) {
                result.add(record);
            } else if (!func.apply(record)) {
                break;
            }
        }
        return result;
    }
    
    // 安全获取列值(处理大对象类型)
    private Object getColumnValue(ResultSet rs, int index, int type) throws SQLException {
        if (type < Types.BLOB) {
            return rs.getObject(index);
        }
        
        switch (type) {
            case Types.CLOB:
                return ModelBuilder.me.handleClob(rs.getClob(index));
            case Types.NCLOB:
                return ModelBuilder.me.handleClob(rs.getNClob(index));
            case Types.BLOB:
                return ModelBuilder.me.handleBlob(rs.getBlob(index));
            default:
                return rs.getObject(index);
        }
    }
    
    // 保留原生元数据处理逻辑
    @Override
    public void buildLabelNamesAndTypes(
        ResultSetMetaData rsmd, String[] labelNames, int[] types) throws SQLException {
        for (int i = 1; i < labelNames.length; i++) {
            labelNames[i] = rsmd.getColumnLabel(i);
            types[i] = rsmd.getColumnType(i);
        }
    }
}
/**
 * 本项目采用《JFinal 俱乐部授权协议》,保护知识产权,就是在保护我们自己身处的行业。
 * 
 * Copyright (c) 2011-2021, jfinal.com
 */

package com.jfinal.admin.common;

import com.jfinal.admin.common.pgsql.JsonbMetaBuilder;
import com.jfinal.admin.common.pgsql.JsonbPostgreSqlDialect;
import com.jfinal.plugin.activerecord.dialect.MysqlDialect;
import com.jfinal.plugin.activerecord.dialect.PostgreSqlDialect;
import com.jfinal.plugin.activerecord.generator.ColumnMeta;
import com.jfinal.plugin.activerecord.generator.Generator;
import com.jfinal.plugin.activerecord.generator.MetaBuilder;
import com.jfinal.plugin.druid.DruidPlugin;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * Model、BaseModel、_MappingKit 生成器
 * 
 * 添加新表,或者修改表结构时重新运行该生成器即可
 */
public class _Generator {
    
    /**
     * 在此统一添加不参与生成的 table。不参与生成的 table 主要有:
     * 1:用于建立表之间关系的关联表,如 account_article
     * 2:部分功能使用 Db + Record 模式实现,所涉及到的 table 也不参与生成
     */
    private static String[] excludedTable = {

    };
    
    /**
     * 重用 JFinalClubConfig 中的数据源配置,避免冗余配置
     */
    public static DataSource getDataSource() {
       DruidPlugin druidPlugin = AppConfig.getDruidPlugin();
       druidPlugin.start();
       return druidPlugin.getDataSource();
    }
    
    public static void main(String[] args) {
       // model 所使用的包名 (MappingKit 默认使用的包名)
       String modelPackageName = "com.jfinal.admin.common.model";
       
       // base model 所使用的包名
       String baseModelPackageName = modelPackageName + ".base";
       
       // base model 文件保存路径
       String baseModelOutputDir = System.getProperty("user.dir")
             + "/src/main/java/" + baseModelPackageName.replace('.', '/');
       
       // model 文件保存路径 (MappingKit 与 DataDictionary 文件默认保存路径)
       String modelOutputDir = baseModelOutputDir + "/..";
       
       System.out.println("输出路径:"+ baseModelOutputDir);


       // 创建自定义方言实例
       JsonbPostgreSqlDialect dialect = new JsonbPostgreSqlDialect();

       // 创建自定义 MetaBuilder
       JsonbMetaBuilder metaBuilder = new JsonbMetaBuilder(getDataSource());
       metaBuilder.setDialect(dialect); // 设置方言
       metaBuilder.setGenerateRemarks(true);

       // 创建生成器
       Generator gen = new Generator(getDataSource(), baseModelPackageName, baseModelOutputDir, modelPackageName, modelOutputDir);

       // 设置自定义 MetaBuilder
       gen.setMetaBuilder(metaBuilder);

       // 设置方言
       gen.setDialect(dialect);

       // 设置自定义的 MetaBuilder(关键修改)
       // 设置数据库方言
       //gen.setDialect(new JsonbPostgreSqlDialect());
       //gen.setDialect(new PostgreSqlDialect());

       // 设置是否生成字段备注
       gen.setGenerateRemarks(true);

       // 添加不需要生成的表名
       for (String table : excludedTable) {
          gen.addExcludedTable(table.trim());
       }
       
       // 设置是否在 Model 中生成 dao 对象
       gen.setGenerateDaoInModel(false);
       
       // 设置是否生成字典文件
       gen.setGenerateDataDictionary(false);

       // 设置需要被移除的表名前缀用于生成modelName。例如表名 "osc_user",移除前缀 "osc_"后生成的model名为 "User"而非 OscUser
       // gen.setRemovedTableNamePrefixes("t_");
       
       // 生成
       gen.generate();
    }
}
// 配置 ActiveRecordPlugin
ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);
arp.setDialect(new JsonbPostgreSqlDialect()); // 关键配置


评论区

杜福忠

2025-07-12 00:06

没必要if (jsonStr.startsWith 吧? 直接扔JSON.parse(jsonStr)他里面有判断

热门分享

扫码入社