【问题标题】:JPA bidirectional relationsJPA 双向关系
【发布时间】:2012-04-01 11:51:43
【问题描述】:

如果我的 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

【问题讨论】:

  • 您还没有收到任何反馈吗?
  • 您好,非常感谢您的帮助,但不幸的是它并没有解决问题。每当我想从相关实体的 set/add 方法中获取另一个实体的集合时,我都会得到这个 LazyInitializationException :/ 我会尝试拆开我的解决方案,看看是否可以获得更多详细信息,为什么会发生这种情况 :( 谢谢你的帮助
  • 也许你应该在 Hibernate 论坛上问一下,他们会更好地了解实现的内部结构。

标签: java jpa relationship bidirectional one-to-many


【解决方案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);
   }
}

最后,稍微更改一下 Grid 类中 addCluster 方法的实现:

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

希望对你有帮助

【讨论】:

  • 感谢您的回复。我犯了一个错误..在方法 setB 中应该是 if(!b.getAs().contains(this)) ...所以我已经使用了 get 方法。并且这条线出现在异常的堆栈跟踪中。
  • 你能用这些信息更新你的问题吗?你应该找到一个链接,可以让你编辑它。 (如果您发布它,堆栈跟踪将非常有用)
  • 它不必是懒惰的——你可以指定默认的加载模式。请参阅 javax.persistence.FetchType。
  • 没错,迈克,他的模型只是这两个实体的复合,使用 EAGER 加载将解决问题......但是,从我的角度来看,必须决定是懒惰还是急切收集始终在每种特定情况下进行分析,因为将模型中的所有关系都声明为 EAGER 会破坏大型数据集的性能。
【解决方案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方法。
我希望你能成为一个提示。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-29
    • 2011-03-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多