example java date jdbc java-8

example - ¿Se perdió la oportunidad de arreglar el manejo de la fecha JDBC en Java 8?



java.sql.date example (2)

¿Puede algún experto en Java 8 + JDBC decirme si algo está mal en el siguiente razonamiento? Y, si en los secretos de los dioses, ¿por qué no se ha hecho esto?

Un java.sql.Date es actualmente el tipo utilizado por JDBC para asignar al tipo DATE SQL, que representa una fecha sin hora y sin zona horaria. Pero esta clase está muy bien diseñada, ya que de hecho es una subclase de java.util.Date , que almacena un instante preciso en el tiempo, hasta el milisegundo.

Para representar la fecha 2015-09-13 en la base de datos, nos vemos obligados a elegir una zona horaria, analizar la cadena "2015-09-13T00: 00: 00.000" en esa zona horaria como java.util.Fecha para obtener un milisegundo valor, luego construya un java.sql.Date partir de este valor de milisegundos, y finalmente llame a setDate() en la declaración preparada, pasando un calendario que contiene la zona horaria elegida para que el controlador JDBC pueda volver a calcular correctamente la fecha 2015-09 -13 a partir de este valor de milisegundos. Este proceso se simplifica un poco al usar la zona horaria predeterminada en todas partes y no pasar un calendario.

Java 8 introduce una clase LocalDate, que es mucho mejor para el tipo de base de datos DATE, ya que no es un momento preciso en el tiempo y, por lo tanto, no depende de la zona horaria. Y Java 8 también introduce métodos predeterminados, que permitirían realizar cambios compatibles con versiones anteriores de las interfaces PreparedStatement y ResultSet.

Entonces, ¿no hemos perdido una gran oportunidad para limpiar el desorden en JDBC mientras seguimos manteniendo la compatibilidad con versiones anteriores? Java 8 podría simplemente haber agregado esos métodos predeterminados a PreparedStatement y ResultSet:

default public void setLocalDate(int parameterIndex, LocalDate localDate) { if (localDate == null) { setDate(parameterIndex, null); } else { ZoneId utc = ZoneId.of("UTC"); java.util.Date utilDate = java.util.Date.from(localDate.atStartOfDay(utc).toInstant()); Date sqlDate = new Date(utilDate.getTime()); setDate(parameterIndex, sqlDate, Calendar.getInstance(TimeZone.getTimeZone(utc))); } } default LocalDate getLocalDate(int parameterIndex) { ZoneId utc = ZoneId.of("UTC"); Date sqlDate = getDate(parameterIndex, Calendar.getInstance(TimeZone.getTimeZone(utc))); if (sqlDate == null) { return null; } java.util.Date utilDate = new java.util.Date(sqlDate.getTime()); return utilDate.toInstant().atZone(utc).toLocalDate(); }

Por supuesto, el mismo razonamiento se aplica al soporte de Instant para el tipo TIMESTAMP, y al soporte de LocalTime para el tipo TIME.


Para representar la fecha 2015-09-13 en la base de datos, nos vemos obligados a elegir una zona horaria, analizar la cadena "2015-09-13T00: 00: 00.000" en esa zona horaria como java.util.Fecha para obtener un milisegundo valor, luego construya un java.sql.Fecha a partir de este valor de milisegundos, y finalmente llame a setDate () en la declaración preparada, pasando un calendario que contiene la zona horaria elegida para que el controlador JDBC pueda volver a calcular correctamente la fecha 2015-09 -13 a partir de este valor de milisegundos

¿Por qué? Solo llama

Date.valueOf("2015-09-13"); // From String Date.valueOf(localDate); // From java.time.LocalDate

El comportamiento será el correcto en todos los controladores JDBC: la fecha local sin zona horaria. La operación inversa es:

date.toString(); // To String date.toLocalDate(); // To java.time.LocalDate

Nunca debe confiar en la dependencia de java.sql.Date en java.util.Date , y el hecho de que, por lo tanto, hereda la semántica de java.time.Instant través de Date(long) o Date.getTime()

Entonces, ¿no hemos perdido una gran oportunidad para limpiar el desorden en JDBC mientras seguimos manteniendo la compatibilidad con versiones anteriores? [...]

Depende. La especificación JDBC 4.2 especifica que puede vincular un tipo LocalDate mediante setObject(int, localDate) y obtener un tipo getObject(int, LocalDate.class) mediante getObject(int, LocalDate.class) , si el controlador está listo para eso. No es tan elegante como los métodos default más formales, como sugirió, por supuesto.


Respuesta corta: no, el manejo de la fecha y la hora se corrige en Java 8 / JDBC 4.2 porque es compatible con los tipos de fecha y hora de Java 8 . Esto no puede ser emulado usando métodos por defecto.

Java 8 podría simplemente haber agregado esos métodos predeterminados a PreparedStatement y ResultSet:

Esto no es posible por varias razones:

  • java.time.OffsetDateTime no tiene equivalente en java.sql
  • java.time.LocalDateTime rompe en las transiciones DST al convertir a través de java.sql.Timestamp porque la última depende de la zona horaria de la JVM, consulte el código a continuación
  • java.sql.Time tiene una resolución de milisegundos, pero java.time.LocalTime tiene una resolución de nanosegundos

Por lo tanto, necesita un soporte de controlador adecuado, los métodos predeterminados tendrían que convertirse a través de los tipos java.sql que introducen la pérdida de datos.

Si ejecuta este código en una zona horaria JVM con horario de verano, se interrumpirá. Busca la siguiente transición en la que los relojes están "adelantados" y elige un LocalDateTime que está justo en el medio de la transición. Esto es perfectamente válido porque Java LocalDateTime o SQL TIMESTAMP no tienen zona horaria y, por lo tanto, no tienen reglas de zona horaria y, por lo tanto, no tienen horario de verano. java.sql.Timestamp otro lado, java.sql.Timestamp está vinculado a la zona horaria de la JVM y, por lo tanto, está sujeto al horario de verano.

ZoneId systemTimezone = ZoneId.systemDefault(); Instant now = Instant.now(); ZoneRules rules = systemTimezone.getRules(); ZoneOffsetTransition transition = rules.nextTransition(now); assertNotNull(transition); if (!transition.getDateTimeBefore().isBefore(transition.getDateTimeAfter())) { transition = rules.nextTransition(transition.getInstant().plusSeconds(1L)); assertNotNull(transition); } Duration gap = Duration.between(transition.getDateTimeBefore(), transition.getDateTimeAfter()); LocalDateTime betweenTransitions = transition.getDateTimeBefore().plus(gap.dividedBy(2L)); Timestamp timestamp = java.sql.Timestamp.valueOf(betweenTransitions); assertEquals(betweenTransitions, timestamp.toLocalDateTime());