5 Replies Latest reply on Feb 3, 2009 1:36 PM by adamw

    Saving new collection and its first element with same sessio

      I have a GreetingPO and a GreetingSetPO which are related in a one-to-many relationship. Both are @Versioned using envers.

      I am getting an IllegalArgumentException during the envers beforeCompletion() synchronization hook when I save a new GreetingSetPO and its first GreetingPO together using a single session.save() call, when I use the JTA transaction manager. (If I use Hibernate local transactions or if I save the empty GreetingSetPO first and then save the GreetingPO separately, I do not get the exception and the versioning records are written as expected.)

      Here is some code showing the two different approaches:

      /**
       * Results in an IllegalArgumentException with envers + JTA
       * transaction manager; works without envers or with Hibernate
       * local transaction manager.
       */
      public void complexCreate() {
       GreetingSetPO greetingSetPO = new GreetingSetPO();
       greetingSetPO.setName("Latin greetings");
      
       GreetingPO greetingPO = new GreetingPO();
       greetingPO.setGreeting("Quid est nomen tibi?");
      
       // Point the GreetingPO to its GreetingSetPO
       greetingPO.setGreetingSet(greetingSetPO);
      
       // Point the GreetingSetPO to the GreetingPO
       Set<GreetingPO> greetings = new HashSet<GreetingPO>();
       greetings.add(greetingPO);
       greetingSetPO.setMembers(greetings);
      
       // Create the greeting set AND the greeting it points to
       greetingSetDao.create(greetingSetPO);
      }

      /**
       * Works with envers + JTA transaction manager (and also with
       * envers + Hibernate transaction manager)
       */
      public void simpleCreates() {
       // Save the set first, with no greetings in it
       final String setName = "Saludos";
       GreetingSetPO greetingSet = new GreetingSetPO();
       greetingSet.setName(setName);
       greetingSetDao.create(greetingSet);
      
       // Pull the saved set id back from disk and save the greeting
       GreetingPO greetingPO = new GreetingPO();
       greetingPO.setGreeting("Hola");
       greetingPO.setGreetingSet(greetingSetDao.findByName(setName));
       greetingDao.create(greetingPO);
      }
      


      Should I be able to save a collection its elements with one session.save(), or do we need to be doing a separate explicit session.save() on each record in the data tree (tedious if we can avoid it)?

      -Daniel-

        • 1. Re: Saving new collection and its first element with same se

          The title of the post should be: Saving new collection and its first element with same session.save() yields exception when using JTA transaction manager

          • 2. Re: Saving new collection and its first element with same se
            adamw

            Hello,

            what's the exact exception? And what's the mapping of the Greeting classes?

            --
            Adam

            • 3. Re: Saving new collection and its first element with same se

               

              what's the exact exception?

              The exception is:
              org.hibernate.PropertyAccessException: IllegalArgumentException occurred while calling setter of com.ontsys.db.GreetingPO.id

              The exception is coming from a reflective call to method.invoke() in org.hibernate.property.BasicPropertyAccessor.BasicSetter.set():

              method.invoke( target, new Object[] { value } );

              During the invocation that triggers the exception, target and value hold the following data:

              • target=(java.util.HashMap<K,V>) {VER_REV_TYPE=ADD, originalId={nullid=419, VER_REV=com.ontsys.db.RevisionEntityPO@ac3e4, greetingSet_id=418}}
              • value=(java.lang.Long) 421(RevisionEntityPO is the @RevisionEntity table, configured for Oracle.)

                BasicPropertyAccessor.BasicSetter.set() catches the IllegalArgumentException, logs these messages:
                IllegalArgumentException in class: com.ontsys.db.GreetingPO, setter method of property: id
                expected type: java.lang.Long, actual value: java.lang.Long

                ...and then throws the PropertyAccessException.
                And what's the mapping of the Greeting classes?


                Here's GreetingPO.java:
                package com.ontsys.db;
                
                import javax.persistence.Column;
                import javax.persistence.Entity;
                import javax.persistence.GeneratedValue;
                import javax.persistence.Id;
                import javax.persistence.JoinColumn;
                import javax.persistence.ManyToOne;
                import javax.persistence.Table;
                
                import org.jboss.envers.Versioned;
                
                @Entity
                @Table
                @Versioned
                public class GreetingPO {
                 private Long id;
                 private String theGreeting;
                 private GreetingSetPO greetingSet;
                
                 @Id
                 @GeneratedValue
                 public Long getId() {
                 return id;
                 }
                
                 public void setId(Long id) {
                 this.id = id;
                 }
                
                 @Column
                 public String getGreeting() {
                 return theGreeting;
                 }
                
                 public void setGreeting(String greeting) {
                 this.theGreeting = greeting;
                 }
                
                 @ManyToOne
                 @JoinColumn
                 public GreetingSetPO getGreetingSet() {
                 return greetingSet;
                 }
                
                 public void setGreetingSet(GreetingSetPO greetingSet) {
                 this.greetingSet = greetingSet;
                 }
                }
                


                GreetingSetPO.java:
                package com.ontsys.db;
                
                import java.util.Set;
                
                import javax.persistence.CascadeType;
                import javax.persistence.Column;
                import javax.persistence.Entity;
                import javax.persistence.GeneratedValue;
                import javax.persistence.Id;
                import javax.persistence.OneToMany;
                import javax.persistence.Table;
                
                import org.jboss.envers.Versioned;
                
                @Entity
                @Table
                @Versioned
                public class GreetingSetPO {
                 private Long id;
                 private String name;
                 private Set<GreetingPO> members;
                
                 @Id
                 @GeneratedValue
                 public Long getId() {
                 return id;
                 }
                
                 public void setId(Long id) {
                 this.id = id;
                 }
                
                 @OneToMany(cascade = CascadeType.ALL, mappedBy = "greetingSet")
                 public Set<GreetingPO> getMembers() {
                 return members;
                 }
                
                 public void setMembers(Set<GreetingPO> members) {
                 this.members = members;
                 }
                
                 @Column
                 public String getName() {
                 return name;
                 }
                
                 public void setName(String name) {
                 this.name = name;
                 }
                }
                


                RevisionEntityPO.java:
                package com.ontsys.db;
                
                import javax.persistence.Column;
                import javax.persistence.Entity;
                import javax.persistence.GeneratedValue;
                import javax.persistence.Id;
                import javax.persistence.Table;
                
                import org.jboss.envers.RevisionEntity;
                import org.jboss.envers.RevisionNumber;
                import org.jboss.envers.RevisionTimestamp;
                
                @Entity
                @RevisionEntity
                @Table(name = "REV_ENT")
                public class RevisionEntityPO {
                 private Integer id;
                 private Long timestamp;
                
                 @Id
                 @GeneratedValue
                 @RevisionNumber
                 @Column(name = "ID")
                 public Integer getId() {
                 return id;
                 }
                
                 public void setId(Integer id) {
                 this.id = id;
                 }
                
                 @RevisionTimestamp
                 @Column(name = "TMSTMP")
                 public Long getTimestamp() {
                 return timestamp;
                 }
                
                 public void setTimestamp(Long timestamp) {
                 this.timestamp = timestamp;
                 }
                }
                


                I'll also include here the Hibernate config, in case it helps:
                <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
                 <property name="dataSource" ref="myDataSource" />
                 <property name="hibernateProperties">
                 <props>
                 <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
                 <prop key="hibernate.hbm2ddl.auto">validate</prop>
                
                 <prop key="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.BTMTransactionManagerLookup</prop>
                 <prop key="hibernate.transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</prop>
                
                 <!--these fields need renamed from their default values. The default names start with an underscore,
                 which is illegal in oracle -->
                 <prop key="org.jboss.envers.revisionTypeFieldName">VER_REV_TYPE</prop>
                 <prop key="org.jboss.envers.revisionFieldName">VER_REV</prop>
                 <prop key="org.jboss.envers.versionsTableSuffix">_V</prop>
                
                 <!-- The following are the default values for these settings. We just wanted to be explicit about it -->
                 <prop key="org.jboss.envers.versionsTablePrefix"></prop>
                 <prop key="org.jboss.envers.revisionOnCollectionChange">true</prop>
                 <prop key="org.jboss.envers.warnOnUnsupportedTypes">false</prop>
                 <prop key="org.jboss.envers.unversionedOptimisticLockingField">false</prop>
                 </props>
                 </property>
                
                 <property name="eventListeners">
                 <map>
                 <entry key="post-insert">
                 <ref local="versionsEventListener" />
                 </entry>
                 <entry key="post-update">
                 <ref local="versionsEventListener" />
                 </entry>
                 <entry key="post-delete">
                 <ref local="versionsEventListener" />
                 </entry>
                 <entry key="pre-collection-update">
                 <ref local="versionsEventListener" />
                 </entry>
                 <entry key="pre-collection-remove">
                 <ref local="versionsEventListener" />
                 </entry>
                 <entry key="post-collection-recreate">
                 <ref local="versionsEventListener" />
                 </entry>
                 </map>
                 </property>
                
                 <property name="annotatedClasses">
                 <list>
                 <value>com.ontsys.db.RevisionEntityPO</value>
                 <value>com.ontsys.db.GreetingPO</value>
                 <value>com.ontsys.db.GreetingSetPO</value>
                 </list>
                 </property>
                 </bean>
                


                And finally, again just in case it helps, here's the call stack at the method.invoke() that throws the PropertyAccessException:

                Thread [main] (Suspended (breakpoint at line 66 in org.hibernate.property.BasicPropertyAccessor$BasicSetter))
                org.hibernate.property.BasicPropertyAccessor$BasicSetter.set(Object, Object, SessionFactoryImplementor) line: 66
                org.hibernate.tuple.entity.PojoEntityTuplizer(AbstractEntityTuplizer).setIdentifier(Object, Serializable) line: 234
                org.hibernate.persister.entity.SingleTableEntityPersister(AbstractEntityPersister).setIdentifier(Object, Serializable, EntityMode) line: 3624
                org.hibernate.event.def.DefaultSaveEventListener(AbstractSaveEventListener).performSave(Object, Serializable, EntityPersister, boolean, Object, EventSource, boolean) line: 194
                org.hibernate.event.def.DefaultSaveEventListener(AbstractSaveEventListener).saveWithGeneratedId(Object, String, Object, EventSource, boolean) line: 144
                org.hibernate.event.def.DefaultSaveEventListener(DefaultSaveOrUpdateEventListener).saveWithGeneratedOrRequestedId(SaveOrUpdateEvent) line: 210
                org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(SaveOrUpdateEvent) line: 56
                org.hibernate.event.def.DefaultSaveEventListener(DefaultSaveOrUpdateEventListener).entityIsTransient(SaveOrUpdateEvent) line: 195
                org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(SaveOrUpdateEvent) line: 50
                org.hibernate.event.def.DefaultSaveEventListener(DefaultSaveOrUpdateEventListener).onSaveOrUpdate(SaveOrUpdateEvent) line: 93
                org.hibernate.impl.SessionImpl.fireSave(SaveOrUpdateEvent) line: 562
                org.hibernate.impl.SessionImpl.save(String, Object) line: 550
                org.jboss.envers.synchronization.work.PersistentCollectionChangeWorkUnit.perform(Session, Object) line: 67
                org.jboss.envers.synchronization.VersionsSync.executeInSession(Session) line: 120
                org.jboss.envers.synchronization.VersionsSync.beforeCompletion() line: 135
                bitronix.tm.BitronixTransaction.fireBeforeCompletionEvent() line: 366
                bitronix.tm.BitronixTransaction.commit() line: 142
                bitronix.tm.BitronixTransactionManager.commit() line: 96
                org.springframework.transaction.jta.JtaTransactionManager.doCommit(DefaultTransactionStatus) line: 1028
                org.springframework.transaction.jta.JtaTransactionManager(AbstractPlatformTransactionManager).processCommit(DefaultTransactionStatus) line: 732
                org.springframework.transaction.jta.JtaTransactionManager(AbstractPlatformTransactionManager).commit(TransactionStatus) line: 701
                org.springframework.transaction.interceptor.TransactionInterceptor(TransactionAspectSupport).commitTransactionAfterReturning(TransactionAspectSupport$TransactionInfo) line: 321
                org.springframework.transaction.interceptor.TransactionInterceptor.invoke(MethodInvocation) line: 116
                org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation(ReflectiveMethodInvocation).proceed() line: 171
                org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(MethodInvocation) line: 89
                org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation(ReflectiveMethodInvocation).proceed() line: 171
                org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Object, Method, Object[], MethodProxy) line: 635
                com.ontsys.db.GreetingSetDAO$$EnhancerByCGLIB$$cd993582.create(GreetingSetPO) line: not available
                com.ontsys.db.EnversWithCollectionsTest.testComplexCreate() line: 112
                sun.reflect.NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
                sun.reflect.NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
                sun.reflect.DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
                java.lang.reflect.Method.invoke(Object, Object...) line: 597
                org.junit.runners.model.FrameworkMethod$1.runReflectiveCall() line: 44
                org.junit.runners.model.FrameworkMethod$1(ReflectiveCallable).run() line: 15
                org.junit.runners.model.FrameworkMethod.invokeExplosively(Object, Object...) line: 41
                org.junit.internal.runners.statements.InvokeMethod.evaluate() line: 20
                org.junit.internal.runners.statements.RunBefores.evaluate() line: 28
                org.junit.internal.runners.statements.RunAfters.evaluate() line: 31
                org.junit.runners.BlockJUnit4ClassRunner.runChild(FrameworkMethod, RunNotifier) line: 73
                org.junit.runners.BlockJUnit4ClassRunner.runChild(Object, RunNotifier) line: 46
                org.junit.runners.BlockJUnit4ClassRunner(ParentRunner).runChildren(RunNotifier) line: 180
                org.junit.runners.ParentRunner.access$000(ParentRunner, RunNotifier) line: 41
                org.junit.runners.ParentRunner$1.evaluate() line: 173
                org.junit.internal.runners.statements.RunBefores.evaluate() line: 28
                org.junit.internal.runners.statements.RunAfters.evaluate() line: 31
                org.junit.runners.BlockJUnit4ClassRunner(ParentRunner).run(RunNotifier) line: 220
                org.eclipse.jdt.internal.junit4.runner.JUnit4TestMethodReference(JUnit4TestReference).run(TestExecution) line: 38
                org.eclipse.jdt.internal.junit.runner.TestExecution.run(ITestReference[]) line: 38
                org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(String[], String, TestExecution) line: 460
                org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(TestExecution) line: 673
                org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run() line: 386
                org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(String[]) line: 196


              • 4. Re: Saving new collection and its first element with same se

                I wonder if my bidirectional one-to-many relationship is set up correctly in the annotated classes? Reading in preparation to post to a Hibernate forum made me realize that there are some Hibernate concepts I may need to understand more deeply.

                I've written in more detail here: http://ourcraft.wordpress.com/2009/01/30/hibernate-one-to-many-foray/

                • 5. Re: Saving new collection and its first element with same se
                  adamw

                  Hello,

                  I didn't have much time to read your posts in detail yet, but the mappings seem ok.

                  There is one very strange thing:
                  "expected type: java.lang.Long, actual value: java.lang.Long"
                  (in the exception)

                  This looks like a classloading problem. In most classloading scenarios I've seen, java.lang was in the base classloader, and not isolated - but maybe Spring is different here. Maybe sth with OSGi? Are you using it?

                  I never used Spring so I don't think I won't be of much help here. Maybe look in the FAQ:
                  https://www.jboss.org/community/docs/DOC-13216

                  --
                  Adam