Announcement Announcement Module
Collapse
No announcement yet.
OneToMany bi-directional relationship - OneToMany set is always empty Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • OneToMany bi-directional relationship - OneToMany set is always empty

    I have a OneToMany, bi-directional relationship set up with Roo/JPA. I am using the following:

    Spring Roo 1.1.5.RELEASE
    EclipseLink
    MySQL 5.5.14
    Java 1.6.0_23
    Windows 7

    My relationship is defined as follows:

    Code:
    @RooJavaBean
    @RooToString
    public class Baby {
    
        @NotNull
        @ManyToOne
        private Mother mother;
    }
    The bi-directional relationship is defined by:

    Code:
    @RooJavaBean
    @RooToString
    public class Mother{
    
        @OneToMany(cascade = {CascadeType.REMOVE }, mappedBy = "mother", fetch =  FetchType.EAGER)
        @OrderBy("sequence")
        Set<Baby> babies= new TreeSet<Baby>();
    }
    Any time I use a Roo finder method to fetch a Mother from the database, the set of baby objects is always null. However, fetching a Baby from the database always has the Mother member variable set correctly. I have tried FetchType.EAGER and FetchType.LAZY. For example, the following log statement always outputs zero:

    Code:
    Mother m = Mother.findMother(id);
    
    logger.info("mother has {} children", m.getBabies().size());
    Thanks, any help is appreciated.

  • #2
    Try using PersistenceUnitUtil to check whether the Set has actually been initialised, as explained here.

    Some non-Roo-standard things I noticed about your code (might be irrelevant):
    • no @RooEntity annotations (presumably you have these in your real code)
    • the Set is not private
    • the Set is a TreeSet, not a HashSet (might be relevant)
    Also try turning on SQL debug mode to see what SQL queries EclipseLink is actually sending to the database.

    Comment


    • #3
      Hi Andrew, thanks for your reply. Following your suggestions:
      • Used PersistenceUnitUtil in my integration test, but to no avail
      • @RooEntity is definitely set in my real code
      • Made Set private like my other fields
      • Changed Set from TreeSet to HashSet

      I still was not getting the Set populated in my integration test, so I enabled SQL debugging as you suggested. This shed some light on things, since I was not seeing the SELECT statement when calling the Roo-generated Mother.findMother(long id) method to fetch the Mother object that I had previously persisted in the test. This has led me to believe that some caching mechanism is in effect with EclipseLink, so I disabled caching for my entity as follows:

      Code:
      @Cache (
      	type=CacheType.WEAK,
      	isolation=CacheIsolationType.SHARED,
      	expiry=60000,
      	alwaysRefresh=true,
      	disableHits=true,
      	coordinationType=CacheCoordinationType.INVALIDATE_CHANGED_OBJECTS
      )
      public class Mother { ... }
      I have also added the following option to persistence.xml:

      Code:
      <property name="eclipselink.query-results-cache" value="false"/>
      So, either I still am not configuring my EclipseLink cache correctly, or there is some other reason why Mother.findMother(long id) is not returning the set of children in the returned Mother object. Here is the code from my integration test:

      Code:
      Mother mom = new Mother();
      mom.persist();
      mom.flush()
      
      // mom is correctly inserted
      Assert.assertNotNull("mom id should not be null", mom.getId());
      
      Baby child = new Baby();
      child.setMother(mom);
      
      // child is correctly inserted, referencing mom
      child = child.merge();
      
      Assert.assertNotNull("child id should not be null", child.getId());
      
      Mother freshMom = Mother.findMother(mom.getId());
      Assert.assertNotNull("freshMom should not be null", freshMom);
      Assert.assertEquals(mom, freshMom);  // this passes... I would think it should not
      
      Assert.assertTrue("freshMom should have at least one child", freshMom.getBabies().size() > 0); // this fails, I would expect it to pass since I just added it
      Is there still an issue my caching, or is there another reason why the Mother.babies Set is not being populated?

      Thanks.

      Comment


      • #4
        I am having a similar issue. When I list the the parent objects in the default Roo-generated entity listing, it does not show values for the child entities.
        When the relationship is many-to-many, this works just fine, and the parent listing will show the child objects. I have not confirmed if the child objects are actually there in memory and just not showing in the UI, but I suspect they aren't. I suspect the Set is null on the parent side.

        Comment


        • #5
          My bad. The child data is there in memory, but its just not appearing in the list.

          Comment


          • #6
            OneToMany set IS empty after all

            After running the following Roo script:


            project --topLevelPackage com.sandbox.roo --projectName roo-sandbox --java 6
            persistence setup --database H2_IN_MEMORY --provider HIBERNATE
            web mvc setup
            entity --class ~.domain.Department --testAutomatically
            entity --class com.sandbox.roo.domain.Employee --testAutomatically
            field reference --fieldName department --type com.sandbox.roo.domain.Department --cardinality MANY_TO_ONE --fetch EAGER
            focus --class ~.domain.Department
            field set --fieldName employees --type com.sandbox.roo.domain.Employee --cardinality ONE_TO_MANY
            field string --fieldName name --notNull
            focus --class com.sandbox.roo.domain.Employee
            field string --fieldName name --notNull
            web mvc all --package ~.web
            perform eclipse


            You get a basic project consisting of Departments and Employees.
            There is a One-To-Many relationship between Department and Employee and the fetch type is eager.

            When I list the Departments, the Employees do not show up in the listing.

            If I set a breakpoint in the controller, I can see that the Employee Set is not populated in the Department object after a call to entityManager.find(). The fetch type is EAGER, so why aren't the employees populated when the Department object is returned?

            See attached image for an example of what is displayed.

            Comment


            • #7
              Problem Solved

              The solution was to use mappedBy on the Department object. I haven't used JPA in a while.


              package com.sandbox.roo.domain;

              import org.springframework.roo.addon.entity.RooEntity;
              import org.springframework.roo.addon.javabean.RooJavaBean ;
              import org.springframework.roo.addon.tostring.RooToString ;
              import java.util.Set;
              import com.sandbox.roo.domain.Employee;
              import java.util.HashSet;

              import javax.persistence.FetchType;
              import javax.persistence.OneToMany;
              import javax.persistence.CascadeType;
              import javax.validation.constraints.NotNull;

              @RooJavaBean
              @RooToString
              @RooEntity
              public class Department {

              @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER,mappedBy="department")
              private Set<Employee> employees = new HashSet<Employee>();

              @NotNull
              private String name;
              }

              Comment


              • #8
                Glad you solved your problem, however jporche already has the "mappedBy" attribute, and it doesn't work for them. Anyone still having this issue should log a JIRA ticket with a minimal example project that demonstrates the problem.

                Comment


                • #9
                  Both sides of the relation count

                  Hope this is not my misunderstanding of the Active Record pattern, but traditionally one would have to satisfy both sides of the JPA relation.

                  JPorche, in their test, was only setting one side.

                  Here is a complete example in which the tests pass:

                  project --topLevelPackage mvm.mums --projectName mums --java 6
                  persistence setup --database HYPERSONIC_PERSISTENT --provider HIBERNATE
                  entity --class ~.model.Baby --testAutomatically
                  entity --class ~.model.Mother --testAutomatically
                  field set --fieldName babies --type ~.model.Baby --cardinality ONE_TO_MANY --mappedBy mother
                  focus --class ~.model.Baby
                  field reference --fieldName mother --type ~.model.Mother --cardinality MANY_TO_ONE

                  Code:
                      @Transactional
                      @Test
                      public void tryMe() {
                      	Mother mum = new Mother();
                      	Baby babs = new Baby();
                      	
                      	// We must satisfy both sides of bi-directional relation
                      	babs.setMother(mum);
                      	mum.getBabies().add(babs);
                      	
                      	mum.persist();
                      	
                      	Mother newMumBefore = Mother.findMother(mum.getId());
                      	Assert.isTrue(newMumBefore.getBabies().size() == 1);
                      	
                      	mum.flush();
                      	mum.clear();
                      	
                      	Mother newMumAfter = Mother.findMother(mum.getId());
                      	
                      	Assert.isTrue(newMumAfter.getBabies().size() == 1);
                      }
                  You could add convenience methods to your entities that take care of this for you, for example the Mother entity:

                  Code:
                  @RooJavaBean
                  @RooToString
                  @RooEntity
                  public class Mother {
                  
                      @OneToMany(cascade = CascadeType.ALL, mappedBy = "mother")
                      private Set<Baby> babies = new HashSet<Baby>();
                      
                      public void addBaby(Baby babs) {
                      	babs.setMother(this);
                      	getBabies().add(babs);
                      }
                  
                  }
                  Hope this helped, found myself scratching my scalp over same problems.

                  M.

                  Comment


                  • #10
                    Related Question: Field set @NotEmpty

                    (yet another newbie using roo)

                    I want to add just a validations rule (@NotEmpty or @NotNull or whatever says that this collections cannot be empty) to a field set of the entity Tagging, but nothing works...

                    Code:
                    @RooJavaBean
                    @RooToString
                    @RooJpaActiveRecord(table = "taggings")
                    @RooJson
                    @RooSolrSearchable
                    public class Tagging extends Event {
                    
                        @NotNull
                        @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "tagging")
                        private Set<Tag> tags = new HashSet<Tag>();
                    Also to mention that Tagging extends Event as you can see...

                    Any help/comment more than welcome!!
                    Thanks,
                    GK

                    Comment

                    Working...
                    X