openjpa-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Bryan Noll (JIRA)" <j...@apache.org>
Subject [jira] Commented: (OPENJPA-13) GenerationType.IDENTITY problem with MS SQL Server
Date Wed, 04 Oct 2006 20:16:20 GMT
    [ 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

        

Mime
View raw message