2

Long time reader, first time asker, so please be gentle. :) I'm using Hibernate as my JPA 2.0 provider (hibernate-core-4.1.16, hibernate-jpa-1.0.1) and I'm having trouble with something seemingly simple.

Short Version

I have two entities A and B, where A holds a list of B. Every time a new list of B is set, I want to process it. Hence I put the processing inside the setter for the list. The same setter is used by Hibernate.

This works fine for loading instances of A from the database and even refreshing works when it is not cascaded. But as soon as I enable CascadeType.REFRESH and do a refresh, accessing the new list of B inside the setter, causes the infamous LazyInitializationException.

I have tried some things (see below) but nothing helped and I'm out of ideas. :(

Long Version

I created a minimal working example to show what is supposed to happen and what actually happens.

Classes

Note: I omitted the id-attributes. (In case this is important: the id-getters are annotated with @Id @GeneratedValue(strategy = GenerationType.IDENTITY).)

Class A
@Entity
@Table(name = "tableA")
public class A {

    private List<B> bar;

    @OneToMany(mappedBy = "theA", cascade = CascadeType.ALL)
    public List<B> getBar() {
        return bar;
    }

    private void setBar(List<B> bar) {
        this.bar = bar;
        System.out.println("Number of Bs during set: " + bar.size());
    }
}
Class B
@Entity
@Table(name = "tableB")
public class B {

    private A theA;

    @ManyToOne
    @JoinColumn(name = "a_id", nullable = false)
    public A getTheA() {
        return theA;
    }

    private void setTheA(A theA) {
        this.theA = theA;
    }
}

Data

The two tables tableA and tableB each contain one row, so there will be one instance of A which holds one instance of B.

Execution

Finally, this is the executed code:

public static void main(String[] args) {
    EntityManager em = Persistence.createEntityManagerFactory("TestDBManager").createEntityManager();
    A theA = (A) em.createQuery("SELECT a FROM A a").getResultList().get(0);
    System.out.println("Number of Bs after load: " + theA.getBar().size());
    em.refresh(theA);
    System.out.println("Number of Bs after refresh: " + theA.getBar().size());
}

What Should Happen vs. What Actually Happens

This is the expected outcome

Number of Bs during set: 1
Number of Bs after load: 1
Number of Bs during set: 1
Number of Bs after refresh: 1

But in fact this happens:

Number of Bs during set: 1
Number of Bs after load: 1
Exception in thread "main" javax.persistence.PersistenceException: org.hibernate.PropertyAccessException: Exception occurred inside setter of isi.power.core.test.A.bar
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1377)
    at org.hibernate.ejb.AbstractEntityManagerImpl.refresh(AbstractEntityManagerImpl.java:939)
    at org.hibernate.ejb.AbstractEntityManagerImpl.refresh(AbstractEntityManagerImpl.java:906)
    at line of call to refresh
Caused by: org.hibernate.PropertyAccessException: Exception occurred inside setter of isi.power.core.test.A.bar
    ... many more
Caused by: java.lang.reflect.InvocationTargetException
    ... many more
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection, no session or session was closed
    ... many more

What I Tried

Don't Touch the List

If I remove the calls to the list inside the setter, Hibernate finishes its magic after calling setBar and I get no exception, when it is called later (after the refresh-call in main). But I loose the possibility to process the list inside the setter, which I'd rather not.

Don't Cascade Refresh

The problem goes away if I do not cascade refreshes, i.e. if I replace

cascade = CascadeType.ALL

with

cascade = { CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE }

Since I mostly do this for the refresh, this is not an option.

Fetch Eagerly

I added fetch = FetchType.EAGER to the @OneToMany-annotation. Same exception...

Transaction

I surrounded the load and refresh calls with em.getTransaction().begin(); and em.getTransaction().commit(); . Same exception...

Hibernate.initialize

I tried to call Hibernate.initialize inside the setter which leads to a different exception:

Number of Bs during set: 1
Number of Bs after load: 1
Exception in thread "main" javax.persistence.PersistenceException: org.hibernate.PropertyAccessException: Exception occurred inside setter of isi.power.core.test.A.bar
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1377)
    at org.hibernate.ejb.AbstractEntityManagerImpl.refresh(AbstractEntityManagerImpl.java:939)
    at org.hibernate.ejb.AbstractEntityManagerImpl.refresh(AbstractEntityManagerImpl.java:906)
    at line of call to refresh
Caused by: org.hibernate.PropertyAccessException: Exception occurred inside setter of isi.power.core.test.A.bar
    ... many more
Caused by: java.lang.reflect.InvocationTargetException
    ... many more
Caused by: org.hibernate.AssertionFailure: force initialize loading collection
    ... many more

Long Story Short

I'm fresh out of ideas... I'd be very thankful for any pointer to a possible solution of this problem.

4

1 回答 1

1

如果可以接受,您可以使用字段访问(即注释字段,而不是方法)。

例如:

@Entity
@Table(name = "tableA")
public class A {

    @OneToMany(mappedBy = "theA", cascade = CascadeType.ALL)
    private List<B> bar;

    public List<B> getBar() {
        return bar;
    }

    private void setBar(List<B> bar) {
        this.bar = bar;
        System.out.println("Number of Bs during set: " + bar.size());
    }
}

使用您的示例类准备了一个简单的项目 - 它与字段访问一起使用。


但是,如果在刷新期间需要进行处理,那么您可能可以使用@PostLoad方法查看,例如:

@PostLoad
public void processB() {
    System.out.println("Number of Bs after load/refresh: " + this.bar.size());
}

完整的示例看起来像这样(这样,当使用代码手动调用 setter 和在实体的加载/刷新中自动调用 setter 时,它都应该工作):

@Entity
@Table(name = "tableA")
public class A {

    @OneToMany(mappedBy = "theA", cascade = CascadeType.ALL)
    private List<B> bar;

    public List<B> getBar() {
        return bar;
    }

    public void setBar(List<B> bar) {
        this.bar = bar;
        processBar();
    }

    @PostLoad
    public void processBar() {
        System.out.println("Number of Bs after load/refresh: " + this.bar.size());
    }
}
于 2013-06-26T22:10:53.457 回答