21

My Tables:

Product: id, name

Offer: id, value, product_id

Entities:

@Entity
@Table(name="product")
public class Product implements Serializable {
    @OneToMany(mappedBy="product")
    private Set<Offer> offers;
    ...
}

@Entity
@Table(name="offer")
public class Offer implements Serializable {
    @ManyToOne
    @JoinColumn(name="PRODUCT_ID")
    private Product product;
    ...
}

When I try to get some data from table Product, I get a java.lang.NullPointerException, and this code: product.getOffers() returns:

{IndirectSet: not instantiated}

How to fix this?

Piotr Nowicki
  • 17,914
  • 8
  • 63
  • 82
Emanuel
  • 6,622
  • 20
  • 58
  • 78

5 Answers5

28

This not an error message. The print instruction results in toString() being invoked on the underlying IndirectSet.

TopLink will place an IndirectSet in the instance variable when the containing domain object is read from the datatabase. With the first message sent to the IndirectSet, the contents are fetched from the database and normal Set behavior is resumed.

IndirectCollection types are specifically implemented not to instantiate on toString():

For debugging purposes, #toString() will not trigger a database read.

However, any other call on the indirect collection, e.g., size() or isEmpty() will instantiate the object.

The database read is ultimately triggered when one of the "delegated" methods makes the first call to getDelegate(), which in turn calls buildDelegate(), which sends the message getValue() to the value holder. The value holder performs the database read. With the first message sent to the IndirectSet, the contents are fetched from the database and normal Set behavior is resumed.

See also IndirectList: not instantiated

Tomasz
  • 5,269
  • 8
  • 56
  • 65
  • 3
    +1'd this response; very informative, as I was wondering why eclipselink indirectlist isEmpty() was showing up in Java Visual VM as I was reviewing, comparing, and reporting on performance after replacing JPA queries of indirectlist with just checking if list != null and !list.isEmpty(). This absolutely solved a performance issue I had; was accessing the database over 600 times, at first, but with the latest implementation, accessing the database only 5 times! – Howard Feb 09 '13 at 14:21
  • 1
    +1'd. Though the answer is for TopLink, it holds true for EclipseLink as well. – gammay Sep 05 '13 at 07:26
  • 4
    @gammay EclipseLink is based on TopLink (fyi) – Patrick Bergner Nov 15 '13 at 14:03
14

If you get {IndirectSet: not instantiated} when accessing product.getOffers() than most probably you're executing this code outside of the transaction.

By default @OneToMany and @ManyToMany relationships are lazy loaded which means that, for better performance, you'll get data fetched only when you want to access it for the first time. This must happen within an active transaction.
If you don't access this data within this scope than you cannot access this data no more. You should either put your invocation code within the active transaction or change the collection to be eager instead of lazy:

@OneToMany(mappedBy="product", fetch=FetchType.EAGER)
Piotr Nowicki
  • 17,914
  • 8
  • 63
  • 82
  • I acualy tried it out myself with a JUnit test and I did wrap around final EntityTransaction txn = em.getTransaction(); txn.begin(); and txn.commit() but I still get the same behavour. – Archimedes Trajano Apr 03 '12 at 04:03
  • So, you did invoke your logic **within** the EntityManager's transaction (and you did load and object and access it **without** leaving the tx) and you still get the same exception? – Piotr Nowicki Apr 03 '12 at 10:03
  • I don't get an "exception" I got the {Indirect set: not instantiated} – Archimedes Trajano Apr 04 '12 at 02:19
  • You're right - it's not an exception; but still - is the rest of my sentence correct? – Piotr Nowicki Apr 04 '12 at 13:36
  • To be honest I never tried outside the transaction. But I don't doubt you anyway. Everything I do is wrapped in a transaction even simple reads. – Archimedes Trajano Apr 05 '12 at 03:02
  • Thanks, this solved my problem. However, I had to remove some of the "reverse" relationships I had in my classes, because the eager fetching ignited a firestorm of queries. Other than using an eager fetching scheme, is there another way to make JPA load the referenced objects only when they're accessed? – Mr. Lance E Sloan Jun 20 '12 at 00:59
  • 1
    Don't think so... Either you load the data when needed while being in a transaction (rationalle behind LAZY) or you load it eagerly so you don't need a tx to access it (effective EAGER). You can eagerly load the objects using FetchType.EAGER, using JPQL `JOIN FETCH` or by iterating over elements of the collection while being in tx. Nevertheless, all of these options are effectively eagerly loading whole collection. – Piotr Nowicki Jun 20 '12 at 09:20
7

Here's what I did

// force load of the set.
entity.getSecrets().isEmpty();
System.out.println(entity.getSecrets());
Archimedes Trajano
  • 35,625
  • 19
  • 175
  • 265
  • Why do you invoke the `entity.getSecrets().isEmpty()`? For what I know, it doesn't load your collection as you still need to access the object itself; the sysout you posted should just do the trick (despite the fact that it's not a great way to invoke object's `toString` method just to do the lazy loading..) – Piotr Nowicki Apr 03 '12 at 10:01
  • 2
    You would think so, but it does not appear to do so with EclipseLink. It may be a "feature" anyway the .isEmpty() forces the set to load I find. – Archimedes Trajano Apr 04 '12 at 02:20
0

This solved my issue

@OneToMany(mappedBy = "columnName", cascade = { CascadeType.ALL}, fetch=FetchType.EAGER)

Boris_Ndong
  • 247
  • 2
  • 4
0

I am using Eclipselink 2.5.2 version as my ORM vendor and I have faced this issue in one-to-many JPA mapping while lazy loading the containing entities. The workaround that is working for me is:-

final List<ContainingEntity> list = Collections.unmodifiableList(new ArrayList<> 
                                    (containerEntity.getContainingEntityList());

The Mapping from Container Entity side is:

@OneToMany(mappedBy = containerEntity, cascade = CascadeType.ALL, fetchType = FetchType.LAZY)
private List<ContainingEntity> containingEntityList;

Mapping from Containing Entity side is:

@ManyToOne
@JoinColumn(name = "container_entity_id")
private ContainerEntity containerEntity;