JDBI select 关于 varbinary 和 uuid

JDBI select on varbinary and uuid

遗留 mysql db table 有一个 id 列,它是非人类可读的原始 varbinary(不要问我为什么 :P)

CREATE TABLE IF NOT EXISTS `tbl_portfolio` (
    `id` varbinary(16) NOT NULL,
    `name` varchar(128) NOT NULL,
    ...
    PRIMARY KEY (`id`)
);

我需要根据 java.util.UUID

select
jdbiReader
    .withHandle<PortfolioData, JdbiException> { handle ->
        handle
            .createQuery(
                """
                    SELECT *
                    FROM tbl_portfolio
                    WHERE id = :id
                    """
            )
            .bind("id", uuid) //mapping this uuid into the varbinary
                              //id db column is the problem
            .mapTo(PortfolioData::class.java) //the mapper out does work
            .firstOrNull()
    }

为了以防万一有人想看到它,这里是映射器输出(但同样,映射器输出不是问题 - 将 uuid 绑定到 varbinary id db 列)

class PortfolioDataMapper : RowMapper<PortfolioData> {

    override fun map(
        rs: ResultSet,
        ctx: StatementContext
    ): PortfolioData = PortfolioData(
        fromBytes(rs.getBytes("id")),
        rs.getString("name"),
        rs.getString("portfolio_idempotent_key")
    )

    private fun fromBytes(bytes: ByteArray): UUID {
        val byteBuff = ByteBuffer.wrap(bytes)
        val first = byteBuff.long
        val second = byteBuff.long
        return UUID(first, second)
    }

}

我已经尝试了各种方法来使绑定工作但没有成功 - 非常感谢任何建议!

终于让它工作了,部分归功于 https://jdbi.org/#_argumentfactory,它实际上专门处理 UUID,但尽管我看了几个小时的 JDBI 文档,但我不知何故还是错过了,哦,好吧

查询可以保持这样

jdbiReader
    .withHandle<PortfolioData, JdbiException> { handle ->
        handle
            .createQuery(
                """
                    SELECT *
                    FROM tbl_portfolio
                    WHERE id = :id
                    """
            )
            .bind("id", uuid)
            .mapTo(PortfolioData::class.java)
            .firstOrNull()
    }

但是jdbi需要注册一个UUIDArgumentFactory

jdbi.registerArgument(UUIDArgumentFactory(VARBINARY))

哪里

class UUIDArgumentFactory(sqlType: Int) : AbstractArgumentFactory<UUID>(sqlType) {

    override fun build(
        value: UUID,
        config: ConfigRegistry?
    ): Argument {
        return UUIDArgument(value)
    }

}

哪里

class UUIDArgument(private val value: UUID) : Argument {

    companion object {
        private const val UUID_SIZE = 16
    }

    @Throws(SQLException::class)
    override fun apply(
        position: Int,
        statement: PreparedStatement,
        ctx: StatementContext
    ) {
        val bb = ByteBuffer.wrap(ByteArray(UUID_SIZE))
        bb.putLong(value.mostSignificantBits)
        bb.putLong(value.leastSignificantBits)
        statement.setBytes(position, bb.array())
    }
    
}

注意,像这样在整个 jdbi 实例上注册一个 ArgumentFactory 会使发送到 .bind 的所有 UUID 类型参数映射到字节,这可能不是你想要的,以防你的代码库中的其他地方有其他 UUID 参数以 VARBINARY 以外的其他内容存储在 mysql 端 - 例如,您可能有另一个 table 带有一列,其中您的 JVM UUID 实际上存储为 VARCHAR 或其他任何内容,在这种情况下您必须,而不是在整个 jdbi 实例上注册 UUID ArgumentFactory,只在适当的地方临时使用它。