Wrong LocalDate stored with hibernate - spring

I'm trying to store inside the database the date of a restaurant booking but, even though the date I submit is correct, hibernate stores inside the database a date one day before the one I submitted. I don't know why... it's probably a timezone problem but I can't understand why... the date should not be affected by the timezones.
Here is my spring boot properties file:
spring:
thymeleaf:
mode: HTML5
encoding: UTF-8
cache: false
jpa:
database: MYSQL
hibernate:
ddl-auto: update
properties:
hibernate:
locationId:
new_generator_mappings: false
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
jdbc:
time_zone: UTC
datasource:
driver:
class: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/databaseName?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
username: username
password: **********
I'm from Italy, so my timezone is this:
GMT/UTC + 1h during Standard Time
GMT/UTC + 2h during Daylight Saving Time
Currently we are UTC + 2h.
The object I'm storing is this one:
#Entity
public class Dinner {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long dinnerId;
private LocalDate date;
...
The controller I'm using to intercept the POST request is this:
#PreAuthorize("hasRole('USER')")
#PostMapping
public String createDinner(#RequestParam(value="dinnerDate") String dinnerDate, Principal principal, Model model){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse(dinnerDate, formatter);
dinnerService.createDinner(date);
return "redirect:/dinners?dinnerDate=" + dinnerDate;
}
Which calls the service method createDinner that call the Jpa method save to store the object.
I'm using thymeleaf to handle the html templates.
If I submit the date 30/6/2019 inside the database I get 29/6/2019. When I retrieve the Dinner object by date, if I insert 30/6/2019, I get the Dinner with the date 29/6/2019. So it seems that spring handle the date by itself in a weird way... considering some sort of timezone but I don't know how to disable or handle it. Any idea?

You do not need to define a format for the pattern yyyy-MM-dd. LocalDate#parse uses DateTimeFormatter.ISO_LOCAL_DATE by default which means LocalDate.parse("2020-06-29") works without applying a format explicitly.
Since you already know that date-time in your time-zone is different from that in UTC, you should never consider just a date; rather you should consider both date and time e.g. 11:30 PM at UTC on 2020-06-29 will fall on 2020-06-30 in your time-zone. Therefore, the first thing you should do is to change the type of the field as TIMESTAMP in the database.
Once you have changed the type of the field to TIMESTAMP, change the method, createDinner as follows:
LocalDateTime dinnerDateTime = LocalDateTime.of(LocalDate.parse(dinnerDate), LocalTime.of(0, 0, 0, 0));
OffsetDateTime odt = dinnerDateTime.atOffset(ZoneOffset.UTC);
dinnerService.createDinner(odt);
Then inside DinnerService (or DinnerServiceDAO wherever you have written the logic to insert/update record in the database):
pst.setObject(index, odt);
where pst represents the object of PreparedStatement and index represents the index (starting with 1) of this field in your insert/update query.

same problem (and same country! :-) ).
I suspect that if hibernate or jpa are set with timezone UTC, while the machine is set with default timezone == Europe/Rome when a date is persisted, then it will be converted automatically from machine timezone to database timezone, which is not a bad feature if you have all dates stored in UTC format on the DB.
The problem happens when you convert the date before persisting: it gets converted twice. At least, this is my case.
Still looking for the best solution! In case I'll find one, then I'll add it later to the answer.

Assuming your time zone is : Europe/Italy , You have to set up your serverTimezone variable like this :
url: jdbc:mysql://localhost:3306/databaseName?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Europe/Italy

Related

Select Datetime as Date in criteria query

