JPA двунаправленные отношения - PullRequest
2 голосов
/ 01 апреля 2012

Если у меня двунаправленные отношения в моей DataModel, мое приложение обязано поддерживать ссылки в актуальном состоянии в коде Java.

Каков наилучший способ сделать это?

Например, бидир. Соотношение 1: N между А и В.

@Entity
class A {

@ManyToOne
private B b;

}


@Entity
class B {

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

}

Если я скажу B.addA (b), это не позволит переменной b в A указать ссылку, которую я добавил. И если я вызываю A.setB (b), это не добавляет ссылку b на коллекцию в B.

Один из возможных способов - вызвать setB AND 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?

1020 * ТНХ *

UPDATE : вот оригинальный код:

@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

Ответы [ 2 ]

1 голос
/ 15 сентября 2012

Это идея.

Я использую два слоя, "слой модели персистентности" и "слой модели домена".

Классы "слоя модели персистентности"имеют некоторые аннотации 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.
Надеюсь, у вас будет подсказка.

1 голос
/ 01 апреля 2012

Hibernate и другие ORM на основе JPA используют для загрузки коллекций, которые определяют отношения именно тогда, когда это необходимо (отложенная загрузка). Я понимаю, что 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);
}

Надеюсь, это поможет

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...