Skip to content

Commit 8ea2890

Browse files
author
tcordel
committed
HHH-19872: CriteriaUpdate for generic fields
1 parent 1620d49 commit 8ea2890

File tree

2 files changed

+173
-2
lines changed

2 files changed

+173
-2
lines changed

hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.hibernate.metamodel.model.domain.DomainType;
1212
import org.hibernate.metamodel.model.domain.EmbeddableDomainType;
1313
import org.hibernate.metamodel.model.domain.ManagedDomainType;
14+
import org.hibernate.metamodel.model.domain.MappedSuperclassDomainType;
1415
import org.hibernate.query.sqm.SqmBindableType;
1516
import org.hibernate.query.sqm.tuple.TupleType;
1617
import org.hibernate.metamodel.model.domain.internal.EntityDiscriminatorSqmPathSource;
@@ -331,10 +332,13 @@ private static boolean isTypeAssignable(
331332
}
332333

333334
// entities can be assigned if they belong to the same inheritance hierarchy
334-
335335
if ( targetType instanceof EntityType<?> targetEntity
336336
&& expressionType instanceof EntityType<?> expressionEntity ) {
337-
return isEntityTypeAssignable( targetEntity, expressionEntity, bindingContext);
337+
return isEntityTypeAssignable( targetEntity, expressionEntity, bindingContext );
338+
}
339+
if ( targetType instanceof MappedSuperclassDomainType<?> expressionMappedSuperclass
340+
&& expressionType instanceof EntityType<?> expressionEntity ) {
341+
return isMappedSuperclassTypeAssignable( expressionMappedSuperclass, expressionEntity, bindingContext );
338342
}
339343

340344
// Treat the expression as assignable to the target path if they belong
@@ -394,6 +398,23 @@ private static Class<?> canonicalize(Class<?> lhs) {
394398
};
395399
}
396400

