@OneToMany(fetch = FetchType.LAZY) list is always null

#1

Hello.

@OneToMany(fetch = FetchType.LAZY) doesn't work when using EJB or something else related to J2EE. parent.getChildren() returns null. Here is a simple example:

Bean:

@Named("test")
@RequestScoped
public class TestBean {
    private String mode;
    @PersistenceContext(unitName  = "my-pu")
    private EntityManager entityManager;
    @EJB
    private Manager manager;
    private List<Parent> parents;

    @PostConstruct
    private void postConstruct() {
        Query query = entityManager.createQuery("SELECT p FROM Parent p");
        parents = query.getResultList();
    }

    public void addParent() {
        Parent parent = new Parent();
        manager.persist(parent);
        postConstruct();
    }

    public void addChild(Parent parent) {
        Child child = new Child();
        child.setParent(parent);
        manager.persistChild(child);
    }

    public String getMode() {
        return mode;
    }

    public void setMode(String mode) {
        this.mode = mode;
    }

    public List<Parent> getParents() {
        return parents;
    }

    public void setParents(List<Parent> parents) {
        this.parents = parents;
    }
}

Entities:

@Entity
@SequenceGenerator(name = "b", allocationSize = 1)
public class Parent {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "b")
    private int id;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<Child> children;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public List<Child> getChildren() {
        return children;
    }

    public void setChildren(List<Child> children) {
        this.children = children;
    }
}

@Entity
@SequenceGenerator(name = "b", allocationSize = 1)
public class Child {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "b")
    private int id;

    @ManyToOne
    private Parent parent;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Parent getParent() {
        return parent;
    }

    public void setParent(Parent parent) {
        this.parent = parent;
    }
}

Manager:

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class Manager {
    @Resource
    private UserTransaction transaction;
    @PersistenceContext(unitName  = "my-pu")
    private EntityManager entityManager;

    public void persist(Object o) {
        try {
            transaction.begin();
            entityManager.persist(o);
            transaction.commit();
        } catch (NotSupportedException e) {
            e.printStackTrace();
        } catch (SystemException e) {
            e.printStackTrace();
        } catch (RollbackException e) {
            e.printStackTrace();
        } catch (HeuristicMixedException e) {
            e.printStackTrace();
        } catch (HeuristicRollbackException e) {
            e.printStackTrace();
        }
    }

    public void persistChild(Child child) {
        try {
            transaction.begin();
            entityManager.persist(child);
            if (child.getParent().getChildren() == null) {
                child.getParent().setChildren(new ArrayList<Child>());
            }
            child.getParent().getChildren().add(child);
            entityManager.merge(child.getParent());
            transaction.commit();
        } catch (NotSupportedException e) {
            e.printStackTrace();
        } catch (SystemException e) {
            e.printStackTrace();
        } catch (RollbackException e) {
            e.printStackTrace();
        } catch (HeuristicMixedException e) {
            e.printStackTrace();
        } catch (HeuristicRollbackException e) {
            e.printStackTrace();
        }
    }
}

JSF:

<f:view>
    <h:form>
        <h:commandButton value="Run a method" action="#{test.addParent}"/>
    </h:form>
    <ui:repeat value="#{test.parents}" var="p">
        #{p.id}<br />
        <ui:repeat value="#{p.children}" var="c">
            ---#{c.id}<br />
        </ui:repeat>
        <h:form>
            <h:commandButton value="asdasd" action="#{test.addChild(p)}" />
        </h:form>
    </ui:repeat>
    <h:messages />
</f:view>
#2

A lazy relationship is available only when the EntityManager is open.

After closing the EntityManager your entity objects are detached and lazy relationships cannot be used, if not already loaded before when the EntityManager was open.

See also this issue and the solution that it provides.

ObjectDB Support
#3

Thanks for the reply.

Isn't it open when using @PersistenceContext?

#4

It may be open in session bean methods and closed when building the output in JSF.

ObjectDB Support
#5

So it should be open in my case (1st post)?

#6

Not in JSF. Please follow the link in #2 above.

ObjectDB Support
#7

OK, I'll try setting objectdb.temp.no-detach.

#8

Thank you! It works (as the next my problem does).

#9

When I turn on this option and MERGE the entity containing another one (OneToOne) entity with a byte[] array without calling the getter of the array of the child entity, the array becomes null.

public class ByteData {
    // ... id
    private byte[] data;

    public ByteData(byte[] data) {
        this.data = data;
    }

    // getters and setters
}

public class Parent {
    // ...id

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private ByteData bydaData;

    public Parent(ByteData byteData) {
        this.byteData = byteData;
    }

    //getters and setters
}

//...

Parent parent = new Parent(new ByteData(new byte[]{1, 2, 3, 4, 5}));
persist(parent); // all is OK when persisting

id = parent.getId;
parent = find(id);
parent.getByteData().getData();
merge(parent); // all is OK when merging with calling the getter of data of byteData

parent = find(id);
merge(parent); // parent.byteData.data is getting null
#10

The exact issue is unclear.

Could you please provide a complete small test case (in this format) that demonstrates the issue? If it is related to the no-detach option then the same test case should work differently, depending if that option is enabled or not.

ObjectDB Support
#11

I think it relates to the no-detach option, because all is OK when using EAGER loading, but when I use LAZY with PersistenceUnit (not EntityManagerFactory), it fails.

Entities:

@Entity
public class Parent {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private OTO oto;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public OTO getOto() {
        return oto;
    }

    public void setOto(OTO oto) {
        this.oto = oto;
    }
}
@Entity
public class OTO {
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Id
    private int id;

    @Lob
    private byte[] bytes;

    public OTO() {
    }

    public OTO(byte[] bytes) {
        this.bytes = bytes;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public byte[] getBytes() {
        return bytes;
    }

    public void setBytes(byte[] bytes) {
        this.bytes = bytes;
    }
}

Manager:

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class Manager {
    @Resource
    private UserTransaction transaction;
    @PersistenceContext(unitName  = "my-pu")
    private EntityManager entityManager;

    public void persist(Object o) {
        try {
            transaction.begin();
            entityManager.persist(o);
            transaction.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void merge(Object o) {
        try {
            transaction.begin();
            entityManager.merge(o);
            transaction.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Parent findParent(int id) {
        return entityManager.find(Parent.class, id);
    }
}

Bean:

public class TestBean {
    @PersistenceContext(unitName  = "my-pu")
    private EntityManager entityManager;
    @EJB
    private Manager manager;
    private List<Parent> parents;

    @PostConstruct
    private void postConstruct() {
        Query query = entityManager.createQuery("SELECT p FROM Parent p");
        parents = query.getResultList();
    }

    public void addParent() {
        Parent parent = new Parent();
        byte[] bytes = new byte[]{1, 2, 3, 4, 5};
        parent.setOto(new OTO(bytes));
        manager.persist(parent);
        postConstruct();
    }

    public void merge(int id) {
        Parent parent = manager.findParent(id);
        manager.merge(parent); // parent.getOto().getBytes() becomes NULL but should be {1, 2, 3, 4, 5}.
    }

    public List<Parent> getParents() {
        return parents;
    }

    public void setParents(List<Parent> parents) {
        this.parents = parents;
    }
}
#12

Please minimize the test case to a simple console application in this format.

ObjectDB Support
#13

All works normally in the console application.

#14

> I think it relates to the no-detach option, because all is OK when using EAGER loading, but when I use LAZY with PersistenceUnit (not EntityManagerFactory), it fails.

The no-detach option is currently not fully supported, so if it doesn't work well in your project you will probably have to use EAGER when necessary.

ObjectDB Support

Reply