1. Сучасний стан справ з часом
З часів, коли вигадали JDBC і стандартизували його інтерфейси, минуло років 20, і за цей час багато чого змінилося.
По-перше, світ став глобальним, і тепер один сервер може обслуговувати користувачів з усього світу. Швидкість інтернету цьому посприяла. Тому до SQL додався ще один тип даних для роботи з часом. Тепер типи виглядають так:
- DATE — зберігає дату: рік, місяць, день.
- TIME — зберігає час: години, хвилини, секунди.
- TIMESTAMP — зберігає конкретний момент часу: дата, час та мілісекунди.
- TIMESTAMP WITH TIME ZONE — TIMESTAMP та часова зона (ім'я зони або зсув).
По-друге, у Java з'явився DateTime API для глобальної роботи з часом. У ньому є такі класи:
- Дата та час:
- LocalDate
- LocalTime
- Точна мить:
- java.time.Instant
- java.time.LocalDateTime
- java.time.OffsetDateTime
- java.time.ZonedDateTime
- Час із часовим поясом:
- java.time.OffsetDateTime
- java.time.ZonedDateTime
Третій цікавий момент полягає в тому, що дуже багатьом SQL-клієнтам хотілося б отримувати час із сервера вже у своїй локальній зоні. Перетворювати час на льоту, звісно, можна, але це не зручно, та й помилки бувають.
Наприклад, я хочу отримати з бази всі завдання на сьогодні. SQL-сервер має функцію CURDATE() для цієї справи. Ось тільки сервер знаходиться в США, а я — в Японії. І хотілося б, щоб він мені повернув усі записи за “моє сьогодні”, а не “його сьогодні”.
Загалом SQL-сервер теж повинен вміти по-розумному працювати з клієнтами в різних часових зонах.
2. Сучасні проблеми потребують сучасних рішень
В принципі, нові типи з Java DateTime API та типи з SQL можна зручно зіставляти. Щоб представити тип DATE в Java потрібно використовувати клас java.time.LocalDate з JDK 8 DateTime API.
Тип TIME з бази даних можна представити двома типами з Java: java.time.LocalTime та java.time.OffsetTime. Теж нічого складного.
Конкретний момент часу, представлений типом TIMESTAMP в базі, Java можна представити 4 типами:
- java.time.Instant
- java.time.LocalDateTime
- java.time.OffsetDateTime
- java.time.ZonedDateTime
Ну, і нарешті, TIMESTAMP WITH TIME ZONE можна представити двома типами:
- java.time.OffsetDateTime
- java.time.ZonedDateTime
Оскільки ти вже багато чого знаєш про DateTime API, запам'ятати все це тобі не складе :)
Запишу у вигляді таблиці, так буде простіше:
SQL TYPE | Java Type |
---|---|
DATE | java.time.LocalDate |
TIME | java.time.LocalTime java.time.OffsetTime |
TIMESTAMP | java.time.Instant java.time.LocalDateTime java.time.OffsetDateTime java.time.ZonedDateTime |
TIMESTAMP WITH TIME ZONE | java.time.OffsetDateTime java.time.ZonedDateTime |
3. Отримання дати
У мене для тебе гарна новина. Перша за довгий час. Ми можемо обійти обмеження методу getDate(), який повертає тип java.sql Date.
Справа в тому, що об'єкт ResultSet має ще один цікавий метод — getObject(). Цей метод приймає два параметри — колонку і тип, — і повертає значення колонки, перетворене до вказаного типу. Загальний вид методу такий:
Ім'яКласу ім'я = getObject(column, Ім'яКласу);
І якщо ти хочеш перетворити тип DATE на тип java.time.LocalDate, потрібно написати щось на кшталт:
LocalDate localDate = results.getObject(4, LocalDate.class);
А будь-який TIMESTAMP взагалі можна перетворити на купу типів:
java.time.Instant instant = results.getObject(9, java.time.Instant.class);
java.time.LocalDateTime local = results.getObject(9, java.time. LocalDateTime.class);
java.time.OffsetDateTime offset = results.getObject(9, java.time. OffsetDateTime.class);
java.time.ZonedDateTime zoned = results.getObject(9, java.time. ZonedDateTime.class);
Важливо! Цей код не буде працювати, якщо у тебе застарілий MySQL JDBC Driver. Зверни увагу на версію “mysql-connector-java”, прописану у твоєму pom.xml, або додану до Libraries до налаштувань проєкту.
До речі, у такий же спосіб можна обійти і неможливість зберігати null у примітивних типів. Якщо стовпчик таблиці має тип INT, є пара способів отримати з неї null. Дивись приклад нижче:
Integer id1 = results.getObject(8, Integer.class); //так працюватиме
Integer id2 = results.getObject(8, int.class); //так теж працюватиме
int id3 = results.getObject(8, Integer.class); //метод поверне null, JVM кине NPE
int id4 = results.getObject(8, int.class); //метод поверне null, JVM кине NPE
4. Налаштування часового поясу в MySQL
З MySQL теж сталося багато цікавого. Як ти знаєш, під час створення з'єднання з MySQL до нього можна додавати різні параметри:mysql://localhost: 3306/db_scheme?ім'я=значення&ім'я=значення
Так ось, для роботи з часовими зонами до MySQL додали три параметри. Ці параметри ти можеш надсилати, коли встановлюєш з'єднання з сервером.
Нижче я наведу таблицю з ними:
Параметр | Значення | Значення за замовчуванням |
---|---|---|
connectionTimeZone | LOCAL | SERVER | user-zone | SERVER |
forceConnectionTimeZoneToSession | true | false | true |
preserveInstants | true | false | false |
За допомогою параметра connectionTimeZone ми обираємо часову зону (часовий пояс), в якій будуть виконуватися всі запити. З точки зору клієнта сервер працює у зазначеній часовій зоні.
Параметр forceConnectionTimeZoneToSession змушує ігнорувати змінну time_zone сесії та замінити її на connectionTimeZone.
І нарешті, параметр preserveInstants управляє перетворенням точного-моменту-часу між JVM timeZone та connectionTimeZone.
Найчастіші конфігурації такі:
connectionTimeZone=LOCAL & forceConnectionTimeZoneToSession=false — відповідає старому MySQL JDBC драйверу версії 5.1 з параметром useLegacyDatetimeCode=true.
connectionTimeZone=LOCAL & forceConnectionTimeZoneToSession=true — новий режим, який забезпечує найбільш природний спосіб обробки значень дати та часу.
connectionTimeZone=SERVER & preserveInstants=true — відповідає старому MySQL JDBC драйверу версії 5.1 з параметром useLegacyDatetimeCode=false.
connectionTimeZone=user_defined & preserveInstants=true — допомагає подолати ситуацію, коли часовий пояс сервера не може бути розпізнаний конектором, тому що він вказаний як загальна абревіатура, така як CET/CEST.
Так, дати — тема цікава, і проблем із ними багато. Як то кажуть: сміливий наскок — половина-спасіння.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