10 Replies Latest reply on Sep 19, 2008 5:47 AM by adamw

    Support to joined inheritance strategy

    morman

      Hi All.

      I know that this strategy is not supported, but for the project I'am trying integrate envers to it is required (from business point of view it will be to much effort to change mappings to other strategy).

      I've tried to write such support myself but I've stucked on the following problem. Hibernate in the entity persister uses Map that maps Class to entity name (for mapping subclasses). While our version entities are created on the fly they have no classes so subclasses may not be mapped. Does anybody have idea for some simple solution for this problem (excluding writing custom Persister or generating and compiling class on the fly)? After all support for such inheritance strategy should be added to envers ;).

      Michal

        • 1. Re: Support to joined inheritance strategy
          adamw

          Hello,

          is the problem in generating the mapping in VersionsMetadataGenerator? I guess creating "dynamic" subclasses shold be possible - it works with @SecondaryTable (which creates a join - see the addJoins method) and single-table inheritance strategy (when reading a dynamically mapped "sub"-entity, the entity name is one of the elements of the map).

          --
          Adam

          • 2. Re: Support to joined inheritance strategy
            morman

            Hi Adam, thanks for your response ;).

            The problem is with org.hibernate.persister.entity.AbstractEntityPersister. As far as i understand for entities involved in joined inheritance we need to use org.hibernate.persister.entity.JoinedSubclassEntityPersister, which extends AbstractEntityPersister. And the problem is in this abstract class's constructor in following piece of code:

            if ( persistentClass.hasPojoRepresentation() ) {
             //TODO: this is currently specific to pojos, but need to be available for all entity-modes
             Iterator iter = persistentClass.getSubclassIterator();
             while ( iter.hasNext() ) {
             PersistentClass pc = ( PersistentClass ) iter.next();
             entityNameBySubclass.put( pc.getMappedClass(), pc.getEntityName() );
             }
            }
            


            Problem is, that dynamicaly crreated entities (those with _versions suffix) has no pojo representation therefore hibernate is unable to map subclass entities.

            The persistentClass.hasPojoRepresentation() is quite simple it just looking if given persistentClass has className property set. In current implementation this property is always null and I was unable to find place where this class is being populated and where I can set this property. Could you point me appropriate place? ;).

            Nevertheless the problem is that hibernate will try to load class with this name, and no classes with "_versions" suffix are possible to load. It needs it to map the subclass entities.

            Hope you understand my point :).

            Currently I am experimenting with dynamic class compilation using BCEL.

            Michal

            • 3. Re: Support to joined inheritance strategy
              adamw

              Ah, I see. Hmm, well, there must be a reason the persister has this flag :) Though, you can try compiling hibernate with this turned off and see what happens :) Maybe it's also possible to create a subclass of this persister, overriding the method that has this check?

              The mappings for the dynamic entities are generated in VersionsMetadataGenerator.

              With BCEL - you want to create subclasses of the entities with the additonal fields added, and then map this class, instead of using dynamic entities? I thought about that initially too, but then decided that using dynamic entities will be easier. However it has its drawbacks. For example, when mapping a relation, you can't leave it as a one-to-many relation, you have to map the ID properties directly.

              --
              Adam

              • 4. Re: Support to joined inheritance strategy
                morman

                I've added dynamically compiled class for every classes that are annotated with @Versioned and also set the className property for PersistentClasses in configuration, but it still not work :). Now I've got the exception:

                Caused by: org.hibernate.PropertyNotFoundException: Could not find a getter for originalId in class com.consileon.reportingtool.data.dataobjects.InvoiceInput_versions
                 at org.hibernate.property.BasicPropertyAccessor.createGetter(BasicPropertyAccessor.java:282)
                 at org.hibernate.property.BasicPropertyAccessor.getGetter(BasicPropertyAccessor.java:275)
                 at org.hibernate.tuple.PropertyFactory.getGetter(PropertyFactory.java:168)
                 at org.hibernate.tuple.PropertyFactory.buildIdentifierProperty(PropertyFactory.java:44)
                 at org.hibernate.tuple.entity.EntityMetamodel.<init>(EntityMetamodel.java:124)
                 at org.hibernate.persister.entity.AbstractEntityPersister.<init>(AbstractEntityPersister.java:434)
                 at org.hibernate.persister.entity.SingleTableEntityPersister.<init>(SingleTableEntityPersister.java:109)
                 at org.hibernate.persister.PersisterFactory.createClassPersister(PersisterFactory.java:55)
                 at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:226)
                 at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1300)
                 at org.springframework.orm.hibernate3.LocalSessionFactoryBean.newSessionFactory(LocalSessionFactoryBean.java:814)
                 at org.springframework.orm.hibernate3.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:732)
                 at org.springframework.orm.hibernate3.AbstractSessionFactoryBean.afterPropertiesSet(AbstractSessionFactoryBean.java:211)
                 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1368)
                 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1334)
                 ... 78 more
                


                The funniest thing is that property 'orginalId' doesn't exist in whole InvoiceInput class hierarchy, so I cannot simply copy InvoiceInput classes properties to InvoiceInvput_versions.

                Have any idea where this property comes from? Is envers adds this property to mapping/configuration in some step?

                • 5. Re: Support to joined inheritance strategy
                  adamw

                  Hello,

                  wow! So now the mappings are to real classes, dynamically generated? Nice :).

                  The originalId property is the primary key for the versions class. It consists of fields that constituted the original id of the entity plus the revision number.

                  If you go to org.jboss.envers.configuration.EntitiesConfigurator and uncomment the "writeDocument" invocations, you'll see exactly how things are mapped (when you run your application).

                  Adam

                  • 6. Re: Support to joined inheritance strategy
                    morman

                    While I have some problems with generating methods with BCEL I've done a little workaround and write *_versions classes by hand. I've copied all properties from the orginal classes and add: orginalId, _revision and _revision_type (together with appropriate setters and getters). And guess what... it still not works :).

                    Currently I've got problem with getSubclassEntityPersister method in AbstractEntityPersister where I've got the following exception:

                    org.hibernate.HibernateException: instance not of expected entity type: java.util.HashMap is not a: com.consileon.reportingtool.data.dataobjects.Company_versions
                     at org.hibernate.persister.entity.AbstractEntityPersister.getSubclassEntityPersister(AbstractEntityPersister.java:3640)
                     at org.hibernate.impl.SessionImpl.getEntityPersister(SessionImpl.java:1347)
                     at org.hibernate.id.Assigned.generate(Assigned.java:28)
                     at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:99)
                     at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:187)
                     at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:33)
                     at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:172)
                     at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:27)
                     at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
                     at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:535)
                     at org.hibernate.impl.SessionImpl.save(SessionImpl.java:523)
                     at org.jboss.envers.synchronization.work.ModWorkUnit.perform(ModWorkUnit.java:56)
                     at org.jboss.envers.synchronization.VersionsSync.executeInSession(VersionsSync.java:120)
                    
                    ...(cut)
                    


                    What can be observed is that hibernate expecting 'instance' to be set to versioned class, instead of this we have HashMap. As I investigated this map is set in ModWorkUnit. Could you explain me what is the purpose of this class and why HashMap is used?

                    As I understand, this map contains properties for all versioned entities. Map is used beacuse there was no real classes for *_versions entities, and now if I dynamically create those classes I can made appropriate instances, set appropriate properties and use this object instead of HashMap. Am I right?

                    • 7. Re: Support to joined inheritance strategy
                      morman

                      I've done a little more investigation regarding this issue, and I think that if Hibernate has ability to map objects as Map of properties, than it must exist a way to map joined inheritance as such Map. Perhaps there is no need to have concrete classes, and just do appropriate mapping (maybe a Map of Maps ;)). Perhaps some other persister is required to be used instead of JoinedSubclassEntityPersister.

                      This approach should be easier to implement. Have any clues how to map joined inheritence using Map's in Hibernate?

                      • 8. Re: Support to joined inheritance strategy
                        adamw

                        Hello,

                        regarding your last but one post: you are right. The ModWorkUnit, similarly to other work units, just maps the entity state to a map, using a property mapper (which has a method for that). The property mapper stores informatino about an entities' properties, reads them from the bean and writes to a map.

                        As to your second post, well, maybe it would be good first to try implementing joined-inhertiance persistence for dynamic models. So, create a dynamic model, a custom persister (the code of the persister could be copied from the original one, but with the problematic "if" removed) and see if it works :).

                        --
                        Adam

                        • 9. Re: Support to joined inheritance strategy
                          morman

                          Well it not so simple to remove the problematic if, while it is in constructor of AbstractEntityPersister so it is not possible to do simple inheritance, but copy-paste the code and rewrite the constructor ;). And the problem is if hibernate represent entity subclasses using Class to entity name map then it is highly possible that in some place it will try to read this mapping and then it will not find suitable class ;).

                          I've added to my dynamic class compilation model such behaviour that instead of persisting entities represented by maps i use the instances of classes that i create dynamically. And new exception occurs:

                          java.lang.ClassCastException: com.consileon.reportingtool.data.dataobjects.Branch_versions cannot be cast to java.util.Map
                          (...)
                          


                          In my application Branch is extending Company (wich was present in my previous posts :)). So it seems that if I use maps the Company_versions causes the exception (superclass) and if I use real instances the Branch_versions causes the eception (subclass) :).

                          I think that the best solution will be to find out if it is possible (and how) to represent joined-inheritance in Hibernate using map of properties representation. Otherwise it seems that lot of changes will be required to be done (including writing custom persister from scratch ;)).

                          • 10. Re: Support to joined inheritance strategy
                            adamw

                            Hello,

                            maybe try the hibernate forum? And copying the abstract entities perister constructor :).

                            When using dynamic models with inheritance, the type of the entity is stored in a special field: $type$. Also, the single-table strategy works with dynamic models.

                            --
                            Adam