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?
Related
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;
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.
I am trying to convert the DB2 data base Date field format into H2 in memory database date format as shown below. It did not work for me.
Please help.
select CREATE_DATE from PX.MY_DB2TABLE ;
Here DB2 DATE format in database table is 'MM/DD/YYYY'. CREATE_DATE is DATE data type in db2.
H2 database is accepting only 'yyyy-MM-dd' format.
Tried the following way in H2:
select PARSEDATETIME(CREATE_DATE,'MM/dd/yyyy') FROM PX.MY_DB2TABLE;
select PARSEDATETIME(CREATE_DATE,'yyyy-MM-dd') FROM PX.MY_DB2TABLE;
Error:
Cannot parse DATE constant
To obtain a character-string representation from a DATE data-type value with the format 'YYYY-MM-DD', the DATE field selected in the original query [from the OP] can be wrapped in a CHAR casting scalar with an additional argument requesting that particular date-formatting for the Datetime to character casting; no mention of which variant of DB2, but here is some doc: DB2 for Linux UNIX and Windows 9.7.0->Database fundamentals->SQL->Functions->Scalar functions->CHAR:
select CHAR(CREATE_DATE, ISO) from PX.MY_DB2TABLE ;
For the second argument, see Date Strings Table 1. Formats for String Representations of Dates in [imagining LOCAL as the valid argument, in place of the LOC as seen in the table]:
DB2 for Linux UNIX and Windows 9.7.0->Database fundamentals->SQL->Language elements->Data types->Data type list->Datetime values
I have an action in struts2 that will query the database for an object and then copy it with a few changes. Then, it needs to retrieve the new objectID from the copy and create a file called objectID.txt.
Here is relevant the code:
Action Class:
ObjectVO objectVOcopy = objectService.searchObjects(objectId);
//Set the ID to 0 so a new row is added, instead of the current one being updated
objectVOcopy.setObjectId(0);
Date today = new Date();
Timestamp currentTime = new Timestamp(today.getTime());
objectVOcopy.setTimeStamp(currentTime);
//Add copy to database
objectService.addObject(objectVOcopy);
//Get the copy object's ID from the database
int newObjectId = objectService.findObjectId(currentTime);
File inboxFile = new File(parentDirectory.getParent()+"\\folder1\\folder2\\"+newObjectId+".txt");
ObjectDAO
//Retrieve identifying ID of copy object from database
List<ObjectVO> object = getHibernateTemplate().find("from ObjectVO where timeStamp = ?", currentTime);
return object.get(0).getObjectId();
The problem is that more often than not, the ObjectDAO search method will not return anything. When debugging I've noticed that the Timestamp currentTime passed to it is usually about 1-2ms off the value in the database. I have worked around this bug changing the hibernate query to search for objects with a timestamp within 3ms of the one passed, but I'm not sure where this discrepancy is coming from. I'm not recalculating the currentTime; I'm using the same one to retrieve from the database as I am to write to the database. I'm also worried that when I deploy this to another server the discrepancy might be greater. Other than the objectID, this is the only unique identifier so I need to use it to get the copy object.
Does anyone know why this is occuring and is there a better work around than just searching through a range? I'm using Microsoft SQL Server 2008 R2 btw.
Thanks.
Precision in SQL Server's DATETIME data type does not precisely match what you can generate in other languages. SQL Server rounds to the nearest 0.003 - this is why you can say:
DECLARE #d DATETIME = '20120821 23:59:59.997';
SELECT #d;
Result:
2012-08-21 23:59:59.997
Then try:
DECLARE #d DATETIME = '20120821 23:59:59.999';
SELECT #d;
Result:
2012-08-22 00:00:00.000
Since you are using SQL Server 2008 R2, you should make sure to use the DATETIME2 data type instead of DATETIME.
That said, #RedFilter makes a good point - why are you relying on the time stamp when you can use the generated ID instead?
This feels wrong.
Other than the objectID, this is the only unique identifier
Databases have the concept of a unique identifier for a reason. You should really use that to retrieve an instance of your object.
You can use the get method on the Hibernate session and take advantage of the session and second level caches as well.
With your approach you execute a query everytime you retrieve your object.
Previously the column Data type is Date now I am changed to Timestamp
Now if I tried to run the program am getting them exception
java.sql.SQLException: Cannot convert value '0000-00-00 00:00:00' from column 12 to TIMESTAMP.
at com.mysql.jdbc.ResultSetRow.getTimestampFast(ResultSetRow.java:1298)
at com.mysql.jdbc.ByteArrayRow.getTimestampFast(ByteArrayRow.java:124)
at com.mysql.jdbc.ResultSetImpl.getTimestampInternal(ResultSetImpl.java:6610)
at com.mysql.jdbc.ResultSetImpl.getTimestamp(ResultSetImpl.java:5928)
at com.mysql.jdbc.ResultSetImpl.getTimestamp(ResultSetImpl.java:5966)
at org.hibernate.type.TimestampType.get(TimestampType.java:30)
at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:163)
at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:154)
at org.hibernate.type.AbstractType.hydrate(AbstractType.java:81)
at org.hibernate.persister.entity.AbstractEntityPersister.hydrate(AbstractEntityPersister.java:2096)
at org.hibernate.loader.Loader.loadFromResultSet(Loader.java:1380)
at org.hibernate.loader.Loader.instanceNotYetLoaded(Loader.java:1308)
at org.hibernate.loader.Loader.getRow(Loader.java:1206)
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:580)
at org.hibernate.loader.Loader.doQuery(Loader.java:701)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:236)
at org.hibernate.loader.Loader.doList(Loader.java:2220)
... 40 more
You can just add zeroDateTimeBehavior=convertToNull to your connection jdbc:mysql://localhost/test?zeroDateTimeBehavior=convertToNull.
For me, it works perfectly.
pls refer to this link for more detail.
0000-00-00 00:00:00 is outside the range of a TIMESTAMP value (in fact, it won't work with a DATE field either). From the MySQL manual:
The TIMESTAMP data type has a range of '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC.
You can Use UNIX_TIMESTAMP(date) function to explicitly convert the value to TIMESTAMP.
I'm going to take a wild guess here that you're using MySQL :-) It uses "zero dates" as special placeholder - unfortunatelly, JDBC can not handle them by default.
The solution is to specify "zeroDateTimeBehavior=convertToNull" as parameter to your MySQL connection (either in datasource URL or as an additional property), e.g.:
jdbc:mysql://localhost/myDatabase?zeroDateTimeBehavior=convertToNull
This will cause all such values to be retrieved as NULLs.
Make sure that in you java code the field type is java.sql.Timestamp