I'm using JPA CriteriaBuilder to select attributes from the database. I have a specific use case that involves Conversion of datetime to date. The Projection segment of the code is as follows.
CriteriaQuery<ResponseDTO> query = cb.createQuery(ResponseDTO.class);
query.multiselect(root.get(MasterPackagesV1_.masterPackageName),
packageJoin.get(PackagesV1_.packageId),
packageJoin.get(PackagesV1_.packageName),
packageJoin.get(PackagesV1_.startSellingDate),
packageJoin.get(PackagesV1_.endSellingDate),
packageJoin.get(PackagesV1_.createdDate),
packageJoin.get(PackagesV1_.modifiedDate),
packagePaymentJoin.get(PackagePaymentPlanV1_.mrp),
packageSubscriptionJoin.get(PackageSubscriptionsV1_.packageSubscriptionId)
);
All of the date attributes are stored as datetime datatype in the MySQL database. I'm want the datetime to be formatted to date. i.e Instead of retrieving the Dates as LocalDateTime I want it to be converted to LocalDate during Instantiation.
I have made changes in the ResponseDTO class accordingly to support LocalDate. Thanks in Advance.

#JsonFormat Date field to localtimezone

I have a field called expiredDate. The value of this field in the DB is: 2019-11-06 22:48:04. I am using SpringBoot and I have defined the field in POJO as:
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MMM-dd HH:mm:ss")
#JsonProperty("expiration_date")
Date expirationDate;
When I retrieve the date I get the following: 2019-Nov-07 04:48:04. My requirement is to get it in the following format: 2019-11-06T22:48:04.000Z.
I have given the timezone='CST' but I dont want to mention the timezone. I want my program to identify the timezone and show the result.
If I dont give the #JsonFormat annotation, I get the result in long.
What am I missing here?

set Oracle Current Timestamp to JPA entity

I Have simple question,
I want to set Oracle CURRENT_TIMESTAMP to my JPA entity, I do not want Java to send value.
I tried below but did not work.
#Column(name="TMSP", columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP", nullable=false)
private Timestamp tmsp;
its either inserting null or date I set in java but not the DB date.
You can use
updatable= false
#Column(name="TMSP", updatable= false, columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP", nullable=false)
private Timestamp tmsp;

Oracle "TIMESTAMP WITH TIMEZONE" Java type mapping issue with raw sql query

I am using an Oracle 11.2 database with column types "TIMESTAMP WITH TIMEZONE" to store time relevant information. I can't change it because it is used by an existing system.
The Java application uses ojdbc7 (v12.1.0.2) + hibernate as JPA provider.
I simplified the following example for easer understanding!
There is an entity "Product" which maps "TIMESTAMP WITH TIMEZONE" columns to java.util.Calendar
#Entity
#Table(name = "PRODUCT")
public class Product {
#Id
#Column(name = "ID")
private String id;
#Column(name = "START_DATE", columnDefinition = "TIMESTAMP WITH TIME ZONE")
private Calendar startDate;
....
}
If I load it through a HQL query it works fine.
1. Issue
If I use a raw sql query:
SELECT * FROM PRODUCT where id='123456';
I get the following exception:
org.hibernate.MappingException: No Dialect mapping for JDBC type: -101
The sql query is a lot more complex I just used a very simple example here. So I can not simply convert it to an HQL query.
2. Issue
If I persist a new product with a start date having a time zone like AEDT. E.g.
2014-01-01 12:00:00 AEDT
it get saved in the database as
01-JAN-14 01.00.00.000000000 UTC
If I query it again from the Java application the Calendar object has the time in UTC as shown above and not the time in AEDT. It is not just a display issue.
The conversion from AEDT to UTC is correct, but the problem is that the information is lost that it was inserted with timezone AEDT.
Is there a way to prevent this? Why is this happening anyway? I searched a lot today, but could not find a good hint.
You can solve the first issue the following way:
First, you need to unwrap the Query implementation. You said that you were using JPA + Hibernate, so:
Query jpaQuery = entityManager.createNativeQuery("select, t.int_c, t.str_c, t.timestamp_c from table t");
SQLQuery hibernateQuery = jpaQuery.unwrap(SQLQuery.class);
(org.hibernate.SQLQuery.class is the wrapped class)
Second, you have to set the type for ALL the columns that you want to retrieve:
hibernateQuery.addScalar("int_c", IntegerType.INSTANCE);
hibernateQuery.addScalar("str_c", StringType.INSTANCE);
hibernateQuery.addScalar("timestamp_c", TimestampType.INSTANCE);
Third, just make the query using the original JPA Query instance
List<Object[]> results = jpaQuery.getResultList();

