I've been experiencing a problem with excessive select statements bogging down our database, and I'm curious if anybody can shed some light on the situation. The code I've provided below shows the basic structure of the relationships that seem to be giving us the problem.
We use a @MappedSuperclass to provide @Id and @Version for all of our classes. Class A and class B have a many-to-many relationship managed through OccurrenceOfB, but there is also extra information (not shown) associated with each OccurrenceOfB instance (hence why we are not merely using a JoinTable).
The problem becomes manifest when we attempt to create a string representation of A. In this case, I'm actually constructing JSON, but I've excluded the bulk of that here. The pertinent code, included below, involves iterating over all OccurrenceOfBs in the A and getting the ID of each. However, when we do this (and specifically, when the iteration begins), Hibernate is retrieving all of the OccurrenceOfB objects. This is just as I would expect. However, at the same time, it is
also retrieving all of the corresponding B objects, and the C objects associated with each B. In other words, when it lazily retrieves the OccurrenceOfBs, it is then eagerly fetching the rest of the entity graph and not honoring the FetchType.LAZY annotations (which are admittedly, according to the documentation, merely a hint). This is
not what I would expect--and is causing some significant performance hits when the size of A.occurrences is in the thousands. (Our actual objects are slightly more complex than this, but serializing one A with 1500 OccurrenceOfBs results in 10502 select statements in our system.)
Curiously, the opposite does not hold true. If I have a B that occurs in 1500 As, converting the B to a string (which likewise iterates over the occurrences collection and retrieves the ID of each) does NOT perform all of the excessive select statements.
And now, at long last to the actual question: How can I improve performance in my database in this scenario? Setting the default batch fetch size provides some relief, but it seems to be a band-aid on the underlying problem. Ideally, how can I convince Hibernate to actually honor lazy fetching for transitive associations?
In advance, thanks for your help!
Code:
@MappedSuperclass
public class PersistentObject
{
@Id
@GeneratedValue
@Column
protected Long id;
@Version
@Column
protected Long version;
}
@Entity
public class A extends PersistentObject
{
@OneToMany(mappedBy = "a",
fetch = FetchType.LAZY,
orphanRemoval = true,
cascade = { CascadeType.ALL })
private final Set<OccurrenceOfB> occurrences = new HashSet<>();
// Other columns here
@Override
public String toString()
{
// ...
List<Long> ids = new LinkedList<>();
for ( OccurrenceOfB occurrence : occurrences )
{
ids.add(occurrence.getId());
}
// ...
}
}
@Entity
public class OccurrenceOfB extends PersistentObject
{
@ManyToOne(fetch = FetchType.LAZY,
cascade = { CascadeType.PERSIST,
CascadeType.MERGE },
optional = false)
private A a;
@ManyToOne(fetch = FetchType.LAZY,
cascade = { CascadeType.PERSIST,
CascadeType.MERGE },
optional = false)
private B b;
// Other columns here
}
@Entity
public class B extends PersistentObject
{
@OneToMany(mappedBy = "b",
fetch = FetchType.LAZY,
orphanRemoval = true,
cascade = { CascadeType.ALL })
private final Set<OccurrenceOfB> occurrences = new HashSet<>();
@OneToOne(fetch = FetchType.LAZY,
cascade = { CascadeType.ALL },
optional = false)
@JoinColumn(name = "c",
nullable = false)
private C c;
// Other columns here
}
@Entity
public class C extends PersistentObject
{
@OneToOne(mappedBy = "c",
optional = false)
@JoinColumn(name = "b",
nullable = false)
private B b;
// Other columns here
}