2

I've been trying to persist a LocalDateTime into a TIMESTAMP column in DB2 10.5 but fails with the error:

[jcc][1091][10824][4.26.14] Invalid data conversion: Parameter instance 2020-01-02T12:34:56 is invalid for the requested conversion. ERRORCODE=-4461, SQLSTATE=42815

I'm using the IBM JDBC driver: "JDBC driver IBM Data Server Driver for JDBC and SQLJ 4.26.14" that implements JDBC spec 4.1.

The table definition is:

create table t1 (
  id int,
  a timestamp
);

And the Java code is:

LocalDateTime dt = LocalDateTime.of(2020, 1, 2, 12, 34, 56);
PreparedStatement ps = conn.prepareStatement(
  "insert into t1 (id, a) values (?, ?)"
);
int id = 1;
ps.setInt(1, id);
ps.setObject(2, dt); // fails here
ps.execute();

I can see it doesn't even get to the ps.execute() method. I've read that a JDBC 4.2 driver may fix this, but I can't find it anywhere.

The Impaler
  • 45,731
  • 9
  • 39
  • 76

2 Answers2

3

According to the Data types that map to database data types in Java applications topic, you should use java.sql.Timestamp working with TIMESTAMP table column.

Table 1. Mappings of Java data types to database server data types for updating database tables

Java data type      Database data type
------------------  ------------------
...
java.sql.Timestamp  TIMESTAMP, ...
Mark Barinstein
  • 11,456
  • 2
  • 8
  • 16
  • OK, I think I could do that. but it's strange. A `TIMESTAMP` (local time) follows a different timeline than a `java.sql.Timestamp` (world time). There's no direct transformation, since it's necessary for a time offset/zone to participate in the conversion. – The Impaler Feb 29 '20 at 17:45
  • 1
    JDBC 4.2 added support for mapping a `java.time.LocalDateTime` to a `Timestamp` column (see JDBC 4.2 specification, section 3.1 Overview of changes) – Mark Rotteveel Feb 29 '20 at 17:50
  • @MarkRotteveel I still think a database `TIMESTAMP` and `TIMESTAMP WITH TIME ZONE` are quite different data types. I feel like data should not flow from one to another without proper **explicit conversion**, never seamless/automatic/hidden conversions. Maybe there should be two separate JDBC methods; something like: `ps.setTimestamp()` and `ps.setTimestampTZ()` or similar but that's just me. – The Impaler Feb 29 '20 at 18:01
  • @TheImpaler That is already the case in JDBC 4.2. A `TIMESTAMP WITH TIME ZONE` is mapped to `java.time.OffsetDateTime`, not to `java.time.LocalDateTime` (that one is mapped to a `TIMESTAMP (WITHOUT TIME ZONE)`). And those `java.time` types are both set using `setObject` and retrieved with `getObject`. The JDBC Expert Group decided that there will no longer be explicit setters for new types. – Mark Rotteveel Feb 29 '20 at 19:44
2

Change code to use setTimestamp(...), using Timestamp.valueOf(LocalDateTime dateTime):

ps.setTimestamp(2, Timestamp.valueOf(dt));

If dt can be null, use ternary operator:

ps.setTimestamp(2, (dt == null ? null : Timestamp.valueOf(dt)));
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • 1
    I think transforming a `LocalDateTime` (local time) into a `Timestamp` (world time) is fundamentally wrong. I still don't see any better workaround, though. – The Impaler Feb 29 '20 at 17:48
  • @TheImpaler Since the JDBC driver doesn't support `LocalDateTime`, the only way is `Timestamp` or `String`, and I'd strongly discourage using `String`, which means the only way is to use `Timestamp`, so whether you consider it "fundamentally wrong" or not, you don't have a choice. --- Well, unless you can find a newer version of the JDBC driver that does support `LocalDateTime`. – Andreas Feb 29 '20 at 17:51
  • I agree. It seems the JDBC spec missed the difference between `TIMESTAMP` and `TIMESTAMP WITH TIME ZONE` altogether for 20 years (1997?) since it has a single `setTimestamp()` method to handle both [quite different] data types. Also, Java 8 may have missed a great opportunity to fix this. +1 – The Impaler Feb 29 '20 at 17:55
  • @TheImpaler No, JDBC has always *(Java 1.2+)* had support for `TIMESTAMP WITH TIME ZONE`, with the [`setTimestamp(int parameterIndex, Timestamp x, Calendar cal)`](https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setTimestamp-int-java.sql.Timestamp-java.util.Calendar-) method, where the time zone is specified in the `Calendar` object. – Andreas Feb 29 '20 at 18:01
  • 2
    Currently only the Db2 for Z/OS platform supports `timestamp with time zone` natively as a column datatype, it is not yet available in shipping versions of Db2 for Linux Unix Windows, or i-series. Additionally, if you want to keep the time zone detail, the jdbc application must use `com.ibm.db2.jcc.DBTimestamp` objects . But your question specifies Db2 v10.5 which is LUW version. – mao Feb 29 '20 at 18:10
  • @Andreas Not to add to the controversy, but aren't those two methods tackling the same problem and ignoring the local timestamp? Both handle "world time", that would correspond to a database `TIMESTAMP WITH TIME ZONE` type, that doesn't yet exist in DB2. None handle a database `TIMEZONE` type, that does exist in DB2. – The Impaler Feb 29 '20 at 18:11
  • @TheImpaler Different databases handle `TIMESTAMP` differently. Some treat it as a `LocalDateTime` style value, i.e. a date and time without any time zone. Some treat is as an `OffsetDateTime` with a fixed time zone, either UTC or the "database time zone", and auto-adjusts to/from the "client" or "session" time zone. As such, there is no single answer, and handling of time zones is left to the JDBC driver to figure out. The JDBC API default to use the JVM default time zone, but allows an app to specify the time zone with a `Calendar` object. The JDBC driver decides what that means. – Andreas Feb 29 '20 at 18:26