yktoo
yktoo

Reputation: 2986

Hibernate: avoid implicit initialization of a collection

We're using entity classes for two purposes:

  1. As a database model, i.e. Hibernate @Entity
  2. As a data model sent over to the front end as JSON

Imagine an @Entity has a number of bulky collections, e.g.:

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

    @Id
    private Integer id;

    @Column(name = "name")
    private String name;

    @OneToMany(mappedBy = "customer", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<Order> orders;

    @OneToMany(mappedBy = "customer", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<Counterparty> counterparties;

    /* ... a lot of other properties, including collections ... */

}

Now, we work with Customer in two major ways:

  1. Fetch an individual customer for viewing/editing, complete with all collections.
  2. Fetch a list of customers, with just their "core" properties required for display; we don't need any of its child collections loaded as we're not using them and they generate a lot of additional queries.

My preferred solution would be explicit initialization of collections for the case 1, and keeping them null or empty for the case 2.

The problem is that when Jackson serializes an object into JSON, it goes over all properties, including collection ones, so they are forced to be initialized. Adding @JsonIgnore is not an option (we need those collections for case 1), adding @Transient to keep Hibernate away from them is not an option either (we have to be able to store collections after editing).

Another alternative would be, of course, creating a different model of Customer without collections and using that for scenario 2, but that means maintaining two varieties of the same entity and I'd like to avoid that.

How can I disable Hibernate's implicitly loading of these collections, so that they are only initialized explicitly (via e.g. Hibernate.initialize(customer.orders)), while retaining the possibility of persisting them when needed?

Upvotes: 1

Views: 1395

Answers (3)

yktoo
yktoo

Reputation: 2986

Eventually I've solved it in the "DTO-fashion" but with minimal overhead, by adding an alternative getter for each property, like so:

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

    ...

    @OneToMany(mappedBy = "customer", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JsonIgnore
    private List<Order> orders;

    /* "Normal" getter to be used in the back-end */ 
    @JsonIgnore // <-- Important as it hides it from Jackson
    public List<Order> getOrders() {
        return orders;
    }

    /* Non-initializing getter for serialization for the front-end */ 
    public List<Order> getOrdersNonInit() {
        return Hibernate.isInitialized(orders) ? orders : null;
    }

    ...

}

Upvotes: 1

Dragan Bozanovic
Dragan Bozanovic

Reputation: 23562

There is no way to do it in Hibernate.

Alternatives:

  1. Use DTOs. This has the advantage of tailoring the objects to be serialized to the exact needs of the client consuming the resulting JSON. Also, the domain model (Hibernate entities) is decoupled from the serialization logic, allowing the two to evolve independently.
  2. Use custom serializers.

Upvotes: 1

mh-dev
mh-dev

Reputation: 5523

The following is just one possible solution and depends on your environment.

There's a project called "jackson-datatype-hibernate" from the FasterXML project. You can find it at https://github.com/FasterXML/jackson-datatype-hibernate which allows to define a custom serializer with includes disabling of lazy fields. (I think this default for this serializer).

Code could look like the following:

ObjectMapper mapper = new ObjectMapper();
Hibernate4Module hibernateModule = new Hibernate4Module();
mapper.registerModule(hibernateModule);

Upvotes: 0

Related Questions