401+
private static boolean isMappedSuperclassTypeAssignable(
402+
MappedSuperclassDomainType<?> lhsType,
403+
EntityType<?> rhsType,
404+
BindingContext bindingContext) {
405+
406+
for ( ManagedDomainType<?> candidate : lhsType.getSubTypes() ) {
407+
if ( candidate instanceof EntityType<?> candidateEntityType
408+
&& isEntityTypeAssignable( candidateEntityType, rhsType, bindingContext ) ) {
409+
return true;
410+
}
411+
else if ( candidate instanceof MappedSuperclassDomainType<?> candidateMappedSuperclass
412+
&& isMappedSuperclassTypeAssignable( candidateMappedSuperclass, rhsType, bindingContext ) ) {
413+
return true;
414+
}
415+
}
416+
return false;
417+
}
397418
private static boolean isEntityTypeAssignable(
398419
EntityType<?> lhsType, EntityType<?> rhsType,
399420
BindingContext bindingContext) {
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.annotations.generics;
6+
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.GeneratedValue;
9+
import jakarta.persistence.Id;
10+
import jakarta.persistence.JoinColumn;
11+
import jakarta.persistence.ManyToOne;
12+
import jakarta.persistence.MappedSuperclass;
13+
import jakarta.persistence.criteria.CriteriaBuilder;
14+
import jakarta.persistence.criteria.CriteriaUpdate;
15+
import jakarta.persistence.criteria.Root;
16+
import org.hibernate.testing.orm.junit.DomainModel;
17+
import org.hibernate.testing.orm.junit.JiraKey;
18+
import org.hibernate.testing.orm.junit.SessionFactory;
19+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
20+
import org.hibernate.testing.orm.junit.SessionFactoryScopeAware;
21+
import org.junit.jupiter.api.AfterAll;
22+
import org.junit.jupiter.params.ParameterizedTest;
23+
import org.junit.jupiter.params.provider.Arguments;
24+
import org.junit.jupiter.params.provider.MethodSource;
25+
26+
import java.util.Objects;
27+
import java.util.function.Consumer;
28+
import java.util.stream.Stream;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
32+
@DomainModel(
33+
annotatedClasses = {
34+
GenericMappedSuperclassPropertyUpdateTest.CommonEntity.class,
35+
GenericMappedSuperclassPropertyUpdateTest.SpecificEntity.class
36+
}
37+
)
38+
@SessionFactory
39+
@JiraKey(value = "HHH-19872")
40+
public class GenericMappedSuperclassPropertyUpdateTest implements SessionFactoryScopeAware {
41+
private SessionFactoryScope scope;
42+
43+
@AfterAll
44+
public void tearDown(SessionFactoryScope scope) {
45+
scope.getSessionFactory().getSchemaManager().truncate();
46+
}
47+
48+
@ParameterizedTest
49+
@MethodSource("criteriaUpdateFieldSetters")
50+
void testGenericHierarchy(Consumer<UpdateContext> updater) {
51+
scope.inTransaction( session -> {
52+
SpecificEntity relative = new SpecificEntity();
53+
SpecificEntity base = new SpecificEntity();
54+
session.persist( relative );
55+
session.persist( base );
56+
57+
final CriteriaBuilder cb = session.getCriteriaBuilder();
58+
CriteriaUpdate<SpecificEntity> criteriaUpdate = cb.createCriteriaUpdate( SpecificEntity.class );
59+
Root<SpecificEntity> root = criteriaUpdate.from( SpecificEntity.class );
60+
61+
updater.accept( new UpdateContext( criteriaUpdate, root, base.getId(), relative ) );
62+
criteriaUpdate.where( cb.equal( root.get( GenericMappedSuperclassPropertyUpdateTest_.SpecificEntity_.id ), base.getId() ) );
63+
64+
int updates = session.createQuery( criteriaUpdate ).executeUpdate();
65+
session.refresh( base );
66+
67+
assertThat( updates )
68+
.isEqualTo( 1L );
69+
assertThat( base.getRelative() )
70+
.isEqualTo( relative );
71+
} );
72+
73+
}
74+
75+
76+
static class UpdateContext {
77+
final CriteriaUpdate<SpecificEntity> query;
78+
final Root<SpecificEntity> root;
79+
final Long id;
80+
final SpecificEntity relative;
81+
82+
UpdateContext(CriteriaUpdate<SpecificEntity> query, Root<SpecificEntity> root, Long id, SpecificEntity relative) {
83+
this.query = query;
84+
this.root = root;
85+
this.id = id;
86+
this.relative = relative;
87+
}
88+
}
89+
90+
static Stream<Arguments> criteriaUpdateFieldSetters() {
91+
Consumer<UpdateContext> updateUsingPath = context ->
92+
context.query.set( context.root.get( GenericMappedSuperclassPropertyUpdateTest_.SpecificEntity_.relative ), context.relative );
93+
Consumer<UpdateContext> updateUsingSingularAttribute = context ->
94+
context.query.set( GenericMappedSuperclassPropertyUpdateTest_.SpecificEntity_.relative, context.relative );
95+
Consumer<UpdateContext> updateUsingName = context ->
96+
context.query.set( GenericMappedSuperclassPropertyUpdateTest_.SpecificEntity_.RELATIVE, context.relative );
97+
return Stream.of(
98+
Arguments.of( updateUsingPath ),
99+
Arguments.of( updateUsingSingularAttribute ),
100+
Arguments.of( updateUsingName )
101+
);
102+
}
103+
104+
@Override
105+
public void injectSessionFactoryScope(SessionFactoryScope scope) {
106+
this.scope = scope;
107+
}
108+
109+
@MappedSuperclass
110+
public abstract static class CommonEntity<E extends CommonEntity<?>> {
111+
112+
@Id
113+
@GeneratedValue
114+
private Long id;
115+
116+
Long getId() {
117+
return id;
118+
}
119+
120+
@ManyToOne
121+
@JoinColumn
122+
private E relative;
123+
124+
void setRelative(E relative) {
125+
this.relative = relative;
126+
}
127+
128+
E getRelative() {
129+
return relative;
130+
}
131+
132+
@Override
133+
public boolean equals(Object o) {
134+
if ( o == null || getClass() != o.getClass() ) {
135+
return false;
136+
}
137+
CommonEntity<?> that = (CommonEntity<?>) o;
138+
return Objects.equals( id, that.id ) && Objects.equals( relative, that.relative );
139+
}
140+
141+
@Override
142+
public int hashCode() {
143+
return Objects.hash( id, relative );
144+
}
145+
}
146+
147+
@Entity(name = "SpecificEntity")
148+
public static class SpecificEntity extends CommonEntity<SpecificEntity> {
149+
}
150+
}

0 commit comments

Comments
 (0)