Skip to content

Commit 66a723e

Browse files
committed
HHH-17623 Test and fix use of association in @orderby
1 parent 7f12b50 commit 66a723e

File tree

3 files changed

+221
-32
lines changed

3 files changed

+221
-32
lines changed

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDomainPath.java

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
1313
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
1414
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
15+
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
1516
import org.hibernate.metamodel.mapping.ModelPart;
1617
import org.hibernate.metamodel.mapping.SelectableMapping;
1718
import org.hibernate.metamodel.mapping.ordering.ast.DomainPath;
@@ -161,19 +162,8 @@ else if ( referenceModelPart instanceof EntityValuedModelPart ) {
161162
subPart = ( (EntityValuedModelPart) referenceModelPart ).getEntityMappingType().getIdentifierMapping();
162163
}
163164
else {
164-
subPart = ( (EntityValuedModelPart) referenceModelPart ).findSubPart( modelPartName );
165-
if ( subPart == null && referenceModelPart instanceof ToOneAttributeMapping ) {
166-
// this is the case of sort by to-one attribute inside an embedded item,
167-
// at this stage the foreign key descriptor should have been set on the attribute mapping,
168-
// so we can generate a sub part valid for the order-by generation
169-
ToOneAttributeMapping toOneAttribute = (ToOneAttributeMapping) referenceModelPart;
170-
String foreignKeyModelPart = toOneAttribute.getAttributeName() + "."
171-
+ toOneAttribute.getTargetKeyPropertyName();
172-
173-
if ( modelPartName.equals( foreignKeyModelPart ) ) {
174-
subPart = toOneAttribute.findSubPart( toOneAttribute.getTargetKeyPropertyName() );
175-
}
176-
}
165+
// Default to using the foreign key of an entity valued model part
166+
subPart = ( (EntityValuedModelPart) referenceModelPart ).findSubPart( ForeignKeyDescriptor.PART_NAME );
177167
}
178168
apply(
179169
subPart,

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/FkDomainPathContinuation.java

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,18 @@ public class FkDomainPathContinuation extends DomainPathContinuation {
2121
private final Set<String> possiblePaths;
2222

2323
public FkDomainPathContinuation(
24-
NavigablePath navigablePath, DomainPath lhs,
24+
NavigablePath navigablePath,
25+
DomainPath lhs,
2526
ToOneAttributeMapping referencedModelPart) {
2627
super( navigablePath, lhs, referencedModelPart );
2728
this.possiblePaths = referencedModelPart.getTargetKeyPropertyNames();
2829
}
2930

3031
public FkDomainPathContinuation(
31-
NavigablePath navigablePath, DomainPath lhs,
32-
ModelPart referencedModelPart, Set<String> possiblePaths) {
32+
NavigablePath navigablePath,
33+
DomainPath lhs,
34+
ModelPart referencedModelPart,
35+
Set<String> possiblePaths) {
3336
super( navigablePath, lhs, referencedModelPart );
3437
this.possiblePaths = possiblePaths;
3538
}
@@ -40,25 +43,17 @@ public SequencePart resolvePathPart(
4043
String identifier,
4144
boolean isTerminal,
4245
TranslationContext translationContext) {
43-
HashSet<String> furtherPaths = new LinkedHashSet<>( possiblePaths.size() );
44-
for ( String path : possiblePaths ) {
45-
if ( !path.startsWith( name ) ) {
46-
return new DomainPathContinuation(
47-
navigablePath.append( name ),
48-
this,
49-
// unfortunately at this stage the foreign key descriptor could not be set
50-
// on the attribute mapping yet, so we need to defer the sub part extraction later
51-
referencedModelPart
52-
);
53-
}
54-
55-
furtherPaths.add( path.substring( name.length() + 1 ) );
56-
}
57-
58-
if ( furtherPaths.isEmpty() ) {
46+
if ( !possiblePaths.contains( name ) ) {
5947
throw new PathResolutionException( "Domain path of type `" + referencedModelPart.getPartMappingType() + "` -> `" + name + "`" );
6048
}
6149

50+
final HashSet<String> furtherPaths = new HashSet<>();
51+
for ( String possiblePath : possiblePaths ) {
52+
if ( possiblePath.startsWith( name ) && possiblePath.length() > name.length()
53+
&& possiblePath.charAt( name.length() ) == '.' ) {
54+
furtherPaths.add( possiblePath.substring( name.length() + 2 ) );
55+
}
56+
}
6257
return new FkDomainPathContinuation(
6358
navigablePath.append( name ),
6459
this,
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
package org.hibernate.orm.test.orderby;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import org.hibernate.testing.orm.junit.DomainModel;
7+
import org.hibernate.testing.orm.junit.JiraKey;
8+
import org.hibernate.testing.orm.junit.SessionFactory;
9+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
10+
import org.junit.jupiter.api.AfterEach;
11+
import org.junit.jupiter.api.Assertions;
12+
import org.junit.jupiter.api.BeforeEach;
13+
import org.junit.jupiter.api.Test;
14+
15+
import jakarta.persistence.CascadeType;
16+
import jakarta.persistence.Entity;
17+
import jakarta.persistence.FetchType;
18+
import jakarta.persistence.GeneratedValue;
19+
import jakarta.persistence.Id;
20+
import jakarta.persistence.JoinColumn;
21+
import jakarta.persistence.ManyToOne;
22+
import jakarta.persistence.OneToMany;
23+
import jakarta.persistence.OrderBy;
24+
25+
import static org.junit.jupiter.api.Assertions.assertEquals;
26+
27+
/**
28+
* @author Christian Beikov
29+
*/
30+
@DomainModel(
31+
annotatedClasses = {
32+
OrderByToOneTest.Task.class,
33+
OrderByToOneTest.TaskVersion.class,
34+
OrderByToOneTest.User.class
35+
}
36+
)
37+
@SessionFactory
38+
public class OrderByToOneTest {
39+
40+
@BeforeEach
41+
protected void prepareTest(SessionFactoryScope scope) {
42+
scope.inTransaction(
43+
session -> {
44+
User u1 = new User( 1L, "u1" );
45+
session.persist( u1 );
46+
User u2 = new User( 2L, "u2" );
47+
session.persist( u2 );
48+
49+
Task t = new Task();
50+
t.setId( 1L );
51+
TaskVersion tv1 = new TaskVersion();
52+
tv1.setName( "tv1" );
53+
tv1.setAssignee( u2 );
54+
List<TaskVersion> versions = new ArrayList<>();
55+
versions.add( tv1 );
56+
t.setTaskVersions( versions );
57+
tv1.setTask( t );
58+
59+
TaskVersion tv2 = new TaskVersion();
60+
tv2.setName( "tv2" );
61+
tv2.setAssignee( u1 );
62+
t.getTaskVersions().add( tv2 );
63+
tv2.setTask( t );
64+
session.persist( t );
65+
}
66+
);
67+
}
68+
69+
@AfterEach
70+
protected void cleanupTest(SessionFactoryScope scope) {
71+
scope.inTransaction(
72+
session -> {
73+
session.createMutationQuery( "delete from TaskVersion" ).executeUpdate();
74+
session.createMutationQuery( "delete from UUser" ).executeUpdate();
75+
session.createMutationQuery( "delete from Task" ).executeUpdate();
76+
}
77+
);
78+
}
79+
80+
@Test
81+
@JiraKey("HHH-17623")
82+
public void testOrderByToOne(SessionFactoryScope scope) {
83+
scope.inTransaction(
84+
session -> {
85+
final Task task = session.createQuery( "from Task t", Task.class ).getSingleResult();
86+
final List<TaskVersion> taskVersions = task.getTaskVersions();
87+
assertEquals( 2, taskVersions.size() );
88+
assertEquals( "tv2", taskVersions.get( 0 ).getName() );
89+
assertEquals( "tv1", taskVersions.get( 1 ).getName() );
90+
}
91+
);
92+
}
93+
94+
@Entity(name = "Task")
95+
public static class Task {
96+
97+
@Id
98+
private Long id;
99+
100+
@OneToMany(mappedBy = "task", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
101+
@OrderBy("assignee ASC")
102+
private List<TaskVersion> taskVersions;
103+
104+
public Long getId() {
105+
return id;
106+
}
107+
108+
public void setId(Long id) {
109+
this.id = id;
110+
}
111+
112+
public List<TaskVersion> getTaskVersions() {
113+
return taskVersions;
114+
}
115+
116+
public void setTaskVersions(List<TaskVersion> taskVersions) {
117+
this.taskVersions = taskVersions;
118+
}
119+
}
120+
121+
@Entity(name = "TaskVersion")
122+
public static class TaskVersion {
123+
124+
@Id
125+
@GeneratedValue
126+
private Long id;
127+
128+
@ManyToOne(fetch = FetchType.EAGER)
129+
@JoinColumn(name = "assignee", nullable = true)
130+
private User assignee;
131+
132+
@ManyToOne(fetch = FetchType.EAGER)
133+
@JoinColumn(name = "task_id", nullable = false)
134+
private Task task;
135+
136+
private String name;
137+
138+
public String getName() {
139+
return name;
140+
}
141+
142+
public void setName(String name) {
143+
this.name = name;
144+
}
145+
146+
public Task getTask() {
147+
return task;
148+
}
149+
150+
public void setTask(Task task) {
151+
this.task = task;
152+
}
153+
154+
public User getAssignee() {
155+
return assignee;
156+
}
157+
158+
public void setAssignee(User assignee) {
159+
this.assignee = assignee;
160+
}
161+
162+
public Long getId() {
163+
return id;
164+
}
165+
166+
public void setId(Long id) {
167+
this.id = id;
168+
}
169+
}
170+
171+
@Entity(name = "UUser")
172+
public static class User {
173+
174+
@Id
175+
private Long id;
176+
177+
private String name;
178+
179+
public User() {
180+
}
181+
182+
public User(Long id, String name) {
183+
this.id = id;
184+
this.name = name;
185+
}
186+
187+
public Long getId() {
188+
return id;
189+
}
190+
191+
public void setId(Long id) {
192+
this.id = id;
193+
}
194+
195+
public String getName() {
196+
return name;
197+
}
198+
199+
public void setName(String name) {
200+
this.name = name;
201+
}
202+
}
203+
204+
}

0 commit comments

Comments
 (0)