Kuali Rice Development
  1. Kuali Rice Development
  2. KULRICE-11414

Extension framework does not work with objects using sequences for primary key

    Details

    • Type: Bug Fix Bug Fix
    • Status: Closed Closed
    • Priority: Critical Critical
    • Resolution: Fixed
    • Affects Version/s: None
    • Fix Version/s: 2.4
    • Component/s: JPA
    • Security Level: Public (Public: Anyone can view)
    • Labels:
      None
    • Similar issues:
      KULRICE-10493Notification message that lookup is by primary key is not working.
      KULRICE-9529Document how to use the new extension framework
      KULRICE-9119Review design for extension framework in new data layer
      KULRICE-10452Primary keys should not be editable on maintenance edit action
      KULRICE-4442Person service does not handle extension objects properly
      KULRICE-4025Direct inquiry does not work properly in some conditions
      KULRICE-11602Create extension points to override ExpressionEvaluator
      KULRICE-3658Extension attributes on new objects are deleted by workflow processing
      KULRICE-4802Quickfinder error when searching for related object not by primary key
      KULRICE-11182Clear out all locking keys on Maintenance copy, not just the main object
    • Application Requirement:
      KC
    • Sprint:
      2.4.0-m4 Dev Sprint 4 (Real)
    • KAI Review Status:
      Not Required
    • KTI Review Status:
      Not Required
    • Code Review Status:
      Not Required
    • Include in Release Notes?:
      Yes

      Description

      Currently, KC has code that allows implementers extend BOs at the db level with minimal java code changes via the extension framework in the kns. The complication with this is that, the new table will not only have a FK to the parent table but this FK will also be its own PK and that value is generated from a sequence on the parent table. Need JPA support for this style of extensions. For more info on this see here : https://wiki.kuali.org/display/KULDOC/Extended+Attributes+3

      KC customized there base Persistable Business Object class to support what is described above. See KraPersistableBusinessObjectBase

      Also see https://wiki.kuali.org/display/KRACOEUS/JPA+POC+-+ProposalDevelopmentDocument for more information about this.

        Activity

        Hide
        Eric Westfall added a comment -

        I did some research here, unfortunately it appears that in order for these OneToOne relationships to work properly with generated identifiers, we have to essentially construct a mapping similar to the following:

            /**
             * An entity with an extension object where a sequence is used for the primary key.
             */
            @Entity
            @Table(name = "KRTST_WITH_SEQ_EXT_T")
            public static class WithSequenceExtension {
        
                @Id
                @GeneratedValue(generator = "KRTST_WITH_SEQ_EXT_S")
                @PortableSequenceGenerator(name = "KRTST_WITH_SEQ_EXT_S")
                @Column(name = "ID")
                private Long id;
        
                @Column(name = "VAL")
                private String value;
        
                @OneToOne(cascade = CascadeType.ALL, mappedBy = "withSequenceExtension")
                private SequenceExtension extension;
        
                public Long getId() {
                    return id;
                }
        
                public void setId(Long id) {
                    this.id = id;
                }
        
                public String getValue() {
                    return value;
                }
        
                public void setValue(String value) {
                    this.value = value;
                }
        
                public SequenceExtension getExtension() {
                    return extension;
                }
        
                public void setExtension(SequenceExtension extension) {
                    this.extension = extension;
                }
            }
        
            @Entity
            @Table(name = "KRTST_SEQ_EXT_T")
            public static class SequenceExtension {
        
                @Id
                @OneToOne
                @JoinColumn(name = "ID")
                private WithSequenceExtension withSequenceExtension;
        
                @Column(name = "VAL")
                private String value;
        
                public Long getId() {
                    if (withSequenceExtension != null) {
                        return withSequenceExtension.getId();
                    }
                    return null;
                }
        
                public WithSequenceExtension getWithSequenceExtension() {
                    return withSequenceExtension;
                }
        
                public void setWithSequenceExtension(WithSequenceExtension withSequenceExtension) {
                    this.withSequenceExtension = withSequenceExtension;
                }
        
                public String getValue() {
                    return value;
                }
        
                public void setValue(String value) {
                    this.value = value;
                }
            }
        
        

        So, we need to have the "dependent" entity (in our case the extension) have a relationship back to it's parent which is mapped with @Id. From what I can tell there is no way around this if we are using generated identifiers unless we were to do something like manually generate the ID first and then set it on our extension object. That may be one option to consider as well I suppose, but that means the framework would need to be smart enough to handle that and I really don't want to be baking things like that into the core framework to cover JPA edge cases.

        Some additional info, for the above mapping the following test passes:

            @Test
            public void testExtension_withSequence() {
                WithSequenceExtension parent = new WithSequenceExtension();
                parent.setValue("the-value");
                SequenceExtension extension = new SequenceExtension();
                extension.setValue("the-extension-value");
                parent.setExtension(extension);
                extension.setWithSequenceExtension(parent);
        
                DataObjectService dataObjectService = KradDataServiceLocator.getDataObjectService();
                parent = dataObjectService.save(parent, PersistenceOption.FLUSH);
        
                assertNotNull(parent.getId());
                assertEquals("the-value", parent.getValue());
                extension = parent.getExtension();
                assertNotNull(extension.getId());
                assertEquals(parent, extension.getWithSequenceExtension());
                assertEquals(parent.getId(), extension.getId());
                assertEquals("the-extension-value", extension.getValue());
            }
        
        Show
        Eric Westfall added a comment - I did some research here, unfortunately it appears that in order for these OneToOne relationships to work properly with generated identifiers, we have to essentially construct a mapping similar to the following: /** * An entity with an extension object where a sequence is used for the primary key. */ @Entity @Table(name = "KRTST_WITH_SEQ_EXT_T" ) public static class WithSequenceExtension { @Id @GeneratedValue(generator = "KRTST_WITH_SEQ_EXT_S" ) @PortableSequenceGenerator(name = "KRTST_WITH_SEQ_EXT_S" ) @Column(name = "ID" ) private Long id; @Column(name = "VAL" ) private String value; @OneToOne(cascade = CascadeType.ALL, mappedBy = "withSequenceExtension" ) private SequenceExtension extension; public Long getId() { return id; } public void setId( Long id) { this .id = id; } public String getValue() { return value; } public void setValue( String value) { this .value = value; } public SequenceExtension getExtension() { return extension; } public void setExtension(SequenceExtension extension) { this .extension = extension; } } @Entity @Table(name = "KRTST_SEQ_EXT_T" ) public static class SequenceExtension { @Id @OneToOne @JoinColumn(name = "ID" ) private WithSequenceExtension withSequenceExtension; @Column(name = "VAL" ) private String value; public Long getId() { if (withSequenceExtension != null ) { return withSequenceExtension.getId(); } return null ; } public WithSequenceExtension getWithSequenceExtension() { return withSequenceExtension; } public void setWithSequenceExtension(WithSequenceExtension withSequenceExtension) { this .withSequenceExtension = withSequenceExtension; } public String getValue() { return value; } public void setValue( String value) { this .value = value; } } So, we need to have the "dependent" entity (in our case the extension) have a relationship back to it's parent which is mapped with @Id. From what I can tell there is no way around this if we are using generated identifiers unless we were to do something like manually generate the ID first and then set it on our extension object. That may be one option to consider as well I suppose, but that means the framework would need to be smart enough to handle that and I really don't want to be baking things like that into the core framework to cover JPA edge cases. Some additional info, for the above mapping the following test passes: @Test public void testExtension_withSequence() { WithSequenceExtension parent = new WithSequenceExtension(); parent.setValue( "the-value" ); SequenceExtension extension = new SequenceExtension(); extension.setValue( "the-extension-value" ); parent.setExtension(extension); extension.setWithSequenceExtension(parent); DataObjectService dataObjectService = KradDataServiceLocator.getDataObjectService(); parent = dataObjectService.save(parent, PersistenceOption.FLUSH); assertNotNull(parent.getId()); assertEquals( "the-value" , parent.getValue()); extension = parent.getExtension(); assertNotNull(extension.getId()); assertEquals(parent, extension.getWithSequenceExtension()); assertEquals(parent.getId(), extension.getId()); assertEquals( "the-extension-value" , extension.getValue()); }
        Hide
        Eric Westfall added a comment -

        Slight modification to title of this issue and changed it to a Bug to reflect that it's a bug in the current implementation.

        Show
        Eric Westfall added a comment - Slight modification to title of this issue and changed it to a Bug to reflect that it's a bug in the current implementation.
        Hide
        Eric Westfall added a comment -

        Note, it seems like the path forward here is that for situations where someone wants to use a sequenced primary key with the extension frameworks, then they need to implement their extensions as above. The framework will need to handle making sure that the reference back to the parent object needs to be set as well, so that is something which still needs to determine how exactly that will happen.

        Show
        Eric Westfall added a comment - Note, it seems like the path forward here is that for situations where someone wants to use a sequenced primary key with the extension frameworks, then they need to implement their extensions as above. The framework will need to handle making sure that the reference back to the parent object needs to be set as well, so that is something which still needs to determine how exactly that will happen.
        Hide
        Eric Westfall added a comment - - edited

        This does work as of my latest commit for KULRICE-10937. The main thing is you have to annotate them properly. Essentially, on your extension object you need to use an object pointing back to the parent class rather than a field.

        So, a previous implementation might have looked like this:

        @Entity
        @Table(name = "MY_EXTENSION_T")
        @ExtensionFor(MyParent.class)
        public class MyExtension {
        
            @Id
            @Column(name = "PARENT_ID")
            private Long parentId;
        
            // other fields, getters, setters, etc.
        }
        

        Instead it needs to be mapped like this:

        @Entity
        @Table(name = "MY_EXTENSION_T")
        @ExtensionFor(MyParent.class)
        public class MyExtension {
        
            @Id
            @OneToOne
            @JoinColumn(name = "PARENT_ID")
            private MyParent myParent;
        
           // other fields, getters, setters, etc.
        }
        
        Show
        Eric Westfall added a comment - - edited This does work as of my latest commit for KULRICE-10937 . The main thing is you have to annotate them properly. Essentially, on your extension object you need to use an object pointing back to the parent class rather than a field. So, a previous implementation might have looked like this: @Entity @Table(name = "MY_EXTENSION_T" ) @ExtensionFor(MyParent.class) public class MyExtension { @Id @Column(name = "PARENT_ID" ) private Long parentId; // other fields, getters, setters, etc. } Instead it needs to be mapped like this: @Entity @Table(name = "MY_EXTENSION_T" ) @ExtensionFor(MyParent.class) public class MyExtension { @Id @OneToOne @JoinColumn(name = "PARENT_ID" ) private MyParent myParent; // other fields, getters, setters, etc. }
        Hide
        Eric Westfall added a comment -

        This has been committed to trunk for milestone 4.

        Show
        Eric Westfall added a comment - This has been committed to trunk for milestone 4.

          People

          • Assignee:
            Eric Westfall
            Reporter:
            Gayathri Athreya
          • Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Time Tracking

              Estimated:
              Original Estimate - 6 hours
              6h
              Remaining:
              Remaining Estimate - 6 hours
              6h
              Logged:
              Time Spent - Not Specified
              Not Specified

                Agile

                  Structure Helper Panel