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

JDBC不识别自定义类型安全字段包装类的解决方案咨询

问题描述

为数据库ID字段实现类型安全包装类后,将其作为PreparedStatement参数使用时遇到类型不支持异常,使用的是ActiveJDBC,问题根源在JDBC层面。

示例代码:

ModelDelegate.count(PSetAnswer::class.java, "psetproblemid in (?)", new PSetProblemId(333661));

执行时抛出异常:

org.javalite.activejdbc.DBException: java.sql.SQLException: Type com.ktbyte.dto.id.PSetProblemId not supported type, query: SELECT COUNT(*) FROM psetanswers WHERE psetproblemid in (?), params: 333661
    at [...]
Caused by: java.sql.SQLException: Type com.ktbyte.dto.id.PSetProblemId not supported type
    at [...]

ID类结构:

public class PSetProblemId extends Id.IntId {
    public PSetProblemId(Integer value) {
        super(value);
    }
}

abstract public class Id<T> {
    public final T value;

    public Id(T value) {
        this.value = requireNonNull(value);
    }

    @Override
    public String toString() {
        return value.toString();
    }

    // 其他省略方法

    public static abstract class IntId extends Id<Integer> {
        public IntId(Integer value) {
            super(value);
        }
    }
}

通过栈跟踪发现,MariaDB JDBC驱动的Configuration包含Codecs数组,用于定义Java类到特定类型的转换逻辑:

// deep in org.mariadb.jdbc.BasePreparedStatement
for (Codec<?> codec : con.getContext().getConf().codecs()) {
  if (codec.canEncode(x)) {
    Parameter p = new Parameter(codec, x, scaleOrLength);
    parameters.set(parameterIndex - 1, p);
    return;
  }
}

throw new SQLException(String.format("Type %s not supported type", x.getClass().getName()));

尝试让Id实现RowId接口无效,需要找到可行的转换方案。


解决方案

方案1:自定义MariaDB JDBC Codec

实现MariaDB的Codec接口,让驱动自动识别IntId子类并转换为整数:

  1. 编写自定义Codec类
import org.mariadb.jdbc.codec.Codec;
import org.mariadb.jdbc.codec.DataType;
import org.mariadb.jdbc.message.server.ColumnDefinitionPacket;
import java.sql.SQLException;
import java.util.Optional;

public class IntIdCodec implements Codec<Id.IntId> {

    @Override
    public String className() {
        return Id.IntId.class.getName();
    }

    @Override
    public boolean canDecode(ColumnDefinitionPacket column, Class<?> type) {
        // 仅处理编码场景,解码返回false
        return false;
    }

    @Override
    public Id.IntId decode(ColumnDefinitionPacket column, byte[] data, Class<?> type, long maxLength, boolean useAlias) throws SQLException {
        throw new SQLException("Decode not supported for IntId");
    }

    @Override
    public boolean canEncode(Object value) {
        // 判断是否为IntId子类实例
        return value instanceof Id.IntId;
    }

    @Override
    public void encode(StringBuilder buffer, Object value) {
        // 直接拼接包装的整数值
        buffer.append(((Id.IntId) value).value);
    }

    @Override
    public byte[] encodeBinary(Object value) throws SQLException {
        // 二进制编码转换为整数字节数组
        Integer intValue = ((Id.IntId) value).value;
        return new byte[]{
                (byte) (intValue >> 24),
                (byte) (intValue >> 16),
                (byte) (intValue >> 8),
                (byte) intValue
        };
    }

    @Override
    public Optional<DataType> getBinaryEncodeType() {
        return Optional.of(DataType.LONG);
    }
}
  1. 注册Codec到连接配置
    在获取JDBC连接时,通过连接属性添加自定义Codec:
Properties props = new Properties();
props.setProperty("user", "your_user");
props.setProperty("password", "your_password");
// 注册自定义Codec,多个用逗号分隔
props.setProperty("codec", "com.ktbyte.dto.id.IntIdCodec");

Connection conn = DriverManager.getConnection("jdbc:mariadb://host:port/db", props);

如果使用ActiveJDBC连接池,需在对应的连接配置中添加该属性。

方案2:利用ActiveJDBC类型转换器

ActiveJDBC支持自定义类型转换器,自动在参数传递时拆包:

  1. 实现Converter接口
import org.javalite.activejdbc.Converter;

public class IntIdConverter implements Converter<Id.IntId, Integer> {

    @Override
    public Integer toDb(Id.IntId source) {
        return source.value;
    }

    @Override
    public Id.IntId fromDb(Integer source) {
        // 若无需从数据库读取转换回IntId,可抛出异常或留空
        throw new UnsupportedOperationException("Conversion from DB to IntId not implemented");
    }

    @Override
    public Class<Id.IntId> sourceClass() {
        return Id.IntId.class;
    }

    @Override
    public Class<Integer> destinationClass() {
        return Integer.class;
    }
}
  1. 注册转换器
    在应用启动时执行注册:
import org.javalite.activejdbc.Registry;

Registry.registerConverter(new IntIdConverter());

注册后,ActiveJDBC会自动将IntId子类转换为Integer传递给JDBC驱动。

方案3:手动拆包(临时快速方案)

若不想修改框架配置,可在调用时手动取出包装的数值:

ModelDelegate.count(PSetAnswer::class.java, "psetproblemid in (?)", new PSetProblemId(333661).value);

优点是快速生效,缺点是每次调用都需手动拆包,削弱了类型安全的便利性。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 15:23:27