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
}
このメソッドがこの例外をスローする理由はわかりません。保存する前に、データベース内にあるかどうかを確認するだけでよいからです。クエリを実行しているだけなのに、オブジェクトが保存されていないと不平を言うのはなぜですか?
タグ リストに含まれるタグが 1 つだけの場合、この方法は機能します。保存する一時タグの数が 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 の複雑さはそれほど良いことではないからです。動作を考えると、これはバグでさえあると思います。それはまだ私には意味がありません。