0

I am trying to make some of my many-to-one relationships in my hibernate HBM files lazy. Concretely I have a DataElement class that references the Filter element in a many-to-one relation, I want to make it lazy while keeping access to Filter eager in other parts of the application.

I have tried changing my HBM files and found that the lazy="true" is only available at the class level

DataElement's mapping (I want the many-to-one with filter to be lazy)

<hibernate-mapping package="project.me" default-lazy="false" auto-import="false">
  <class name="DataElement" table="DATA_ELEMENT">
    <id name="id" type="java.lang.Long" column="ID"/>
    <many-to-one name="filter" class="project.me.Filter" column="FILTER_ID" cascade="all"/>
  </class>
</hibernate-mapping>

FilterAudit's mapping (I want the many-to-one with filter to be eager)

<hibernate-mapping package="project.me" default-lazy="false" auto-import="false">
  <class name="FilterAudit" table="FILTER_AUDIT">
    <id name="id" type="java.lang.Long" column="ID"/>
    <many-to-one name="filter" class="project.me.Filter" column="FILTER_ID" cascade="all"/>
  </class>
</hibernate-mapping>

Filter's mapping

<hibernate-mapping package="project.me" default-lazy="false" auto-import="false">
    <class name="Filter" table="FILTERS" lazy="true">
        <id name="id" type="java.lang.Long" column="M_ID"/>
        <property name="type" type="filterType" column="M_FILTER_TYPE"/>
        <many-to-one name="predicate" class="Predicate" column="PRED_ID" unique="true" not-null="true" cascade="all"/>
    </class>
</hibernate-mapping>

By putting lazy="true" at the Filter class's level, I make all the DTOs in the application load Filter lazily and I don't want that

Is there a way to specify the lazy load only for the relation between DataElement and Filter while keeping the load between FilterAudit and Filter eager ?

jemailix
  • 75
  • 6

1 Answers1

1

My recommendation: Do not use eager fetching. It results in inconsistency and sometimes weird queries. Furthermore, it is difficult to optimise the performance when you use eager fetching. In the mentioned case, I would create different methods to call the database with some fetch joins or entity graphs.

I do not use HBM (or other XML based mapping). I use annotations why I cannot provide you with an answer with XML mapping (HBM seems to be outdated anyway).

My preferred way is using the JPA Criteria API instead of JPQL. Some people say that JPQL is better understandable but I prefer the type-safe Criteria API which is less error prone. I prefer entity graphs because they are easier to use dynamically than fetch joins. Nevertheless, the other options work as well.

At first, I would set up the entity classes appropriate to Vlad Mihalcea's article. (I infer a unidirectional relationship.)

@Entity
@Table(name = "DATA_ELEMENT")
public class DataElement {

    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "FILTER_ID")
    private Filter filter;

    //Constructors, getters and setters...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof DataElement )) return false;
        return id != null && id.equals(((DataElement) o).getId());
    }

    @Override
    public int hashCode() {
        return 56;
    }
}

Similar for FilterAudit (also FetchType.LAZY)...

And then Filter (not that interesting because it is unidirectional).

@Entity
@Table(name = "FILTERS")
public class Filter {

    @Id
    @GeneratedValue
    @Column(name = "M_ID")
    private Long id;

    // Other attributes omitted...

    //Constructors, getters and setters...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Filter )) return false;
        return id != null && id.equals(((Filter) o).getId());
    }

    @Override
    public int hashCode() {
        return 14;
    }
}

Now, the relevant part for initialising the lazy loads (or not) gets realised in this case with the JPA Criteria API and entity graphs.

public DataElement findByIdWithFilter(long id) {

    EntityGraph<DatElement> graph = entityManager.createEntityGraph(DataElement.class);
    graph.addAttributeNodes("filter");

    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<DatElement> cq = cb.createQuery(DatElement.class);
    Root<DatElement> root = cq.from(DatElement.class);

    cq.where(cb.equal(root.get("id"), id));

    TypedQuery<DatElement> typedQuery = entityManager.createQuery(cq);
    typedQuery.setHint("javax.persistence.fetchgraph", graph);

    DatElement response = null;
    try {
        response = typedQuery.getSingleResult();
    }
    catch(NoResultException nre) {}

    return response;
}

For retrieving an object of the same class without its relationship, just omit the entity graph.

public DataElement findByIdWithoutFilter(long id) {

    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<DatElement> cq = cb.createQuery(DatElement.class);
    Root<DatElement> root = cq.from(DatElement.class);

    cq.where(cb.equal(root.get("id"), id));

    return entityManager.createQuery(cq).getSingleResult();
}
Felix Seifert
  • 552
  • 1
  • 9
  • 19