我在Java中有2个POJO类:短语和标签,多对多关系:
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);
}
}
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
// …
标记实体的主键是其名称。我想保留Phrase.java
个Set
个标记名称&#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]
答案 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);