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 中的集合。

一种可能的方法是在我的应用程序代码中调用 setB 和 addA。

另一种可能性是像这样编写 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);
}

}

在我的一个查询中,我得到一个异常,它说 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

这是一个想法。

我使用了两层,“持久模型层”和“域模型层”。

“持久性模型层”的类有一些 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方法真的不同,但首先你的实体需要实现方法equalshashCode. 其他修改将是:

将您的集群集合更改为一个集合。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 回答