2

DataModel に双方向のリレーションシップがある場合、Java コードで参照を最新の状態に保つのはアプリケーションの責任です。

これを行う最善の方法は何ですか?

たとえば、双方向。AとBは1:Nの関係。

@Entity
class A {

@ManyToOne
private B b;

}


@Entity
class B {

@OneToMany(mappedBy="b")
private Collection<A> as; 

}

私が B.addA(b) と言った場合、これは A の変数 b が私が追加した参照を指すようにしません。そして、私が A.setB(b) を呼び出すと、これは b の参照を B のコレクションに追加しません。

考えられる 1 つの方法は、アプリケーション コードで setB AND addA を呼び出すことです。

もう 1 つの可能性は、次のように setA(..) メソッドを記述することです。

public setB(B b) {
    this.b = b;
    if(!b.contains(this) {
    b.add(this);
    }
}



public addA(A a) {
    if(!as.conatains(a)) {
      as.add(a);
    }
    a.setB(this);
    }

ただし、これにより次のような例外がスローされることがあります。

org.hibernate.LazyInitializationException: illegal access to loading collection

フレームワークがある時点でこの setMethod を呼び出し、「this」参照をロードしたいからだと思います...?!? なぜこれが起こるのか誰かが私に説明できますか?そして、私のJavaコードにクリーンな双方向関係があることを保証する方法は何ですか?

どうも

更新: 元のコードは次のとおりです。

@Entity
class Cluster{

private Grid grid

//someother fields

@ManyToOne
    public Grid getGrid() {
        return grid;
    }

    public void setGrid(Grid grid) {
        this.grid = grid;
        if(!grid.getClusters().contains(this)) { //HERE AN EXCEPTION IS THROWN
            grid.addCluster(this);
        }
    }

}

@Entity
class Grid {

    private Collection<Cluster> clusters = new ArrayList<Cluster>();

    //some other fields

    @OneToMany(mappedBy = "grid", cascade = CascadeType.PERSIST, orphanRemoval = true)
    public Collection<Cluster> getClusters() {
        return clusters;
    }

    public void setClusters(Collection<Cluster> clusters) {
        this.clusters = clusters;
    }

    public void addCluster(Cluster c) {
    this.clusters.add(c);
    c.setGrid(this);
}

}

私のクエリの1つで、setGridメソッド内の何かが間違っているという例外が発生します...行を削除するとすべて問題ありません..しかし、双方向がありません... :/

スタックトレース:

Exception in thread "main" javax.persistence.PersistenceException: org.hibernate.PropertyAccessException: Exception occurred inside setter of dst1.model.Cluster.grid
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1214)
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1147)
    at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:255)
    at dst1.Main.dst02b(Main.java:828)
    at dst1.Main.main(Main.java:38)
Caused by: org.hibernate.PropertyAccessException: Exception occurred inside setter of dst1.model.Cluster.grid
    at org.hibernate.property.BasicPropertyAccessor$BasicSetter.set(BasicPropertyAccessor.java:89)
    at org.hibernate.tuple.entity.AbstractEntityTuplizer.setPropertyValues(AbstractEntityTuplizer.java:583)
    at org.hibernate.tuple.entity.PojoEntityTuplizer.setPropertyValues(PojoEntityTuplizer.java:229)
    at org.hibernate.persister.entity.AbstractEntityPersister.setPropertyValues(AbstractEntityPersister.java:3822)
    at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:152)
    at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:982)
    at org.hibernate.loader.Loader.doQuery(Loader.java:857)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274)
    at org.hibernate.loader.Loader.loadEntity(Loader.java:2037)
    at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:86)
    at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:76)
    at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3268)
    at org.hibernate.event.def.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:496)
    at org.hibernate.event.def.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:477)
    at org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:227)
    at org.hibernate.event.def.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:285)
    at org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:152)
    at org.hibernate.impl.SessionImpl.fireLoad(SessionImpl.java:1090)
    at org.hibernate.impl.SessionImpl.internalLoad(SessionImpl.java:1038)
    at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:630)
    at org.hibernate.type.EntityType.resolve(EntityType.java:438)
    at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:139)
    at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:982)
    at org.hibernate.loader.Loader.doQuery(Loader.java:857)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274)
    at org.hibernate.loader.Loader.doList(Loader.java:2533)
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2276)
    at org.hibernate.loader.Loader.list(Loader.java:2271)
    at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:452)
    at org.hibernate.hql.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:363)
    at org.hibernate.engine.query.HQLQueryPlan.performList(HQLQueryPlan.java:196)
    at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1268)
    at org.hibernate.impl.QueryImpl.list(QueryImpl.java:102)
    at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:246)
    ... 2 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.hibernate.property.BasicPropertyAccessor$BasicSetter.set(BasicPropertyAccessor.java:66)
    ... 35 more
Caused by: org.hibernate.PropertyAccessException: Exception occurred inside setter of dst1.model.Cluster.grid
    at org.hibernate.property.BasicPropertyAccessor$BasicSetter.set(BasicPropertyAccessor.java:89)
    at org.hibernate.tuple.entity.AbstractEntityTuplizer.setPropertyValues(AbstractEntityTuplizer.java:583)
    at org.hibernate.tuple.entity.PojoEntityTuplizer.setPropertyValues(PojoEntityTuplizer.java:229)
    at org.hibernate.persister.entity.AbstractEntityPersister.setPropertyValues(AbstractEntityPersister.java:3822)
    at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:152)
    at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:982)
    at org.hibernate.loader.Loader.doQuery(Loader.java:857)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274)
    at org.hibernate.loader.Loader.loadCollection(Loader.java:2166)
    at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:62)
    at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:627)
    at org.hibernate.event.def.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:83)
    at org.hibernate.impl.SessionImpl.initializeCollection(SessionImpl.java:1863)
    at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:369)
    at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:111)
    at org.hibernate.collection.AbstractPersistentCollection.readElementExistence(AbstractPersistentCollection.java:167)
    at org.hibernate.collection.PersistentBag.contains(PersistentBag.java:262)
    at dst1.model.Cluster.setGrid(Cluster.java:114)
    ... 40 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.hibernate.property.BasicPropertyAccessor$BasicSetter.set(BasicPropertyAccessor.java:66)
    ... 57 more
Caused by: org.hibernate.LazyInitializationException: illegal access to loading collection
    at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:366)
    at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:111)
    at org.hibernate.collection.AbstractPersistentCollection.readElementExistence(AbstractPersistentCollection.java:167)
    at org.hibernate.collection.PersistentBag.contains(PersistentBag.java:262)
    at dst1.model.Cluster.setGrid(Cluster.java:114)
    ... 62 more
4

2 に答える 2

1

これはアイデアです。

「永続モデル層」と「ドメインモデル層」の2層を使用しています。

「永続モデル層」のクラスにはいくつかの JPA アノテーションがありますが、適用規則はありません。
「ドメイン モデル レイヤー」のクラスには、JPA アノテーションはありません。

JPA/Hibernate は「永続モデル層」のクラスを認識していますが、「ドメイン モデル層」のクラスを認識していません。

「永続モデル層」のクラスは、JPA/Hibernate にとって非常に単純です。
したがって、この質問のような問題はほとんど発生しません。

この場合、「ドメイン モデル層」のクラスは、A と B の間の双方向の関係を維持する責任があります。(A#setB、B#addA)
ORM の影響を心配する必要はありません。

サンプルコードがあります。
「永続モデル層」には A と B が含まれます。
「ドメイン モデル層」には MA と MB が含まれます。
MA のインスタンスには A のインスタンスがあり、MA はその状態を A に委譲します。

/** persistence model layer */
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
@Entity
public class A {
    private Long id;
    private B b;
    public A(){
    }
    @Id
    @GeneratedValue
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    @ManyToOne
    public B getB() {
        return b;
    }
    public void setB(B b) {
        this.b = b;
    }
}
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
@Entity
public class B {
    private Long id;
    private Collection<A> as = new ArrayList<A>();
    public B(){
    }
    @Id
    @GeneratedValue
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    @OneToMany(cascade=CascadeType.ALL, mappedBy="b", fetch=FetchType.LAZY)
    public Collection<A> getAs() {
        return as;
    }
    public void setAs(Collection<A> as) {
        this.as = as;
    }
}
/** domain model layer */
public class MA {
    private A entity;
    public MA(A a){
        this.entity = a;
    }
    public A getEntity(){
        return this.entity;
    }
    public MB getB(){
        return new MB(entity.getB());
    }
    public void setB(MB mb){
        if (mb != null && this.entity.getB() != mb.getEntity()){
                this.entity.setB(mb.getEntity());
                mb.addA(this);
        }
        return;
    }
}
import java.util.ArrayList;
import java.util.List;
public class MB {
    private B entity;
    public MB(B b){
        this.entity = b;
    }
    public B getEntity(){
        return this.entity;
    }
    public void addA(MA ma){
        if (ma != null && ! this.getEntity().getAs().contains(ma.getEntity())){
            this.entity.getAs().add(ma.getEntity());
            ma.setB(this);
        }
        return;
    }
    public List<MA> getAs(){
        List<MA> resultList = new ArrayList<MA>();
        for(A a : entity.getAs()){
            resultList.add(new MA(a));
        }
        return resultList;
    }
}

equals/hashCode メソッドを実装することをお勧めします。
ヒントになれば幸いです。

于 2012-09-15T08:13:59.950 に答える
1

Hibernate およびその他の JPA ベースの ORM は、必要なときに関係を定義するコレクションをロードするために使用します (遅延ロード)。まだロードされていない、または中間状態のコレクションを変更しようとすると、Hibernate がその例外をスローしていることを理解しています。

Hibernate はプロキシを使用してエンティティを処理し、特定のコレクションの get メソッドを呼び出すときにコレクションを使用する必要があることを認識します。

私はあなたのsetGridメソッドを本当に異なる方法で実装しますが、最初にあなたのエンティティはメソッドequalsとを実装する必要がありますhashCode. その他の変更は次のとおりです。

クラスターのコレクションをセットに変更します。containsセットには重複するインスタンスが含まれていないため、コレクションに要素を追加する前にそのチェックを行う必要はありません。

Set<Cluster> clusters = new HashSet<Cluster>();

次に、宣言したメソッドではなく、コレクション自体setGridのメソッドを呼び出すようにメソッドを変更します。add

setGrid(Grid grid) {
   Grid oldGrid = this.grid;
   this.grid = grid;
   if (oldGrid != null) {
       oldGrid.getClusters().remove(this);
   }
   if (grid != null) {
       grid.getClusters().add(this);
   }
}

addCluster最後に、Grid クラスのメソッドの実装を少し変更します。

public void addCluster(Cluster c) {
    //this.clusters.add(c); -- no needed anymore
    c.setGrid(this);
}

お役に立てれば

于 2012-04-01T12:13:37.803 に答える