[ 
http://issues.apache.org/jira/browse/OPENJPA-13?page=comments#action_12439954 ] 
            
Bryan Noll commented on OPENJPA-13:
-----------------------------------

So... after looking into this issue for a bit, the problem is this:

Assumptions:
-----------------
Driver: jtds-1.2.jar
Column Type defined as: [ID] [int] IDENTITY (1, 1) NOT NULL

The ClassCastException is coming from this line of the generated code in the 
PersistenceCapable class...

id = (Integer)pcStateManager.replaceObjectField(this, i);

As a result of the previous method call, we eventually arrive at the 
'org.apache.openjpa.jdbc.sql.DBDictionary.getGeneratedKey' method, where there 
is this line of code...

Object key = rs.getObject(1);

When examining this 'key' object at runtime, its type is java.math.BigDecimal, 
so... the jtds driver is returning this '[int] IDENTITY (1, 1)' type as a 
BigDecimal.

This problem does not occur when using a primitive 'int' as the type of the @Id 
mapping because the 
org.apache.openjpa.jdbc.meta.strats.PrimitiveFieldStrategy.setAutoAssignedValue 
method simply up-casts the 'autoInc' value to a Number (legally because it is a 
BigDecimal), and then calls the 'intValue' method (all of this inside the 'case 
JavaTypes.INT' section of the switch statement).  The corresponding class that 
has the setAutoAssignedValue method for the non-primitive types is 
HandlerFieldStrategy.


The same problem occurs with MySQL when attempting to use a 'java.lang.Integer' 
as the type of the @Id field, because the value is returned by the driver as a 
'java.lang.Long' (driver: mysql-connector-java-3.1.11.jar, colum type of 
'int(11) - auto_increment').  This doesn't seem as hinky, because using a Long 
as the @Id type seems more reasonable than having to use a primitive or a 
BigDecimal.


I've thought of a couple of ways to go about resolving this, none of which I 
really like, and am hoping one of the people more familiar with the code base 
can point me in the right direction.

- Modify the bytecode enhancement so that it is try-catching for a 
ClassCastException, then instead of casting, explicitly construct the wrapper 
type that you need, in this case Integer, by casting to a Number, and then 
calling methods on that.

- Override the getGeneratedKey method in the SQLServerDictionary class to 
return a cast-safe value.  Not good at all, because I don't see, as its modeled 
now, that this object is capable of finding out at runtime in a dynamic way 
what exactly it needs to return.  As a hack, I had it return an Integer, but 
that only worked because I knew the @Id field was mapped as an Integer.

- In the StateManager, add methods that correspond to the 
replace<Primitive>Field (replaceIntField gets called in the enhanced code when 
the @Id type is int), such as replaceIntegerWrapperField, 
replaceLongWrapperField, etc... instead of just having replaceObjectField.

- Maybe someone can point out somewhere in the object hierarchy (thinking 
somewhere around HandlerFieldStrategy.setAutoAssignedValue) where we can get to 
the information in the PersistenceCapable implementor using the fieldName or 
index so we can find out what type it is supposed to be, and construct it there 
from the BigDecimal (or whatever it is, Long with MySQL for instance).

- Don't change any code, and document somewhere what we know about what a 
person can and cannot use for auto-increment @Id types for different databases. 
 This one seems good enough for MySQL.


Thoughts... suggestions?

> GenerationType.IDENTITY problem with MS SQL Server
> --------------------------------------------------
>
>                 Key: OPENJPA-13
>                 URL: http://issues.apache.org/jira/browse/OPENJPA-13
>             Project: OpenJPA
>          Issue Type: Bug
>          Components: jpa
>         Environment: Microsoft SQL Server 2000
> Windows XP
> Java SE 1.5 
> OpenJPA - source downloaded today (Aug 14, 2006)
>            Reporter: Megan
>            Priority: Critical
>
> Cannot persist entity with identity column.   To reproduce, create a simple 
> object with identity column
> @Entity
> @Table(name="JpaType")
> public class JpaType implements Serializable
> {
>   @Id
>   @GeneratedValue(strategy=GenerationType.IDENTITY)
>   @Column(name="Id")
>   private Integer id = null;
>   
>   @Column(name="Name")
>   private String name = null;
>   
>   public Integer getId() { return id; }
>   public String getName() { return name;  }
>   public void setName(String name) { this.name = name; }
> }
> create table JpaType (
>     Id int identity(1, 1) not null
>   , Name varchar(50) null
>   , constraint JpaType_PK primary key (Id)
> )
> JpaType jpa = new JpaType();
> jpa.setName("Test 1");
> em.persist(jpa);
> em.flush();
> It works OK if I remove identity column (and set ID myself).
> Stack trace
> <0|true|0.9.0> org.apache.openjpa.persistence.PersistenceException: 
> java.math.BigDecimal
>       at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:1576)
>       at 
> org.apache.openjpa.kernel.DelegatingBroker.flush(DelegatingBroker.java:927)
>       at 
> org.apache.openjpa.persistence.EntityManagerImpl.flush(EntityManagerImpl.java:421)
>       at mytest.domain.JpaTest.testJpa(JpaTest.java:30)
>       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
>       at 
> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
>       at 
> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
>       at java.lang.reflect.Method.invoke(Method.java:585)
>       at 
> org.junit.internal.runners.TestMethodRunner.executeMethodBody(TestMethodRunner.java:99)
>       at 
> org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:81)
>       at 
> org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
>       at 
> org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75)
>       at 
> org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45)
>       at 
> org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:71)
>       at 
> org.junit.internal.runners.TestClassMethodsRunner.run(TestClassMethodsRunner.java:35)
>       at 
> org.junit.internal.runners.TestClassRunner$1.runUnprotected(TestClassRunner.java:42)
>       at 
> org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
>       at 
> org.junit.internal.runners.TestClassRunner.run(TestClassRunner.java:52)
>       at 
> org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38)
>       at 
> org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
>       at 
> org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
>       at 
> org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
>       at 
> org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
>       at 
> org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
> Caused by: java.lang.ClassCastException: java.math.BigDecimal
>       at mytest.domain.model.JpaType.pcReplaceField(JpaType.java)
>       at 
> org.apache.openjpa.kernel.StateManagerImpl.replaceField(StateManagerImpl.java:2824)
>       at 
> org.apache.openjpa.kernel.StateManagerImpl.storeObjectField(StateManagerImpl.java:2284)
>       at 
> org.apache.openjpa.kernel.StateManagerImpl.storeField(StateManagerImpl.java:2380)
>       at 
> org.apache.openjpa.kernel.StateManagerImpl.storeField(StateManagerImpl.java:723)
>       at 
> org.apache.openjpa.kernel.StateManagerImpl.store(StateManagerImpl.java:719)
>       at 
> org.apache.openjpa.jdbc.meta.strats.HandlerFieldStrategy.setAutoAssignedValue(HandlerFieldStrategy.java:361)
>       at 
> org.apache.openjpa.jdbc.kernel.PreparedStatementManagerImpl.flushInternal(PreparedStatementManagerImpl.java:119)
>       at 
> org.apache.openjpa.jdbc.kernel.PreparedStatementManagerImpl.flush(PreparedStatementManagerImpl.java:68)
>       at 
> org.apache.openjpa.jdbc.kernel.OperationOrderUpdateManager.flushPrimaryRow(OperationOrderUpdateManager.java:199)
>       at 
> org.apache.openjpa.jdbc.kernel.OperationOrderUpdateManager.flush(OperationOrderUpdateManager.java:86)
>       at 
> org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.flush(AbstractUpdateManager.java:88)
>       at 
> org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.flush(AbstractUpdateManager.java:68)
>       at 
> org.apache.openjpa.jdbc.kernel.JDBCStoreManager.flush(JDBCStoreManager.java:512)
>       at 
> org.apache.openjpa.kernel.DelegatingStoreManager.flush(DelegatingStoreManager.java:127)
>       at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:1876)
>       at org.apache.openjpa.kernel.BrokerImpl.flushSafe(BrokerImpl.java:1772)
>       at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:1567)
>       ... 23 more

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: 
http://issues.apache.org/jira/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

        

Reply via email to