2

我正在使用 Hibernate 4.1,每当我尝试存储已添加到对象集合中的瞬态标记列表时,都会出现以下异常。

org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.myapplication.domain.Tag
    at org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:249)
    at org.hibernate.type.EntityType.getIdentifier(EntityType.java:459)
    at org.hibernate.type.ManyToOneType.nullSafeSet(ManyToOneType.java:132)
    at org.hibernate.persister.collection.AbstractCollectionPersister.writeElement(AbstractCollectionPersister.java:811)

现在,我需要稍微解释一下。我的 Tag 类有一组同义词。在添加此关系之前,我从未遇到此异常,并且一切正常:

<class name="com.myapplication.domain.Tag">
    <id name="id" column="tag_id" type="long">
        <generator class="native"/>
    </id>
    <property name="term" index="termIndex" unique="true" not-null="true" />
    <property name="description" />
    <property name="approved" />

    <many-to-one name="masterTag" column="master_tag_id"
                 class="com.myapplication.domain.Tag" />

    <bag name="synonyms">
        <key column="master_tag_id" />
        <one-to-many class="com.myapplication.domain.Tag" />
    </bag>
</class>

这是对象映射标签的方式。该类Tags只是一个组件类。Tags.list 是 Hibernate 持久化的实际集合。

    <component name="tags">
        <bag name="list" table="user_to_tag">
            <key column="user_id" />
            <many-to-many class="com.myapplication.domain.Tag" column="tag_id"/>
        </bag>
    </component>

我试图保存在数据库中的标签实际上根本不包含任何同义词,所以我不相信它与瞬态同义词标签有任何关系。我不希望这些级联,这就是为什么我没有在<bag>标签上给它一个级联属性。

用户通常会通过字符串列表将标签插入系统。我这样做是因为我想在保存包含标签的对象时即时创建标签或链接到现有标签。也许这比它的价值更麻烦?如果我只让他们链接到存在的标签,那么我知道我可以解决我的问题。也许在 Hibernate 中处理动态标签创建的复杂性比它可以处理的要多一点?

这是我用来一次性保存新/现有标签的方法。每当一个对象被保存到数据库时,它首先调用这个方法来持久化标签集合:

public void saveAll(Tags tags) {
    List<Tag> tagsToOverwrite = new ArrayList<Tag>();

    Iterator<Tag> i = tags.getList().iterator();
    while(i.hasNext()) {
        Tag tag = i.next();

        if(stopListService.hasWord(tag.getTerm())) {
            i.remove();
        } else {
            if(tag.isTransient()) {
                Tag dbTag = findByTerm(tag.getTerm());

                if(dbTag == null) {
                    save(tag);
                } else {
                    tagsToOverwrite.add(dbTag);
                }
            }
        }
    }

    tags.overwriteAll(tagsToOverwrite);
}

由于某种原因,它在Tag dbTag = findByTerm(tag.getTerm());抛出TransientObjectException. 这个方法只是一个简单的查询:

public Tag findByTerm(final String term) {
    Query query = getCurrentSession().createQuery(
        "from Tag tag " +
        "where tag.term = :term "
    );

    return (Tag) query
        .setParameter("term", term.toLowerCase())
        .setCacheable(true)
        .uniqueResult(); // This is where the exception gets thrown specifically
}

我不确定为什么这个方法会抛出这个异常,因为我想做的只是在保存之前检查它是否在数据库中。当我所做的只是查询对象时,为什么抱怨对象没有被保存?

如果标签列表仅包含单个标签,则该方法有效。仅当要保存的临时标签数量为 2 个或更多时,它才会失败。例如,这会失败:

@Test
public void saveShouldPersistTransientTags() {
    User user = userRepository.find(1);
            // makes list of 3 tags that are transient (id = 0)
    user.getTags().setTagsAsString("training learning business");

    userRepository.save(user);
    flush();

    user = userRepository.find(1);
    assertEquals(3, user.getTags().getList().size());
}

这是 UserRepository.save() 方法:

@Override
public void save(User user) {
    tagRepository.saveAll(user.getTags());

    super.save(user);
}

如您所见,我试图在保存用户之前先保存所有临时标签(或在数据库中查找它们)。然而,Hibernate 似乎希望user.tags.list在调用期间将集合保留tagRepository.saveAll(user.getTags());在抛出异常的位置,即使我从未要求它这样做。

当然,如果我synonyms从 Hibernate 映射中删除该集合,该测试将毫无问题地通过。它仅在我启用synonym集合时失败,并且仅在到达learning标签时才失败(training在数据库中找不到标签,并且保存得很好。)

关于同义词集合为什么会导致此问题的任何想法?

如果我不能很快解决这个问题,为了节省时间,我很想只使用 JDBC 来解决这个问题,因为 Hibernate 在这种情况下的复杂性并不是一件好事。我认为考虑到这种行为,这甚至可能是一个错误。这对我来说仍然没有任何意义。

4

1 回答 1

4

当你准备好查询 Hibernate 时可能会查看它的托管对象。那些脏的它会尝试刷新到数据库,因此查询会命中最新的数据存储。在代码中的某处,您正在向集合中添加非托管(可能是新的?)同义词,然后当 Hibernate 尝试通过刷新脏对象来“帮助”您时,您会得到该异常。

于 2012-10-16T19:40:39.657 回答