HHH-13877 - Make @SortNatural by default

This commit is contained in:
Nathan Xu 2020-02-11 19:16:49 -05:00 committed by Steve Ebersole
parent cf46163958
commit 7e2987ac79
7 changed files with 341 additions and 33 deletions

View File

@ -663,14 +663,6 @@ public abstract class CollectionBinder {
}
}
if ( isSortedCollection ) {
if ( ! hadExplicitSort && !hadOrderBy ) {
throw new AnnotationException(
"A sorted collection must define an ordering or sorting : " + safeCollectionRole()
);
}
}
collection.setSorted( isSortedCollection || hadExplicitSort );
if ( comparatorClass != null ) {

View File

@ -47,12 +47,15 @@ public class MapOperationTests {
entityContainingMaps.addBasicByBasic( "someKey", "someValue" );
entityContainingMaps.addBasicByBasic( "anotherKey", "anotherValue" );
entityContainingMaps.addSortedBasicByBasic( "key1", "value1" );
entityContainingMaps.addSortedBasicByBasic( "key2", "value2" );
entityContainingMaps.addSortedBasicByBasic( "key1", "value1" );
entityContainingMaps.addSortedBasicByBasicWithComparator( "kEy1", "value1" );
entityContainingMaps.addSortedBasicByBasicWithComparator( "KeY2", "value2" );
entityContainingMaps.addSortedBasicByBasicWithSortNaturalByDefault( "key2", "value2" );
entityContainingMaps.addSortedBasicByBasicWithSortNaturalByDefault( "key1", "value1" );
entityContainingMaps.addBasicByEnum( EnumValue.ONE, "one" );
entityContainingMaps.addBasicByEnum( EnumValue.TWO, "two" );
@ -164,6 +167,30 @@ public class MapOperationTests {
);
}
@Test
public void testSortedMapWithSortNaturalByDefaultAccess(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final EntityOfMaps entity = session.get( EntityOfMaps.class, 1 );
assertThat( entity.getSortedBasicByBasicWithSortNaturalByDefault(), InitializationCheckMatcher.isNotInitialized() );
// trigger the init
Hibernate.initialize( entity.getSortedBasicByBasicWithSortNaturalByDefault() );
assertThat( entity.getSortedBasicByBasicWithSortNaturalByDefault(), InitializationCheckMatcher.isInitialized() );
assertThat( entity.getSortedBasicByBasicWithSortNaturalByDefault().size(), is( 2 ) );
assertThat( entity.getBasicByEnum(), InitializationCheckMatcher.isNotInitialized() );
final Iterator<Map.Entry<String, String>> iterator = entity.getSortedBasicByBasicWithSortNaturalByDefault().entrySet().iterator();
final Map.Entry<String, String> first = iterator.next();
final Map.Entry<String, String> second = iterator.next();
assertThat( first.getKey(), is( "key1" ) );
assertThat( first.getValue(), is( "value1" ) );
assertThat( second.getKey(), is( "key2" ) );
assertThat( second.getValue(), is( "value2" ) );
}
);
}
@Test
public void testOrderedMap(SessionFactoryScope scope) {
// atm we can only check the fragment translation

View File

@ -78,7 +78,7 @@ public class PluralAttributeMappingTests {
final MappingMetamodel domainModel = scope.getSessionFactory().getDomainModel();
final EntityMappingType containerEntityDescriptor = domainModel.getEntityDescriptor( EntityOfSets.class );
assertThat( containerEntityDescriptor.getNumberOfAttributeMappings(), is( 11 ) );
assertThat( containerEntityDescriptor.getNumberOfAttributeMappings(), is( 12 ) );
final AttributeMapping setOfBasics = containerEntityDescriptor.findAttributeMapping( "setOfBasics" );
assertThat( setOfBasics, notNullValue() );
@ -86,6 +86,9 @@ public class PluralAttributeMappingTests {
final AttributeMapping sortedSetOfBasics = containerEntityDescriptor.findAttributeMapping( "sortedSetOfBasics" );
assertThat( sortedSetOfBasics, notNullValue() );
final AttributeMapping sortedSetOfBasicsWithSortNaturalByDefault = containerEntityDescriptor.findAttributeMapping( "sortedSetOfBasicsWithSortNaturalByDefault" );
assertThat( sortedSetOfBasicsWithSortNaturalByDefault, notNullValue() );
final AttributeMapping orderedSetOfBasics = containerEntityDescriptor.findAttributeMapping( "orderedSetOfBasics" );
assertThat( orderedSetOfBasics, notNullValue() );
@ -113,8 +116,7 @@ public class PluralAttributeMappingTests {
final MappingMetamodel domainModel = scope.getSessionFactory().getDomainModel();
final EntityMappingType containerEntityDescriptor = domainModel.getEntityDescriptor( EntityOfMaps.class );
// 14 for now, until entity-valued map keys is supported
assertThat( containerEntityDescriptor.getNumberOfAttributeMappings(), is( 14 ) );
assertThat( containerEntityDescriptor.getNumberOfAttributeMappings(), is( 16 ) );
final PluralAttributeMapping basicByBasic = (PluralAttributeMapping) containerEntityDescriptor.findAttributeMapping( "basicByBasic" );
assertThat( basicByBasic, notNullValue() );
@ -126,6 +128,11 @@ public class PluralAttributeMappingTests {
assertThat( sortedBasicByBasic.getKeyDescriptor(), notNullValue() );
assertThat( sortedBasicByBasic.getElementDescriptor(), notNullValue() );
final PluralAttributeMapping sortedBasicByBasicWithSortNaturalByDefault = (PluralAttributeMapping) containerEntityDescriptor.findAttributeMapping( "sortedBasicByBasicWithSortNaturalByDefault" );
assertThat( sortedBasicByBasicWithSortNaturalByDefault, notNullValue() );
assertThat( sortedBasicByBasicWithSortNaturalByDefault.getKeyDescriptor(), notNullValue() );
assertThat( sortedBasicByBasicWithSortNaturalByDefault.getElementDescriptor(), notNullValue() );
final PluralAttributeMapping basicByEnum = (PluralAttributeMapping) containerEntityDescriptor.findAttributeMapping( "basicByEnum" );
assertThat( basicByEnum, notNullValue() );
assertThat( basicByEnum.getKeyDescriptor(), notNullValue() );
@ -166,6 +173,11 @@ public class PluralAttributeMappingTests {
assertThat( sortedManyToManyByBasic.getKeyDescriptor(), notNullValue() );
assertThat( sortedManyToManyByBasic.getElementDescriptor(), notNullValue() );
final PluralAttributeMapping sortedManyToManyByBasicWithSortNaturalByDefault = (PluralAttributeMapping) containerEntityDescriptor.findAttributeMapping( "sortedManyToManyByBasicWithSortNaturalByDefault" );
assertThat( sortedManyToManyByBasicWithSortNaturalByDefault, notNullValue() );
assertThat( sortedManyToManyByBasicWithSortNaturalByDefault.getKeyDescriptor(), notNullValue() );
assertThat( sortedManyToManyByBasicWithSortNaturalByDefault.getElementDescriptor(), notNullValue() );
final PluralAttributeMapping componentByBasicOrdered = (PluralAttributeMapping) containerEntityDescriptor.findAttributeMapping( "componentByBasicOrdered" );
assertThat( componentByBasicOrdered, notNullValue() );
assertThat( componentByBasicOrdered.getKeyDescriptor(), notNullValue() );

View File

@ -6,9 +6,12 @@
*/
package org.hibernate.orm.test.metamodel.mapping.collections;
import java.util.Iterator;
import org.hibernate.Hibernate;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.testing.hamcrest.InitializationCheckMatcher;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.gambit.EntityOfSets;
import org.hibernate.testing.orm.domain.gambit.EnumValue;
@ -25,6 +28,7 @@ import org.hamcrest.collection.IsIterableContainingInOrder;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.hibernate.testing.hamcrest.CollectionMatchers.hasSize;
import static org.hibernate.testing.hamcrest.InitializationCheckMatcher.isInitialized;
import static org.hibernate.testing.hamcrest.InitializationCheckMatcher.isNotInitialized;
@ -56,6 +60,9 @@ public class SetOperationTests {
entity.addSortedBasicWithComparator( "Efg" );
entity.addSortedBasicWithComparator( "aBC" );
entity.addSortedBasicWithSortNaturalByDefault( "def" );
entity.addSortedBasicWithSortNaturalByDefault( "abc" );
entity.addEnum( EnumValue.ONE );
entity.addEnum( EnumValue.TWO );
@ -206,6 +213,28 @@ public class SetOperationTests {
);
}
@Test
public void testSortedSetWithSortNaturalByDefaultAccess(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final EntityOfSets entity = session.get( EntityOfSets.class, 1 );
assertThat( entity.getSortedSetOfBasicsWithSortNaturalByDefault(), InitializationCheckMatcher.isNotInitialized() );
// trigger the init
Hibernate.initialize( entity.getSortedSetOfBasicsWithSortNaturalByDefault() );
assertThat( entity.getSortedSetOfBasicsWithSortNaturalByDefault(), InitializationCheckMatcher.isInitialized() );
assertThat( entity.getSortedSetOfBasicsWithSortNaturalByDefault().size(), is( 2 ) );
assertThat( entity.getSetOfEnums(), InitializationCheckMatcher.isNotInitialized() );
final Iterator<String> iterator = entity.getSortedSetOfBasicsWithSortNaturalByDefault().iterator();
final String first = iterator.next();
final String second = iterator.next();
assertThat( first, is( "abc" ) );
assertThat( second, is( "def" ) );
}
);
}
@Test
public void testOrderedSet(SessionFactoryScope scope) {
// atm we can only check the fragment translation

View File

@ -0,0 +1,185 @@
package org.hibernate.orm.test.metamodel.mapping.collections;
import java.util.Arrays;
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import org.hamcrest.collection.IsIterableContainingInOrder;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* Tests Hibernate specific feature that {@link org.hibernate.annotations.SortNatural @SortNatural} will take effect implicitly
* when no <i>sort</i> or <i>order</i> related annotations exist, including:
* <ul>
* <li>{@link org.hibernate.annotations.SortNatural @SortNatural}</li>
* <li>{@link org.hibernate.annotations.SortComparator @SortComparator}</li>
* <li>{@link org.hibernate.annotations.Sort @Sort}</li>
* <li>{@link org.hibernate.annotations.OrderBy @OrderBy(from hibernate)}</li>
* <li>{@link javax.persistence.OrderBy @OrderBy(from JPA)}</li>
* </ul>
*
* @author Nathan Xu
*/
@ServiceRegistry
@DomainModel(
annotatedClasses = {
SortNaturalByDefaultTests.Person.class,
SortNaturalByDefaultTests.Phone.class
}
)
@SessionFactory
@TestForIssue( jiraKey = "HHH-13877" )
public class SortNaturalByDefaultTests {
@Test
void test(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final Person person = session.createQuery( "select p from Person p", Person.class ).getSingleResult();
final SortedSet<Phone> phones = person.getPhones();
assertThat( phones, IsIterableContainingInOrder.contains(
new Phone( "123-456-789" ),
new Phone( "234-567-891" ),
new Phone( "345-678-912" ),
new Phone( "456-789-123" ),
new Phone( "567-891-234" ),
new Phone( "678-912-345" ),
new Phone( "789-123-456" ),
new Phone( "891-234-567" ),
new Phone( "912-345-678" ) )
);
}
);
}
@BeforeEach
void createTestData(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final Person person = new Person();
final SortedSet<Phone> phones = new TreeSet<>(
Arrays.asList(
new Phone( "678-912-345" ),
new Phone( "234-567-891" ),
new Phone( "567-891-234" ),
new Phone( "456-789-123" ),
new Phone( "123-456-789" ),
new Phone( "912-345-678" ),
new Phone( "789-123-456" ),
new Phone( "345-678-912" ),
new Phone( "891-234-567" )
)
);
person.setPhones( phones );
session.persist( person );
}
);
}
@AfterEach
void cleanUpTestData(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "delete Person" ).executeUpdate();
}
);
}
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
@OneToMany(cascade = CascadeType.ALL)
private SortedSet<Phone> phones = new TreeSet<>(); // no '@SortNatural', '@SortComparator' or '@OrderBy' annotation present
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public SortedSet<Phone> getPhones() {
return phones;
}
public void setPhones(SortedSet<Phone> phones) {
this.phones = phones;
}
}
@Entity(name = "Phone")
public static class Phone implements Comparable<Phone> {
@Id
@GeneratedValue
private Long id;
private String number;
public Phone() {
}
public Phone(String number) {
this.number = number;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
@Override
public int compareTo(Phone o) {
return number.compareTo( o.getNumber() );
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Phone phone = (Phone) o;
return Objects.equals( number, phone.number );
}
@Override
public int hashCode() {
return Objects.hash( number );
}
}
}

