Custom EntityUniqueKey possible?


#1

I’ve got three classes, with two classes referencing the third class via a JoinColumn(referencedColumnName):

public class A {
  ...
  @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
  @JoinColumn(name = "NONPK", referencedColumnName="NONPK")
  private C cValue;
  ...
}

public class B {
  ...
  @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
  @JoinColumn(name = "NONPK", referencedColumnName="NONPK")
  private C cValue;
  ...
}

public class C {
  ...
  @Id
  @Column("ID", unique = true, nullable = false, precision = 10)
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "IDNR")
  @NotNull
  private Long id;

  @Column("NONPK", unique = true, nullable = false, length = 10)
  @NotNull
  private String nonPk;
  ...
}

When I have a query which first loads all entities of class A, with an EntityGraph to collect the entity C in the same query, it creates the entity A without any extra queries. When I load all entities of B, again loaded with an EntityGraph to load C, then an extra query is performed. Both entities have the same entity C with the same NONPK-value.

I’ve traced it to the following:
When A is loaded, it also loads the entity C. Because entity C is not already loaded in the session, it will load it from the resultset. Then it constructs a EntityUniqueKey based on C and A and puts that in the persistanceContext of the session (see Loader.loadFromResultSet). Later when B is loaded, it already sees entity C loaded and does not try to load it again. For B there won’t be a EntityUniqueKey. When it is time to hydrate the relationship, it tries to search for C based on the EntityUniqueKey of C and B (see TwoPhaseLoad.doInitializeEntity and EntityType.resolve, isReferenceToPrimaryKey = false). Because the entity is only available on the key of C and A, it will not find anything, therefore it will issue a query.

Is it possible to generate a custom EntityUniqueKey, so both A and B will use the same key and the extra query is not issued?

I am using Hibernate 5.2.17.


#2

I don’t understand your use case. Add the data access code you are using and the queries executed by Hibernate.


#3

It can be reproduced with a JOIN FETCH also, so for simplicity I will provide the JOIN FETCH. The records in the database are:

A: id = 1, nonpk = "TEMP"
B: id = 1, nonpk = "TEMP"
C: id = 2, nonpk = "TEMP"

The following HQL-queries are used:

Query<A> query1 = session.createQuery("SELECT a FROM A a JOIN FETCH a.cValue WHERE a.id = 1");
Query<B> query2 = session.createQuery("SELECT b FROM B b JOIN FETCH b.cValue WHERE b.id = 1");

When query1.list() is executed, the following SQL is executed:

SELECT
     a0_.id        AS id1_996_0_,
     c1_.id        AS id1_332_1_,
     a0_.nonpk     AS nonpk2_996_0_,
     c1_.nonpk     AS nonpk2_332_1_
FROM a a0_
INNER JOIN c c1_ ON a0_.nonpk = c1_.nonpk
WHERE a0_.id = ?
;

Next, when query2 is executed, the following SQL-queries are executed:

SELECT
     b0_.id        AS id1_903_0_,
     c1_.id        AS id1_332_1_,
     b0_.nonpk     AS nonpk2_903_0_,
     c1_.nonpk     AS nonpk2_332_1_
FROM b b0_
INNER JOIN c c1_ ON b0_.nonpk = c1_.nonpk
WHERE b0_.id = ?
;

SELECT
     c0_.id    AS id1_332_0_,
     c0_.nonpk AS nonpk2_332_0_
FROM c c0_
WHERE c0_.nonpk = ?
;

As you can see, with the second query a single query to retrieve data for class C is issued, even though it is the same data.


#4

Try to emulate with the following GitHub test case because the only explanation for that secondary query is if you have a mapping like this:

  @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
  @JoinColumn(name = "NONPK", referencedColumnName="NONPK")
  private C cValue;

or

  @ManyToOne(cascade = CascadeType.PERSIST)
  @JoinColumn(name = "NONPK", referencedColumnName="NONPK")
  private C cValue;

#5

The first mapping is exactly the mapping I have, see my first post. Is there any way around to not have this extra secondary query? It is used in some processing logic which is executed many times by many different users at the same time, so it could pose a performance problem.


#6

If you can write that replicating test case, I’ll investigate it. Otherwise, you’ll have to debug to see what causes the extra SQL query.


#7

I have the testcase available. I don’t have a bugreport. How can I make sure you get the testcase?


#8

If you have a replicating test case, you should open a Hibernate Jira issue and attach the test case to it.


#9

I’ve created HHH-13024