I'm currently wrestling with an Oracle SQL DATE conversion problem using iBATIS from Java.
Am using the Oracle JDBC thin driver ojdbc14 version 10.2.0.4.0. iBATIS version 2.3.2. Java 1.6.0_10-rc2-b32.
The problem revolves around a column of DATE type that is being returned by this snippet of SQL:
SELECT *
FROM TABLE(pk_invoice_qry.get_contract_rate(?,?,?,?,?,?,?,?,?,?)) order by from_date
The package procedure call returns a ref cursor that is being wrapped in a TABLE to where is then easy to read the result set as though were a select query against a table.
In PL/SQL Developer, one of the columns returned, FROM_DATE, of SQL DATE type, has precision to time of day:
Tue Dec 16 23:59:00 PST 2008
But when I access this via iBATIS and JDBC, the value only retains precision to day:
Tue Dec 16 12:00:00 AM PST 2008
This is clearer when displayed like so:
Should have been:
1229500740000 milliseconds since epoch
Tuesday, December 16, 2008 11:59:00 PM PST
But getting this instead:
1229414400000 milliseconds since epoch
Tuesday, December 16, 2008 12:00:00 AM PST
(as instance of class java.sql.Date)
No matter what I try, I am unable to expose the full precision of this DATE column to be returned via Java JDBC and iBATIS.
What iBATIS is mapping from is this:
FROM_DATE : 2008-12-03 : class java.sql.Date
The current iBATIS mapping is this:
<result property="from_date" jdbcType="DATE" javaType="java.sql.Date"/>
I've also tried:
<result property="from_date" jdbcType="DATETIME" javaType="java.sql.Date"/>
or
<result property="from_date" jdbcType="TIMESTAMP" javaType="java.sql.Timestamp"/>
But all attempted mappings yield the same truncated Date value. It's as though JDBC has already done the damage of losing data precision before iBATIS even touches it.
Clearly I'm losing some of my data precision by going through JDBC and iBATIS that is not happening when I stay in PL/SQL Developer running the same SQL snippet as a test script. Not acceptable at all, very frustrating, and ultimately very scary.
The full info (and it's more complex than described here and might depend upon which particular version of the Oracle drivers are in use) is in Richard Yee's answer here - [now expired link to Nabble]
Quick grab before it expires from nabble...
Roger,
See: http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-faq-090281.html#08_01
Specifically:
Simple Data Types
What is going on with DATE and TIMESTAMP?
This section is on simple data types. :-)
Prior to 9.2, the Oracle JDBC drivers mapped the DATE SQL type to java.sql.Timestamp. This made a certain amount of sense because the Oracle DATE SQL type contains both date and time information as does java.sql.Timestamp. The more obvious mapping to java.sql.Date was somewhat problematic as java.sql.Date does not include time information. It was also the case that the RDBMS did not support the TIMESTAMP SQL type, so there was no problem with mapping DATE to Timestamp.
In 9.2 TIMESTAMP support was added to the RDBMS. The difference between DATE and TIMESTAMP is that TIMESTAMP includes nanoseconds and DATE does not. So, beginning in 9.2, DATE is mapped to Date and TIMESTAMP is mapped to Timestamp. Unfortunately if you were relying on DATE values to contain time information, there is a problem.
There are several ways to address this problem:
Alter your tables to use TIMESTAMP instead of DATE. This is probably rarely possible, but it is the best solution when it is.
Alter your application to use defineColumnType to define the columns as TIMESTAMP rather than DATE. There are problems with this because you really don't want to use defineColumnType unless you have to (see What is defineColumnType and when should I use it?).
Alter you application to use getTimestamp rather than getObject. This is a good solution when possible, however many applications contain generic code that relies on getObject, so it isn't always possible.
Set the V8Compatible connection property. This tells the JDBC drivers to use the old mapping rather than the new one. You can set this flag either as a connection property or a system property. You set the connection property by adding it to the java.util.Properties object passed to DriverManager.getConnection or to OracleDataSource.setConnectionProperties. You set the system property by including a -D option in your java command line.
java -Doracle.jdbc.V8Compatible="true" MyApp
Oracle JDBC 11.1 fixes this problem. Beginning with this release the driver maps SQL DATE columns to java.sql.Timestamp by default. There is no need to set V8Compatible to get the correct mapping. V8Compatible is strongly deprecated. You should not use it at all. If you do set it to true it won't hurt anything, but you should stop using it.
Although it was rarely used that way, V8Compatible existed not to fix the DATE to Date issue but to support compatibility with 8i databases. 8i (and older) databases did not support the TIMESTAMP type. Setting V8Compatible not only caused SQL DATE to be mapped to Timestamp when read from the database, it also caused all Timestamps to be converted to SQL DATE when written to the database. Since 8i is desupported, the 11.1 JDBC drivers do not support this compatibility mode. For this reason V8Compatible is desupported.
As mentioned above, the 11.1 drivers by default convert SQL DATE to Timestamp when reading from the database. This always was the right thing to do and the change in 9i was a mistake. The 11.1 drivers have reverted to the correct behavior. Even if you didn't set V8Compatible in your application you shouldn't see any difference in behavior in most cases. You may notice a difference if you use getObject to read a DATE column. The result will be a Timestamp rather than a Date. Since Timestamp is a subclass of Date this generally isn't a problem. Where you might notice a difference is if you relied on the conversion from DATE to Date to truncate the time component or if you do toString on the value. Otherwise the change should be transparent.
If for some reason your app is very sensitive to this change and you simply must have the 9i-10g behavior, there is a connection property you can set. Set mapDateToTimestamp to false and the driver will revert to the default 9i-10g behavior and map DATE to Date.
If possible, you should change your column type to TIMESTAMP instead of DATE.
-Richard
Roger Voss wrote:
I posted following question/problem on stackoverflow, so if anyone knows a resolution, would be good to see it answered there:
Oracle SQL DATE conversion problem using iBATIS via Java JDBC
Here's the problem description:
I'm currently wrestling with an Oracle sql DATE conversion problem using iBATIS from Java.
Am using the Oracle JDBC thin driver ojdbc14 version 10.2.0.4.0. iBATIS version 2.3.2. Java 1.6.0_10-rc2-b32.
The problem revolves around a column of DATE type that is being returned by this snippet of SQL:
SELECT *
FROM TABLE(pk_invoice_qry.get_contract_rate(?,?,?,?,?,?,?,?,?,?)) order by from_date
The package procedure call returns a ref cursor that is being wrapped in a TABLE to where is then easy to read the result set as though were a select query against a table.
In PL/SQL Developer, one of the columns returned, FROM_DATE, of SQL DATE type, has precision to time of day:
Tue Dec 16 23:59:00 PST 2008
But when I access this via iBATIS and JDBC, the value only retains precision to day:
Tue Dec 16 12:00:00 AM PST 2008
This is clearer when displayed like so:
Should have been:
1229500740000 milliseconds since epoch
Tuesday, December 16, 2008 11:59:00 PM PST
But getting this instead:
1229414400000 milliseconds since epoch
Tuesday, December 16, 2008 12:00:00 AM PST
(as instance of class java.sql.Date)
No matter what I try, I am unable to expose the full precision of this DATE column to be returned via Java JDBC and iBATIS.
What iBATIS is mapping from is this:
FROM_DATE : 2008-12-03 : class java.sql.Date
The current iBATIS mapping is this:
I've also tried:
or
But all attempted mappings yield the same truncated Date value. It's as though JDBC has already done the damage of loosing data precision before iBATIS even touches it.
Clearly I'm loosing some of my data precision by going through JDBC and iBATIS that is not happening when I stay in PL/SQL Developer running the same SQL snippet as a test script. Not acceptable at all, very frustrating, and ultimately very scary.
I found out how to solve this problem. iBATIS permits custom type handlers to be registered. So in my sqlmap-config.xml file I added this:
<typeAlias alias="OracleDateHandler" type="com.tideworks.ms.CustomDateHandler"/>
<typeHandler callback="OracleDateHandler" jdbcType="DATETIME" javaType="date"/>
And then added this class which implements the iBATIS TypeHandlerCallback interface:
// corrected getResult()/setParameter() to correctly deal with when value is null
public class CustomDateHandler implements TypeHandlerCallback {
@Override
public Object getResult(ResultGetter getter) throws SQLException {
final Object obj = getter.getTimestamp();
return obj != null ? (Date) obj : null;
}
@Override
public void setParameter(ParameterSetter setter,Object value) throws SQLException {
setter.setTimestamp(value != null ? new Timestamp(((Date)value).getTime()) : null);
}
@Override
public Object valueOf(String datetime) {
return Timestamp.valueOf(datetime);
}
}
Whennever I need to map an Oracle DATE I now describe it like so:
<result property="from_date" jdbcType="DATETIME" javaType="date"/>
The problem is with the Oracle Driver.
The best solution I found was to change all jdbcType="DATE" to jdbcType="TIMESTAMP"
and all #column_name:DATE# to #column_name:TIMESTAMP#
So change:
<result property="from_date" jdbcType="DATE" javaType="java.sql.Date"/>
to
<result property="from_date" jdbcType="TIMESTAMP" javaType="java.sql.Date"/>
The problem is the use of java.sql.Date
. According to the Javadoc, the millisecond values wrapped by a java.sql.Date
instance must be 'normalized' by setting the hours, minutes, seconds, and milliseconds to zero in the particular time zone with which the instance is associated, to conform with the definition of SQL DATE
.
Yes, I see - the plain SQL DATE standard must be to only store to day resolution. Indeed, here is a snippet on Oracle's DATE type:
Oracle supports both date and time,
albeit differently from the SQL2
standard. Rather than using two
separate entities, date and time,
Oracle only uses one, DATE. The DATE
type is stored in a special internal
format that includes not just the
month, day, and year, but also the
hour, minute, and second.
Which makes the point that Oracle's DATE exceeds standard SQL DATE.
Hmm, Oracle PL/SQL folks use DATE extensively to hold values where they depend on the resolution being to the second. Looks like iBATIS needs something like the Hibernate sql dialect concept where instead of interpreting DATE via java.sql.Date, could override and instead interpret via java.util.Date, which Javadocs defines as permitting millisecond resolution.
Unfortunately when I've changed the mapping to something like:
<result property="from_date" jdbcType="DATE" javaType="java.util.Date"/>
or
<result property="from_date" jdbcType="DATETIME" javaType="java.util.Date"/>
It's still seemingly first translated the SQL DATE to a java.sql.Date and lost the time of day precision.