Skip to content

Commit f1760ce

Browse files
committed
HHH-18830 fallout from fix
improve handling of @mapkey and add tests Signed-off-by: Gavin King <[email protected]>
1 parent 4bd5223 commit f1760ce

File tree

8 files changed

+209
-21
lines changed

8 files changed

+209
-21
lines changed

hibernate-core/src/main/java/org/hibernate/boot/model/internal/MapBinder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,10 @@ protected Collection createCollection(PersistentClass owner) {
8888

8989
@Override
9090
SecondPass getSecondPass() {
91-
return new CollectionSecondPass( MapBinder.this.collection ) {
91+
return new CollectionSecondPass( collection ) {
9292
public void secondPass(java.util.Map<String, PersistentClass> persistentClasses)
9393
throws MappingException {
94+
getMap().setHasMapKeyProperty( hasMapKeyProperty );
9495
bindStarToManySecondPass( persistentClasses );
9596
bindKeyFromAssociationTable(
9697
getElementType(),

hibernate-core/src/main/java/org/hibernate/mapping/IndexedCollection.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ public final boolean isIndexed() {
4747
return true;
4848
}
4949

50+
public boolean hasMapKeyProperty() {
51+
return false;
52+
}
53+
5054
@Override
5155
public boolean isSame(Collection other) {
5256
return other instanceof IndexedCollection

hibernate-core/src/main/java/org/hibernate/mapping/Map.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
public class Map extends IndexedCollection {
2323

2424
private String mapKeyPropertyName;
25+
private boolean hasMapKeyProperty;
2526

2627
public Map(MetadataBuildingContext buildingContext, PersistentClass owner) {
2728
super( buildingContext, owner );
@@ -75,4 +76,13 @@ public void createAllKeys() throws MappingException {
7576
public Object accept(ValueVisitor visitor) {
7677
return visitor.accept(this);
7778
}
79+
80+
@Override
81+
public boolean hasMapKeyProperty() {
82+
return hasMapKeyProperty;
83+
}
84+
85+
public void setHasMapKeyProperty(boolean hasMapKeyProperty) {
86+
this.hasMapKeyProperty = hasMapKeyProperty;
87+
}
7888
}

hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -400,14 +400,14 @@ public AbstractCollectionPersister(
400400

401401
// INDEX AND ROW SELECT
402402

403-
final boolean hasIndex = collectionBootDescriptor.isIndexed();
404-
if ( hasIndex ) {
403+
if ( collectionBootDescriptor instanceof IndexedCollection indexedCollection ) {
404+
assert collectionBootDescriptor.isIndexed();
405405
// NativeSQL: collect index column and auto-aliases
406-
final IndexedCollection indexedCollection = (IndexedCollection) collectionBootDescriptor;
407-
indexType = indexedCollection.getIndex().getType();
408-
final int indexSpan = indexedCollection.getIndex().getColumnSpan();
409-
final boolean[] indexColumnInsertability = indexedCollection.getIndex().getColumnInsertability();
410-
final boolean[] indexColumnUpdatability = indexedCollection.getIndex().getColumnUpdateability();
406+
final Value index = indexedCollection.getIndex();
407+
indexType = index.getType();
408+
final int indexSpan = index.getColumnSpan();
409+
final boolean[] indexColumnInsertability = index.getColumnInsertability();
410+
final boolean[] indexColumnUpdatability = index.getColumnUpdateability();
411411
indexColumnNames = new String[indexSpan];
412412
indexFormulaTemplates = new String[indexSpan];
413413
indexFormulas = new String[indexSpan];
@@ -416,7 +416,7 @@ public AbstractCollectionPersister(
416416
indexColumnAliases = new String[indexSpan];
417417
int i = 0;
418418
boolean hasFormula = false;
419-
for ( Selectable selectable: indexedCollection.getIndex().getSelectables() ) {
419+
for ( Selectable selectable: index.getSelectables() ) {
420420
indexColumnAliases[i] = selectable.getAlias( dialect );
421421
if ( selectable.isFormula() ) {
422422
final Formula indexForm = (Formula) selectable;
@@ -428,19 +428,16 @@ public AbstractCollectionPersister(
428428
indexFormulas[i] = indexForm.getFormula();
429429
hasFormula = true;
430430
}
431-
// Treat a mapped-by index like a formula to avoid trying to set it in insert/update
432-
// Previously this was a sub-query formula, but was changed to represent the proper mapping
433-
// which enables optimizations for queries. The old insert/update code wasn't adapted yet though.
434-
// For now, this is good enough, because the formula is never used anymore,
435-
// since all read paths go through the new code that can properly handle this case
436-
else if ( indexedCollection instanceof org.hibernate.mapping.Map map
437-
&& map.getMapKeyPropertyName() != null ) {
438-
final Column indexCol = (Column) selectable;
439-
indexFormulaTemplates[i] = Template.TEMPLATE + indexCol.getQuotedName( dialect );
440-
indexFormulas[i] = indexCol.getQuotedName( dialect );
441-
hasFormula = true;
442-
}
443431
else {
432+
if ( indexedCollection.hasMapKeyProperty() ) {
433+
// If the Map key is set via @MapKey, it should not be written
434+
// since it is a reference to a field of the associated entity.
435+
// (Note that the analogous situation for Lists never arises
436+
// because @OrderBy is treated as defining a sorted bag.)
437+
indexColumnInsertability[i] = false;
438+
indexColumnUpdatability[i] = false;
439+
hasFormula = true; // this is incorrect, but needed for some reason
440+
}
444441
final Column indexCol = (Column) selectable;
445442
indexColumnNames[i] = indexCol.getQuotedName( dialect );
446443
indexColumnIsGettable[i] = true;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.collection.mapkey;
6+
7+
import jakarta.persistence.Column;
8+
import jakarta.persistence.Entity;
9+
import jakarta.persistence.Id;
10+
import jakarta.persistence.MapKey;
11+
import jakarta.persistence.OneToMany;
12+
import jakarta.validation.constraints.Size;
13+
14+
import java.util.HashMap;
15+
import java.util.Map;
16+
17+
import static jakarta.persistence.CascadeType.PERSIST;
18+
19+
@Entity
20+
class Book {
21+
@Id
22+
@Size(min=10, max = 13)
23+
String isbn;
24+
25+
@OneToMany(cascade = PERSIST,
26+
mappedBy = "isbn")
27+
@MapKey(name = "name")
28+
Map<String,Chapter> chapters;
29+
30+
Book(String isbn) {
31+
this.isbn = isbn;
32+
chapters = new HashMap<>();
33+
}
34+
35+
Book() {
36+
}
37+
}
38+
39+
@Entity
40+
class Chapter {
41+
@Id String isbn;
42+
@Id @Column(name = "chapter_name") String name;
43+
String text;
44+
45+
Chapter(String isbn, String name, String text) {
46+
this.isbn = isbn;
47+
this.name = name;
48+
this.text = text;
49+
}
50+
51+
Chapter() {
52+
}
53+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.collection.mapkey;
6+
7+
import org.hibernate.testing.jdbc.SQLStatementInspector;
8+
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
9+
import org.hibernate.testing.orm.junit.Jpa;
10+
import org.junit.jupiter.api.Test;
11+
12+
import java.util.List;
13+
14+
import static org.junit.jupiter.api.Assertions.assertEquals;
15+
16+
@Jpa(annotatedClasses = {Chapter.class, Book.class},
17+
useCollectingStatementInspector = true)
18+
public class MapKeyMappedByTest {
19+
@Test void test(EntityManagerFactoryScope scope) {
20+
scope.inTransaction( em -> {
21+
Book book = new Book( "XXX" );
22+
em.persist( book );
23+
book.chapters.put( "one", new Chapter("XXX", "one", "Lorem ipsum") );
24+
} );
25+
scope.inTransaction( em -> {
26+
Book book = em.find( Book.class, "XXX" );
27+
book.chapters.put( "two", new Chapter("XXX", "two", "Lorem ipsum") );
28+
} );
29+
List<String> queries = ((SQLStatementInspector) scope.getStatementInspector()).getSqlQueries();
30+
assertEquals( 5, queries.size() ); // we can't put in a Map without initializing it
31+
scope.inTransaction( em -> {
32+
assertEquals( em.find( Book.class, "XXX" ).chapters.size(), 2 );
33+
} );
34+
}
35+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.collection.mapkeycolumn;
6+
7+
import jakarta.persistence.Column;
8+
import jakarta.persistence.Entity;
9+
import jakarta.persistence.Id;
10+
import jakarta.persistence.MapKeyColumn;
11+
import jakarta.persistence.OneToMany;
12+
import jakarta.validation.constraints.Size;
13+
14+
import java.util.HashMap;
15+
import java.util.Map;
16+
17+
import static jakarta.persistence.CascadeType.PERSIST;
18+
19+
@Entity
20+
class Book {
21+
@Id
22+
@Size(min=10, max = 13)
23+
String isbn;
24+
25+
@OneToMany(cascade = PERSIST,
26+
mappedBy = "isbn")
27+
@MapKeyColumn(name = "chapter_name")
28+
Map<String,Chapter> chapters;
29+
30+
Book(String isbn) {
31+
this.isbn = isbn;
32+
chapters = new HashMap<>();
33+
}
34+
35+
Book() {
36+
}
37+
}
38+
39+
@Entity
40+
class Chapter {
41+
@Id String isbn;
42+
@Id @Column(name = "chapter_name") String name;
43+
String text;
44+
45+
Chapter(String isbn, String name, String text) {
46+
this.isbn = isbn;
47+
this.name = name;
48+
this.text = text;
49+
}
50+
51+
Chapter() {
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.collection.mapkeycolumn;
6+
7+
import org.hibernate.testing.jdbc.SQLStatementInspector;
8+
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
9+
import org.hibernate.testing.orm.junit.Jpa;
10+
import org.junit.jupiter.api.Test;
11+
12+
import java.util.List;
13+
14+
import static org.junit.jupiter.api.Assertions.assertEquals;
15+
16+
@Jpa(annotatedClasses = {Chapter.class, Book.class},
17+
useCollectingStatementInspector = true)
18+
public class MapKeyColumnMappedByTest {
19+
@Test void test(EntityManagerFactoryScope scope) {
20+
scope.inTransaction( em -> {
21+
Book book = new Book( "XXX" );
22+
em.persist( book );
23+
book.chapters.put( "one", new Chapter("XXX", "one", "Lorem ipsum") );
24+
} );
25+
scope.inTransaction( em -> {
26+
Book book = em.find( Book.class, "XXX" );
27+
book.chapters.put( "two", new Chapter("XXX", "two", "Lorem ipsum") );
28+
} );
29+
List<String> queries = ((SQLStatementInspector) scope.getStatementInspector()).getSqlQueries();
30+
assertEquals( 5, queries.size() ); // we can't put in a Map without initializing it
31+
scope.inTransaction( em -> {
32+
assertEquals( em.find( Book.class, "XXX" ).chapters.size(), 2 );
33+
} );
34+
}
35+
}

0 commit comments

Comments
 (0)