如何在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; }
- 嵌套POJO
Dimension3D:
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
相关产品推荐
相关产品推荐