How to define current timestamp in yaml with doctrine?

I tried the following yaml code:
columns:
created_time:
type: timestamp
notnull: true
default: default CURRENT_TIMESTAMP
In the outputted sql statement, the field is treated as datetime instead of timestamp, which I cannot define the current timestamp in it...
If I insist to use timestamp to store current time, how to do so in yaml?
If you are willing to sacrifice some portability (see description of columnDefinition attribute) for the ability to use MySQL's automatic initialization TIMESTAMP (see MySQL timestamp initialization), then you can use the following:
Yaml:
created_time:
type: datetime
columnDefinition: TIMESTAMP DEFAULT CURRENT_TIMESTAMP
Annotation:
#ORM\Column(type="datetime", columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
Notice that DEFAULT CURRENT_TIMESTAMP does not work the same as Timestampable, and thus you cannot blindly exchange one for the other.
First and foremost, the former uses the date/time of the DB server, while the latter uses a Doctrine magic that calls PHP's date() function on your webserver. In other words, they are two distinct ways of getting the date/time, from two entirely different clock sources. You may be on big trouble if you use Timestampable, your web server runs on a different machine than your DB server, and you don't keep your clocks in sync using e.g. NTP.
Also, the DEFAULT CURRENT_TIMESTAMP being on the table definition makes for a much more consistent database model IMHO, as no matter how you insert the data (for instance, running INSERTs on the DB engine command line), you'll always get the current date/time on the column.
BTW, I'm also looking for an answer to the CURRENT_TIMESTAMP problem mentioned in the initial question, as this is (due to the reasons outlined above) my preferred way of keeping "timestamp" columns.
You could use the 'Timestampable' functionality in doctrine, eg:
actAs:
Timestampable:
created:
name: created_time
updated:
disabled: true
columns:
created_time:
type: timestamp
notnull: true
/**
* #var int
* #ORM\Column(type="datetime", columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
*/
protected $created;
after run ./vendor/bin/doctrine-module orm:schema-tool:update --force
Updating database schema... Database schema updated successfully! "1"
queries were executed
and run ./vendor/bin/doctrine-module orm:validate-schema
[Mapping] OK - The mapping files are correct. [Database] FAIL - The
database schema is not in sync with the current mapping file.
But FAIL for sync appear
Sorry for necroposting.
But i have encoutered the same problem. There is solution for doctrine 2 and postgreSql. I have used Gemdo extension and added following strings:
$evm = new \Doctrine\Common\EventManager();
$timestampableListener = new \Gedmo\Timestampable\TimestampableListener;
$timestampableListener->setAnnotationReader($cachedAnnotationReader);
$evm->addEventSubscriber($timestampableListener);
YAML:
created:
type: date
options:
default: 0
nullable: true
gedmo:
timestampable:
on: create
updated:
type: datetime
options:
default: 0
nullable: true
gedmo:
timestampable:
on: update
dump-sql:
ALTER TABLE users ADD created DATE DEFAULT CURRENT_DATE NOT NULL;
ALTER TABLE users ADD updated TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL;
I suggest not to use "default" for timestamp at all.
It will bring unpredictable state in yaml in your application.
This video (PHP UK Conference 2016 - Marco Pivetta - Doctrine ORM Good Practices and Tricks) provides some more information about this topic.
I suggest you to to go through it and create a named constructor.
public function createTimestamp(string $priority, int $priorityNormalized)
{
$this->priority = $priority;
$this->priorityNormalized = $priorityNormalized;
}
I suggest to be stateless, good luck!
You can use:
default: '<?php echo date('Y-m-d H:i:s') ?>'

Resources