Маппинг даты и времени в Java
Сначала определяют семантику поля, затем тип SQL и Java. «Дата» и «момент» — разные доменные значения.
Java 8+
| Смысл | Java | SQL-идея |
|---|---|---|
| календарная дата | LocalDate | DATE |
| время суток | LocalTime | TIME |
| локальные дата-время без зоны | LocalDateTime | TIMESTAMP WITHOUT TIME ZONE |
| момент | Instant | тип/настройка для момента |
| момент со смещением | OffsetDateTime | зависит от диалекта |
| правила географической зоны | ZonedDateTime | часто момент + отдельный zone id |
Поддержка ORM и JDBC зависит от версии драйвера, JPA provider и СУБД. Маппинг проверяют интеграционным тестом, а не только типом поля.
@Entity
class PurchaseOrder {
@Id
private Long id;
private Instant createdAt;
private LocalDate deliveryDate;
}Instant подходит для факта создания. LocalDate — для календарного дня доставки, если время не нужно.
До Java 8
java.sql.Date— SQLDATE;java.sql.Time— SQLTIME;java.sql.Timestamp— дата-время;java.util.Date— момент, несмотря на название;Calendarдобавляет работу с зоной, но API mutable и неудобен.
В старом JPA для java.util.Date/Calendar применялся @Temporal:
@Temporal(TemporalType.TIMESTAMP)
private Date createdAt;С типами java.time @Temporal не используется.
Часовые пояса
Нужно согласовать:
- JVM default zone;
- JDBC driver/session zone;
- тип столбца и семантику СУБД;
- формат API;
- правила отображения пользователю.
Настройка Hibernate hibernate.jdbc.time_zone может нормализовать JDBC-обмен для поддерживаемых типов, но не исправляет неверно выбранную доменную семантику.
Проверки
Интеграционные тесты должны включать:
- не-UTC зону;
- границу суток/месяца;
- дробные секунды и их точность;
- переход DST для зон, где он существует;
- запись и чтение разными сессиями;
- миграцию старых данных.
Связано: TIMESTAMP, JPA Hibernate ORM, Маппинг Hibernate.
Вопросы
Чем Instant отличается от LocalDateTime? Instant — единственный момент; LocalDateTime не содержит зоны или смещения.
Нужен ли @Temporal для LocalDate? Нет, он предназначался для старых Date/Calendar.
Достаточно ли хранить OffsetDateTime для будущего расписания? Не всегда: смещение не содержит правил географической зоны.
Зачем интеграционный тест с реальной БД? Драйвер и СУБД могут преобразовывать зону и точность иначе, чем ожидает unit-тест.