Java + Postgres: time shift between app and database - java

One of field of my table has following format:
trackdate TIMESTAMP WITH TIME ZONE NOT NULL,
POJO:
private Timestamp trackDate;
where Timestamp is java.sql.Timestamp.
The problem is that when I have a date, for instance, 2017-05-08 22:16:15.551 in Europe/Kiev time zone, the database adds 3 hours (Europe/Kiev itself) and I have 2017-05-09 01:16:27.551+03 there.
hibernate mapping is pretty simple:
<property name="trackDate" type="timestamp">
<column name="TRACKDATE" not-null="true"/>
</property>
No any additional conversions between app and database are. The Tomcat starts with:
export TOMCAT_TIMEZONE="-Duser.timezone=Europe/Kiev"
Database' time zone is also set to:
timezone = 'Europe/Kiev'
What is the problem? Why I see additional three hours?

Seems like your database thinks stores your time as UTC and converts it to Kiev-Time. Some litterature on hibernate and timestamps: http://in.relation.to/2016/09/12/jdbc-time-zone-configuration-property/ Look at workarounds at the bottom of the page...

Your code and database are working correctly. The time in the database is listing the time in one of the standard Postgre time stamp forms documented here (see section 8.5.1.3, "Time Stamps", also this table might help clear things up as well).
In other words, 2017-05-09 01:16:27.551+03 has the same meaning of 2017-05-08 22:16:15.551 Europe/Kiev, with +03 in 2017-05-09 01:16:27.551+03 indicating the 3-hour offset that the "Europe/Kiev" timezone has from UTC.

Related

Mismatch between DATETIME values in H2 and MySQL databases inserted from Java/Kotlin

