[ 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