简体   繁体   English

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

[英]JPA many-to-many relationship, keeping a list of ids

I have 2 POJO classes in Java: Phrase and Tag, in a many-to-many relationship: 我在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 // … 

The primary key for the tag entity is its name. 标签实体的主键是它的名称。 I want to keep in Phrase.java a Set of tag names "synchronized" with the tagObjects field of the many-to-many relationship, and viceversa. 我想在Phrase.javaPhrase.javaSet与多对多关系的tagObjects字段“同步”的标记名,反之亦然。 For doing this, I add a listener to Phrase.java : 为此,我向Phrase.java添加了一个侦听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);
    }
  }
}

which after loading, it creates the set of tag names from the tag objects and before persisting it reads the set of tag names, and fetch or create tag objects. 加载后,它将根据标记对象创建标记名称集,而在持久存储之前,它将读取标记名称集,并获取或创建标记对象。

My problem is that if I try to create multiple phrases which share tags, JPA instead of only creating the relationship (insert into the join table) it also create tag objects which violate primary key constraint. 我的问题是,如果我尝试创建共享标签的多个短语,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();

Exception in thread "main" javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException Internal Exception: java.sql.SQLException: [SQLITE_CONSTRAINT] Abort due to constraint violation (column uname is not unique) Error Code: 0 Call: INSERT INTO TAG (uname, DESCRIPTION) VALUES (?, ?) bind => [apple, null] 线程“主”中的异常javax.persistence.RollbackException:异常[EclipseLink-4002](Eclipse Persistence Services-2.5.0.v20130507-3faac2b):org.eclipse.persistence.exceptions.DatabaseException内部异常:java.sql.SQLException: [SQLITE_CONSTRAINT]由于违反约束而中止(列名不唯一)错误代码:0调用:INSERT INTO TAG(uname,DESCRIPTION)值(?,?)bind => [apple,null]

You haven't shown your addTag method in Phrase, but I assume that you have somewhere in there an expression new Tag() and it would look somehow similar to this: 您尚未在Phrase中显示addTag方法,但我假设您在其中的某个位置有一个表达式new Tag() ,它看起来与此类似:

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

In this case the method addTag will create new object of type Tag everytime the method is called, which will result in different entries in the relational table, cause Hibernate persists the whole objects, not only particular fields of theirs, regardless if these fields are primary keys or not. 在这种情况下,方法addTag将在每次调用该方法时创建一个Tag类型的新对象,这将导致关系表中的条目不同,这是因为Hibernate持久化了整个对象,不仅持久化了对象的特定字段,无论这些字段是否是主要字段关键与否。 After calling the method addTag two times, you will create two different objects and Hibernate could not know if those two objects relate to the same entry in the DB or not. 在两次调用方法addTag之后,您将创建两个不同的对象,并且Hibernate无法知道这两个对象是否与数据库中的同一条目相关。 This means that even though they have the same uniqueName , they could have a different description. 这意味着即使它们具有相同的uniqueName ,它们也可能具有不同的描述。

Imagine the following scenario: 想象以下情况:

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();

With this example the difference should be more obvious and should illustrate why it is impossible to Hibernate to know when are you referring to the same entry in the DB with two or more different objects. 在这个例子中,区别应该更加明显,并且应该说明为什么当您使用两个或更多不同的对象引用数据库中的同一条目时,Hibernate无法知道的原因。

As a solution I would suggest you to change the method addTag so that it has the following signature public void addTag(Tag tag) {... and keep track of the existing tags somewhere centralized or you can try out em.merge(p); 作为解决方案,我建议您更改方法addTag ,使其具有以下签名public void addTag(Tag tag) {...并在某个位置集中跟踪现有标签,或者可以尝试em.merge(p); instead of em.persist(p); 而不是em.persist(p);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM