3

環境

Stackoverflow は何年にもわたって私にとってかけがえのないものでした。私が最近かなりの時間を費やしたものについて質問と回答を投稿することで恩返しをしたいと思いました。

バックグラウンド

私の状況では、エンティティのシリアル化された JSON を Memcached に保存しています。さまざまな理由から、休止状態のキャッシング (第 2 レベルおよびクエリ キャッシュ) の動作が気に入らなかったため、独自のキャッシング レイヤーを作成することにしました。しかし、これにより問題が発生しました。以前にキャッシュされた POJO をハイバネート セッションに再アタッチする簡単な方法がないように見えました@ElementCollection

以下は、JSON から逆シリアル化し、休止状態のセッションに再アタッチしたいいくつかの POJO のおおよその定義です。

@Entity
@Table
public class User extends AbstractModel {

    @ElementCollection
    @CollectionTable (name = "UserAttribute", joinColumns = { @JoinColumn (name = "UserId") })
    @MapKeyColumn (name = "Name")
    @Column (name = "Value")
    private Map<String, String> attributes = new HashMap<String, String>();
    ...
}

@Entity
@Table
public class Content extends AbstractModel {

    @ManyToOne(cascade = CascadeType.ALL) @JoinColumn (name = "UserId")
    private User user;

    @ElementCollection
    @CollectionTable(name = "ContentAttribute", joinColumns = { @JoinColumn(name = "ContentId") })
    @MapKeyColumn(name = "Name")
    @Column(name = "Value")
    private Map<String, String> attributes = new HashMap<String, String>();
    ...
}

この質問に対する答えを探していると、次の場所に最も近いものに一致します。

4

1 に答える 1

1

質問のリンクからポスターが中断したところを拾い上げると、どうやって再接続したかのおおよその/関連するコードがここにあります。その下に、何が起こっているかの概要があります。

@Repository
public abstract class DaoHibernate<T> implements Dao<T> {

    @Override
    public T reattach(T entity) {
        if (getCurrentSession().contains(entity)) {
            return entity;
        }
        if (entity instanceof User) {
            return (T) reattachedUser((User) entity);
        }
        if (entity instanceof Content) {
            Content content = (Content) entity;
            User user = content.getUser();
            if (!currentSession().contains(user)) {
                content.setUser(reattachedUser(user));
            }
            content.setAttributes(persistentAttributesMap(content.getId(), content.getAttributes(), Content.class);
            getCurrentSession().lock(content, LockMode.NONE);
            return entity;
        }
        throw new UnsupportedOperationException("reattach is not supported for entity: " + entity.getClass().getName());
    }

    private User reattachedUser(User user) {
        user.setAttributes(persistentAttributesMap(user.getId(), user.getAttributes(), User.class));
        getCurrentSession().lock(user, LockMode.NONE);
        return user;
    }

    @SuppressWarnings ("unchecked")
    private Map<String, String> persistentAttributesMap(long id, Map<String, String> attributes, Class clazz) {
        SessionFactory sessionFactory = getSessionFactory();
        Session currentSession = sessionFactory.getCurrentSession();
        String role = clazz.getName() + ".attributes";
        CollectionPersister collectionPersister = ((SessionFactoryImplementor) sessionFactory).getCollectionPersister(role);
        MapType mapType = (MapType) collectionPersister.getCollectionType();
        PersistentMap persistentMap = (PersistentMap) mapType.wrap((SessionImplementor) currentSession, attributes);
        persistentMap.setOwner(id);
        persistentMap.setSnapshot(id, role, ImmutableMap.copyOf(attributes));
        persistentMap.setCurrentSession(null);
        return persistentMap;
    }

    ...
}

ウォークスルー

ご覧のとおり、現在のセッションに既に存在するエンティティを再アタッチしないようにする必要があります。再アタッチしないと、hibernate が例外をスローします。そのため、 で行う必要getCurrentSession().contains(entity)がありreattach()ます。contains()hibernate はentity.hachCode()エンティティのルックアップに を使用せず、 を使用することに注意してくださいSystem.identityHashCode(entity)。これにより、同等のインスタンスであるだけでなく、セッションに既に存在する可能性のあるまったく同じインスタンスが保証されます。つまり、インスタンスの再利用を適切に管理する必要があります。

関連付けられたエンティティが でマークされている限りCascade.ALL、休止状態は正しいことを行うはずです。つまり@ElementCollection、属性のマップのような休止状態で管理されたコレクションがない限りです。この場合、PersistentCollection( PersistentMap、正確には ) を手動で作成し、 のように適切なプロパティを設定するpersistentAttributesMap必要があります。そうしないと、hibernate が例外をスローします。つまり、 では、次のPersistentMapことを行う必要があります。

  • 所有者とスナップショット キーを所有エンティティの ID として設定します。
  • hibernate が認識するように、完全修飾された entity.property 名としてスナップショット ロールを設定します。
  • スナップショットSerializable引数を既存のコレクションの不変のコピーとして設定します
  • セッションを設定してnull、休止状態が既存のセッションに 2 回アタッチしようとしていると認識しないようにします。

再接続を完了するには、 を呼び出しますsession.lock(entity, LockMode.NONE)。この時点で、私のテストからわかる限り、休止状態はこのエンティティを尊重し、 を呼び出すとすべての変更を正しく保持しますsaveOrUpdate()

注意事項

これは、すべてのケースに対する一般的な解決策ではないことを認識しています。これは、他の人がうまく利用して改善できる、私の特定の問題に対する簡単な解決策にすぎません。ソフトウェアは反復的です。

于 2013-04-25T14:34:50.837 に答える