Java JPA 单元测试保存 2 项
Java JPA Unit Test saving 2 items
我有一个简单的 @OneToMany
设置,其中包含 2 个实体 - Item
和 Group
,但我遇到了一个有趣的问题。当我的集成测试创建 Group
、向其添加 Item
并保存 Group
时,Item
的 2 个实例最终被保存。任何想法为什么?我正在使用 Hibernate,如果重要的话:
The Item entity
@Entity
public class NamedEntity implements java.io.Serializable {
Long id;
@NotNull
String name;
NamedEntityGroup namedEntityGroup;
NamedEntityType type;
@Enumerated(EnumType.STRING)
public NamedEntityType getType() { return type; }
@Id
@GeneratedValue
public Long getId() { return id; }
public String getName() { return name; }
@JoinColumn(name = "NamedEntityGroupId")
@JsonBackReference
@ManyToOne(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER)
public NamedEntityGroup getNamedEntityGroup() { return this.namedEntityGroup; }
}
The Group entity
@Entity
public class NamedEntityGroup implements Serializable {
Long id;
String name;
List<NamedEntity> namedEntities;
@Id
@GeneratedValue
public Long getId() { return this.id; }
public String getName() { return this.name; }
@JsonManagedReference
@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "namedEntityGroup", fetch = FetchType.EAGER)
public List<NamedEntity> getNamedEntities() { return this.namedEntities; }
public void addNamedEntity(NamedEntity ne) {
if (this.namedEntities == null) {
this.namedEntities = new ArrayList<NamedEntity>();
}
if (!namedEntities.contains(ne)) {
this.namedEntities.add(ne);
}
ne.setNEG(this);
}
}
The DAO class
public void save(NamedEntity ne) throws EntityValidationException {
validate(ne);
if(ne.getNamedEntityGroup() != null) {
if(!em.contains(ne.getNamedEntityGroup())) {
ne.setNamedEntityGroup(em.merge(ne.getNamedEntityGroup()));
em.persist(ne);
return;
}
}
em.persist(ne);
}
The test method
@Test
public void testAddNamedEntityToExistingGroup() throws EntityValidationException {
int neSize = ed.getAllNamedEntities().size();
NamedEntityGroup neg = ed.getAllNamedEntityGroups().iterator().next();
assertNotNull(neg);
assertTrue(neg.getNamedEntities().size() == 0);
em.detach(neg);
NamedEntity n = new NamedEntity();
n.setName("Hello");
neg.addNamedEntity(n);
n.setNamedEntityGroup(neg);
n.setType(NamedEntityType.DEFAULT);
ed.save(n);
for(NamedEntity e : ed.getAllNamedEntities()) {
L.error("Entity: {}", e);
}
assertTrue("Size is " + ed.getAllNamedEntities().size() + " but it should be " + (neSize + 1) + " the group has "
+ neg.getNamedEntities().size() + " entities (should be 1) " + ed.getAllNamedEntities(), neSize + 1 == ed.getAllNamedEntities().size());
}
这是输出:
Entity: NamedEntity [id=1, name=Super Mario Brothers, namedEntityGroup=null, type=null]
Entity: NamedEntity [id=3, name=Mario Kart, namedEntityGroup=null, type=null]
Entity: NamedEntity [id=5, name=F-Zero, namedEntityGroup=null, type=null]
Entity: NamedEntity [id=7, name=Hello, namedEntityGroup=NamedEntityGroup [id=2, name=Super Mario Brothers, COUNT(entity)=1], type=DEFAULT]
Entity: NamedEntity [id=8, name=Hello, namedEntityGroup=NamedEntityGroup [id=2, name=Super Mario Brothers, COUNT(entity)=1], type=DEFAULT]
Id 的 7
和 8
在输出中重复,因为 save
方法插入一个项目 2 次。
你的 DAO save
方法很丑,重构它,否则会出现更多类似的问题。 :)
问题出在 DAO save
方法的 em.merge(ne.getNamedEntityGroup())
中。因为 NamedEntityGroup
级联 ALL
到 namedEntities
,所以 MERGE
级联到 ne
这是暂时的(未保存),导致 new NamedEntity
要创建的实例。 ne
实例已从 namedEntities
集合中删除,并将其副本放入其中。 Session.merge javadoc:
Copy the state of the given object onto the persistent object with the
same identifier. If there is no persistent instance currently
associated with the session, it will be loaded. Return the persistent
instance. If the given instance is unsaved, save a copy of and
return it as a newly persistent instance. The given instance does
not become associated with the session. This operation cascades to
associated instances if the association is mapped with cascade="merge".
之后你也持久化传入的 ne
,所以你最终得到两个持久化的 NamedEntity
实例:一个由 merge
创建(复制),一个你明确地持久化(ne
).
正确的解决方案是将 save
重构为不包含 if(!em.contains(ne.getNamedEntityGroup()))
等类似代码的内容。
丑陋的快速修复是从 if
正文中删除不必要的 em.persist(ne)
。这将使事情正常进行,但会非常混乱(传入的 ne
在 save
returns 之后仍然是短暂的)。
一个更好的快速解决方法是加载持久化 NamedEntityGroup
:
NamedEntityGroup group = em.getReference(NamedEntityGroup.class, ne.getNamedEntityGroup().getId());
ne.setNamedEntityGroup(group);
我有一个简单的 @OneToMany
设置,其中包含 2 个实体 - Item
和 Group
,但我遇到了一个有趣的问题。当我的集成测试创建 Group
、向其添加 Item
并保存 Group
时,Item
的 2 个实例最终被保存。任何想法为什么?我正在使用 Hibernate,如果重要的话:
The Item entity
@Entity
public class NamedEntity implements java.io.Serializable {
Long id;
@NotNull
String name;
NamedEntityGroup namedEntityGroup;
NamedEntityType type;
@Enumerated(EnumType.STRING)
public NamedEntityType getType() { return type; }
@Id
@GeneratedValue
public Long getId() { return id; }
public String getName() { return name; }
@JoinColumn(name = "NamedEntityGroupId")
@JsonBackReference
@ManyToOne(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER)
public NamedEntityGroup getNamedEntityGroup() { return this.namedEntityGroup; }
}
The Group entity
@Entity
public class NamedEntityGroup implements Serializable {
Long id;
String name;
List<NamedEntity> namedEntities;
@Id
@GeneratedValue
public Long getId() { return this.id; }
public String getName() { return this.name; }
@JsonManagedReference
@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "namedEntityGroup", fetch = FetchType.EAGER)
public List<NamedEntity> getNamedEntities() { return this.namedEntities; }
public void addNamedEntity(NamedEntity ne) {
if (this.namedEntities == null) {
this.namedEntities = new ArrayList<NamedEntity>();
}
if (!namedEntities.contains(ne)) {
this.namedEntities.add(ne);
}
ne.setNEG(this);
}
}
The DAO class
public void save(NamedEntity ne) throws EntityValidationException {
validate(ne);
if(ne.getNamedEntityGroup() != null) {
if(!em.contains(ne.getNamedEntityGroup())) {
ne.setNamedEntityGroup(em.merge(ne.getNamedEntityGroup()));
em.persist(ne);
return;
}
}
em.persist(ne);
}
The test method
@Test
public void testAddNamedEntityToExistingGroup() throws EntityValidationException {
int neSize = ed.getAllNamedEntities().size();
NamedEntityGroup neg = ed.getAllNamedEntityGroups().iterator().next();
assertNotNull(neg);
assertTrue(neg.getNamedEntities().size() == 0);
em.detach(neg);
NamedEntity n = new NamedEntity();
n.setName("Hello");
neg.addNamedEntity(n);
n.setNamedEntityGroup(neg);
n.setType(NamedEntityType.DEFAULT);
ed.save(n);
for(NamedEntity e : ed.getAllNamedEntities()) {
L.error("Entity: {}", e);
}
assertTrue("Size is " + ed.getAllNamedEntities().size() + " but it should be " + (neSize + 1) + " the group has "
+ neg.getNamedEntities().size() + " entities (should be 1) " + ed.getAllNamedEntities(), neSize + 1 == ed.getAllNamedEntities().size());
}
这是输出:
Entity: NamedEntity [id=1, name=Super Mario Brothers, namedEntityGroup=null, type=null]
Entity: NamedEntity [id=3, name=Mario Kart, namedEntityGroup=null, type=null]
Entity: NamedEntity [id=5, name=F-Zero, namedEntityGroup=null, type=null]
Entity: NamedEntity [id=7, name=Hello, namedEntityGroup=NamedEntityGroup [id=2, name=Super Mario Brothers, COUNT(entity)=1], type=DEFAULT]
Entity: NamedEntity [id=8, name=Hello, namedEntityGroup=NamedEntityGroup [id=2, name=Super Mario Brothers, COUNT(entity)=1], type=DEFAULT]
Id 的 7
和 8
在输出中重复,因为 save
方法插入一个项目 2 次。
你的 DAO save
方法很丑,重构它,否则会出现更多类似的问题。 :)
问题出在 DAO save
方法的 em.merge(ne.getNamedEntityGroup())
中。因为 NamedEntityGroup
级联 ALL
到 namedEntities
,所以 MERGE
级联到 ne
这是暂时的(未保存),导致 new NamedEntity
要创建的实例。 ne
实例已从 namedEntities
集合中删除,并将其副本放入其中。 Session.merge javadoc:
Copy the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. Return the persistent instance. If the given instance is unsaved, save a copy of and return it as a newly persistent instance. The given instance does not become associated with the session. This operation cascades to associated instances if the association is mapped with cascade="merge".
之后你也持久化传入的 ne
,所以你最终得到两个持久化的 NamedEntity
实例:一个由 merge
创建(复制),一个你明确地持久化(ne
).
正确的解决方案是将 save
重构为不包含 if(!em.contains(ne.getNamedEntityGroup()))
等类似代码的内容。
丑陋的快速修复是从 if
正文中删除不必要的 em.persist(ne)
。这将使事情正常进行,但会非常混乱(传入的 ne
在 save
returns 之后仍然是短暂的)。
一个更好的快速解决方法是加载持久化 NamedEntityGroup
:
NamedEntityGroup group = em.getReference(NamedEntityGroup.class, ne.getNamedEntityGroup().getId());
ne.setNamedEntityGroup(group);