Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
maksymgendin opened this issue Feb 9, 2017 · 25 comments

Comments

@maksymgendin
Copy link

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?

@marschall
Copy link
Owner

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

@maksymgendin
Copy link
Author

I unfortunately still get the same error...

@marschall
Copy link
Owner

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

@maksymgendin
Copy link
Author

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
Copy link
Author

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

@marschall
Copy link
Owner

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.

@maksymgendin
Copy link
Author

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?

@marschall
Copy link
Owner

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?

@maksymgendin
Copy link
Author

maksymgendin commented Feb 9, 2017

I will let in on your table 😄

There are two ZonedDateTime field references inside an ORDER BY.

@maksymgendin
Copy link
Author

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

marschall added a commit that referenced this issue Feb 9, 2017
@marschall
Copy link
Owner

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
Copy link
Owner

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

@maksymgendin
Copy link
Author

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

marschall added a commit that referenced this issue Feb 19, 2017
Better tests for #23 and work arounds
@marschall
Copy link
Owner

@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:

@maksymgendin
Copy link
Author

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

@marschall
Copy link
Owner

@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?

@maksymgendin
Copy link
Author

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

@marschall
Copy link
Owner

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

@maksymgendin
Copy link
Author

TIMESTAMP WITH TIME ZONE

marschall added a commit that referenced this issue Feb 23, 2017
@marschall
Copy link
Owner

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
Copy link
Owner

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

@maksymgendin
Copy link
Author

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

@LumnitzF
Copy link

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

@marschall
Copy link
Owner

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

@LumnitzF
Copy link

nevermind, was an error in my project setup

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants