Announcement Announcement Module
Collapse
No announcement yet.
Odd problem with JPA / Hibernate Entitymanager and Spring 2.0 Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Odd problem with JPA / Hibernate Entitymanager and Spring 2.0

    Hi.

    I have an odd problem with @ManyToMany and hibernate entitymanager.

    I have two classes. Article and Tag. I want to have a Many to many relationship between these.

    When i change the article from my editArticleController and change the tag, the changes does not get persisted. Even if I add cascade = CascadeType.ALL.

    Any thoughts why this is so.

    I can post the classes in question if needed.

  • #2
    It would be helpful if you could post your Entities as well as the client code that uses these Entities.

    Comment


    • #3
      Please post the Article and Tag classes. Specifically, which one of them is marked with the "mappedBy" attribute?
      Last edited by ASavitsky; Dec 27th, 2006, 09:49 AM. Reason: Not "mappedTo"... it's "mappedBy"...

      Comment


      • #4
        Are my mappings wrong?
        Client code might be too massive to post here, as the project has grown quite
        Code:
        package no.dusken.aranea.model;
        
        @Entity
        @Table(name = "page")
        @Inheritance(strategy = InheritanceType.JOINED)
        @DiscriminatorColumn(name = "type")
        public abstract class Page extends AraneaObject {
        
            /**
             * The Page's title
             */
            private String title;
        
            /**
             * URLEncoded title
             */
            private String safeTitle;
        
            /**
             * Alternative title (for frontpage use)
             */
            private String altTitle;
        
            /**
             * date this Page was last modified
             */
            private Calendar modified;
        
            /**
             * Date this page was published
             */
            private Calendar published;
        
            /**
             * Visible or Hidden ?
             */
            private PageStatus status;
        
            /**
             * What subtype of page is this?
             */
            private PageType type;
        
            /**
             * The Section assigned to the page
             */
            private Section section;
        
            /**
             * The SubSection assigned to the page
             */
            private SubSection subSection;
        
            /**
             * Who has written this
             */
            private List<Person> authors;
        
            public Page() {
            }
        
            public Page(Long ID) {
                super(ID);
            }
        
            @Id
            @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "page_seq")
            public Long getID() {
                return this.ID;
            }
        
            @Column(nullable = false)
            public String getTitle() {
                return title;
            }
        
            public void setTitle(String title) {
                // reset safeTitle
                safeTitle = null;
                this.title = title;
            }
        
            public String getAltTitle() {
                return altTitle;
            }
        
            public void setAltTitle(String altTitle) {
                this.altTitle = altTitle;
            }
        
        
            @Transient
            public String getSafeTitle() {
                try {
                    safeTitle = URLEncoder.encode(getTitle().toLowerCase(), "UTF-8");
                } catch (UnsupportedEncodingException e) {
                    // do nothing
                }
                return safeTitle;
            }
        
        
            @Temporal(TemporalType.TIMESTAMP)
            public Calendar getModified() {
                return modified;
            }
        
            public void setModified(Calendar modified) {
                this.modified = modified;
            }
        
            @Temporal(TemporalType.TIMESTAMP)
            public Calendar getPublished() {
                return published;
            }
        
            public void setPublished(Calendar published) {
                this.published = published;
            }
        
            @Enumerated(EnumType.STRING)
            public PageStatus getStatus() {
                return status;
            }
        
            public void setStatus(PageStatus status) {
                this.status = status;
            }
        
            @Enumerated(EnumType.STRING)
            public PageType getType() {
                return type;
            }
        
            public void setType(PageType type) {
                this.type = type;
            }
        
            @ManyToOne(fetch = FetchType.LAZY)
            public Section getSection() {
                return section;
            }
        
            public void setSection(Section section) {
                this.section = section;
            }
        
            @ManyToMany(mappedBy = "pages", fetch = FetchType.LAZY, targetEntity = Person.class)
            @JoinTable(name = "page_author")
            @Cascade(org.hibernate.annotations.CascadeType.ALL)
            public List<Person> getAuthors() {
                return authors;
            }
        
            public void setAuthors(List<Person> authors) {
                this.authors = authors;
            }
        
            @ManyToOne(fetch = FetchType.LAZY)
            public SubSection getSubSection() {
                return subSection;
            }
        
            public void setSubSection(SubSection subSection) {
                this.subSection = subSection;
            }
        
        }
        Tag.class
        Code:
        @Entity
        @Table(name = "tag")
        @SequenceGenerator(name = "tag_seq", sequenceName = "tag_id_seq")
                })
        public class Tag extends AraneaObject {
        
            private String name;
            private String text;
            private List<Article> articles;
        
            public Tag(Long ID) {
                super(ID);
            }
        
            public Tag() {
            }
        
            @Id
            @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tag_seq")
            public Long getID() {
                return this.ID;
            }
        
            @Column(nullable = false)
            public String getName() {
                return name;
            }
        
            public void setName(String name) {
                this.name = name;
            }
        
            @Basic(fetch = FetchType.LAZY)
            public String getText() {
                return text;
            }
        
            public void setText(String text) {
                this.text = text;
            }
        
            @ManyToMany(mappedBy = "tags", fetch = FetchType.LAZY,targetEntity = Article.class)
            public List<Article> getArticles() {
                return articles;
            }
        
            public void setArticles(List<Article> articles) {
                this.articles = articles;
            }
        
            public String toString() {
                return "Tag: " + name;
            }
        
        }
        Person.class

        Code:
        @Entity
        @Table(name = "person")
        @SequenceGenerator(name = "person_seq", sequenceName = "person_id_seq")
        public class Person extends AraneaObject {
        
            /**
             * the person's name
             */
            private String name;
            /**
             * the person's username
             */
            private String username;
            /*
            * is the person currently active?
             */
            private boolean active;
        
            private Role role;
        
            private List<Page> pages;
        
            private List<Image> images;
        
            public Person(Long ID) {
                super(ID);
            }
        
            public Person() {
            }
        
            @Id
            @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "person_seq")
            public Long getID() {
                return this.ID;
            }
        
            public String toString() {
                return name + " (" + username + ")";
            }
        
            @ManyToMany(fetch = FetchType.LAZY,targetEntity = Page.class)
            @JoinTable(name = "page_author")
            public List<Page> getPages() {
                return pages;
            }
        
            public void setPages(List<Page> pages) {
                this.pages = pages;
            }
        
            @OneToMany
            public List<Image> getImages() {
                return images;
            }
        
            public void setImages(List<Image> images) {
                this.images = images;
            }
        
            public boolean isActive() {
                return active;
            }
        
            public void setActive(boolean active) {
                this.active = active;
            }
        
            @Column(nullable = false)    
            public String getName() {
                return name;
            }
        
            public void setName(String name) {
                this.name = name;
            }
        
            @Column(nullable = false, unique = true)
            public String getUsername() {
                return username;
            }
        
            public void setUsername(String username) {
                this.username = username;
            }
        
            @ManyToOne
            public Role getRole() {
                return role;
            }
        
            public void setRole(Role role) {
                this.role = role;
            }
        
        
        }

        Comment


        • #5
          I see you are using the CascadeType from the org.hibernate.annotations package. Have you tried using the CascadeType from javax.persistence package. Not sure if this will make a difference, but definitely couples your Entities to Hibernate instead of JPA.

          Comment


          • #6
            I have tested with and without pure jpa.

            No change in behavior.

            In other words.. the assiosication tables does not get updated.

            Comment


            • #7
              I have to confess I haven't used JPA yet, but with assocations and pure Hibernate, you mark cascades on both sides of the relationship but also mark one side inverse=true. I don't know if the same concept exists in JPA.

              Comment


              • #8
                Originally posted by karldmoore View Post
                I have to confess I haven't used JPA yet, but with assocations and pure Hibernate, you mark cascades on both sides of the relationship but also mark one side inverse=true. I don't know if the same concept exists in JPA.
                Yes, JPA has the same concept - one side of the association has to be mapped with the "mappedBy" attribute, and becomes the "inverse" side, while the other one (without the "mappedBy") is the owning side. Only the owning side can save the changes in the many-to-many relationship.

                I suspect that, in this case, wrong entity was set to be the relationship owner (or maybe the "mappedBy" is specified on both sides). Can't verify it, though, as no code for Article class was posted...

                Comment


                • #9
                  I've made my own test checking cascading behavior of ManyToMany relationship.
                  Article
                  Code:
                  @Entity
                  public class Article extends BaseEntity {
                      private static final long serialVersionUID = 13543453L;
                  
                      private String name;
                  
                      @ManyToMany(cascade = CascadeType.PERSIST)
                      private Collection<Tag> tags;
                  
                      public String getName() {
                          return name;
                      }
                  
                      public void setName(String name) {
                          this.name = name;
                      }
                  
                      public Collection<Tag> getTags() {
                          return tags;
                      }
                  
                      public void setTags(Collection<Tag> tags) {
                          this.tags = tags;
                      }
                  
                      @Override
                      public int hashCode() {
                          return new HashCodeBuilder(3, 11).append(getName()).toHashCode();
                      }
                  
                      @Override
                      public boolean equals(Object o) {
                          if (this == o) {
                              return true;
                          }
                          if (o != null && Article.class.isAssignableFrom(o.getClass())) {
                              return hashCode() == ((Article) o).hashCode();
                          }
                          return false;
                      }
                  
                      @Override
                      public String toString() {
                          return new ToStringBuilder(this, ToStringStyle.DEFAULT_STYLE).append(
                                  "name", getName()).toString();
                      }
                  
                  }
                  Tag
                  Code:
                  @Entity
                  public class Tag extends BaseEntity {
                      private static final long serialVersionUID = 123423L;
                  
                      private String name;
                  
                      @ManyToMany(mappedBy = "tags", cascade = CascadeType.PERSIST)
                      private Collection<Article> articles;
                  
                      public Collection<Article> getArticles() {
                          return articles;
                      }
                  
                      public void setArticles(Collection<Article> articles) {
                          this.articles = articles;
                      }
                  
                      public String getName() {
                          return name;
                      }
                  
                      public void setName(String name) {
                          this.name = name;
                      }
                  
                      @Override
                      public int hashCode() {
                          return new HashCodeBuilder(7, 5).append(getName()).toHashCode();
                      }
                  
                      @Override
                      public boolean equals(Object o) {
                          if (this == o) {
                              return true;
                          }
                          if (o != null && Tag.class.isAssignableFrom(o.getClass())) {
                              return hashCode() == ((Tag) o).hashCode();
                          }
                          return false;
                      }
                  
                      @Override
                      public String toString() {
                          return new ToStringBuilder(this, ToStringStyle.DEFAULT_STYLE).append(
                                  "name", getName()).toString();
                      }
                  }
                  test snippet
                  Code:
                      private final static String COUNT_ARTICLES = "select count(*) from article";
                  
                      private final static String COUNT_TAGS = "select count(*) from tag";
                  
                      public void test_ManyToManyCascadingBehaviourFromArticle() throws Exception {
                          logger.debug("test_ManyToManyCascadingBehaviourFromArticle() ");
                          Article article = new Article();
                          List<Tag> tags = new ArrayList<Tag>(3);
                          tags.add(new Tag());
                          tags.add(new Tag());
                          tags.add(new Tag());
                          article.setTags(tags);
                  
                          service.createArticle(article);
                          assertEquals(1, jdbcTemplate.queryForInt(COUNT_ARTICLES));
                          assertEquals(3, jdbcTemplate.queryForInt(COUNT_TAGS));
                  
                          service.deleteAllArticles();
                      }
                  
                      public void test_ManyToManyCascadingBehaviourFromTag() throws Exception {
                          logger.debug("test_ManyToManyCascadingBehaviourFromTag() ");
                  
                          Tag tag = new Tag();
                          List<Article> articles = new ArrayList<Article>(3);
                          articles.add(new Article());
                          articles.add(new Article());
                          articles.add(new Article());
                          tag.setArticles(articles);
                  
                          service.createTag(tag);
                  
                          assertEquals(3, jdbcTemplate.queryForInt(COUNT_ARTICLES));
                          assertEquals(1, jdbcTemplate.queryForInt(COUNT_TAGS));
                  
                          service.deleteAllTags();
                      }
                     
                      public void test_ManyToManyCascadingBehaviourFromArticleUsingList() throws Exception {
                          logger.debug("test_ManyToManyCascadingBehaviourFromArticleUsingList() ");
                          List<Article> articles = new ArrayList<Article>(2);
                         
                          Article one = new Article();
                          List<Tag> tags = new ArrayList<Tag>(3);
                          tags.add(new Tag());
                          tags.add(new Tag());
                          tags.add(new Tag());
                          one.setTags(tags);
                          articles.add(one);
                         
                          Article two = new Article();
                          tags = new ArrayList<Tag>(2);
                          tags.add(new Tag());
                          tags.add(new Tag());
                          two.setTags(tags);
                          articles.add(two);
                         
                          service.saveAllArticles(articles);
                          assertEquals(2, jdbcTemplate.queryForInt(COUNT_ARTICLES));
                          assertEquals(5, jdbcTemplate.queryForInt(COUNT_TAGS));
                  
                          service.deleteAllArticles();
                      }
                  
                      // //////////////////////////////////
                  
                      public static Test suite() {
                          TestSuite suite = new TestSuite();
                          suite.addTest(new JpaTest(
                                  "test_ManyToManyCascadingBehaviourFromArticle"));
                          suite.addTest(new JpaTest("test_ManyToManyCascadingBehaviourFromTag"));
                          suite.addTest(new JpaTest("test_ManyToManyCascadingBehaviourFromArticleUsingList"));
                          return suite;
                      }
                  It's working both with JPA and Hibernate proper. See what you are doing differently and then baby steps, baby steps...

                  Comment


                  • #10
                    Elaboration on problem

                    I work on the same project as erlenha, and has been looking at this oddity as well. It seems that the problem is only partially with JPA.

                    What happens is that when an Article is edited in a form and passed in to the service layer for merging, it is passed as a detached instance and when the merge occurs changes made to the many-to-many relations are not persisted.

                    If one then submits the POST again, the edited Article is now managed and the changes are persisted.

                    What it boils down to is that changes to the relation are not persisted if the owning entity (Article) is detached.

                    So I wonder: Does one have to update both sides of the relationship manually before passing it to merge?

                    Comment


                    • #11
                      If you modifying an object which has a bi-directional relationship, you do have to update both ends if you are adding or removing an item.

                      Code:
                      category.getItems().add(item); // The category now "knows" about the relationship
                      item.getCategories().add(category); // The item now "knows" about the relationship
                      
                      session.update(item); // No effect, nothing will be saved!
                      session.update(category); // The relationship will be saved
                      http://www.hibernate.org/hib_docs/re...-bidirectional

                      Comment


                      • #12
                        asbjorjo,

                        Have you found a solution for your problem yet? I'm facing the same problem as you do. Even if I updated the relationships on both ends, merge doesn't seem to work on a detached entity.

                        Comment


                        • #13
                          Could you explain more about your problem? Is it the same thing e.g. working with many-to-many relationships? Does it work if the entity isn't detached e.g. can you replicate this in a unit test?

                          Comment


                          • #14
                            karldmoore,

                            I found out that there was no transaction available during my update for the many-to-many relationships. This is a separate Spring issue that I filed here

                            http://forum.springframework.org/showthread.php?t=35562

                            Once I figured out the transaction problem, I will attempt the many-to-many relationship again and post my finding.

                            Comment


                            • #15
                              Ok, if there is a no transaction then you might have problem here. I'll try and have a look at the other thread later.

                              Comment

                              Working...
                              X