2 Replies Latest reply on Jan 24, 2008 4:22 PM by Neil McFarlane

    Only on specific scenario - "all-delete-orphan" was no longe

    Neil McFarlane Novice

      Hello.

      I have a demo app where the user can enter people and save them to a database. Each person can have 0..* email addresses. The user can also go to a seperate page where the people are listed and select one to edit (taking them back to the first screen, now pre-populated with the person data)

      The following use cases work just fine:
      * Save new person with and without emails
      * Edit person with emails
      * Edit person without emails, who formerly had emails
      * Edit person (who had an email added, and removed before being saved)
      * Save new person without email, go to list page, select the person, then edit them
      * Etc.


      What's odd is that in one scenario, it doesn't work. That's if I create a new person, save them (without adding any emails), then modify them and save again (still without adding any emails). If, at any point, I add an email, even without saving it, it works.

      The error I get when this does happen is as such:

      A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance


      Here's the relevant snippets of my code:
      @Stateful
      @Name("editPerson")
      public class EditPersonForm implements EditPersonLocal {
       @PersistenceContext(type = EXTENDED)
       private EntityManager em;
      
       @In(required = false)
       @Out(scope = ScopeType.PAGE)
       private Person person;
      
       @In(required = false)
       @Out(required = false)
       private EmailAddress emailAddress;
      
       public void addEmail() {
       log.info("Adding email address #0", emailAddress.getUrl());
       person.addEmailAddress(emailAddress);
      
       //Blank the email address entry point
       emailAddress = new EmailAddress();
       }
      
       public void removeEmail(EmailAddress emailAddress) {
       log.info("Removing email address #0", emailAddress.getUrl());
       person.removeEmailAddress(emailAddress);
       }
      
       public void savePerson() {
       if (person.getId() != null){
       em.merge(person);
       }
       else {
       em.persist(person);
       }
      
       // Note that the id of our person was generated and populated.
       facesMessages.add("#0 was saved with an id of #1", person.getName(), person.getId());
      
       log.info(person.getName() + " was saved.");
       }
      }
      


       Name:
       <h:inputText value="#{person.name}" required="true"
       requiredMessage="You need to enter a name" />
       <h:outputText value="#{person.id}" />
       <br />
       <hr />
       Emails:<br />
       URL: <h:inputText value="#{emailAddress.url}" />
       <h:commandButton action="#{editPerson.addEmail()}" value="Add email" />
      
       <h:dataTable id="emailTable" value="#{person.emailAddresses}"
       var="email" rendered="#{not empty person.emailAddresses}">
       <h:column>
       <f:facet name="header">
       <h:outputText value="URL" />
       </f:facet>
       <h:outputText value="#{email.url}" />
       </h:column>
       <h:column>
       <f:facet name="header">
       <h:outputText value="Action" />
       </f:facet>
       <h:commandButton value="Remove"
       action="#{editPerson.removeEmail(email)}" />
       </h:column>
       </h:dataTable>
       <hr />
       <h:commandButton value="Save" action="#{editPerson.savePerson()}" />
       <h:commandButton value="New" action="#{editPerson.newPerson()}" />
      


      @Entity
      @Name("person")
      public class Person {
      
       @Id @GeneratedValue
       private Long id;
      
       @NotNull
       private String name;
      
       @OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="person")
       @org.hibernate.annotations.Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
       private Collection<EmailAddress> emailAddresses;
      
       public Long getId() {return id;}
       public void setId(Long id) {this.id = id;}
      
       public String getName() {return name;}
       public void setName(String name) {this.name = name;}
      
       public Collection<EmailAddress> getEmailAddresses() {
       return emailAddresses;
       }
       public void setEmailAddresses(Collection<EmailAddress> emailAddresses) {
       this.emailAddresses = emailAddresses;
       }
       public void addEmailAddress(EmailAddress email){
       if (emailAddresses == null) emailAddresses= new ArrayList<EmailAddress>();
       email.setPerson(this);
       emailAddresses.add(email);
       }
       public void removeEmailAddress(EmailAddress emailAddress){
       if (emailAddresses != null){
       emailAddresses.remove(emailAddress);
       }
       }
      }
      


      @Entity
      @Name("emailAddress")
      public class EmailAddress {
      
       @Id @GeneratedValue
       private Long id;
      
       @NotNull
       private String url;
      
       @ManyToOne
       @JoinColumn(name="person_id")
       private Person person;
      
       public Long getId() {return id;}
       public void setId(Long id) {this.id = id;}
      
       public String getUrl() {
       return url;
       }
       public void setUrl(String url) {
       this.url = url;
       }
      
       public Person getPerson() {
       return person;
       }
       public void setPerson(Person person) {
       this.person = person;
       }
      }
      


      Now, I have found that if I put the following code right before the "em.merge(person)" (in the top code block), I no longer have this issue and it works.
      if (person.getEmailAddresses() == null)
       person.setEmailAddresses(new ArrayList<EmailAddress>());
      


      Is this a known issue? Oh, and I apologize if this is isn't the correct forum for this, I wasn't sure if it was more EJB or Hibernate related.