| When loading data using an EntityGraph there is no ability to specify the FetchMode and FetchMode.JOIN is used in all scenarios. If an entity has multiple OneToMany relationships this leads to an SQL Query which generates a Cartesian product of the child entities. Example: (Using Spring data jpa @EntityGraph annotation)
@Entity
public class Member {
@OneToMany
private Set<Address> addresses;
@OneToMany
private Set<Contact> contacts;
@OneToMany
private Set<CustomField> customFields;
@OneToMany
private Set<Invoice> invoices;
}
@Repository
public interface MemberRepository extends JpaRepository<Long, Member> {
@EntityGraph(attributePaths={"addresses", "contacts", "customFields", "invoices"})
Member findById(Long id);
@EntityGraph(attributePaths={"addresses", "contacts", "customFields", "invoices"})
List<Member> findAll();
}
If all members had approximately 5 contacts, 3 addresses, 10 customFields and 15 invoices this leads to a SQL query which returns 5 x 3 x 10 x 15 = 2250 rows to load a single member! If there was a way to specify the FetchMode dynamically using the Criteria api or similar this would potentially solve the issue. FetchMode.SUBSELECT appears to be the ideal solution here, where a new query would be generated to load ALL the entities of a particular type from the origin point, even when loading a collection. Example:
@EntityGraph(attributePaths={"addresses", "contacts", "customFields", "invoices"})
List<Member> findAll();
1 query for all members 1 query for all addresses for all members 1 query for all contacts for all members 1 query for all customFields for all members 1 query for all invoices for all members This leads to a fixed number of queries (avoiding the N+1 issue) and no duplicate data being loaded. However, looking through the source - AbstractEntityGraphVisitationStrategy.class and FetchStyleLoadPlanBuildingAssociationVisitationStrategy.class there appears to be code designed to detect multiple collections and adjust the strategy if needed but it is either not working or incomplete. Line 247 - 264:
protected FetchStrategy adjustJoinFetchIfNeeded(
AssociationAttributeDefinition attributeDefinition,
FetchStrategy fetchStrategy) {
if ( lockMode.greaterThan( LockMode.READ ) ) {
return new FetchStrategy( fetchStrategy.getTiming(),
FetchStyle.SELECT );
}
final Integer maxFetchDepth = sessionFactory().getSettings()
.getMaximumFetchDepth();
if ( maxFetchDepth != null && currentDepth() > maxFetchDepth ) {
return new FetchStrategy( fetchStrategy.getTiming(),
FetchStyle.SELECT );
}
if ( attributeDefinition.getType().isCollectionType() &&
isTooManyCollections() ) {
return new FetchStrategy( fetchStrategy.getTiming(),
FetchStyle.SELECT );
}
return fetchStrategy;
}
|