JPA Criteria API generates extra inner join with the use of subtypes.

Description

Hello,

We have 3 classes Case, Meeting and SpecialMeeting (zie objects below). When we use the JPA Criteria Api in the following way:

static Specification<Case> betweenEnddateSpecialMeeting(LocalDate start, LocalDate end) {
return (root, query, cb) -> {
if (start != null || end != null) {
Join<Case, SpecialMeeting> join = cb.treat(root.join(Case_.meetings), SpecialMeeting.class);
Path<LocalDate> pathEnddate = join.get(SpecialMeeting_.actuelEnddate);

return findByStartAndEndDate(start, end, cb, pathEnddate);
} else {
return null;
}
};
}
private static Predicate findByStartAndEndDate(LocalDate start, LocalDate end, CriteriaBuilder cb, Path<LocalDate> pathEnddate) {
if (start != null && end != null) {
return cb.and(cb.greaterThanOrEqualTo(pathEnddate, start),
cb.lessThanOrEqualTo(pathEnddate, end));
} else if (start != null) {
return cb.greaterThanOrEqualTo(pathEnddate, start);
} else {
return cb.lessThanOrEqualTo(pathEnddate, end);
}
}

Hibernates generates the following query:

select
case0_.id as id1_73_,
case0_.identificationcode as identifi8_73_,
case0_.version as version15_73_
from
l_cases case0_
inner join
l_meetings meeting1_
on case0_.id=meeting1_.case_id
inner join
l_meetings meeting2_
on case0_.id=meeting2_.case_id
inner join
l_special_meetings meeting2_1_
on meeting2_.id=meeting2_1_.id
where
meeting2_1_.actuel_enddate<=?

The cb.treat(root.join(Case_.meetings), SpecialMeeting.class) which we use for the subtype SpecialMeeting,
generates an extra inner join for l_meetings. This results in duplicate records.
Can someone help us to get the right query for only 1 inner join on l_meetings?

We are using the folowing versions
hibernate 5.4.20-Final
spring-boot: 2.3.3.RELEASE

Regards,
Andre Torensma

Entities used in above example.

@Slf4j
@Audited
@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "L_CASES")
@SequenceGenerator(name = "LCN", sequenceName = "L_CASE_SEQ", allocationSize = 1)
public class Case implements IdentifiableEntity {

@Id
@Column(nullable = false, precision = 10)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "LCN")
private Long id;
@Version
private Long version;
@Column(precision = 50)
private String identificationcode;
@Valid
@OneToMany(mappedBy = "case", cascade = { CascadeType.ALL })
private List<Meeting> meetings = new ArrayList<>();

@Override
public String getidentificationCode() {
return identificationcode;
}
}

@Slf4j
@Audited
@Getter
@Setter
@Entity
@NoArgsConstructor
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "TYPE", length = 2, discriminatorType = DiscriminatorType.STRING)
@Table(name = "L_MEETINGS")
@SequenceGenerator(name = "LMS", sequenceName = "L_MEETINGS_SEQ", allocationSize = 1)
public class Meeting {
@Id
@Column(nullable = false, precision = 10)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "LMS")
private Long id;
@Version
private Long version;
@Column(length = 36)
private String identificationcode;
@Column(nullable = false)
private LocalDate ldate;
@Column(name = "DATETIME_REGISTRATION", nullable = false)
private LocalDateTime datetimeRegistration;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "MEETING_ID")
private Meeting relatedMeeting;
@ManyToOne
@JoinColumn(name = "CASE_ID", nullable = false)
private Case case;

public Optional<SpecialMeeting> getSpecialMeeting() {
if (this instanceof SpecialMeeting) {
return Optional.of((SpecialMeeting) this);
}
return Optional.empty();
}
}

@Audited
@Getter
@Setter
@Entity
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Table(name = "L_SPECIAL_MEETINGS")
@DiscriminatorValue("BB")
public class SpecialMeeting extends Meeting {
@Column
private LocalDate startdate;
@Column
private LocalDate enddate;
@Column(name = "ACTUEL_ENDDATE")
private LocalDate actuelEnddate;
@Column
private String description;

}

Environment

None

Assignee

Unassigned

Reporter

Andre Torensma

Fix versions

None

Labels

None

backPortable

None

Suitable for new contributors

None

Requires Release Note

None

Pull Request

None

backportDecision

None

Components

Affects versions

Priority

Major
Configure