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

如何在JDBI v3中将PostgreSQL自定义类型用作嵌套POJO

解决JDBI v3映射PostgreSQL自定义复合类型与嵌套POJO的问题

1. 确保PostgreSQL端类型与表定义正确

先确认PG侧的类型、表定义无格式或逻辑错误:

-- 创建枚举类型(值需与Java枚举严格对齐)
CREATE TYPE spatial_unit AS ENUM ('INCH', 'CENTIMETER', 'METER');

-- 创建复合类型
CREATE TYPE dimension_3d AS (
    width NUMERIC,
    length NUMERIC,
    height NUMERIC,
    unit spatial_unit
);

-- 创建业务表
CREATE TABLE items (
    item_number VARCHAR PRIMARY KEY,
    dimensions dimension_3d
);

2. Java端类型定义对齐PG侧规则

  • 枚举类LengthUnit必须与PG的spatial_unit枚举值大小写、拼写完全一致:
public enum LengthUnit {
    INCH, CENTIMETER, METER;
}
  • 嵌套POJODimension3D:
public class Dimension3D {
    private BigDecimal width;
    private BigDecimal length;
    private BigDecimal height;
    private LengthUnit unit = LengthUnit.INCH; // 默认值匹配PG逻辑

    // 构造器、getter/setter 自行补充
}
  • 主实体Item:
public class Item {
    private String itemNumber;
    private Dimension3D dimensions;

    // 构造器、getter/setter 自行补充
}

3. 实现PGobject子类处理复合类型映射

这是核心步骤,负责在Java对象与PG自定义类型间做格式转换:

public class Dimension3DPGType extends PGobject {
    private static final String TYPE_NAME = "dimension_3d";
    private Dimension3D dimension;

    public Dimension3DPGType() {
        setType(TYPE_NAME);
    }

    public Dimension3DPGType(Dimension3D dimension) {
        this();
        this.dimension = dimension;
    }

    @Override
    public String getValue() throws SQLException {
        if (dimension == null) return null;
        // 严格遵循PG复合类型的文本格式:(width,length,height,unit)
        return String.format("(%s,%s,%s,%s)",
                dimension.getWidth().toPlainString(),
                dimension.getLength().toPlainString(),
                dimension.getHeight().toPlainString(),
                dimension.getUnit().name());
    }

    @Override
    public void setValue(String value) throws SQLException {
        if (value == null || value.isEmpty()) {
            this.dimension = null;
            return;
        }
        // 解析PG返回的复合类型字符串,例如"(10,20,30,INCH)"
        String trimmed = value.substring(1, value.length() - 1);
        String[] parts = trimmed.split(",", 4);

        Dimension3D dim = new Dimension3D();
        dim.setWidth(new BigDecimal(parts[0]));
        dim.setLength(new BigDecimal(parts[1]));
        dim.setHeight(new BigDecimal(parts[2]));
        dim.setUnit(LengthUnit.valueOf(parts[3]));
        this.dimension = dim;
    }

    public Dimension3D getDimension() {
        return dimension;
    }
}

4. 注册JDBI的ArgumentFactory与ColumnMapper

4.1 ArgumentFactory(Java对象转PG类型)

public class Dimension3DArgumentFactory implements ArgumentFactory.Preparable<Dimension3D> {
    @Override
    public boolean accepts(Class<?> type, Object value, StatementContext ctx) {
        return Dimension3D.class.isAssignableFrom(type);
    }

    @Override
    public Argument prepare(Dimension3D value, StatementContext ctx) {
        if (value == null) return Arguments.nullArgument();
        return (pos, stmt, ctx1) -> stmt.setObject(pos, new Dimension3DPGType(value));
    }
}

4.2 ColumnMapper(PG类型转Java对象)

public class Dimension3DColumnMapper implements ColumnMapper<Dimension3D> {
    @Override
    public Dimension3D map(ResultSet r, int columnNumber, StatementContext ctx) throws SQLException {
        PGobject pgObj = (PGobject) r.getObject(columnNumber);
        if (pgObj == null) return null;
        
        // 兼容未直接注册PG类型的场景,手动解析值
        Dimension3DPGType dimType = new Dimension3DPGType();
        dimType.setValue(pgObj.getValue());
        return dimType.getDimension();
    }
}

5. 初始化JDBI并注册组件

// 基于已有DataSource初始化Jdbi
Jdbi jdbi = Jdbi.create(dataSource);

// 安装PostgreSQL扩展插件
jdbi.installPlugin(new PostgresPlugin());

// 注册PG自定义类型映射
jdbi.withHandle(handle -> {
    PGConnection pgConn = handle.getConnection().unwrap(PGConnection.class);
    pgConn.addDataType("dimension_3d", Dimension3DPGType.class);
    return null;
});

// 注册自定义参数处理器与结果映射器
jdbi.registerArgument(new Dimension3DArgumentFactory());
jdbi.registerColumnMapper(new Dimension3DColumnMapper());

6. 编写SQL Object接口

无需手动CAST,直接使用@BindBean自动映射:

@RegisterBeanMapper(Item.class)
public interface ItemDao {
    @SqlUpdate("INSERT INTO items (item_number, dimensions) VALUES (:itemNumber, :dimensions)")
    void insertItem(@BindBean Item item);

    @SqlQuery("SELECT * FROM items WHERE item_number = :itemNumber")
    Item getItem(@Bind("itemNumber") String itemNumber);
}

常见问题排查

  • 枚举值无效:检查Java枚举名称与PG枚举值的大小写、拼写是否完全一致(PG枚举大小写敏感)。
  • 记录格式错误:确认Dimension3DPGType.getValue()生成的字符串严格符合(val1,val2,val3,val4)格式,无多余空格。
  • 列类型不匹配:注册PGDataType时的类型名称必须与PG自定义类型名完全一致(如dimension_3d)。
  • 数值语法错误:解析PG返回值时,用BigDecimal的toPlainString()确保数值格式为纯数字,避免科学计数法干扰。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 14:05:55