JPA多对多关系,保留一份ID列表

时间:2014-06-11 12:35:05

标签: java jpa

我在Java中有2个POJO类:短语和标签,多对多关系:

  1. Phrase.java

    @Entity
    @EntityListeners(value={PhraseListener.class})
    public class Phrase {
      @Id
      @GeneratedValue
      @Column(name="id")
      private Long phraseId;
      @Column(nullable=false)
      private String text;
      @ManyToMany(cascade=CascadeType.ALL)
      @JoinTable(name="phrase_has_tag",
                 joinColumns={@JoinColumn(name="phrase_id",referencedColumnName="id")},
                 inverseJoinColumns={@JoinColumn(name="tag_uname",referencedColumnName="uname")})
      private Collection<Tag> tagObjects;
      @Transient
      private Set<String> tags;
    
      public Phrase() {
        tagObjects = new ArrayList<Tag>();
        tags = new HashSet<String>();
      }
    
      // getters and setters
      // …
    
      public void addTagObject(Tag t) {
        if (!getTagObjects().contains(t)) {
          getTagObjects().add(t);
        }
        if (!t.getPhrases().contains(this)) {
          t.getPhrases().add(this);
        }
      }
    
      public void addTag(String tagName) {      
        if (!getTags().contains(tagName)) {
          getTags().add(tagName);
        }       
      }
    
  2. Tag.java

    @Entity
    public class Tag {
      @Id
      @Column(name="uname")
      private String uniqueName;
      private String description;
      @ManyToMany(mappedBy="tagObjects")
      private Collection<Phrase> phrases;
    
      public Tag() {
        phrases = new ArrayList<Phrase>();
      }
    
      // getters and setters
      // … 
    
  3. 标记实体的主键是其名称。我想保留Phrase.javaSet个标记名称&#34; synchronized&#34;与多对多关系的tagObjects字段,反之亦然。为此,我向Phrase.java添加了一个监听器:

    public class PhraseListener {
      @PostLoad
      public void postLoad(Phrase p) {
        System.out.println("In post load");
        for (Tag tag : p.getTagObjects()) {
          p.addTag(tag.getUniqueName());
        }        
      }
    
      @PrePersist
      public void prePersist(Phrase p) {
        System.out.println("In pre persist");
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("TestJPA");
        EntityManager em = emf.createEntityManager();
        for (String tagName : p.getTags()) {
          Tag t = em.find(Tag.class, tagName);
          if (t == null) t = new Tag(tagName);
          p.addTagObject(t);
        }
      }
    }
    

    加载后,它从标记对象创建一组标记名称,并在持久化之前读取标记名称集,并获取或创建标记对象。

    我的问题是,如果我尝试创建多个共享标签的短语,JPA而不是仅创建关系(插入连接表),它还会创建违反主键约束的标记对象。

    transaction.begin();  
    Phrase p = new Phrase("Never ask what sort of computer a guy drives. If he's a Mac user, he'll tell you. If not, why embarrass him?", "Tom Clancy");
    p.addTag("apple");
    p.addTag("macintosh");
    em.persist(p);
    transaction.commit();
    
    transaction.begin();  
    p = new Phrase("It's better to be a pirate than to join the Navy.", "Steve Jobs");
    p.addTag("apple");
    em.persist(p);
    transaction.commit();
    
      

    线程中的异常&#34; main&#34; javax.persistence.RollbackException:Exception [EclipseLink-4002](Eclipse Persistence Services - 2.5.0.v20130507-3faac2b):org.eclipse.persistence.exceptions.DatabaseException   内部异常:java.sql.SQLException:[SQLITE_CONSTRAINT]由于约束违规而中止(列uname不唯一)   错误代码:0   调用:INSERT INTO TAG(uname,DESCRIPTION)VALUES(?,?)       bind =&gt; [apple,null]

1 个答案:

答案 0 :(得分:0)

您还没有在短语中显示addTag方法,但我认为您在某处有一个表达式new Tag(),它看起来与此类似:

public void addTag(String tagName) {
   Tag tag = new Tag();
   tag.setUniqueName(tagName);
   tag.getPhrases().add(this); 
   this.tagObjects.add(tag);
}

在这种情况下,方法addTag将在每次调用方法时创建类型为Tag的新对象,这将导致关系表中的不同条目,导致Hibernate持久化整个对象,不仅他们的特定领域,无论这些领域是否是主键。 在调用方法addTag两次之后,您将创建两个不同的对象,而Hibernate无法知道这两个对象是否与DB中的相同条目相关。这意味着即使它们具有相同的uniqueName,它们也可以有不同的描述。

想象一下以下场景:

transaction.begin();  
Phrase p = new Phrase("Never ask what sort of computer a guy drives. If he's a Mac user, he'll tell you. If not, why embarrass him?", "Tom Clancy");
Tag t = new Tag();
t.setUniqueName("apple");
t.setDescription("This is an example apple");
p.getTagObjects().add(t);
em.persist(p);
transaction.commit();

transaction.begin();  
p = new Phrase("It's better to be a pirate than to join the Navy.", "Steve Jobs");
t = new Tag();
t.setUniqueName("apple");
t.setDescription("Another description of the apple");
em.persist(p);
transaction.commit();

通过这个例子,差异应该更加明显,并且应该说明为什么Hibernate无法知道何时在DB中引用具有两个或更多不同对象的相同条目。

作为一种解决方案,我建议您更改方法addTag,使其具有以下签名public void addTag(Tag tag) {...,并在某处集中跟踪现有标记,或者您可以尝试em.merge(p);而不是em.persist(p);