Recommendation - Use List

The recommendation is to use List to map collection properties of @OneToMany or @ManyToMany.

This means we avoid implicit use of hashCode() / equals() which occurs with the use of Set.

List vs Set

Hibernate bag semantics

With Hibernate there could be a preference for using Set because Set and List have different semantics with Hibernate. Set uses "bag semantics" and is often preferred with Hibernate.

hashCode() / equals()

Using Set by implication means that hashCode()/equals() implementation is used. This is problematic as the nature of entity beans is that they a mutating and often don't have an @Id value until after the bean has been persisted.

The difficulty in implementing hashCode()/equals() on entity beans means that List is the recommended collection type to hold @OneToMany and @ManyToMany collections.

@Entity
@Table(name="customer")
public class Customer extends BaseModel {
  ...

  // List is recommended for collection types

  @OneToMany(mappedBy="customer", cascade=CascadeType.PERSIST)
  List<Contact> contacts;
  ...

Enhancement

When you define a collection type enhancement will ensure:

  • Any List/Set initialisation is removed
  • The List/Set is always initialised by Ebean (and never null)

List/Set initialisation is removed

// initialisation of the new ArrayList() is removed

@OneToMany(mappedBy="customer")
List<Contact> contacts = new ArrayList<Contact>;

// you can declare an un-initialised List if you wish
// and there is no actual difference to an initialised one
// because enhancement will always initialise it

@OneToMany(mappedBy="customer")
List<Contact> contacts;

In Kotlin we'd typically always want to define it as being not null:

// kotlin: contacts type not nullable
@OneToMany(mappedBy = "customer")
var contacts: MutableList<Contact> = ArrayList()

The List/Set is always initialised (never null)

Ebean needs to control the initialisation of the List/Set in order to support:

  • Lazy loading
  • Support @PrivateOwned where we need the list/set to be aware of removals
  • Support @ManyToMany where we need the list/set to be aware of adds and removals

Enhancement ensures that whenever you access a List/Set Ebean will always initialise it and setup the List/Set to listen for adds/removals if necessary (for @PrivateOwned and @ManyToMany).

This has the effect that accessing the list/set it will always be not null.

// it "looks" like contacts could be null ...

@OneToMany(mappedBy="customer")
List<Contact> contacts;

public void addContact(Contact contact) {
  // but actually contacts will never be null here
  if (contacts == null) {
    contacts = new ArrayList<>();
  }
  contacts.add(contact);
}

Enhancement replaces all the GETFIELD instructions for persistent collections and replaces that with code that ensures the List/Set is initialised if required.

@OneToMany(mappedBy="customer")
List<Contact> contacts;

public void addContact(Contact contact) {
  // this is safe to write as contacts
  // will never be null here
  contacts.add(contact);
}