Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 325916 - ClassCastException thrown when using FIELD access and entity fields of type long(primitive) have value null in the DB
Summary: ClassCastException thrown when using FIELD access and entity fields of type l...
Status: CLOSED FIXED
Alias: None
Product: z_Archived
Classification: Eclipse Foundation
Component: Eclipselink (show other bugs)
Version: unspecified   Edit
Hardware: Macintosh Mac OS X - Carbon (unsup.)
: P2 blocker (vote)
Target Milestone: ---   Edit
Assignee: Nobody - feel free to take it CLA
QA Contact:
URL:
Whiteboard:
Keywords:
: 313023 (view as bug list)
Depends on:
Blocks:
 
Reported: 2010-09-21 21:43 EDT by welljim CLA
Modified: 2022-06-09 10:33 EDT (History)
5 users (show)

See Also:


Attachments
Proposed fix (3.07 KB, patch)
2010-10-06 15:24 EDT, Tom Ware CLA
peter.krogh: iplog+
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description welljim CLA 2010-09-21 21:43:38 EDT
Build Identifier: 20100218-1602 (Glassfish v3.0, upgraded with EclipseLink 2.1)

Assume a DB table that contains a column of type long, which allows NULL values. For instance:

TABLE_NAME
----------
ID       -> LONG, NOT NULL 
FIELD -> LONG NULL

And the corresponding entity:

@Entity
@Table(name="TABLE_NAME")
public class MyTable implements Serializable {
	private static final long serialVersionUID = 1L;

	@Id
	private long ID;

	private long FIELD;
}

When column FIELD in the DB is populated with a value, then everything works as expected. However, when the column contains a NULL value then the following exception is thrown during entity instantiation of the entity.

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long

As a result, it is not possible to map DB columns of type long, bigint, etc.., when the column permits NULL entries.


Reproducible: Always

Steps to Reproduce:
1. create a simple table like the one described above
2. insert a row in the table with ID=1 and FIELD=null
3. read that row like so: em.find(MyTable.class, 1);
4. The following exception is thrown: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long
Comment 1 Tom Ware CLA 2010-09-23 13:26:13 EDT
This is addressed in EclipseLink with @Converter

http://wiki.eclipse.org/Using_EclipseLink_JPA_Extensions_%28ELUG%29#Using_EclipseLink_JPA_Converters
Comment 2 welljim CLA 2010-09-23 15:20:52 EDT
Please have another look at this as I don't believe that a @Converter should be required for this simple case. Here is why:

When a DB column of type Long has a value (i.e., it is not NULL), then EclipseLink correctly populates the corresponding entity field with an object of type Long. Very importantly, this also works correctly when the value in that column happens to be in the range of INT, like '1', or even just '0'. That is, no converter is required, and EclipseLink correctly uses the column type of the DB to identify the field type without relying on the actual value of the column.

When the same DB column happens to have a value of NULL, EclipseLink forgets to make the same type check as above and assumes the column is of type INT. We are talking about the same DB column here, EclipseLink has the exact same amount the information about the column as it did before, yet it incorrectly returns '0' as Integer, not as Long. Again, please note that if the same DB column is populated with an actual '0' instead of NULL, then EclipseLink correctly gives back '0' as Long. So it looks as if when the value happens to be NULL, the column type is not checked by mistake.

That surely must be a bug, users shouldn't have to use EclipseLink-specific, non-portable annotations for mapping correctly columns of type LONG - especially since it is proven that when there is a value in the column, EclipseLink can handle them correctly. Columns of type Long are used all the time as table identifiers and FKs to other tables, so it is important that they are handled correctly without requiring special solutions.
Comment 3 Tom Ware CLA 2010-09-23 15:53:24 EDT
It looks like the issue may be in our JPAConversionManager:

    public Object getDefaultNullValue(Class theClass) {
        Object defaultNullValue = null;
        if (this.defaultNullValues != null){
            defaultNullValue = getDefaultNullValues().get(theClass);
        }
        if (defaultNullValue == null && theClass.isPrimitive()) {
            return 0;
        } else {
            return defaultNullValue;
        }
    }

The return 0 must be autoboxing to Integer.

The workaround of using a conversion manager will work.

Also, another workaround that may be more portable is to use a DescriptorCustomizer and get the mappig for your attribute and call setDefaultNullValue(Long.valueOf(0))
Comment 4 welljim CLA 2010-09-24 00:05:20 EDT
well spotted, that will fail for any of long, float, double, boolean, and char.

Any idea when someone will be able to look at it?

Thanks
Comment 5 Martin JANDA CLA 2010-09-24 03:03:44 EDT
Please can you consider fixing this bug in 2.2? It looks like 'simple' fix. With more time spend in test case. Its difficult for eclipselink user to fix NULL conversions for every primitive type. Even through DescriptorCustomizer.
  Thank you
