threeten-jpa icon indicating copy to clipboard operation
threeten-jpa copied to clipboard

java.lang.ClassCastException: org.eclipse.persistence.internal.platform.database.oracle.TIMESTAMPTZWrapper cannot be cast to oracle.sql.TIMESTAMPTZ

Open maksymgendin opened this issue 8 years ago • 25 comments

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?

maksymgendin avatar Feb 09 '17 13:02 maksymgendin

Can you try to remove or comment out the @Temporal annotation?

marschall avatar Feb 09 '17 13:02 marschall

I unfortunately still get the same error...

maksymgendin avatar Feb 09 '17 14:02 maksymgendin

What operation do you perform when this happens? Are you using criteria API?

marschall avatar Feb 09 '17 14:02 marschall

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...

maksymgendin avatar Feb 09 '17 14:02 maksymgendin

I am executing a javax.persistence.TypedQuery constructed via entityManager.createQuery(""); and then it fails on query.getResultList();

maksymgendin avatar Feb 09 '17 14:02 maksymgendin

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.

marschall avatar Feb 09 '17 14:02 marschall

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?

maksymgendin avatar Feb 09 '17 15:02 maksymgendin

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?

marschall avatar Feb 09 '17 16:02 marschall

I will let in on your table 😄

There are two ZonedDateTime field references inside an ORDER BY.

maksymgendin avatar Feb 09 '17 16:02 maksymgendin

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);
    }
}

maksymgendin avatar Feb 09 '17 17:02 maksymgendin

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.

marschall avatar Feb 09 '17 19:02 marschall

I filed bug 511999, let's see where it goes from here.

marschall avatar Feb 09 '17 19:02 marschall

Thanks for your effort. I will follow the opened bug.

maksymgendin avatar Feb 10 '17 10:02 maksymgendin

@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 avatar Feb 20 '17 21:02 marschall

@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 avatar Feb 22 '17 12:02 maksymgendin

@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 avatar Feb 22 '17 16:02 marschall

@marschall We unfortunately have some older shared entity classes with Calendar fields. At some point we will migrate them to ZonedDateTime.

maksymgendin avatar Feb 22 '17 16:02 maksymgendin

@maksymgendin do you use Calendar for TIMESTAMP or for TIMESTAMP WITH TIME ZONE columns?

marschall avatar Feb 22 '17 18:02 marschall

TIMESTAMP WITH TIME ZONE

maksymgendin avatar Feb 23 '17 14:02 maksymgendin

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.

marschall avatar Mar 01 '17 15:03 marschall

@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 avatar Mar 01 '17 23:03 marschall

@marschall You're so right! Thank you very much for that point. You may have saved me from future headaches with finding bugs :)

maksymgendin avatar Mar 02 '17 16:03 maksymgendin

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

LumnitzF avatar Mar 23 '18 11:03 LumnitzF

@frl7082 unless you have exactly the same setup and issue as OP would you mind opening a separate issue instead?

marschall avatar Mar 23 '18 13:03 marschall

nevermind, was an error in my project setup

LumnitzF avatar Mar 28 '18 10:03 LumnitzF