9

When using SQL Object argument binding, does JDBI work out-of-the-box with UUID parameters?

I have a method such as this:

@SqlQuery("EXECUTE [MyProcedure] :myField")
MyDto myMethod(@Bind("myField") UUID myField);

which is bound to a SQL Server stored procedure that receives a parameter like this:

@myField uniqueidentifier

When executed, this exception is thrown:

! com.microsoft.sqlserver.jdbc.SQLServerException: The conversion from UNKNOWN to UNKNOWN is unsupported.
! at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDriverError(SQLServerException.java:190)
! at com.microsoft.sqlserver.jdbc.DataTypes.throwConversionError(DataTypes.java:1117)
! at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.setObject(SQLServerPreparedStatement.java:991)

If I change the parameter type on JDBI to String, and call it using the toString() method from the UUID object, it works:

@SqlQuery("EXECUTE [MyProcedure] :myField")
MyDto trash(@Bind("myField") String myField);

Is there a way to write my DAO methods accepting UUID parameters and have them converted to strings before binding?

Fernando Correia
  • 21,803
  • 13
  • 83
  • 116

1 Answers1

18

JDBI only exposes explicit type based bindings for the types which JDBC exposes them for. JDBC does not expose a UUID type for binding, so it is defaulting to setting it as an Object. Unfortunately, JDBC offers not explicit UUID binding mechanism, so going through String is probably the most portable way :-(

If you want to bind it as a UUID in Java and have it converted to a String internally, there are two paths. The first, if you always want to bind UUIDs as Strings is to use an ArgumentFactory, see https://github.com/brianm/jdbi/blob/master/src/test/java/org/skife/jdbi/v2/sqlobject/TestRegisterArgumentFactory.java for an example.

The second is, if you want to do it only in specific cases, to create a custom Binder, such as with http://jdbi.org/sql_object_api_argument_binding/

Example of implementation of a global binding from UUIDs to Strings using an ArgumentFactory:

UUIDArgumentFactory.java:

public class UUIDArgumentFactory implements ArgumentFactory<UUID> {

    @Override
    public boolean accepts(Class<?> expectedType, Object value, StatementContext ctx) {
        return value instanceof UUID;
    }

    @Override
    public Argument build(Class<?> expectedType, UUID value, StatementContext ctx) {
        return new UUIDArgument(value);
    }
}

UUIDArgument.java:

public class UUIDArgument implements Argument {
    private final UUID value;

    public UUIDArgument(UUID value) {
        this.value = value;
    }

    @Override
    public void apply(int position, PreparedStatement statement, StatementContext ctx) throws SQLException {
        statement.setString(position, value.toString());
    }    
}

Register:

final DatabaseFactory factory = new DatabaseFactory(environment);
final Database db = factory.build(configuration.getDatabaseConfiguration(), "sqlserver");
db.registerArgumentFactory(new UUIDArgumentFactory());
David Phillips
  • 10,723
  • 6
  • 41
  • 54
brianm
  • 2,015
  • 1
  • 16
  • 12
  • That solved the issue. I can defined the DAO API using UUIDs and have them converted to Strings when they're bound to the parameters. I added my code to the answer. – Fernando Correia Aug 20 '12 at 23:22
  • 1
    Note that the ability to accept UUID parameters for binding isn't an attribute of JDBI, but rather of the underlying JDBC driver for your database. For example, I'm using Postgres, which has a native UUID column type, and the Postgres JDBC driver accepts UUIDs using PreparedStatement.setObject(). As a result, native binding of UUIDs works without an ArgumentFactory. – Chris Westin Nov 22 '12 at 17:39
  • 1
    If you want to register an ArgumentFactory globally, rather than having to annotate every class affected (as in the example in TestRegisterArgumentFactory), call DBI.registerArgumentFactory() when you set up your DBI handle. – Chris Westin Nov 22 '12 at 17:40
  • It's really helpful. I have been struggling for storing values to a column of type varchar[] (ARRAY) and tried all the possible ways out using java but nothing worked. Kudos to @brianm – ahmar hashmi Jun 04 '23 at 19:39