threeten-jpa
threeten-jpa copied to clipboard
java.lang.ClassCastException: org.eclipse.persistence.internal.platform.database.oracle.TIMESTAMPTZWrapper cannot be cast to oracle.sql.TIMESTAMPTZ
I'm am using org.eclipse.persistence:eclipselink:2.6.4 with com.oracle.jdbc:ojdbc7:12.1.0.2 and this is my TIMESTAMP WITH TIMEZONE
entity field:
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "CREATION_DATE")
private ZonedDateTime creationDate;
On querying the database I get:
Caused by: java.lang.ClassCastException: org.eclipse.persistence.internal.platform.database.oracle.TIMESTAMPTZWrapper cannot be cast to oracle.sql.TIMESTAMPTZ at com.github.marschall.threeten.jpa.oracle.OracleZonedDateTimeConverter.convertToEntityAttribute(OracleZonedDateTimeConverter.java:15) ~[threeten-jpa-oracle-eclipselink-1.5.1.jar:?] at org.eclipse.persistence.mappings.converters.ConverterClass.convertDataValueToObjectValue(ConverterClass.java:124) ~[org.eclipse.persistence.core-2.6.4.jar:?]
Do you have any ideas how can I fix this without extending the EclipseLink Oracle-specific Platform class and overwriting the methods which wrap TIMESTAMPTZ
in TIMESTAMPTZWrapper
?
Can you try to remove or comment out the @Temporal
annotation?
I unfortunately still get the same error...
What operation do you perform when this happens? Are you using criteria API?
The problem is getObjectFromResultSet:205
in org.eclipse.persistence.platform.database.oracle.Oracle9Platform
. It uses getTIMESTAMPTZFromResultSet
to wrap the TIMESTAMPTZ
object into TIMESTAMPTZWrapper
. The comment says that the bug has been fixed in the next version for both streams, but with the current version of the Oracle JDBC Driver 12.1.0.2.0 this code piece is still executed...
I am executing a javax.persistence.TypedQuery
constructed via entityManager.createQuery("");
and then it fails on query.getResultList();
That's really interesting, the tests have no dependency on org.eclipse.persistence.oracle
and therefore org.eclipse.persistence.platform.database.OraclePlatform
is used instead. I haven't tested criteria API though, only EntityManager#find
.
TIMESTAMPTZ
is certainly serializable in both 11g and 12c. There seems to be a related bug 369617.
If I set eclipselink.target-database
property to org.eclipse.persistence.platform.database.OraclePlatform
, then indeed my code works...so how we should continue? I could of course override the wrapping with a custom overridden Platform class and set this class as eclipselink.target-database
, but maybe there are other solutions?
At this point we have established that it's a bug in EclipseLink so we should get in contact with them and report a bug. They should also be able to recommend the best work around. Coming up with a patch should be doable but a reproducer and test may be more work, I don't know what infrastructure they have in place.
In your typed query do you pass the ZonedDateTime
as a parameter?
I will let in on your table 😄
There are two ZonedDateTime
field references inside an ORDER BY.
Maybe useful for somebody, here is my extended Platform class, which you can set as eclipselink.target-database
:
import com.github.marschall.threeten.jpa.oracle.impl.TimestamptzConverter;
import oracle.sql.TIMESTAMPTZ;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.platform.database.oracle.Oracle12Platform;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.ZonedDateTime;
import java.util.GregorianCalendar;
public class Oracle12WrapperlessPlatform extends Oracle12Platform {
@Override
public Object getTIMESTAMPTZFromResultSet(final ResultSet resultSet,
final int columnNumber,
final int type,
final AbstractSession session) throws SQLException {
return resultSet.getObject(columnNumber);
}
@Override
public Object convertObject(final Object sourceObject, final Class javaClass) {
Object valueToConvert = sourceObject;
if (sourceObject instanceof TIMESTAMPTZ) {
final ZonedDateTime zonedDateTime = TimestamptzConverter.timestamptzToZonedDateTime((TIMESTAMPTZ) valueToConvert);
if (javaClass == ClassConstants.CALENDAR || javaClass == ClassConstants.GREGORIAN_CALENDAR) {
valueToConvert = GregorianCalendar.from(zonedDateTime);
} else {
valueToConvert = new Timestamp(zonedDateTime.toInstant().getEpochSecond() * 1000L);
}
}
return super.convertObject(valueToConvert, javaClass);
}
}
I did some additional testing and the issue is limited to criteria API but reading in general. When I use
<property name="eclipselink.target-database" value="Oracle"/>
I can no longer reproduce the issue. I don't know what other features you use so I don't know if this is an option for you.
I filed bug 511999, let's see where it goes from here.
Thanks for your effort. I will follow the opened bug.
@maksymgendin I did some additional testing and simply calling #getObject
was enough for my tests, in which cases was additionally overriding #convertObject
also necessary?
My platforms look like this:
@marschall I think this happens if you are mixing usage of ZonedDateTime AND Calendar in your entities. The exception then appears if you are reading the entity with Calendar field.
@maksymgendin is there a specific reason why you're doing that or did you simply for testing or migration purposes simply start with just one column?
@marschall We unfortunately have some older shared entity classes with Calendar fields. At some point we will migrate them to ZonedDateTime.
@maksymgendin do you use Calendar
for TIMESTAMP
or for TIMESTAMP WITH TIME ZONE
columns?
TIMESTAMP WITH TIME ZONE
I now understand why you had to implement convertObject
. The more I dig into this issue the more I question whether TIMESTAMPTZWrapper
really exists because of TIMESTAMPTZ
not being serializable or rather because in convertObject
there is no access to the connection.
Unfortunately the bug references don't seem to point to bugs in the Eclipse bug tracker.
@maksymgendin the following path in your fix
new Timestamp(zonedDateTime.toInstant().getEpochSecond() * 1000L);
is limited to second precision, TIMESTAMP WITH TIME ZONE
on Oracle defaults to microsecond precision but can go up to nanosecond precision. java.sql.Timestamp
actually supports nanosecond precision. I went for this instead:
Timestamp.valueOf(zonedDateTime.withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime());
which should give you nanosecond precision. The case is a bit theoretical though, mapping TIMESTAMP WITH TIME ZONE
to java.sql.Timestamp
.
@marschall You're so right! Thank you very much for that point. You may have saved me from future headaches with finding bugs :)
When I use the fixed approach, the Converter cannot be auto applied anymore, as the following Exception is thrown:
Exception [EclipseLink-3002] (Eclipse Persistence Services - 2.6.0.v20150309-bf26070): org.eclipse.persistence.exceptions.ConversionException Exception Description: The object [22.03.18 13:48], of class [class java.sql.Timestamp], from mapping [org.eclipse.persistence.mappings.DirectToFieldMapping[TIMESTAMP_WITH_TIME_ZONE_zonedDateTime-->TEST_TIMES.FIELD_TSTZ]] with descriptor [RelationalDescriptor(jpa.entities.TimezoneTestEntity --> [DatabaseTable(TEST_TIMES)])], could not be converted to [class [B]. at org.eclipse.persistence.exceptions.ConversionException.couldNotBeConverted(ConversionException.java:78) at org.eclipse.persistence.internal.helper.ConversionManager.convertObjectToByteArray(ConversionManager.java:351) at org.eclipse.persistence.internal.helper.ConversionManager.convertObject(ConversionManager.java:138) at org.eclipse.persistence.internal.databaseaccess.DatasourcePlatform.convertObject(DatasourcePlatform.java:179) at org.eclipse.persistence.platform.database.oracle.Oracle9Platform.convertObject(Oracle9Platform.java:432) at com.github.marschall.threeten.jpa.oracle.PatchedOracle12Platform.convertObject(PatchedOracle12Platform.java:42)
When I annotate the field with @Convert(converter = ZonedDateTimeConverter.class)
everything works fine again
@frl7082 unless you have exactly the same setup and issue as OP would you mind opening a separate issue instead?
nevermind, was an error in my project setup