Java JPA 单元测试保存 2 项

Java JPA Unit Test saving 2 items

我有一个简单的 @OneToMany 设置,其中包含 2 个实体 - ItemGroup,但我遇到了一个有趣的问题。当我的集成测试创建 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 的 78 在输出中重复,因为 save 方法插入一个项目 2 次。

你的 DAO save 方法很丑,重构它,否则会出现更多类似的问题。 :)

问题出在 DAO save 方法的 em.merge(ne.getNamedEntityGroup()) 中。因为 NamedEntityGroup 级联 ALLnamedEntities,所以 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)。这将使事情正常进行,但会非常混乱(传入的 nesave returns 之后仍然是短暂的)。

一个更好的快速解决方法是加载持久化 NamedEntityGroup:

NamedEntityGroup group = em.getReference(NamedEntityGroup.class, ne.getNamedEntityGroup().getId());
ne.setNamedEntityGroup(group);