| Summary: | ClassCastException thrown when using FIELD access and entity fields of type long(primitive) have value null in the DB | ||||||
|---|---|---|---|---|---|---|---|
| Product: | z_Archived | Reporter: | welljim | ||||
| Component: | Eclipselink | Assignee: | Nobody - feel free to take it <nobody> | ||||
| Status: | CLOSED FIXED | QA Contact: | |||||
| Severity: | blocker | ||||||
| Priority: | P2 | CC: | eclipselink.orm-inbox, jandam, petarbankovic, peter.krogh, tom.ware | ||||
| Version: | unspecified | ||||||
| Target Milestone: | --- | ||||||
| Hardware: | Macintosh | ||||||
| OS: | Mac OS X - Carbon (unsup.) | ||||||
| Whiteboard: | |||||||
| Attachments: |
|
||||||
This is addressed in EclipseLink with @Converter http://wiki.eclipse.org/Using_EclipseLink_JPA_Extensions_%28ELUG%29#Using_EclipseLink_JPA_Converters 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. 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))
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 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 We will take a look for our next patch release. 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. 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 {
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) 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. Workaround: Use PROPERTY access 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.
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. Created attachment 180368 [details]
Proposed fix
BTW: Other workaround - use Wrapper types. e.g. Long instead of long Deferring to 2.2 because of the plethora of workarounds. 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 Correction: Reviewed by Andrei Ilitchev iplog moved to patch *** Bug 313023 has been marked as a duplicate of this bug. *** The Eclipselink project has moved to Github: https://github.com/eclipse-ee4j/eclipselink The Eclipselink project has moved to Github: https://github.com/eclipse-ee4j/eclipselink |
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