Fix @ElementCollection + @OrderBy with Inheritance

This commit is contained in:
Andrea Boriero 2020-07-28 18:32:34 +01:00
parent b9612247f5
commit 8db9709408
13 changed files with 512 additions and 385 deletions

View File

@ -211,11 +211,6 @@ public abstract class AbstractCompositeIdentifierMapping
return getEntityMapping().getRepresentationStrategy().getInstantiator().instantiate( sessionFactory );
}
@Override
public SingularAttributeMapping getParentInjectionAttributeMapping() {
return null;
}
@Override
public EntityMappingType findContainingEntityMapping() {
return entityMapping;

View File

@ -13,6 +13,7 @@ import org.hibernate.engine.FetchTiming;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
@ -168,6 +169,12 @@ public class EntityCollectionPart
throw new NotYetImplementedFor6Exception( getClass() );
}
@Override
public void visitColumns(ColumnConsumer consumer) {
entityMappingType.visitColumns( consumer );
}
@Override
public void applySqlSelections(
NavigablePath navigablePath,

View File

@ -7,18 +7,24 @@
package org.hibernate.metamodel.mapping.ordering.ast;
import org.hibernate.SortOrder;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.TranslationContext;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SortSpecification;
/**
* Represents a column-reference used in an order-by fragment
*
* @author Steve Ebersole
* @apiNote This is Hibernate-specific feature. For {@link javax.persistence.OrderBy} (JPA)
* all path references are expected to be domain paths (attributes).
*
@ -58,16 +64,18 @@ public class ColumnReference implements OrderingExpression, SequencePart {
String collation,
SortOrder sortOrder,
SqlAstCreationState creationState) {
final TableReference primaryTableReference = tableGroup.getPrimaryTableReference();
TableReference tableReference;
tableReference = getTableReference( tableGroup );
final SqlExpressionResolver sqlExpressionResolver = creationState.getSqlExpressionResolver();
ast.addSortSpecification(
new SortSpecification(
sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( primaryTableReference, columnExpression ),
SqlExpressionResolver.createColumnReferenceKey( tableReference, columnExpression ),
sqlAstProcessingState -> new org.hibernate.sql.ast.tree.expression.ColumnReference(
tableGroup.getPrimaryTableReference(),
tableReference,
columnExpression,
isColumnExpressionFormula,
// because these ordering fragments are only ever part of the order-by clause, there
@ -81,4 +89,29 @@ public class ColumnReference implements OrderingExpression, SequencePart {
)
);
}
TableReference getTableReference(TableGroup tableGroup) {
ModelPartContainer modelPart = tableGroup.getModelPart();
if ( modelPart instanceof PluralAttributeMapping ) {
MappingType partMappingType = ( (PluralAttributeMapping) modelPart ).getElementDescriptor()
.getPartMappingType();
if ( partMappingType instanceof AbstractEntityPersister ) {
AbstractEntityPersister abstractEntityPersister = (AbstractEntityPersister) partMappingType;
int i = abstractEntityPersister.determineTableNumberForColumn( columnExpression );
String tableName = abstractEntityPersister.getTableName( i );
for ( TableReferenceJoin tableReferenceJoin : tableGroup.getTableReferenceJoins() ) {
final TableReference joinedTableReference = tableReferenceJoin.getJoinedTableReference();
if ( joinedTableReference.getTableExpression()
.equals( tableName ) ) {
return joinedTableReference;
}
}
}
else {
return tableGroup.getPrimaryTableReference();
}
}
return null;
}
}

View File

@ -52,9 +52,22 @@ public class PluralAttributePath implements DomainPath {
final ModelPart subPart = pluralAttributeMapping.findSubPart( name, null );
if ( subPart != null ) {
assert subPart instanceof CollectionPart;
if ( subPart instanceof CollectionPart ) {
return new CollectionPartPath( this, (CollectionPart) subPart );
}
else if ( !( subPart instanceof EmbeddableValuedModelPart ) ) {
final CollectionPartPath elementPath = new CollectionPartPath(
this,
pluralAttributeMapping.getElementDescriptor()
);
return new DomainPathContinuation(
elementPath.getNavigablePath().append( name ),
this,
pluralAttributeMapping.getElementDescriptor()
);
}
}
// the above checks for explicit element or index descriptor references
// try also as an implicit element or index sub-part reference...

View File

@ -133,6 +133,7 @@ import org.hibernate.metamodel.RepresentationMode;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.AttributeMetadata;
import org.hibernate.metamodel.mapping.AttributeMetadataAccess;
import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
@ -6441,6 +6442,13 @@ public abstract class AbstractEntityPersister
}
}
@Override
public void visitColumns(ColumnConsumer consumer) {
getAttributeMappings().forEach(
attributeMapping -> attributeMapping.visitColumns( consumer )
);
}
@Override
public void visitSubTypeAttributeMappings(Consumer<AttributeMapping> action) {
if ( subclassMappingTypes != null ) {

View File

@ -1291,8 +1291,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
final BasicType discriminatorType = (BasicType) getDiscriminatorType();
final CaseSearchedExpression caseSearchedExpression = new CaseSearchedExpression( discriminatorType );
discriminatorValuesByTableName.forEach(
(tableName, discriminatorValue) -> {
Boolean addPrimaryTableCaseAsLastCaseExpression = false;
for ( String tableName : discriminatorValuesByTableName.keySet() ) {
if ( !primaryTableReference.getTableExpression().equals( tableName ) ) {
TableReference tableReference = entityTableGroup.getTableReference( tableName );
if ( tableReference == null ) {
@ -1310,15 +1310,19 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
discriminatorType
);
}
else {
addPrimaryTableCaseAsLastCaseExpression = true;
}
}
);
if ( addPrimaryTableCaseAsLastCaseExpression ) {
addWhen(
caseSearchedExpression,
primaryTableReference,
getIdentifierColumnReference( primaryTableReference ),
discriminatorType
);
}
info.caseSearchedExpression = caseSearchedExpression;
return info;

View File

@ -93,14 +93,11 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
@Override
public void resolveKey(RowProcessingState rowProcessingState) {
// todo (6.0) : register "parent resolution listener" if the composite is defined for `@Parent`
// something like:
final PropertyAccess parentInjectionPropertyAccess = embeddedModelPartDescriptor.getParentInjectionAttributePropertyAccess();
if ( parentInjectionPropertyAccess != null ) {
if ( getFetchParentAccess() != null ) {
getFetchParentAccess().findFirstEntityDescriptorAccess().registerResolutionListener(
final FetchParentAccess fetchParentAccess = getFetchParentAccess();
if ( parentInjectionPropertyAccess != null && fetchParentAccess != null ) {
fetchParentAccess.findFirstEntityDescriptorAccess().registerResolutionListener(
// todo (6.0) : this is the legacy behavior
// - the first entity is injected as the parent, even if the composite
// is defined on another composite
@ -117,7 +114,6 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
);
}
}
}
@Override
public void resolveInstance(RowProcessingState rowProcessingState) {
@ -137,7 +133,7 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
final PropertyAccess parentInjectionPropertyAccess = embeddedModelPartDescriptor.getParentInjectionAttributePropertyAccess();
if ( parentInjectionPropertyAccess != null && getFetchParentAccess() == null ) {
if ( parentInjectionPropertyAccess != null ) {
Initializer initializer = rowProcessingState.resolveInitializer( navigablePath.getParent() );
final Object owner;
if ( initializer instanceof CollectionInitializer ) {

View File

@ -12,7 +12,6 @@ import java.util.List;
import java.util.Locale;
import org.hibernate.Filter;
import org.hibernate.Transaction;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.mapping.Collection;
@ -65,12 +64,9 @@ public class DefaultNamingCollectionElementTest {
scope.getMetadataImplementor().getCollectionBinding( Boy.class.getName() + '.' + "favoriteNumbers" )
.getCollectionTable().getName()
);
scope.inSession(
session -> {
Transaction transaction = session.getTransaction();
try {
transaction.begin();
scope.inTransaction(
session -> {
Boy boy = new Boy();
boy.setFirstName( "John" );
boy.setLastName( "Doe" );
@ -92,10 +88,12 @@ public class DefaultNamingCollectionElementTest {
foods.put( "dinner", FavoriteFood.SUSHI );
boy.setFavoriteFood( foods );
session.persist( boy );
transaction.commit();
session.getTransaction().commit();
session.clear();
transaction = session.beginTransaction();
session.beginTransaction();
boy = session.get( Boy.class, boy.getId() );
assertNotNull( boy.getNickNames() );
assertTrue( boy.getNickNames().contains( "Thing" ) );
@ -113,26 +111,14 @@ public class DefaultNamingCollectionElementTest {
.setParameter( "name", "Thing" ).list();
assertEquals( 1, result.size() );
session.delete( boy );
transaction.commit();
}
finally {
if ( transaction.isActive() ) {
transaction.rollback();
}
}
}
);
}
@Test
public void testCompositeElement(SessionFactoryScope scope) {
scope.inSession(
scope.inTransaction(
session -> {
Transaction transaction = session.getTransaction();
try {
transaction.begin();
Boy boy = new Boy();
boy.setFirstName( "John" );
boy.setLastName( "Doe" );
@ -143,11 +129,11 @@ public class DefaultNamingCollectionElementTest {
toy.getBrand().setName( "Bandai" );
boy.getFavoriteToys().add( toy );
session.persist( boy );
transaction.commit();
session.getTransaction().commit();
session.clear();
transaction = session.beginTransaction();
session.beginTransaction();
boy = session.get( Boy.class, boy.getId() );
assertNotNull( boy );
assertNotNull( boy.getFavoriteToys() );
@ -155,24 +141,14 @@ public class DefaultNamingCollectionElementTest {
Toy next = boy.getFavoriteToys().iterator().next();
assertEquals( boy, next.getOwner(), "@Parent is failing" );
session.delete( boy );
transaction.commit();
}
finally {
if ( transaction.isActive() ) {
transaction.rollback();
}
}
}
);
}
@Test
public void testAttributedJoin(SessionFactoryScope scope) {
scope.inSession(
scope.inTransaction(
session -> {
Transaction transaction = session.getTransaction();
try {
transaction.begin();
Country country = new Country();
country.setName( "Australia" );
session.persist( country );
@ -187,22 +163,15 @@ public class DefaultNamingCollectionElementTest {
attitude.setLikes( true );
boy.getCountryAttitudes().add( attitude );
session.persist( boy );
transaction.commit();
session.getTransaction().commit();
session.clear();
transaction = session.beginTransaction();
session.beginTransaction();
boy = session.get( Boy.class, boy.getId() );
assertTrue( boy.getCountryAttitudes().contains( attitude ) );
session.delete( boy );
session.delete( session.get( Country.class, country.getId() ) );
transaction.commit();
}
finally {
if ( transaction.isActive() ) {
transaction.rollback();
}
}
}
);
}
@ -214,11 +183,9 @@ public class DefaultNamingCollectionElementTest {
scope.getMetadataImplementor().getCollectionBinding( Boy.class.getName() + '.' + "favoriteNumbers" )
.getCollectionTable().getName()
);
scope.inSession(
scope.inTransaction(
session -> {
Transaction transaction = session.getTransaction();
try {
transaction.begin();
Boy boy = new Boy();
boy.setFirstName( "John" );
boy.setLastName( "Doe" );
@ -234,11 +201,11 @@ public class DefaultNamingCollectionElementTest {
boy.getCharacters().add( Character.GENTLE );
boy.getCharacters().add( Character.CRAFTY );
session.persist( boy );
transaction.commit();
session.getTransaction().commit();
session.clear();
transaction = session.beginTransaction();
session.beginTransaction();
boy = session.get( Boy.class, boy.getId() );
assertNotNull( boy.getNickNames() );
assertTrue( boy.getNickNames().contains( "Thing" ) );
@ -253,23 +220,14 @@ public class DefaultNamingCollectionElementTest {
.setParameter( "name", "Thing" ).list();
assertEquals( 1, result.size() );
session.delete( boy );
transaction.commit();
}
finally {
if ( transaction.isActive() ) {
transaction.rollback();
}
}
}
);
}
@Test
public void testFetchEagerAndFilter(SessionFactoryScope scope) {
scope.inSession(
scope.inTransaction(
session -> {
Transaction tx = session.beginTransaction();
try {
TestCourse test = new TestCourse();
LocalizedString title = new LocalizedString( "title in english" );
@ -289,39 +247,22 @@ public class DefaultNamingCollectionElementTest {
TestCourse t = session.get( TestCourse.class, test.getTestCourseId() );
assertEquals( 1, t.getTitle().getVariations().size() );
tx.rollback();
}
finally {
if ( tx.isActive() ) {
tx.rollback();
}
}
}
);
}
@Test
public void testMapKeyType(SessionFactoryScope scope) {
scope.inSession(
scope.inTransaction(
session -> {
Matrix m = new Matrix();
m.getMvalues().put( 1, 1.1f );
Transaction tx = session.beginTransaction();
try {
session.persist( m );
session.flush();
session.clear();
m = session.get( Matrix.class, m.getId() );
assertEquals( 1.1f, m.getMvalues().get( 1 ), 0.01f );
}
finally {
if ( tx.isActive() ) {
tx.rollback();
}
}
}
);
}

View File

@ -0,0 +1,147 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.annotations.collectionelement;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.OrderBy;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@DomainModel(
annotatedClasses = {
org.hibernate.orm.test.annotations.collectionelement.OrderByColumnNameTest.Product.class,
org.hibernate.orm.test.annotations.collectionelement.OrderByColumnNameTest.Widgets.class,
org.hibernate.orm.test.annotations.collectionelement.OrderByColumnNameTest.Widget1.class,
org.hibernate.orm.test.annotations.collectionelement.OrderByColumnNameTest.Widget2.class,
}
)
@SessionFactory
public class OrderByColumnNameTest {
@Test
public void testOrderByName(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
Product p = new Product();
HashSet<Widgets> set = new HashSet<>();
Widgets widget = new Widgets();
widget.setName( "hammer" );
set.add( widget );
session.persist( widget );
widget = new Widgets();
widget.setName( "axel" );
set.add( widget );
session.persist( widget );
widget = new Widgets();
widget.setName( "screwdriver" );
set.add( widget );
session.persist( widget );
p.setWidgets( set );
session.persist( p );
session.getTransaction().commit();
session.beginTransaction();
session.clear();
p = session.get( Product.class, p.getId() );
assertTrue( p.getWidgets().size() == 3, "has three Widgets" );
Iterator iter = p.getWidgets().iterator();
assertEquals( "axel", ( (Widgets) iter.next() ).getName() );
assertEquals( "hammer", ( (Widgets) iter.next() ).getName() );
assertEquals( "screwdriver", ( (Widgets) iter.next() ).getName() );
}
);
}
@Entity
public static class Product {
@Id
@GeneratedValue
private Integer id;
@ElementCollection
@OrderBy("name_1 ASC")
private Set<Widgets> widgets;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Set<Widgets> getWidgets() {
return widgets;
}
public void setWidgets(Set<Widgets> widgets) {
this.widgets = widgets;
}
}
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public static class Widgets {
private String name;
private int id;
public Widgets() {
}
@Column(name = "name_1")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
@Entity
public static class Widget1 extends org.hibernate.orm.test.annotations.collectionelement.Widgets {
private String name1;
}
@Entity
public static class Widget2 extends org.hibernate.orm.test.annotations.collectionelement.Widgets {
private String name2;
}
}

View File

@ -9,7 +9,6 @@ package org.hibernate.orm.test.annotations.collectionelement;
import java.util.HashSet;
import java.util.Iterator;
import org.hibernate.Transaction;
import org.hibernate.dialect.TeradataDialect;
import org.hibernate.testing.orm.junit.DomainModel;
@ -26,6 +25,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
annotatedClasses = {
Products.class,
Widgets.class,
Widgets.Widget1.class,
Widgets.Widget2.class,
BugSystem.class
}
)
@ -34,11 +35,8 @@ public class OrderByTest {
@Test
public void testOrderByName(SessionFactoryScope scope) {
scope.inSession(
scope.inTransaction(
session -> {
Transaction tx = session.beginTransaction();
try {
Products p = new Products();
HashSet<Widgets> set = new HashSet<>();
@ -59,9 +57,9 @@ public class OrderByTest {
p.setWidgets( set );
session.persist( p );
tx.commit();
session.getTransaction().commit();
tx = session.beginTransaction();
session.beginTransaction();
session.clear();
p = session.get( Products.class, p.getId() );
assertTrue( p.getWidgets().size() == 3, "has three Widgets" );
@ -69,14 +67,6 @@ public class OrderByTest {
assertEquals( "axel", ( (Widgets) iter.next() ).getName() );
assertEquals( "hammer", ( (Widgets) iter.next() ).getName() );
assertEquals( "screwdriver", ( (Widgets) iter.next() ).getName() );
tx.commit();
}
catch (Exception e) {
if ( tx.isActive() ) {
tx.rollback();
}
throw e;
}
}
);
}
@ -88,10 +78,8 @@ public class OrderByTest {
reason = "HHH-8190, uses Teradata reserved word - summary"
)
public void testOrderByWithDottedNotation(SessionFactoryScope scope) {
scope.inSession(
scope.inTransaction(
session -> {
Transaction tx = session.beginTransaction();
try {
BugSystem bs = new BugSystem();
HashSet<Bug> set = new HashSet<>();
@ -124,9 +112,9 @@ public class OrderByTest {
bs.setBugs( set );
session.persist( bs );
tx.commit();
session.getTransaction().commit();
tx = session.beginTransaction();
session.beginTransaction();
session.clear();
bs = session.get( BugSystem.class, bs.getId() );
assertTrue( bs.getBugs().size() == 3, "has three bugs" );
@ -134,14 +122,6 @@ public class OrderByTest {
assertEquals( "Emmanuel", ( (Bug) iter.next() ).getReportedBy().getFirstName() );
assertEquals( "Steve", ( (Bug) iter.next() ).getReportedBy().getFirstName() );
assertEquals( "Scott", ( (Bug) iter.next() ).getReportedBy().getFirstName() );
tx.commit();
}
catch (Exception e) {
if ( tx.isActive() ) {
tx.rollback();
}
throw e;
}
}
);

View File

@ -39,5 +39,4 @@ public class Products {
public void setWidgets(Set<Widgets> widgets) {
this.widgets = widgets;
}
}

View File

@ -5,11 +5,15 @@
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.annotations.collectionelement;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Widgets {
private String name;
private int id;
@ -18,6 +22,7 @@ public class Widgets {
}
@Column(name = "name_1")
public String getName() {
return name;
}
@ -36,4 +41,14 @@ public class Widgets {
this.id = id;
}
@Entity
public static class Widget1 extends Widgets{
private String name1;
}
@Entity
public static class Widget2 extends Widgets{
private String name2;
}
}

View File

@ -33,9 +33,8 @@ public class TestBasicOps {
public void testLoadAndStore(SessionFactoryScope scope) {
Query q = new Query( new Location( "first", Location.Type.COUNTY ) );
scope.inTransaction(
session -> {
session.save( q );
}
session ->
session.save( q )
);
scope.inTransaction(
@ -52,23 +51,21 @@ public class TestBasicOps {
@Test
@TestForIssue(jiraKey = "HHH-7072")
public void testEmbeddableWithNullables(SessionFactoryScope scope) {
scope.inSession(
scope.inTransaction(
session -> {
Transaction transaction = session.beginTransaction();
try {
Query q = new Query( new Location( null, Location.Type.COMMUNE ) );
session.save( q );
transaction.commit();
session.getTransaction().commit();
session.clear();
transaction = session.beginTransaction();
Transaction transaction = session.beginTransaction();
q.getIncludedLocations().add( new Location( null, Location.Type.COUNTY ) );
session.update( q );
transaction.commit();
session.clear();
transaction = session.beginTransaction();
q = (Query) session.get( Query.class, q.getId() );
q = session.get( Query.class, q.getId() );
// assertEquals( 2, q.getIncludedLocations().size() );
transaction.commit();
session.clear();
@ -81,18 +78,10 @@ public class TestBasicOps {
transaction.commit();
session.clear();
transaction = session.beginTransaction();
q = (Query) session.get( Query.class, q.getId() );
session.beginTransaction();
q = session.get( Query.class, q.getId() );
assertEquals( 1, q.getIncludedLocations().size() );
session.delete( q );
transaction.commit();
}
catch (Exception e) {
if ( transaction.isActive() ) {
transaction.rollback();
}
throw e;
}
}
);
}