TLDR:
How to always save correct UTC date time value into the field of DATETIME type of both H2 and MySQL databases with Java Hibernate?
Full context:
I have a table with DATETIME field in the database and I want to insert rows where:
by default (when no value is given) will be stored current UTC time
or if the UTC date time is given, it should be stored without
additional timezone conversions.
The problem that it has to run on local H2 database as well as on local mysql inside Docker and on external AWS RDS MySQL instance.
And I'm having a hard time making datetime to be saved correctly in all 3 instances.
So far it's either local and aws mysql instances are getting correct values but local H2 gets wrong value, or other way around, when local H2 gets correct value but MySQL instances are getting wrong values.
Here are shortened snippets of kotlin code that I have.
Code that works for H2 but doesn't work for MySQL in Docker and AWS:
#Entity
data class SomeEntity(
val createdAt: LocalDateTime = LocalDateTime.now(Clock.systemUTC())
// If createdAt is not explicitly given when saving new entry in db, the default value will be used
// and H2 will get correct value of '2019-03-28 12:36:56',
// but it will be wrong for MySQL, it will get '2019-03-28 11:36:56'
)
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd H:mm:ss")
createdAt = LocalDateTime.parse("2012-11-30 16:13:21", dateTimeFormatter)
// In this case when createdAt is explicitly given when saving new entry in db,
// H2 gets correct value '2012-11-30 16:13:21',
// but MySQL DBs will get wrong value of '2012-11-30 17:13:21'
Code that works for MySQL in Docker and AWS but doesn't work for H2:
#Entity
data class SomeEntity(
val createdAt: Date = Date()
// If createdAt is not explicitly given when saving new entry in db, the default value will be used
// and MySQL DBs will get correct value of '2019-03-28 12:36:56'
// but it will be wrong for H2 as it will get '2019-03-28 13:36:56' (my current local time instead of UTC)
)
val dateTimeFormatter = SimpleDateFormat("yyyy-MM-dd H:mm:ss")
dateTimeFormatter.timeZone = TimeZone.getTimeZone("UTC")
createdAt = dateTimeFormatter.parse("2012-11-30 16:13:21")
// In this case when createdAt is explicitly given when saving new entry in db,
// MySQL DBs will get correct value '2012-11-30 16:13:21',
// but H2 will get wrong value of '2012-11-30 17:13:21'
This runs on: Spring Boot 2.1.3, Hibernate Core 5.3.7, MySQL 8.0.13, H2 1.4.197
I've seen bunch of questions online and also on stackoverflow but unfortunately none of the solutions could fix my problem.
Update
After additional debugging with multiple approaches, looking through the logs of Hibernate, H2 and MySQL, it looks like UTC time is treated exactly opposite way between H2 and MySQL.
Saving to local H2:
[wrong] using Date, when UTC is 09:55, Hibernate logs value "Fri Mar 29 10:55:09 CET 2019", it's saved as "2019-03-29 10:55:09.412".
[wrong] using Instant, when UTC is 16:48, Hibernate logs value "2019-03-28T16:48:18.270Z", it's saved as "2019-03-28 17:48:18.27".
[wrong] using OffsetDateTime, when UTC is 10:11, Hibernate logs value "2019-03-29T10:11:30.672Z", it's saved as "2019-03-29 11:11:30.672".
[correct] using LocalDateTime, when UTC is 16:50, Hibernate logs value "2019-03-28T16:50:20.697", it's saved as "2019-03-28 16:50:20.697".
Saving to MySQL in local docker:
[correct] using Date, when UTC is 09:51, Hibernate logs value "Fri Mar 29 10:51:56 CET 2019", it's saved as "2019-03-29 09:51:56.519".
[correct] using Instant, when UTC is 09:38, Hibernate logs value "2019-03-29T09:38:59.172Z", it's saved as "2019-03-29 09:38:59.172".
[correct] using OffsetDateTime, when UTC is 10:14, Hibernate logs value "2019-03-29T10:14:22.658Z", it's saved as "2019-03-29 10:14:22.658".
[wrong] using LocalDateTime, when UTC is 16:57, Hibernate logs value "2019-03-28T16:57:35.631", it's saved as "2019-03-28 15:57:35.631".
So looks like the fix was to set UTC timezone for the JDBC connection (instead of JVM):
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
and it relies on using Instant for keeping the value on Java side and with created_at field having DATETIME type in MySQL and H2.
The shortened resulting kotlin code is:
#Entity
data class SomeEntity(
val createdAt: Instant = Instant.now() // default created date is current UTC time
)
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd H:mm:ss")
createdAt = LocalDateTime.parse("2012-11-30 16:13:21", dateTimeFormatter).toInstant(ZoneOffset.UTC)
Ideas taken from comments of "Joop Eggen", this and this article.
Bonus
I guess if you're reading this, you might also need help with debugging SQL queries.
1. To print SQL queries running on H2 add TRACE_LEVEL_FILE=2 and TRACE_LEVEL_SYSTEM_OUT=2 to connection string (see here):
spring.datasource.url=jdbc:h2:mem:dbname;TRACE_LEVEL_FILE=2;TRACE_LEVEL_SYSTEM_OUT=2;
2. To enable hibernate logs:
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.type=TRACE
3. To enable query logs in MySQL (one of the approaches, don't use on production db!):
SET GLOBAL general_log = 'ON';
SET global log_output = 'table';
select * from mysql.general_log ORDER BY event_time DESC;

Hibernate saves/retrieves date minus day if application uses another timezone than MySQL

I have an application started on tomcat on MACHINE_A with timezone GMT+3.
I use remote MySQL server started on MACHINE_B with timezone UTC.
We use spring-data-jpa for persistence.
As an example of the problem, I will show the repository:
public interface MyRepository extends JpaRepository<MyInstance, Long> {
Optional<MyInstance> findByDate(LocalDate localDate);
}
If I pass localDate for 2018-09-06, I get entities where the date is 2018-09-05(previous day)
In the logs I see:
2018-09-06 18:17:27.783 TRACE 13676 --- [nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [DATE] - [2018-09-06]
I googled that question a lot and found several articles with the same content(for example https://moelholm.com/2016/11/09/spring-boot-controlling-timezones-with-hibernate/)
So, I have the following application.yml:
spring:
datasource:
url: jdbc:mysql://localhost:3306/MYDB?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC
username: root
password: *****
jpa:
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
properties:
hibernate:
show_sql: true
use_sql_comments: true
format_sql: true
type: trace
jdbc:
time_zone: UTC
But it doesn't help.
We use the following connector:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
</dependency>
How can I resolve my problem?
P.S.
I tried to run both applications with the same time zone. In this case, everything works as expected.
P.S.2
I tried to use MySQL driver 6.0.6 version but it doesn't change anything.
If you're using LocalDate in Java, you should use a DATE column in MySQL. This way the problem will be solved.
If you use LocalDateTime, try setting the property like this in Spring Boot:
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
To see it working in action, you can find a test case in my High-Performance Java Persistence GitHub repository which demonstrates how this setting works with MySQL.
I faced similar issues while creating some integration tests for a spring-boot application using hibernate. The database I used here was postgreSQL.
As another answer correctly points out, you can set the hibernate.jdbc.time_zone=UTC property like discribed. Nevermind this didn't solve my issues, so I had to set the JVM default time zone with the help of the following in my spring-boot applications main class:
#PostConstruct
public void init(){
TimeZone.setDefault(TimeZone.getTimeZone("UTC")); // It will set UTC timezone
System.out.println("Spring boot application running in UTC timezone :"+new Date()); // It will print UTC timezone
}
This should also solve your problems. You can gather more informations here.
Reason
I guess your problem (retrieving date - 1 day) comes from your specific setup. If your application is running in UTC and requesting timestamps from a database in GMT+3 it resolves in a earlier date, because the applications context (JVM and Hibernate are responsible here) is 3 hours behind the database context in UTC. Simple example:
2018-12-02 00:00:00 - 3hours = 2018-12-01 21:00:00
As you are only looking to the dates: 2018-12-02 - 3hours = 2018-12-01
There was a bug in the MySQL Connector prior to version 8.0.22, see Spring data query for localdate returns wrong entries - minus one day
Ideally, your both servers should be in same time zone and preferred one be in UTC time zone. And to show correct time to user in his timezone; you parse it in browser itself. And while retrieving data from DB; you use UTC time. This way you will not have issue while fetching data from DB
In MySQL...
TIMESTAMP internally stores UTC, but converts to/from the server's timezone based on two settings. Check those settings via SHOW VARIABLES LIKE '%zone%'; Properly configured, the reader may see a different time than the writer (based on tz settings).
DATE and DATETIME take whatever you give it. There is no tz conversion between the string in the client and what is stored in the table. Think of it a storing a picture of a clock. The reader will see the same time string that the writer wrote.
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
It's used when you are working TimeZoned Date, but from your logs it seems you are not passing TimeZone:
binding parameter [1] as [DATE] - [2018-09-06]
Try to remote property:
spring.jpa.properties.hibernate.jdbc.time_zone=UTC
If you add the following parsing to your HQL query, it will return a date without any time zone format or time of day. This is a quick workaround to your issue.
select DATE_FORMAT(date,'%Y-%m-%d') from Entity
I've done everything as instructed in the answers before, like
Adding spring.jpa.properties.hibernate.jdbc.time_zone=America/Sao_Paulo
Setting default timezone: TimeZone.setDefault(TimeZone.getTimeZone("America/Sao_Paulo"))
but none of them worked until I add the parameter serverTimezone=America/Sao_Paulo in my JDBC url:
jdbc:mysql://localhost:3306/aurorabuzz-test?serverTimezone=America/Sao_Paulo
Now it's working just fine!

mysql-connector-java upgrade to 8.0.11 changes all the date values retrieved from the db

For a Spring project, the mysql-connector-java has been migrated from 6.0.6 to 8.0.11.
Thus with 8.0.11 the problem is the following:
Caused by: com.mysql.cj.exceptions.InvalidConnectionAttributeException:
The server time zone value 'PET' is unrecognized or represents more than one time zone.
You must configure either the server or JDBC driver (via the serverTimezone configuration property)
to use a more specifc time zone value if you want to utilize time zone support.
After to do a research
The server time zone value 'AEST' is unrecognized or represents more than one time zone
the solution is change the URL (I don't want return to a previous release)
from: mysql.jdbcUrl = jdbc:mysql://localhost:3306/web_v01?useSSL=false
to: mysql.jdbcUrl = jdbc:mysql://localhost:3306/web_v01?useSSL=false&serverTimezone=UTC
Observe the addition of &serverTimezone=UTC
In my DB I have the following:
mysql> select * from persona;
+-----+--------------+-------------+------------+
| id | nombre | apellido | fecha |
+-----+--------------+-------------+------------+
...
| 088 | Something | Something | 1981-07-06 |
...
+-----+--------------+-------------+------------+
When the Spring application does a retrieve from the db through the RowMapper<Persona> I can confirm that rs.getDate("fecha") returns 1981-07-05 (observe the day has been decreased by one, it is not correct)
If the mysql-connector-java returns to 6.0.6 and thus mysql.jdbcUrl = jdbc:mysql://localhost:3306/web_v01?useSSL=false (no serverTimezone=UTC) rs.getDate("fecha") returns 1981-07-06 (how is expected)
Thus how fix this working with 8.0.11?.
I want have the same behaviour when serverTimezone never was declared from the beginning, of course avoiding the exception.
Therefore the solution would be better if is take it in consideration that does not matter what value for serverTimezone was declared.
There are several attributes related to timezone:
useTimezone:
Convert time/date types between client and server time zones (true/false, defaults to 'false')? This is part of the legacy date-time code, thus the property has an effect only when "useLegacyDatetimeCode=true."
Default: false
useLegacyDatetimeCode:
Use code for DATE/TIME/DATETIME/TIMESTAMP handling in result sets and statements that consistently handles time zone conversions from client to server and back again, or use the legacy code for these datatypes that has been in the driver for backwards-compatibility? Setting this property to 'false' voids the effects of "useTimezone," "useJDBCCompliantTimezoneShift," "useGmtMillisForDatetimes," and "useFastDateParsing."
Default: true
serverTimezone:
Override detection/mapping of time zone. Used when time zone from server doesn't map to Java time zone
If mysql-connector-java is 5.1, you should specify three attributes, like this:
jdbc:mysql://host:port/dbname?useTimezone=true&useLegacyDatetimeCode=true&serverTimezone=GMT%2B08:00
If mysql-connector-java is 8.0, you should specify one attribute, like this:
jdbc:mysql://host:port/dbname?serverTimezone=GMT%2B08:00
Try to use
jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=CST
For my case the most unlikely suspect was jackson.
If your using Spring Boot add the following property to application.properties
spring.jackson.time-zone=Asia/Colombo
I've answered this with detail here -> https://stackoverflow.com/a/68016006/9183199

Hibernate java.time.LocalTime property saves with offset but reads without

I have a java.time.LocalTime property and save it within servlet:
LocalTime localTimeFrom = LocalTime.of(hour, minute);
notificationPreferences.setQuietFrom(localTimeFrom);
hibernateSession.saveOrUpdate(notificationPreferences);
After save I see in database time saved with offset (notificationPreferences.getQuietFrom().toString() => "00:59" , but in database is 22:59). MySQL column type TIME. But when I read this property within other hibernate session I got the time without offset conversion.
NotificationPreferences np =
(NotificationPreferences) s.get(NotificationPreferences.class, 7889);
So the np.getQuietFrom().toString() gives me "22:59". Why this happen?
What should I check to resolve time zone conversion?

Problem persisting a java.util.Date into MySql using Hibernate

I've been debugging this problem for the last couple of hours with no success and figured I'd throw it out to SO and see where that goes.
I'm developing a Java program that persists data into a MySql database using Hibernate and the DAO/DTO pattern. In my database, I have a memberprofile table with a firstLoginDate column. In the database, the SQL type of that column is a DateTime. The corresponding section of the Hibernate XML file is
<property name="firstLoginDate" type="timestamp">
<column name="firstLoginDate" sql-type="DATETIME"/>
</property>
However, when I try to save a Date into that table (in Java), the "date" (year/month/day) part is persisted correctly, but the "time of day" part (hours:minutes:seconds) is not. For instance, if I try to save a Java date representing 2009-09-01 14:02:23, what ends up in the database is instead 2009-09-01 00:00:00.
I've already confirmed that my own code isn't stomping on the time component; as far as I can see source code (while debugging) the time component remains correct. However, after committing changes, I can examine the relevant row using the MySql Query Browser (or just grabbing back out from the database in my Java code), and indeed the time component is missing. Any ideas?
I did try persisting a java.sql.Timestamp instead of a java.util.Date, but the problem remained. Also, I have a very similar column in another table that does not exhibit this behavior at all.
I expect you guys will have questions, so I'll edit this as needed. Thanks!
Edit #Nate:
...
MemberProfile mp = ...
Date now = new Date();
mp.setFirstLoginDate(now);
...
MemberProfile is pretty much a wrapper class for the DTO; setting the first login date sets a field of the DTO and then commits the changes.
Edit 2: It seems to only occur on my machine. I've already tried rebuilding the table schema and wiping out all of my local source and re-checking-out from CVS, with no improvement. Now I'm really stumped.
Edit 3: Completely wiping my MySql installation, reinstalling it, and restoring the database from a known good copy also did not fix the problem.
I have a similar setup to you (except mine works), and my mapping file looks like this:
<property name="firstLoginDate" type="timestamp">
<column name="firstLoginDate" length="19"/>
</property>
My database shows the column definition as datetime.
Edit:
Some more things to check...
Check that the mysql driver the same
on your local as on the working machines.
Try dropping the table, and have
hibernate recreate it for you. If
that works, then there's a problem in
the mapping.
This may or may not be your problem, but we have had serious problems with date/time info - if your database server is on a different time zone than the machine submitting the data, you can have inconsistencies in the data saved.
Beyond that, with our annotation configuration, it looks something like the following:
#Column(name="COLUMN_NAME", length=11)
If it is viable for you, consider using the JodaTime DateTime class which is much nicer than the built in classes and you can also persist them using Hibernate with their Hibernate Support
Using them I mark my fields or getters with the annotation for custom Hibernate Types as:
#org.hibernate.annotations.Type(type = "org.joda.time.contrib.hibernate.PersistentDateTime")
#Column(name = "date")
This works fine for me and it also generates correct schema generation sql
This works fine in MySQL
Use TemporalType.TIMESTAMP beside your Temporal annonation.
Please check the example below.
#Temporal(TemporalType.TIMESTAMP)
public Date getCreated() {
return this.created;
}

Categories

Resources