JPA/Hibernate EntityGraph with Specification and Pagination not loading all related entities correctly
I'm working on a JHipster-generated application and need to create APIs that load entities from the database using JPA Specifications and Pagination, while correctly loading related entities. However, I'm struggling to make this work properly.
Entity Structure
I have three main entities with the following relationships:
BuildingProject Entity:
@Entity
@Table(name = "building_project")
public class BuildingProject implements Serializable {
@Id
@GeneratedValue(strategy = SEQUENCE)
private Long id;
@NotNull
private String name;
@NotNull
private BuildingType type;
@NotNull
private String address;
private String description;
private BigDecimal minPrice;
private Instant completionDate;
@OneToMany(fetch = LAZY, mappedBy = "project")
@JsonIgnoreProperties(value = { "photos", "bookings", "project" }, allowSetters = true)
private Set<Unit> units = new HashSet<>();
@OneToMany(fetch = LAZY, mappedBy = "project")
@JsonIgnoreProperties(value = { "project", "unit" }, allowSetters = true)
private Set<Photo> photos = new HashSet<>();
}
Unit Entity:
@Entity
@Table(name = "unit")
public class Unit implements Serializable {
@Id
@GeneratedValue(strategy = SEQUENCE)
private Long id;
private String location;
@NotNull
private BigDecimal price;
private String description;
@NotNull
private BigDecimal area;
@NotNull
private Integer floor;
@NotNull
private UnitType type;
@NotNull
private UnitStatus status;
private Instant completionDate;
@OneToMany(fetch = LAZY, mappedBy = "unit")
@BatchSize(size = 20)
@JsonIgnoreProperties(value = { "project", "unit" }, allowSetters = true)
private Set<Photo> photos = new HashSet<>();
@OneToMany(fetch = LAZY, mappedBy = "unit")
@JsonIgnoreProperties(value = { "client", "unit" }, allowSetters = true)
private Set<Booking> bookings = new HashSet<>();
@ManyToOne(optional = false)
@NotNull
@JsonIgnoreProperties(value = { "units", "photos" }, allowSetters = true)
private BuildingProject project;
}
Photo Entity:
@Entity
@Table(name = "photo")
public class Photo implements Serializable {
@Id
@GeneratedValue(strategy = SEQUENCE)
private Long id;
@NotNull
private String url;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "project_id")
@JsonIgnoreProperties(value = { "units", "photos" }, allowSetters = true)
private BuildingProject project;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "unit_id")
@JsonIgnoreProperties(value = { "photos", "bookings", "project" }, allowSetters = true)
private Unit unit;
}
API Requirements
I need two APIs:
- Load
BuildingProjectwith all Units and all Photos. - Load all
Unitentities with all Photos.
Both APIs must maintain filtering (using Specifications) and pagination for performance and user requirements.
Current Implementation
I implemented this method:
@Transactional(readOnly = true)
public void findFullByCriteria(BuildingProjectCriteria criteria, Pageable page) {
log.debug("find full by criteria : {}, page: {}", criteria, page);
final Specification<BuildingProject> specification = createSpecification(criteria);
buildingProjectRepository
.findAll(specification, page)
.forEach(b -> log.error("{} with {} photos and {} units",
b.getId(),
b.getPhotos().size(),
b.getUnits().size()));
}
Repository with EntityGraph:
@Repository
public interface BuildingProjectRepository extends JpaRepository<BuildingProject, Long>, JpaSpecificationExecutor<BuildingProject> {
@EntityGraph(attributePaths = {"photos", "units"})
Page<BuildingProject> findAll(Specification<BuildingProject> spec, Pageable pageable);
}
application.yml:
hibernate.query.fail_on_pagination_over_collection_fetch: false
The Problem
The related entities are only loading one item each, even though there are more in the database.
Sample Data from photo table:
id, url, project_id, unit_id
1, https://straight-hepatitis.net/, 1, 1
2, https://funny-serial.net/, 1, 1
3, https://bewitched-efficiency.name/, 2, 2
4, https://partial-chili.info, 2, 2
→ At least two photos per entity
Sample Data from unit table:
id, location, price, project_id
1, finally powerfully, 5169.79, 2
2, helplessly, 19973.88, 2
3, mechanically, 9074.87, 3
→ Multiple units per project
But in logs I get:
1 with 1 photos and 0 units
2 with 1 photos and 1 units
3 with 0 photos and 1 units
4 with 0 photos and 1 units
5 with 0 photos and 1 units
→ Only loading one related entity each instead of all
What I've Tried
- Using
@EntityGraphwithattributePaths - Setting
hibernate.query.fail_on_pagination_over_collection_fetch: false - Verified that all relationships are properly mapped with
mappedBy
About project
Spring Boot: 3.2.5 Java: 17 JHipster Framework: 8.4.0 Source: https://github.com/GGlebux/Construction.git
The Question
Why are my related entities not loading completely when using EntityGraph with Specification and Pagination? How can I properly load all related entity while maintaining filtering and pagination capabilities?
I've already tried many ways. Any help or guidance would be greatly appreciated!
EntityGraph, every relationship of every entity retrieved should be either (eagerly) fetched or (lazily) not (yet) fetched, never fetched only partially. Moreover, as long as the entity is attached to the persistence context, any lazy relationships should be demand-fetched, so that you cannot observe the unfetched state.JOINs, but the paging is causing fewer rows to be considered than should be.