Comment 6 Tom Ware CLA 2010-09-24 10:47:16 EDT
We will take a look for our next patch release.
Comment 7 Tom Ware CLA 2010-10-06 09:16:03 EDT
What is the expectation when the object is put back into the database?  EclipseLink cannot distinguish between 0 and null for fields of type long (or other numeric fields)  As a result, it will not be able to maintain the NULL setting.  That is the reason a converter is currently required.
Comment 8 Martin JANDA CLA 2010-10-06 09:32:47 EDT
You are right. Better way is to use @Converter. ClassCastException is thrown because getDefaultNullValue return Integer.valueOf(0) for long primitive. 
Is there any reason to return correct 0 for int and not for other primitive types?
Fix should look like:

if (defaultNullValue == null && theClass.isPrimitive()) {
   if(Integer.TYPE.equals(theClass))
      return 0;        // return Integer object
   else if(Long.TYPE.equals(theClass))
      return 0L;       // return Long object
   else if(Character.TYPE.equals(theClass))
      return Character.valueOf(0); // return Character object
   else ....
} else {
Comment 9 Tom Ware CLA 2010-10-06 09:49:39 EDT
I think we'll make it behave like int.  (as you suggest above)

The weakness will be that a field that is null in the DB will never be settable to 0 and a field that is null in the database will never be settable to NULL.

I guess that a weakness in the domain model of anyone who allows null in a column that represents a type that is inherantly non-nullable.  (it could be argued that this is, in fact, a bug in the model)
Comment 10 Tom Ware CLA 2010-10-06 09:51:16 EDT
correction:

The weakness will be that a field that is null in the DB will never be settable
to 0 and a field that is 0 in the database will never be settable to NULL.
Comment 11 Tom Ware CLA 2010-10-06 11:11:31 EDT
Workaround: Use PROPERTY access
Comment 12 welljim CLA 2010-10-06 12:30:07 EDT
I think that sounds like another big problem, consider the following scenario:

public enum AttributeStatus {
    statusA,
    statusB,
    statusC
    ...
}

Then a DB column is modeled like so:

@Enumerated(EnumType.ORDINAL)
private AttributeStatus attributeStatus;

Now let's consider the case where 'attributeStatus' is an optional column. Typically, that means that when no status has been defined the associated DB column will have the value of NULL (which is perfectly acceptable, as it's an optional column). Now if I understand Tom's comment correctly, this weakness means that it will be impossible to alter the state of this attribute from 'not set' to 'statusA', and from 'statusA' to 'not set'. If that's the case, then I think we should file another bug dedicated to this. Not being able to distinguish between 0 and null for numeric fields is unacceptably restrictive in my opinion.
Comment 13 Tom Ware CLA 2010-10-06 13:41:06 EDT
We can distinguish between null and 0, just not for primitives.  The problem is the JPA objects are POJOs and if the underlying datastructure cannot represent what is in the DB, we are kind of stuck.  If someone needs to represent a numeric value that could be null, use the wrapper type (i.e. Integer rather than int)

If a user chooses to use a primitive to represent a value in the database, they have made a specific choice to not allow the field to be null.
Comment 14 Tom Ware CLA 2010-10-06 15:24:59 EDT
Created attachment 180368 [details]
Proposed fix
Comment 15 Tom Ware CLA 2010-10-06 15:25:43 EDT
BTW: Other workaround - use Wrapper types. e.g. Long instead of long
Comment 16 Tom Ware CLA 2010-10-07 09:56:03 EDT
Deferring to 2.2 because of the plethora of workarounds.
Comment 17 Tom Ware CLA 2010-10-07 09:59:15 EDT
Checked in fix that includes suggestions in the bug.

Added testNullDouble() to fieldAccess EntityManagerJunityTestSuite

Tested with full JPA LRG - Core LRG not run because change is to a JPA-only class.

Reviewed by Chris Delahunt
Comment 18 Tom Ware CLA 2010-10-07 10:43:05 EDT
Correction: Reviewed by Andrei Ilitchev
Comment 19 Peter Krogh CLA 2010-12-08 14:19:06 EST
iplog moved to patch
Comment 20 Vikram Bhatia CLA 2012-01-31 04:57:59 EST
*** Bug 313023 has been marked as a duplicate of this bug. ***
Comment 21 Eclipse Webmaster CLA 2022-06-09 10:15:13 EDT
The Eclipselink project has moved to Github: https://github.com/eclipse-ee4j/eclipselink
Comment 22 Eclipse Webmaster CLA 2022-06-09 10:33:26 EDT
The Eclipselink project has moved to Github: https://github.com/eclipse-ee4j/eclipselink