After an upgrade of the OJDBC client from version 11.2.0 to 12.1.0, I experience a different behavior in binding a java.sql.Date object to a PreparedStatement.
In the prepared statement, a host variable "f.plan_date = ?" should be binded with the value of a java.util.Date object, being an input obtained elsewhere in the code. The column data type in the Oracle table is "DATE" and only the date part should be taken into account - time is irrelevant.
I translated the java.util.Date object in a java.sql.Date object in the following way:
statementRegisterJobs.setDate(3, new java.sql.Date(planDate.getTime()));
This worked fine with the 11.2.0 client. However, things tend to go wrong after the upgrade towards 12.1.0. No records are retrieved anymore. After hours of debugging, I found out that the issue was related to the date variable. The following way of working gives me my records back:
statementRegisterJobs.setDate(3, java.sql.Date.valueOf("2014-08-21"));
Could someone clarify this behaviour? The java.util.Date object can eventually have a time component, and I have the undefined feeling that this could be related to the problem somehow. On the other hand, the following items should argue that a time component is neglected in a java.sql.Date, no matter how the object was constructed...
In the Java 6 API for java.sql.Date, I found the following statement: "This method is deprecated and should not be used because SQL Date values do not have a time component." (method 'getHours()'). So this would mean that the time aspect is neglected when converting the java.util.Date into a java.sql.Date.
This is confirmed by the information in the constructor documentation: "Constructs a Date object using the given milliseconds time value. If the given milliseconds value contains time information, the driver will set the time components to the time in the default time zone (the time zone of the Java virtual machine running the application) that corresponds to zero GMT."
Moreover, I'm not able to get a possible time aspect out of the java.sql.Date object: toString() gives me only the date, getHours() throws an exception.
And how can this be related to an update in a JDBC client?
Any thoughts are appreciated :) Thank you very much in advance.
Opposed to what the Java API states, when creating a java.sql.Date object by passing a milliseconds time value the time aspect seems to be stored in the object and is not defaulted to zero when using the 12.1.0 OJDBC driver.
This is the test I set up:
java.util.Date utilDate = new Date();
java.sql.Date sqlDate1 = new java.sql.Date(utilDate.getTime());
java.sql.Date sqlDate2 = java.sql.Date.valueOf("2014-08-27");
I prepared the following statement (SELECT to_char(?, 'YYYY-MM-DD HH24:MI:SS') testDate FROM dual), binded both sqlDate1 and sqlDate2 and got the following results.
With driver version 11.2.0
sqlDate1: 2014-08-27 00:00:00
sqlDate2: 2014-08-27 00:00:00
With driver version 12.1.0
sqlDate1: 2014-08-27 14:47:29
sqlDate2: 2014-08-27 00:00:00
This is not in line with the documentation in the API:
If the given milliseconds value contains time information, the driver
will set the time components to the time in the default time zone (the
time zone of the Java virtual machine running the application) that
corresponds to zero GMT.
However, knowing this I can fix the issue by forcing the time information of the sql date object to be midnight.
Related
I am reaching out to the community here because either I found an issue with Hibernate or I just don't understand how to use java.sql.Date and java.time.LocalDate.
I'm running into a problem where my DB is in UTC time zone while my client is in EST. I have a field in the DB called ETA of type DATE that for example is set to 2019-09-10 on a record. When I read the date in EST it becomes 2019-09-09. The DATE field according to documentation has no timezone information.
When Hibernate reads the value, it uses the DateTypeDescriptor.java class. However, the problem with that class is that it will first try to read the value as a java.sql.Date (in the rs.getDate( name ) part) and then call java.sql.Date.toLocalDate() in the javaTypeDescriptor.wrap() part, which is implemented poorly because what it does is it strips off the timezone offset information and just plainly returns a date. This makes 2019-09-09T20:00:00.000-0400 (which is 2019-09-10 in the database) to become 2019-09-09 without the timezone offset part.
What I consider the problem is the part where Hibernate calls rs.getDate() because that must return java.sql.Date. Now, the MySQL driver contains a method public LocalDate getLocalDate(int columnIndex) to obtain a LocalDate, so I don't understand why Hibernate isn't using that method.
I found that someone has already brought up this same problem with them but they don't seem to consider it an issue.
Therefore, I'm reaching out here to understand - is there a bug with Hibernate or am I just not understanding correctly how to convert between DATE (DB) and LocalDate (Java) types.
PS: I use latest Hibernate 5.4.5 and latest JPA 2.2.
Try Hibernate 5 and the configuration option hibernate.jdbc.time_zone. That's an official way to force it to use JDBC APIs which use an Calendar instance in the UTC timezone.
That said, I've found that there are a lot of problems in this area. JDBC drivers and people using databases often get this subtly wrong. That's why we eventually gave up and just put the milliseconds (in UTC) into the database. So the column type is always NUMBER(16) for all three temporal types.
That allows us to write special converters which return predictable results, independent of JDBC drivers, database/VM/OS time zones.
Programmers can then concentrate on trying to understand why timezones don't work they way they think they should :-)
I am trying to query a mysql DATETIME from Java. I know the the time zone of the server, but I cannot pull the datetime out with the time zone as I would expect.
ResultSet rs = st.executeQuery(...);
Date d1=rs.getTime(i, Calendar.getInstance(TimeZone.getTimeZone("UTC")));
Date d2=rs.getTime(i, Calendar.getInstance(TimeZone.getTimeZone("PST")));
System.out.println("d1: "+d1.getTime());
System.out.println("d2: "+d2.getTime());
This leaves me with:
d1: 40258000
d2: 40258000
Am I missing something basic here?
ResultSet.getDate() does take the Calendar into account. But I cant use it because it truncates the time info. It's still strange ResultSet.getTime() wouldn't handle any timezone conversions.
The documentation states:
This method uses the given calendar to construct an appropriate millisecond value for the time if the underlying database does not store timezone information.
So perhaps the database does store time zone information in this case?
What does your value in the database look like, and what is it supposed to represent?
An alternative approach would be letting the MySQL server convert the time zone:
SELECT CONVERT_TZ(timefield,'PST','UTC') AS tf
for example would convert the DATETIME value in the field timefield from PST to UTC time (and then returning it as tf).
getTime on the Date class returns the epoch time according to http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Date.html
so it would adjust out your timezone. I think that page implies you should use the timezone adjusted calendar to get your local time.
So we use Hibernate for Object-relational mapping and we have a Student.java (POJO) and a registrationDate (java.util.date). We save the student object with this date '2012-01-05 10:00:00' and when we look at the db it looks right as we save it.
The issue is when we get it back from the db (student.getRegistrationDate) instead of returning us the right milliseconds in GMT it returns us a modified milliseconds based on our time zone (our time zone -3, the wrong date is '2012-01-05 13:00:00' ). Now this is not the the normal behavior of java.util.date because it doesn't hold time Zone information, so I'm worried if actually Hibernate is adjusting the milliseconds in the date object to match the server local time or it could be something else ?.
Note: I get the date by using date.getTime not with date.ToString.
Conversion happens to the timezone application is running into. As you already know the work-around. Check this - http://community.jboss.org/wiki/UserTypeForNon-defaultTimeZone.
If u want to use it as a Timestamp use java.sql.Timestamp instead of java.util.Date (which assumes Zulu time), which will properly take care of time zone.
Or you might try joda Time but with Hibernate and JodaTime, you might need a slight bit of work see this
So far, I have not found a clear answer to this.
I'd like to know what the equivalent is for a SQL type DATETIME and the java type, using a PreparedStatement.
I have found: http://www.java2s.com/Code/Java/Database-SQL-JDBC/StandardSQLDataTypeswithTheirJavaEquivalents.htm
But it states that SQL type "DATETIME" is the same as sql.date, but when looking at the SQL date docs (http://download.oracle.com/javase/7/docs/api/java/sql/Date.html), it says the time is truncated (all zeros).
What I want is to be able to specify a preparedStatement.setDateTime() or some sort.
The only other way I see is using a timestamp, but that would require me to change the column type, while I cannot imagine someone else never had this problem before?
Any hints?
Edit: I am using MYSQL.
The java.sql package has three date/time types:
java.sql.Date - A date only (no time part)
java.sql.Time - A time only (no date part)
java.sql.Timestamp - Both date and time
You want the last one: java.sql.Timestamp.
If you are using these types, you don't need to call a specific setter; just use:
java.util.Date date = new Date();
Object param = new java.sql.Timestamp(date.getTime());
// The JDBC driver knows what to do with a java.sql type:
preparedStatement.setObject(param);
The equivalent of MS SQL Server or MySQL DATETIME data type or Oracle DATE data type is java.sql.Timestamp.
In Java we have java.util.Date to handle both Date and Time values.
In SQL, you have commonly Dates (only dates), Time (only time) and DateTime/Timestamp (date and time).
In your Java program, usually you'll always have java.util.Date, so each time you're setting Dates/Times/DateTimes in PreparedStatements, always choose exactly which one you need, according to the database.
I had a similar problem with my Mysql having SQL date and locally in my app i only had Date
I solved like this
java.sql.Date dataStartSql = new java.sql.Date(start.getTime());
After that used the setDate normally, and I used a getTimestamp to retrive the first value.
where start is a Date object.
Before writing a Java Date to an SQL TIMESTAMP column, does JDBC translate the date from the Java virtual machine time zone to that of the database session?
For example, suppose the Java virtual machine time zone is UTC and the database session time zone is UTC-5. If a Java program attempts to store 2000-01-01 00:00:00 by passing it to PreparedStatement#setTimestamp(int, Timestamp), according to the JDBC standard, will the database store TIMESTAMP '2000-01-01 00:00:00' or TIMESTAMP '1999-12-31 19:00:00'?
No, JDBC is just an API on how the client can access the database. For timestamp storage, this will have to be dependent by the organisation that writes their database drivers that conforms to the JDBC API standard.
Here's an implementation of MySQL's implementation of PreparedStatement. They seem to take Java's JVM timezone to MySQL Timezone (check the setTimestampInternal() method).
Now my requirement is that it should store the value in GMT/UTC irrespective of the timezone of the JVM. Is there a way to set the timezone on the fly and then to unset it once I'm done with JDBC?
Edit:
Ok, I found a way around that issue. Did the following
TimeZone default = TimeZone.getDefault();
try
{
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
//Do stuff with JDBC
}
finally
{
TimeZone.setDefault(default);
}
You can use overloaded setTimestamp setter accepting Calendar instance to specify timezone
Sample (If you're using Joda datetime):
org.joda.time.DateTime sendDateUTC = new DateTime( DateTimeZone.UTC ).withMillis( millis );
statement.setTimestamp (1, sendDateUTC, sendDateUTC.toGregorianCalendar() );
As per javaDoc:
Sets the designated parameter to the given java.sql.Timestamp value, using the given Calendar object. The driver uses the Calendar object to construct an SQL TIMESTAMP value, which the driver then sends to the database. With a Calendar object, the driver can calculate the timestamp taking into account a custom timezone. If no Calendar object is specified, the driver uses the default timezone, which is that of the virtual machine running the application.
void setTimestamp(int parameterIndex, java.sql.Timestamp x, Calendar cal)
throws SQLException;
The spec is goofy. The java.util.Date stores milliseconds from epoch in the GMT reference frame. Java.sql.Timestamp is a Date plus nanoseconds in the same reference frame. All the non-deprecated getters and setters use the GMT reference frame. For any sort of sanity, the default time zone for a storing a Timestamp should be GMT.
In a multi-tiered application, the front-end, the driver, and the database server could all be in different time zones. Some of the tiers could be in different time zones at the same time; for instance, if you are doing internet load-balancing across a continent, or if you have a mobile app connecting to a central server. A cloud operating environment would be much the same scenario where you have no idea where the JDBC driver will be running, nor any guarantee that will never change.
The only way I know to achieve consistency in these environments is to only use the parameter setter and ResultSet getter that accept a Calendar, and make sure every app that accesses the data uses the some calender, preferrably GMT or UTC.