18. 8. 2020 12:43 (aktualizováno)
Pavel Tavoda
Kedze som potreboval zistit ako funguje v novej verzii Hibernate ukladanie LocalDateTime a aky vplyv maju jednotlive nastavanie v pripade ze potrebujete bezat dislokovany cluster v roznych casovych zonach proti spolocnej DB urobil som nasledovny test. Urobil som si na to malicku RESTovu aplikaciu (majte zlutovanie s nazvami a mapovanim parametrov, povodne to mal byt iba maly test a teraz to koli vam prepisovat nebudem :-P).
Zdrojove kody
Takze Entita (vygenerovane nastrojom Sculptor http://sculptorgenerator.org podobne ako zvysny boilerplate kod), ked uz som test robil tak som ho urobil aj s triedou ZonedDateTime, v DB su pouzite datove typy timestamp a timestamptz:
@Entity
@Table(name = "TESTTIME")
public class TestTime extends AbstractDomainObject implements Identifiable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "ID")
private Long id;
@Column(name = "LOCALTIMENOW", nullable = false)
@Type(type = "org.hibernate.type.LocalDateTimeType")
@NotNull
private LocalDateTime localTimeNow;
@Column(name = "LOCALTIMEUTC", nullable = false)
@Type(type = "org.hibernate.type.LocalDateTimeType")
@NotNull
private LocalDateTime localTimeUtc;
@Column(name = "ZONEDTIMENOWDEF", nullable = false)
@Type(type = "org.hibernate.type.ZonedDateTimeType")
@NotNull
private ZonedDateTime zonedTimeNowDef;
@Column(name = "ZONEDTIMEUTCDEF", nullable = false)
@Type(type = "org.hibernate.type.ZonedDateTimeType")
@NotNull
private ZonedDateTime zonedTimeUtcDef;
@Column(name = "ZONEDTIMENOWUTC", nullable = false)
@Type(type = "org.hibernate.type.ZonedDateTimeType")
@NotNull
private ZonedDateTime zonedTimeNowUtc;
@Column(name = "ZONEDTIMEUTCUTC", nullable = false)
@Type(type = "org.hibernate.type.ZonedDateTimeType")
@NotNull
private ZonedDateTime zonedTimeUtcUtc;
@Column(name = "DEFAULTTZ", nullable = false, length = 100)
@NotNull
private String defaultTz;
@Column(name = "STRINGLOCALTIME", nullable = false, length = 100)
@NotNull
private String stringLocalTime;
... Contstructor + getters + setters vyhodene koli prehladnosti ...
}
A REST client, ostatny boilerplate nie je nijako zaujimavy, metoda save(TestTime testTime) a findById(Long id):
@RequestMapping(value = "/test", method = RequestMethod.GET)
public String testTimestamp(HttpServletRequest request) {
String tz = request.getParameter("tz");
if (tz == null) {
return "ERROR";
}
TimeZone.setDefault(TimeZone.getTimeZone(tz == null ? "UTC" : tz));
TestTime tt = new TestTime();
tt.setStringLocalTime("" + new Date());
tt.setDefaultTz(tz);
LocalDateTime now = LocalDateTime.now();
LocalDateTime utc = LocalDateTime.now(ZoneOffset.UTC);
ZoneId zoneUtc = ZoneId.of("UTC");
tt.setLocalTimeNow(now);
tt.setLocalTimeUtc(utc);
tt.setZonedTimeNowDef(now.atZone(ZoneId.systemDefault()));
tt.setZonedTimeUtcDef(utc.atZone(ZoneId.systemDefault()));
tt.setZonedTimeNowUtc(now.atZone(zoneUtc));
tt.setZonedTimeUtcUtc(utc.atZone(zoneUtc));
tt = testTimeService.save(tt);
return "javaDefaultTz = " + TimeZone.getDefault().getDisplayName()
+ " :: ID = " + tt.getId()
+ " :: createdTz = " + tt.getDefaultTz()
+ " :: createdStringTime = " + tt.getStringLocalTime()
+ " :: localTimeNow = " + tt.getLocalTimeNow()
+ " :: localTimeUtc = " + tt.getLocalTimeUtc()
+ " :: zonedTimeNowDef = " + tt.getZonedTimeNowDef()
+ " :: zonedTimeUtcDef = " + tt.getZonedTimeUtcDef()
+ " :: zonedTimeNowUtc = " + tt.getZonedTimeNowUtc()
+ " :: zonedTimeUtcUtc = " + tt.getZonedTimeUtcUtc()
;
}
@RequestMapping(value = "/testGet", method = RequestMethod.GET)
public String testTimestampGet(HttpServletRequest request) throws TestTimeNotFoundException {
String tz = request.getParameter("tz");
if (tz == null) {
return "ERROR";
}
TimeZone.setDefault(TimeZone.getTimeZone(tz == null ? "UTC" : tz));
String id = request.getParameter("id");
TestTime tt = testTimeService.findById(Long.parseLong(id));
return "javaDefaultTz = " + TimeZone.getDefault().getDisplayName()
+ " :: ID = " + tt.getId()
+ " :: createdTz = " + tt.getDefaultTz()
+ " :: createdStringTime = " + tt.getStringLocalTime()
+ " :: localTimeNow = " + tt.getLocalTimeNow()
+ " :: localTimeUtc = " + tt.getLocalTimeUtc()
+ " :: zonedTimeNowDef = " + tt.getZonedTimeNowDef()
+ " :: zonedTimeUtcDef = " + tt.getZonedTimeUtcDef()
+ " :: zonedTimeNowUtc = " + tt.getZonedTimeNowUtc()
+ " :: zonedTimeUtcUtc = " + tt.getZonedTimeUtcUtc()
;
}
DDL script:
CREATE TABLE TESTTIME (
ID BIGINT NOT NULL,
LOCALTIMENOW TIMESTAMP NOT NULL,
LOCALTIMEUTC TIMESTAMP NOT NULL,
ZONEDTIMENOWDEF TIMESTAMPTZ NOT NULL,
ZONEDTIMEUTCDEF TIMESTAMPTZ NOT NULL,
ZONEDTIMENOWUTC TIMESTAMPTZ NOT NULL,
ZONEDTIMEUTCUTC TIMESTAMPTZ NOT NULL,
DEFAULTTZ VARCHAR(100) NOT NULL,
STRINGLOCALTIME VARCHAR(100) NOT NULL
);
Metodika
Teraz samotny test, vytvoril som jeden objekt v casovej zone Europe/Bratislava (GMT+2) a druhy objekt v zone America/Dominica (GMT-4). Potom som obidva zaznamy precital v obidvoch casovych zonach a naviac este aj v casovej zone Asia/Dubai (GMT+4). Cely tento test som spustil 2×, raz bez nastavenia premennej hibernate.jdbc.time_zone a druhy krat s hibernate.jdbc.time_zone=UTC. Databaza bezala v casovej zone Europe/Bratislava (psql → show timezone).
Pouzity Hibernate 5.4.17.Final a Java 11.0.6.
Prehlad casovych zon
America/Dominica |
GMT-4 |
Atlantic Standard Time |
Europe/Bratislava |
GMT+2 |
Central European Standard Time |
Asia/Dubai |
GMT+4 |
Gulf Standard Time |
Prvy test (bez hibernate.jdbc.time_zone)
Cas vlozenia prveho objektu v jednotlivych zonach
America/Dominica |
01:46 |
UTC |
05:46 |
Europe/Bratislava |
07:46 |
Asia/Dubai |
09:46 |
Vytvorenie
Europe/Bratislava GMT+2
javaDefaultTz = Central European Standard Time
ID = 436
createdTz = Europe/Bratislava
createdStringTime = Mon Aug 17 07:46:01 CEST 2020
localTimeNow = 2020-08-17T07:46:01.943556
localTimeUtc = 2020-08-17T05:46:01.943571
zonedTimeNowDef = 2020-08-17T07:46:01.943556+02:00[Europe/Bratislava]
zonedTimeUtcDef = 2020-08-17T05:46:01.943571+02:00[Europe/Bratislava]
zonedTimeNowUtc = 2020-08-17T07:46:01.943556Z[UTC]
zonedTimeUtcUtc = 2020-08-17T05:46:01.943571Z[UTC]
America/Dominica GMT-4
javaDefaultTz = Atlantic Standard Time
ID = 437
createdTz = America/Dominica
createdStringTime = Mon Aug 17 01:46:22 AST 2020
localTimeNow = 2020-08-17T01:46:22.912326
localTimeUtc = 2020-08-17T05:46:22.912337
zonedTimeNowDef = 2020-08-17T01:46:22.912326-04:00[America/Dominica]
zonedTimeUtcDef = 2020-08-17T05:46:22.912337-04:00[America/Dominica]
zonedTimeNowUtc = 2020-08-17T01:46:22.912326Z[UTC]
zonedTimeUtcUtc = 2020-08-17T05:46:22.912337Z[UTC]
Obsah DB
id |
localTimeNow |
localTimeUtc |
zonedTimeNowDef |
zonedTimeUtcDef |
zonedTimeNowUtc |
zonedTimeUtcUtc |
defaultTz |
stringLocalTime |
436 |
2020–08–17 07:46:01.943556 |
2020–08–17 05:46:01.943571 |
2020–08–17 07:46:01.943556+02 |
2020–08–17 05:46:01.943571+02 |
2020–08–17 09:46:01.943556+02 |
2020–08–17 07:46:01.943571+02 |
Europe/Bratislava |
Mon Aug 17 07:46:01 CEST 2020 |
437 |
2020–08–17 01:46:22.912326 |
2020–08–17 05:46:22.912337 |
2020–08–17 07:46:22.912326+02 |
2020–08–17 11:46:22.912337+02 |
2020–08–17 03:46:22.912326+02 |
2020–08–17 07:46:22.912337+02 |
America/Dominica |
Mon Aug 17 01:46:22 AST 2020 |
Citanie
America/Dominica GMT-4
javaDefaultTz = Atlantic Standard Time
ID = 436
createdTz = Europe/Bratislava
createdStringTime = Mon Aug 17 07:46:01 CEST 2020
localTimeNow = 2020-08-17T07:46:01.943556
localTimeUtc = 2020-08-17T05:46:01.943571
zonedTimeNowDef = 2020-08-17T01:46:01.943556-04:00[America/Dominica]
zonedTimeUtcDef = 2020-08-16T23:46:01.943571-04:00[America/Dominica]
zonedTimeNowUtc = 2020-08-17T03:46:01.943556-04:00[America/Dominica]
zonedTimeUtcUtc = 2020-08-17T01:46:01.943571-04:00[America/Dominica]
javaDefaultTz = Atlantic Standard Time
ID = 437
createdTz = America/Dominica
createdStringTime = Mon Aug 17 01:46:22 AST 2020
localTimeNow = 2020-08-17T01:46:22.912326
localTimeUtc = 2020-08-17T05:46:22.912337
zonedTimeNowDef = 2020-08-17T01:46:22.912326-04:00[America/Dominica]
zonedTimeUtcDef = 2020-08-17T05:46:22.912337-04:00[America/Dominica]
zonedTimeNowUtc = 2020-08-16T21:46:22.912326-04:00[America/Dominica]
zonedTimeUtcUtc = 2020-08-17T01:46:22.912337-04:00[America/Dominica]
Europe/Bratislava GMT+2
javaDefaultTz = Central European Standard Time
ID = 436
createdTz = Europe/Bratislava
createdStringTime = Mon Aug 17 07:46:01 CEST 2020
localTimeNow = 2020-08-17T07:46:01.943556
localTimeUtc = 2020-08-17T05:46:01.943571
zonedTimeNowDef = 2020-08-17T07:46:01.943556+02:00[Europe/Bratislava]
zonedTimeUtcDef = 2020-08-17T05:46:01.943571+02:00[Europe/Bratislava]
zonedTimeNowUtc = 2020-08-17T09:46:01.943556+02:00[Europe/Bratislava]
zonedTimeUtcUtc = 2020-08-17T07:46:01.943571+02:00[Europe/Bratislava]
javaDefaultTz = Central European Standard Time
ID = 437
createdTz = America/Dominica
createdStringTime = Mon Aug 17 01:46:22 AST 2020
localTimeNow = 2020-08-17T01:46:22.912326
localTimeUtc = 2020-08-17T05:46:22.912337
zonedTimeNowDef = 2020-08-17T07:46:22.912326+02:00[Europe/Bratislava]
zonedTimeUtcDef = 2020-08-17T11:46:22.912337+02:00[Europe/Bratislava]
zonedTimeNowUtc = 2020-08-17T03:46:22.912326+02:00[Europe/Bratislava]
zonedTimeUtcUtc = 2020-08-17T07:46:22.912337+02:00[Europe/Bratislava]
Asia/Dubai GMT+4
javaDefaultTz = Gulf Standard Time
ID = 436
createdTz = Europe/Bratislava
createdStringTime = Mon Aug 17 07:46:01 CEST 2020
localTimeNow = 2020-08-17T07:46:01.943556
localTimeUtc = 2020-08-17T05:46:01.943571
zonedTimeNowDef = 2020-08-17T09:46:01.943556+04:00[Asia/Dubai]
zonedTimeUtcDef = 2020-08-17T07:46:01.943571+04:00[Asia/Dubai]
zonedTimeNowUtc = 2020-08-17T11:46:01.943556+04:00[Asia/Dubai]
zonedTimeUtcUtc = 2020-08-17T09:46:01.943571+04:00[Asia/Dubai]
javaDefaultTz = Gulf Standard Time
ID = 437
createdTz = America/Dominica
createdStringTime = Mon Aug 17 01:46:22 AST 2020
localTimeNow = 2020-08-17T01:46:22.912326
localTimeUtc = 2020-08-17T05:46:22.912337
zonedTimeNowDef = 2020-08-17T09:46:22.912326+04:00[Asia/Dubai]
zonedTimeUtcDef = 2020-08-17T13:46:22.912337+04:00[Asia/Dubai]
zonedTimeNowUtc = 2020-08-17T05:46:22.912326+04:00[Asia/Dubai]
zonedTimeUtcUtc = 2020-08-17T09:46:22.912337+04:00[Asia/Dubai]
Druhy test (hibernate.jdbc.time_zone=UTC)
Priblizny cas vlozenia prveho objektu v jednotlivych zonach
America/Dominica |
02:02 |
UTC |
06:02 |
Europe/Bratislava |
08:02 |
Asia/Dubai |
10:02 |
Vytvorenie
Europe/Bratislava GMT+2
javaDefaultTz = Central European Standard Time
ID = 439
createdTz = Europe/Bratislava
createdStringTime = Mon Aug 17 08:02:03 CEST 2020
localTimeNow = 2020-08-17T08:02:03.888033
localTimeUtc = 2020-08-17T06:02:03.888100
zonedTimeNowDef = 2020-08-17T08:02:03.888033+02:00[Europe/Bratislava]
zonedTimeUtcDef = 2020-08-17T06:02:03.888100+02:00[Europe/Bratislava]
zonedTimeNowUtc = 2020-08-17T08:02:03.888033Z[UTC]
zonedTimeUtcUtc = 2020-08-17T06:02:03.888100Z[UTC]
America/Dominica GMT-4
javaDefaultTz = Atlantic Standard Time
ID = 440
createdTz = America/Dominica
createdStringTime = Mon Aug 17 02:02:28 AST 2020
localTimeNow = 2020-08-17T02:02:28.691584
localTimeUtc = 2020-08-17T06:02:28.691599
zonedTimeNowDef = 2020-08-17T02:02:28.691584-04:00[America/Dominica]
zonedTimeUtcDef = 2020-08-17T06:02:28.691599-04:00[America/Dominica]
zonedTimeNowUtc = 2020-08-17T02:02:28.691584Z[UTC]
zonedTimeUtcUtc = 2020-08-17T06:02:28.691599Z[UTC]
Obsah DB
id |
localTimeNow |
localTimeUtc |
zonedTimeNowDef |
zonedTimeUtcDef |
zonedTimeNowUtc |
zonedTimeUtcUtc |
defaultTz |
stringLocalTime |
439 |
2020–08–17 06:02:03.888033 |
2020–08–17 04:02:03.8881 |
2020–08–17 08:02:03.888033+02 |
2020–08–17 06:02:03.8881+02 |
2020–08–17 10:02:03.888033+02 |
2020–08–17 08:02:03.8881+02 |
Europe/Bratislava |
Mon Aug 17 08:02:03 CEST 2020 |
440 |
2020–08–17 06:02:28.691584 |
2020–08–17 10:02:28.691599 |
2020–08–17 08:02:28.691584+02 |
2020–08–17 12:02:28.691599+02 |
2020–08–17 04:02:28.691584+02 |
2020–08–17 08:02:28.691599+02 |
America/Dominica |
Mon Aug 17 02:02:28 AST 2020 |
Citanie
America/Dominica GMT-4
javaDefaultTz = Atlantic Standard Time
ID = 439
createdTz = Europe/Bratislava
createdStringTime = Mon Aug 17 08:02:03 CEST 2020
localTimeNow = 2020-08-17T02:02:03.888033
localTimeUtc = 2020-08-17T00:02:03.888100
zonedTimeNowDef = 2020-08-17T02:02:03.888033-04:00[America/Dominica]
zonedTimeUtcDef = 2020-08-17T00:02:03.888100-04:00[America/Dominica]
zonedTimeNowUtc = 2020-08-17T04:02:03.888033-04:00[America/Dominica]
zonedTimeUtcUtc = 2020-08-17T02:02:03.888100-04:00[America/Dominica]
javaDefaultTz = Atlantic Standard Time
ID = 440
createdTz = America/Dominica
createdStringTime = Mon Aug 17 02:02:28 AST 2020
localTimeNow = 2020-08-17T02:02:28.691584
localTimeUtc = 2020-08-17T06:02:28.691599
zonedTimeNowDef = 2020-08-17T02:02:28.691584-04:00[America/Dominica]
zonedTimeUtcDef = 2020-08-17T06:02:28.691599-04:00[America/Dominica]
zonedTimeNowUtc = 2020-08-16T22:02:28.691584-04:00[America/Dominica]
zonedTimeUtcUtc = 2020-08-17T02:02:28.691599-04:00[America/Dominica]
Europe/Bratislava GMT+2
javaDefaultTz = Central European Standard Time
ID = 439
createdTz = Europe/Bratislava
createdStringTime = Mon Aug 17 08:02:03 CEST 2020
localTimeNow = 2020-08-17T08:02:03.888033
localTimeUtc = 2020-08-17T06:02:03.888100
zonedTimeNowDef = 2020-08-17T08:02:03.888033+02:00[Europe/Bratislava]
zonedTimeUtcDef = 2020-08-17T06:02:03.888100+02:00[Europe/Bratislava]
zonedTimeNowUtc = 2020-08-17T10:02:03.888033+02:00[Europe/Bratislava]
zonedTimeUtcUtc = 2020-08-17T08:02:03.888100+02:00[Europe/Bratislava]
javaDefaultTz = Central European Standard Time
ID = 440
createdTz = America/Dominica
createdStringTime = Mon Aug 17 02:02:28 AST 2020
localTimeNow = 2020-08-17T08:02:28.691584
localTimeUtc = 2020-08-17T12:02:28.691599
zonedTimeNowDef = 2020-08-17T08:02:28.691584+02:00[Europe/Bratislava]
zonedTimeUtcDef = 2020-08-17T12:02:28.691599+02:00[Europe/Bratislava]
zonedTimeNowUtc = 2020-08-17T04:02:28.691584+02:00[Europe/Bratislava]
zonedTimeUtcUtc = 2020-08-17T08:02:28.691599+02:00[Europe/Bratislava]
Asia/Dubai GMT+4
javaDefaultTz = Gulf Standard Time
ID = 439
createdTz = Europe/Bratislava
createdStringTime = Mon Aug 17 08:02:03 CEST 2020
localTimeNow = 2020-08-17T10:02:03.888033
localTimeUtc = 2020-08-17T08:02:03.888100
zonedTimeNowDef = 2020-08-17T10:02:03.888033+04:00[Asia/Dubai]
zonedTimeUtcDef = 2020-08-17T08:02:03.888100+04:00[Asia/Dubai]
zonedTimeNowUtc = 2020-08-17T12:02:03.888033+04:00[Asia/Dubai]
zonedTimeUtcUtc = 2020-08-17T10:02:03.888100+04:00[Asia/Dubai]
javaDefaultTz = Gulf Standard Time
ID = 440
createdTz = America/Dominica
createdStringTime = Mon Aug 17 02:02:28 AST 2020
localTimeNow = 2020-08-17T10:02:28.691584
localTimeUtc = 2020-08-17T14:02:28.691599
zonedTimeNowDef = 2020-08-17T10:02:28.691584+04:00[Asia/Dubai]
zonedTimeUtcDef = 2020-08-17T14:02:28.691599+04:00[Asia/Dubai]
zonedTimeNowUtc = 2020-08-17T06:02:28.691584+04:00[Asia/Dubai]
zonedTimeUtcUtc = 2020-08-17T10:02:28.691599+04:00[Asia/Dubai]
Zaver
Pre mna vzniklo niekolko zaujimavych zisteni. Co sa tyka LocalDateTime ak chceme vidiet na vsetkych serveroch rovnaky cas v UTC musime si ho zarovnat v kode a nepouzivat premennu time_zone. To znamena tak ako je v prvom teste v stlpci localTimeUtc. Vsetky localTimeUtc ukazuju 05:46 bez ohladu na tom v akej casovej zone boli vytvorene a kde su citane, v pripade ze sa nastavi premenna time_zone tak hibernate urobi ‚nadpracu‘ a shiftne udaje znovu co sposobi ich prehadzanie pre stlpec localTimeUtc tak ako je ukazane v druhom teste. Treba si vsimnut aj problem v prvom teste v DB v stlpci localTimeNow kde sa udaje ulozia pre casovu zonu kde bol udaj vytvoreny, takze prvy objekt ma cas vlozenia 07:46 a druhy aj ked bol vlozeny neskor z pohladu plynutia casu ma v DB hodnotu 01:46. To znamena ze aj keby ste si niekde pamatali timezonu kde bol udaj vytvoreny, mohlo by to sposobovat problemy pri triedeni cez SQL ORDER BY.
V druhom teste zas dopadol lepsie stlpec localTimeNow. V DB su ulozene udaje spravne v UTC (06:02) a pri vyberani udajov dojde ku spravnemu casovemu posunutiu takze sice nevidime v kazdej casovej zone rovnaky udaj ale vidime cas v ktorom bol zaznam vytvoreny akoby bol vytvoreny prave na tom prislusnom servri (02:02, 08:02, 10:02).
Co sa tyka ZonedDateTime tak jedine zaujimave zistenie je ze zonedTimeNowUtc a zonedTimeUtcUtc sa nijako nelisia, to sa sice dalo ocakavat ale ja davam prednost experimentu.
Ostatne zavery si musite urobit sami po nastudovani vysledkov a pouzit taky mod zarovnavania casu a nastavenia hibernate ktory vam vyhovuje. Neda sa urcit ktory je spravny zalezi od sposobu pozadovaneho vyuzitia. V kazdom pripade sa da aspon zistit ktore su nespravne ;-).