jfinal操作pgsql的jsonb类型

_Generator.java:
/**
 * 本项目采用《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();
    }
}

JsonbMetaBuilder.java:

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 类型为 Map
        cm.javaType = "java.util.Map<String, Object>";

        // 添加类型备注
        if (generateRemarks) {
            String typeName = rsmd.getColumnTypeName(columnIndex);
            if (cm.remarks == null || cm.remarks.isEmpty()) {
                cm.remarks = "JSONB 数据 (自动转换为 Map)";
            } else {
                cm.remarks += " [JSONB]";
            }
        }
    }
}


JsonbPostgreSqlDialect.java :

package com.jfinal.admin.common.pgsql;

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.util.List;
import java.util.Map;
import java.util.Set;

public class JsonbPostgreSqlDialect extends PostgreSqlDialect {
    @Override
    public void fillStatement(PreparedStatement pst, List<Object> paras) throws SQLException {
        processJsonbParameters(paras);
        super.fillStatement(pst, paras);
    }

    @Override
    public void fillStatement(PreparedStatement pst, Object... paras) throws SQLException {
        processJsonbParameters(paras);
        super.fillStatement(pst, paras);
    }

    private void processJsonbParameters(List<Object> paras) {
        for (int i = 0; i < paras.size(); i++) {
            Object value = paras.get(i);
            if (value instanceof Map) {
                paras.set(i, createJsonbObject((Map<?, ?>) value));
            }
        }
    }

    private void processJsonbParameters(Object[] paras) {
        for (int i = 0; i < paras.length; i++) {
            Object value = paras[i];
            if (value instanceof Map) {
                paras[i] = createJsonbObject((Map<?, ?>) value);
            }
        }
    }

    private PGobject createJsonbObject(Map<?, ?> map) {
        try {
            PGobject jsonObject = new PGobject();
            jsonObject.setType("jsonb");
            jsonObject.setValue(JsonKit.toJson(map));
            return jsonObject;
        } catch (SQLException e) {
            throw new RuntimeException(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);
    }

    // JsonbPostgreSqlDialect.java
    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();
            PGobject pgObj = null;

            if (value instanceof Map) {
                pgObj = new PGobject();
                pgObj.setType("jsonb");
                pgObj.setValue(JSON.toJSONString(value));
            }
            // 新增:处理JSON格式字符串
            else if (value instanceof String && isJsonString((String) value)) {
                pgObj = new PGobject();
                pgObj.setType("jsonb");
                pgObj.setValue((String) value);
            }

            if (pgObj != null) {
                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("}");
    }
}


JsonbRecordBuilder.java:

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())) {
                        value = JSON.parseObject(pgObj.getValue(), Map.class);
                    }
                }
                
                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);
        }
    }
}

AppConfig.java

// 配置 ActiveRecordPlugin
ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);
arp.setDialect(new JsonbPostgreSqlDialect()); // 关键配置


评论区

杜福忠

2025-07-10 15:43

点赞,这个方案和我们有一个 json存储的业务类似,也是判断 字段名是否使用json开头,自动给转换为json对象。Db查询出来Record里面的json字段已经自动转换为了json对象,直接使用非常方便!

fmpoffice

2025-07-10 17:11

@杜福忠 哥,快去帮波总撸代码,aifei需要你

杜福忠

2025-07-10 21:56

@fmpoffice 还没到我们入场了,需要aifei-core发布了,aifei-ext才是我们展拳脚的地方

热门分享

扫码入社