View File

@ -37,6 +37,10 @@ public class EntityOfMaps {
private Map<String, String> basicByBasic;
private SortedMap<String, String> sortedBasicByBasic;
private SortedMap<String, String> sortedBasicByBasicWithComparator;
private SortedMap<String, String> sortedBasicByBasicWithSortNaturalByDefault;
private Map<EnumValue, String> basicByEnum;
private Map<EnumValue, String> basicByConvertedEnum;
@ -49,10 +53,9 @@ public class EntityOfMaps {
private Map<String, SimpleEntity> manyToManyByBasic;
private Map<String, SimpleComponent> componentByBasicOrdered;
private SortedMap<String, String> sortedBasicByBasic;
private SortedMap<String, String> sortedBasicByBasicWithComparator;
private SortedMap<String, SimpleEntity> sortedManyToManyByBasic;
private SortedMap<String, SimpleEntity> sortedManyToManyByBasicWithComparator;
private SortedMap<String, SimpleEntity> sortedManyToManyByBasicWithSortNaturalByDefault;
public EntityOfMaps() {
}
@ -139,6 +142,25 @@ public class EntityOfMaps {
sortedBasicByBasicWithComparator.put( key, val );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// sortedBasicByBasicWithSortNaturalByDefault
@ElementCollection
public SortedMap<String, String> getSortedBasicByBasicWithSortNaturalByDefault() {
return sortedBasicByBasicWithSortNaturalByDefault;
}
public void setSortedBasicByBasicWithSortNaturalByDefault(SortedMap<String, String> sortedBasicByBasicWithSortNaturalByDefault) {
this.sortedBasicByBasicWithSortNaturalByDefault = sortedBasicByBasicWithSortNaturalByDefault;
}
public void addSortedBasicByBasicWithSortNaturalByDefault(String key, String val) {
if ( sortedBasicByBasicWithSortNaturalByDefault == null ) {
sortedBasicByBasicWithSortNaturalByDefault = new TreeMap<>();
}
sortedBasicByBasicWithSortNaturalByDefault.put( key, val );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// basicByEnum
@ -281,6 +303,30 @@ public class EntityOfMaps {
manyToManyByBasic.put( key, value );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// componentByBasicOrdered
// NOTE : effectively the same as a natural-sorted map in terms of reading
@ElementCollection
@MapKeyColumn( name = "ordered_component_key")
@OrderBy( clause = "ordered_component_key, ordered_component_key" )
public Map<String, SimpleComponent> getComponentByBasicOrdered() {
return componentByBasicOrdered;
}
public void setComponentByBasicOrdered(Map<String, SimpleComponent> componentByBasicOrdered) {
this.componentByBasicOrdered = componentByBasicOrdered;
}
public void addComponentByBasicOrdered(String key, SimpleComponent value) {
if ( componentByBasicOrdered == null ) {
componentByBasicOrdered = new LinkedHashMap<>();
}
componentByBasicOrdered.put( key, value );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// sortedManyToManyByBasic
@ -307,33 +353,30 @@ public class EntityOfMaps {
this.sortedManyToManyByBasicWithComparator = sortedManyToManyByBasicWithComparator;
}
public void addSortedManyToManyByComponent(String key, SimpleEntity value) {
if ( sortedManyToManyByBasic == null ) {
sortedManyToManyByBasic = new TreeMap<>();
public void addSortedManyToManyByBasicWithComparator(String key, SimpleEntity value) {
if ( sortedManyToManyByBasicWithComparator == null ) {
sortedManyToManyByBasicWithComparator = new TreeMap<>();
}
sortedManyToManyByBasic.put( key, value );
sortedManyToManyByBasicWithComparator.put( key, value );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// componentByBasicOrdered
// sortedManyToManyByBasicWithSortNaturalByDefault
// NOTE : effectively the same as a natural-sorted map in terms of reading
@ElementCollection
@MapKeyColumn( name = "ordered_component_key")
@OrderBy( clause = "ordered_component_key, ordered_component_key" )
public Map<String, SimpleComponent> getComponentByBasicOrdered() {
return componentByBasicOrdered;
@ManyToMany
public SortedMap<String, SimpleEntity> getSortedManyToManyByBasicWithSortNaturalByDefault() {
return sortedManyToManyByBasicWithSortNaturalByDefault;
}
public void setComponentByBasicOrdered(Map<String, SimpleComponent> componentByBasicOrdered) {
this.componentByBasicOrdered = componentByBasicOrdered;
public void setSortedManyToManyByBasicWithSortNaturalByDefault(SortedMap<String, SimpleEntity> sortedManyToManyByBasicWithSortNaturalByDefault) {
this.sortedManyToManyByBasicWithSortNaturalByDefault = sortedManyToManyByBasicWithSortNaturalByDefault;
}
public void addComponentByBasicOrdered(String key, SimpleComponent value) {
if ( componentByBasicOrdered == null ) {
componentByBasicOrdered = new LinkedHashMap<>();
public void addSortedManyToManyByBasicWithSortNaturalByDefault(String key, SimpleEntity value) {
if ( sortedManyToManyByBasicWithSortNaturalByDefault == null ) {
sortedManyToManyByBasicWithSortNaturalByDefault = new TreeMap<>();
}
componentByBasicOrdered.put( key, value );
sortedManyToManyByBasicWithSortNaturalByDefault.put( key, value );
}
}

View File

@ -40,7 +40,7 @@ public class EntityOfSets {
private Set<String> setOfBasics;
private SortedSet<String> sortedSetOfBasics;
private SortedSet<String> sortedSetOfBasicsWithComparator;
private SortedSet<String> sortedSetOfBasicsWithSortNaturalByDefault;
private Set<String> orderedSetOfBasics;
private Set<EnumValue> setOfEnums;
@ -164,6 +164,26 @@ public class EntityOfSets {
sortedSetOfBasicsWithComparator.add( value );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// sortedSetOfBasicsWithSortNaturalByDefault
@ElementCollection()
@CollectionTable( name = "EntityOfSet_sortedBasicsWithSortNaturalByDefault")
public SortedSet<String> getSortedSetOfBasicsWithSortNaturalByDefault() {
return sortedSetOfBasicsWithSortNaturalByDefault;
}
public void setSortedSetOfBasicsWithSortNaturalByDefault(SortedSet<String> sortedSetOfBasicsWithSortNaturalByDefault) {
this.sortedSetOfBasicsWithSortNaturalByDefault = sortedSetOfBasicsWithSortNaturalByDefault;
}
public void addSortedBasicWithSortNaturalByDefault(String value) {
if ( sortedSetOfBasicsWithSortNaturalByDefault == null ) {
sortedSetOfBasicsWithSortNaturalByDefault = new TreeSet<>();
}
sortedSetOfBasicsWithSortNaturalByDefault.add( value );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// setOfConvertedEnums