Подтвердить что ты не робот

Отсутствует возможность исправить обработку данных JDBC в Java 8?

Может ли эксперт по Java 8 + JDBC сказать мне, если что-то не так в следующих рассуждениях? И, если в тайнах богов, почему это не было сделано?

java.sql.Date в настоящее время является типом, используемым JDBC для сопоставления с типом DATE SQL, который представляет дату без времени и без часового пояса. Но этот класс разработан ужасно, поскольку на самом деле он является подклассом java.util.Date, который хранит точный момент времени, вплоть до миллисекунды.

Чтобы представить дату 2015-09-13 в базе данных, мы вынуждены выбрать часовой пояс, проанализировав строку "2015-09-13T00: 00: 00.000" в этом часовом поясе как java.util.Date, чтобы получить миллисекунду. значение, затем создайте java.sql.Date из этого значения в миллисекундах и, наконец, вызовите setDate() для подготовленного оператора, передавая Calendar, содержащий часовой пояс, выбранный для того, чтобы драйвер JDBC мог правильно пересчитать дату 2015-09-13 из этого значения в миллисекундах. Этот процесс немного упрощается благодаря повсеместному использованию часового пояса по умолчанию и отсутствию календаря.

Java 8 представляет класс LocalDate, который намного лучше подходит для типа базы данных DATE, поскольку он не является точным моментом времени и, следовательно, не зависит от часового пояса. В Java 8 также представлены методы по умолчанию, которые позволяют вносить обратно совместимые изменения в интерфейсы PreparedStatement и ResultSet.

Итак, не упустили ли мы огромную возможность навести порядок в JDBC при сохранении обратной совместимости? Java 8 могла бы просто добавить эти методы по умолчанию в PreparedStatement и 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();
}

Конечно, то же самое относится и к поддержке Instant для типа TIMESTAMP, и к поддержке LocalTime для типа TIME.

4b9b3361

Ответ 1

Чтобы представить дату 2015-09-13 в базе данных, мы вынуждены выбрать часовой пояс, проанализируем строку "2015-09-13T00: 00: 00.000" в этот часовой пояс как java.util.Date для получить значение миллисекунды, а затем построить java.sql.Date из этого значения в миллисекундах и, наконец, вызвать setDate() в подготовленном операторе, передав календарь, содержащий выбранный часовой пояс, чтобы драйвер JDBC мог правильно пересчитать дату 2015-09-13 от этой миллисекундной стоимости

Почему? Просто позвоните

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

Поведение будет правильным во всех драйверах JDBC: локальная дата без часовой пояс. Обратная операция:

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

Вы не должны полагаться на java.sql.Date зависимость от java.util.Date и тот факт, что это таким образом, наследует семантику java.time.Instant через Date(long) или Date.getTime()

Итак, разве мы не упустили огромную возможность убрать беспорядок в JDBC, сохраняя при этом обратную совместимость? [...]

Это зависит. Спецификация JDBC 4.2 специфицирует, что вы можете привязать тип LocalDate через setObject(int, localDate) и для получения типа LocalDate с помощью getObject(int, LocalDate.class), если драйвер готов к этому. Не так элегантно, как более формальные методы default, как вы и предполагали, конечно.

Ответ 2

Короткий ответ: нет, обработка времени даты фиксирована в Java 8/JDBC 4.2, поскольку она поддерживает Java 8 Типы даты и времени, Эту функцию нельзя использовать с использованием методов по умолчанию.

Java 8 может просто добавить эти методы по умолчанию в PreparedStatement и ResultSet:

Это невозможно по нескольким причинам:

  • java.time.OffsetDateTime не имеет эквивалента в java.sql
  • java.time.LocalDateTime ломается при переходах DST при преобразовании через java.sql.Timestamp, потому что последний зависит от часового пояса JVM, см. код ниже
  • java.sql.Time имеет миллисекундное разрешение, но java.time.LocalTime имеет наносекундное разрешение.

Поэтому вам нужна правильная поддержка драйверов, методы по умолчанию должны были бы преобразовывать через типы java.sql, которые приводят к потере данных.

Если вы запустите этот код в часовом поясе JVM с летним временем, он сломается. Он ищет следующий переход, в котором часы "установлены вперед" и выбирает LocalDateTime, который находится прямо в середине перехода. Это совершенно верно, потому что Java LocalDateTime или SQL TIMESTAMP не имеют часового пояса и, следовательно, не имеют правил часового пояса и, следовательно, не имеют летнего времени. java.sql.Timestamp, с другой стороны, привязан к часовому поясу JVM и, следовательно, к летнему времени.

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());