使用 JDBI 插入 localdate 类型字段的问题

Issue inserting localdate type field using JDBI

我目前正在使用 JDBI 将我的 POJO 保存到我的数据库中。我使用 MS SQL Server 作为我的数据库。我目前无法在 table 中插入一行,因为 LocalDate 变量没有映射值的问题。

scoreDate 字段是 LocalDate 类型,是非强制性的,我没有为此字段传递任何值。但是在尝试 运行 下面的测试时,我得到了下面的错误。

请问可能是什么问题以及如何解决?

测试Class

@Test
    public void testInsertClientScore() throws Exception {
        final DBI dbi = databaseResource.getDBI();
        final ClientScoreDao clientScoreDao = dbi.onDemand(ClientScoreDao.class);

        final ClientScore clientScore = ImmutableClientScore.builder()
                .id(1L)
                .ClientName("TESTCLIENT")
                .build();

        assertEquals(1,clientScoreDao.insert(clientScore));

    }

错误跟踪

org.skife.jdbi.v2.exceptions.UnableToExecuteStatementException: com.microsoft.sqlserver.jdbc.SQLServerException: 
Implicit conversion from data type varbinary to date is not allowed. Use the CONVERT function to run this query. [statement:"insert into ICEBERG.ClientScore (scoreId, clientname, scoredate) values (:id, :ClientName, :scoreDate)", located:"insert into ICEBERG.ClientScore (scoreId, clientname, scoredate) values (:id, :ClientName, :scoreDate)", rewritten:"insert into ICEBERG.ClientScore (scoreId, clientname, scoredate) values (?, ?, ?)", arguments:{ positional:{}, named:{ClientName:'TESTCLIENT',id:1,scoreDate:null}, finder:[]}]

    at org.skife.jdbi.v2.SQLStatement.internalExecute(SQLStatement.java:1338)
    at org.skife.jdbi.v2.Update.execute(Update.java:56)
    at org.skife.jdbi.v2.sqlobject.UpdateHandler.value(UpdateHandler.java:67)

.....
....

Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Implicit conversion from data type varbinary to date is not allowed. Use the CONVERT function to run this query.
    at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(SQLServerException.java:217)

数据库Table定义

-- ClientScore table
CREATE TABLE ICEBERG.ClientScore (
   ScoreId                 INTEGER                                         NOT NULL,
   ClientName              VARCHAR(50)                                  NOT NULL,
   ScoreDate               DATE                                            NULL,
   CONSTRAINT PK_CLIENTSCORE_TEST PRIMARY KEY (ScoreId)
)

POJO

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.mercuria.dali.jdbi.Jdbi;
import com.mercuria.dali.jdbi.JdbiOptions;
import org.immutables.value.Value;

import javax.annotation.Nullable;
import java.io.Serializable;
import java.time.LocalDate;

@Value.Immutable
@Jdbi(tableName = "ICEBERG.ClientScore")
@JsonSerialize(as = ImmutableClientScore.class)
@JsonDeserialize(as = ImmutableClientScore.class)
public interface ClientScore extends Serializable {

    @JsonProperty("scoreid")
    @JdbiOptions(columnName = "scoreId")
    Long id();

    @JsonProperty("clientname")
    @JdbiOptions(columnName = "clientname")
    String ClientName();

    @JsonProperty("scoredate")
/*    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)*/
    @JdbiOptions(columnName = "scoredate")
    @Nullable
    LocalDate scoreDate();  
}

我在配置我的 JDBI 连接时为 LocalDate 数据类型设置了映射器。

 public static void configureDbi(DBI dbi) {
        // Register argument factories
        dbi.registerArgumentFactory(new NullDoubleArgumentFactory());
        dbi.registerArgumentFactory(new UuidArgumentFactory());
        dbi.registerArgumentFactory(new LocalDateArgumentFactory());
        dbi.registerArgumentFactory(new InstantArgumentFactory(Optional.of(TimeZone.getTimeZone("UTC"))));
        dbi.registerColumnMapper(new InstantMapper(Optional.of(TimeZone.getTimeZone("UTC"))));

        dbi.registerArgumentFactory(new OptionalArgumentFactory("com.microsoft.sqlserver.jdbc.SQLServerDriver"));
        dbi.registerArgumentFactory(new OptionalIntArgumentFactory());
        dbi.registerArgumentFactory(new OptionalDoubleArgumentFactory());

        // Register column mappers
        dbi.registerColumnMapper(new UuidColumnMapper());
        dbi.registerColumnMapper(new LocalDateMapper());
    }

我 运行 也进入了他的问题,发现 Dropwizard LocalDateArgumentFactory class 确定是否创建 Argument class 来处理值的方式是使用 instanceof检查:

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

因为值是 null 它 returns 是假的,我通过创建这个解决了这个问题:

public class NullAwareLocalDateArgumentFactory extends LocalDateArgumentFactory {
    @Override
    public boolean accepts(Class<?> expectedType, Object value, StatementContext ctx) {
        if(value == null){
            //Look at the expected type. Tbh. not sure why it isn't simply doing this by default ...
            return LocalDate.class.isAssignableFrom(expectedType);
        }
        else {
            return super.accepts(expectedType, value, ctx);
        }
    }
}