From 0bcb21bfd1da3f61e58202cb86d29dcc89d51cdf Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 26 Jan 2022 12:26:15 +0100 Subject: [PATCH] [#1436] Fix boot issue when model contains an association to an entity type that has an id class --- CHANGELOG.md | 5 +- .../persistence/testsuite/Issue1436Test.java | 72 +++++++++++ .../hibernate/base/HibernateJpaProvider.java | 113 ++++++++++++++---- 3 files changed, 165 insertions(+), 25 deletions(-) create mode 100644 core/testsuite/src/test/hibernate/com/blazebit/persistence/testsuite/Issue1436Test.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 70bd896def..75248aa8cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,12 @@ Not yet released ### New features -None yet +* Support Spring Boot/Data 2.6 ### Bug fixes -None yet +* Fix issues in Quarkus integration preventing the use of the Hibernate 5.6 integration in native mode +* Fix boot issue when model contains an association to an entity type that has an id class ### Backwards-incompatible changes diff --git a/core/testsuite/src/test/hibernate/com/blazebit/persistence/testsuite/Issue1436Test.java b/core/testsuite/src/test/hibernate/com/blazebit/persistence/testsuite/Issue1436Test.java new file mode 100644 index 0000000000..80a8eb4465 --- /dev/null +++ b/core/testsuite/src/test/hibernate/com/blazebit/persistence/testsuite/Issue1436Test.java @@ -0,0 +1,72 @@ +/* + * Copyright 2014 - 2022 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.testsuite; + +import com.blazebit.persistence.parser.EntityMetamodel; +import com.blazebit.persistence.spi.ExtendedAttribute; +import com.blazebit.persistence.spi.ExtendedManagedType; +import org.junit.Test; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import java.io.Serializable; + +import static org.junit.Assert.assertEquals; + +/** + * @author Christian Beikov + * @since 1.6.6 + */ +public class Issue1436Test extends AbstractCoreTest { + + @Override + protected Class[] getEntityClasses() { + return new Class[]{ OrderItem.class, Item.class }; + } + + @Test + public void testBuild() { + ExtendedAttribute attribute = cbf.getService(EntityMetamodel.class) + .getManagedType(ExtendedManagedType.class, OrderItem.class) + .getAttribute("item.id1"); + assertEquals(1, attribute.getColumnNames().length); + } + + @Entity + public static class Item implements Serializable { + @Id + Long id1; + @Id + Long id2; + + public Item() { + } + } + + @Entity + public static class OrderItem implements Serializable { + @Id + Long id; + @Id + @ManyToOne + Item item; + + public OrderItem() { + } + } +} diff --git a/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java b/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java index 58cf62c171..dbf1021c37 100644 --- a/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java +++ b/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java @@ -22,6 +22,7 @@ import com.blazebit.persistence.spi.JpaMetamodelAccessor; import com.blazebit.persistence.spi.JpaProvider; import org.hibernate.MappingException; +import org.hibernate.QueryException; import org.hibernate.engine.spi.CascadingAction; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.Mapping; @@ -47,6 +48,7 @@ import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.ComponentType; +import org.hibernate.type.CompositeType; import org.hibernate.type.CustomType; import org.hibernate.type.EmbeddedComponentType; import org.hibernate.type.ForeignKeyDirection; @@ -522,7 +524,7 @@ public String[] getDiscriminatorColumnCheck(EntityType entityType) { @Override public boolean isForeignJoinColumn(EntityType ownerType, String attributeName) { AbstractEntityPersister persister = getEntityPersister(ownerType); - Type propertyType = persister.getPropertyType(attributeName); + Type propertyType = getPropertyType(persister, attributeName); if (propertyType instanceof org.hibernate.type.EntityType) { org.hibernate.type.EntityType entityType = (org.hibernate.type.EntityType) propertyType; @@ -579,7 +581,7 @@ public ConstraintType requiresTreatFilter(EntityType ownerType, String attrib return ConstraintType.WHERE; } AbstractEntityPersister persister = getEntityPersister(ownerType); - Type propertyType = persister.getPropertyType(attributeName); + Type propertyType = getPropertyType(persister, attributeName); if (!(propertyType instanceof AssociationType)) { return ConstraintType.NONE; @@ -638,7 +640,7 @@ private boolean isColumnShared(AbstractEntityPersister subclassPersister, String List propertiesToCheck = new ArrayList<>(Arrays.asList(subclassPersister.getPropertyNames())); while (!propertiesToCheck.isEmpty()) { String propertyName = propertiesToCheck.remove(propertiesToCheck.size() - 1); - Type propertyType = subclassPersister.getPropertyType(propertyName); + Type propertyType = getPropertyType(subclassPersister, propertyName); if (propertyType instanceof ComponentType) { ComponentType componentType = (ComponentType) propertyType; for (String subPropertyName : componentType.getPropertyNames()) { @@ -666,8 +668,8 @@ public String getMappedBy(EntityType ownerType, String attributeName) { return ""; } } else { - EntityPersister entityPersister = getEntityPersister(ownerType); - Type propertyType = entityPersister.getPropertyType(attributeName); + AbstractEntityPersister entityPersister = getEntityPersister(ownerType); + Type propertyType = getPropertyType(entityPersister, attributeName); if (propertyType instanceof OneToOneType) { return ((OneToOneType) propertyType).getRHSUniqueKeyPropertyName(); } @@ -770,7 +772,7 @@ private Set removeIdentifierAccess(EntityType elementType, Set addedAttributeNames = new ArrayList<>(); for (String attributeName : columnMatchingAttributeNames) { String elementAttributeName = iterator.next(); - Type propertyType = entityPersister.getPropertyType(attributeName); + Type propertyType = getPropertyType(entityPersister, attributeName); if (propertyType instanceof org.hibernate.type.EntityType) { // If the columns refer to an association, we map that through instead of trying to set just the identifier properties if (elementPersister.getEntityName().equals(((org.hibernate.type.EntityType) propertyType).getAssociatedEntityName())) { @@ -807,16 +809,54 @@ protected String getMappedBy(CollectionPersister persister) { public String[] getColumnNames(EntityType entityType, String attributeName) { QueryableCollection collectionPersister = getCollectionPersister(entityType, attributeName); if (collectionPersister == null) { - try { - return getEntityPersister(entityType).getPropertyColumnNames(attributeName); - } catch (MappingException e) { - throw new RuntimeException("Unknown property [" + attributeName + "] of entity [" + entityType.getJavaType() + "]", e); - } + return getColumnNames(getEntityPersister(entityType), attributeName); } else { return collectionPersister.getElementColumnNames(); } } + public String[] getColumnNames(AbstractEntityPersister entityPersister, String attributeName) { + try { + return entityPersister.getPropertyColumnNames(attributeName); + } catch (MappingException e) { + // Workaround for HHH-15051 + int dotIndex = attributeName.lastIndexOf('.'); + if (dotIndex != -1) { + String attributePrefix = attributeName.substring(0, dotIndex); + Type propertyType = getPropertyType(entityPersister, attributePrefix); + if (propertyType instanceof org.hibernate.type.EntityType) { + String[] columnNames = getColumnNames(entityPersister, attributePrefix); + org.hibernate.type.EntityType hibernateEntityType = (org.hibernate.type.EntityType) propertyType; + Type idType = hibernateEntityType.getIdentifierOrUniqueKeyType(entityPersister.getFactory()); + String attributeSubName = attributeName.substring(dotIndex + 1); + if (idType instanceof CompositeType && ((CompositeType) idType).isEmbedded()) { + CompositeType idClassType = (CompositeType) idType; + int columnSpan = 0; + int propertyIndex = -1; + String[] propertyNames = idClassType.getPropertyNames(); + Type[] subtypes = idClassType.getSubtypes(); + for (int i = 0; i < propertyNames.length; i++) { + if (propertyNames[i].equals(attributeSubName)) { + propertyIndex = i; + break; + } + columnSpan += subtypes[i].getColumnSpan(entityPersister.getFactory()); + } + + if (propertyIndex != -1) { + String[] actualColumns = new String[subtypes[propertyIndex].getColumnSpan(entityPersister.getFactory())]; + System.arraycopy(columnNames, columnSpan, actualColumns, 0, actualColumns.length); + return actualColumns; + } + } else if (attributeSubName.equals(hibernateEntityType.getIdentifierOrUniqueKeyPropertyName(entityPersister.getFactory()))) { + return columnNames; + } + } + } + throw new RuntimeException("Unknown property [" + attributeName + "] of entity [" + entityPersister.getEntityName() + "]", e); + } + } + @Override public String[] getColumnNames(EntityType ownerType, String elementCollectionPath, String attributeName) { QueryableCollection persister = getCollectionPersister(ownerType, elementCollectionPath); @@ -891,7 +931,7 @@ private String[] columnNamesByPropertyName(AbstractEntityPersister persister, St int offset = 0; for (int i = 0; i < propertyNames.length; i++) { String propertyName = propertyNames[i]; - Type propertyType = persister.getPropertyType(prefix + propertyName); + Type propertyType = getPropertyType(persister, prefix + propertyName); int span = propertyType.getColumnSpan(factory); if (subAttributeName.equals(propertyName)) { String[] columnNames = new String[span]; @@ -925,7 +965,7 @@ public String[] getColumnTypes(EntityType entityType, String attributeName) { if (collectionPersister == null) { AbstractEntityPersister entityPersister = getEntityPersister(entityType); SessionFactoryImplementor sfi = entityPersister.getFactory(); - String[] columnNames = entityPersister.getPropertyColumnNames(attributeName); + String[] columnNames = getColumnNames(entityPersister, attributeName); Database database = sfi.getServiceRegistry().locateServiceBinding(Database.class).getService(); Table[] tables; @@ -951,7 +991,7 @@ public String[] getColumnTypes(EntityType entityType, String attributeName) { boolean isSubselect = tables.length == 1 && tables[0] == null; if (isSubselect || isFormula(columnNames)) { - Type propertyType = entityPersister.getPropertyType(attributeName); + Type propertyType = getPropertyType(entityPersister, attributeName); return getColumnTypeForPropertyType(entityType, attributeName, sfi, propertyType); } @@ -1163,7 +1203,7 @@ public JoinTable getJoinTable(EntityType ownerType, String attributeName) { String identifierOrUniqueKeyPropertyName = ((ManyToOneType) persister.getElementType()).getIdentifierOrUniqueKeyPropertyName(persister.getFactory()); String[] targetPrimaryKeyColumnMetaData = identifierOrUniqueKeyPropertyName == null ? elementPersister.getKeyColumnNames() : // IdClass returns null for getIdentifierOrUniqueKeyPropertyName - elementPersister.getPropertyColumnNames(identifierOrUniqueKeyPropertyName); + getColumnNames(elementPersister, identifierOrUniqueKeyPropertyName); Map targetIdColumnMapping = new LinkedHashMap<>(); @@ -1198,7 +1238,7 @@ private JoinTable createJoinTable(EntityType ownerType, QueryableCollection q String[] propertyNames = ((EmbeddedComponentType) queryableCollection.getKeyType()).getPropertyNames(); List columnNames = new ArrayList<>(propertyNames.length); for (String propertyName : propertyNames) { - for (String propertyColumnName : ownerEntityPersister.getPropertyColumnNames(propertyName)) { + for (String propertyColumnName : getColumnNames(ownerEntityPersister, propertyName)) { columnNames.add(propertyColumnName); } } @@ -1411,7 +1451,7 @@ public boolean hasJoinCondition(ManagedType owner, String elementCollectionPa propertyType = componentType.getSubtypes()[componentType.getPropertyIndex(propertyParts[propertyParts.length - 1])]; } else { - propertyType = entityPersister.getPropertyType(attributeName); + propertyType = getPropertyType(entityPersister, attributeName); } if (propertyType instanceof CollectionType) { @@ -1450,6 +1490,33 @@ public boolean hasJoinCondition(ManagedType owner, String elementCollectionPa return false; } + private Type getPropertyType(AbstractEntityPersister entityPersister, String attributeName) { + try { + return entityPersister.getPropertyType(attributeName); + } catch (QueryException ex) { + // Workaround for HHH-15051 + int dotIndex = attributeName.lastIndexOf('.'); + if (dotIndex != -1) { + Type propertyType = getPropertyType(entityPersister, attributeName.substring(0, dotIndex)); + if (propertyType instanceof org.hibernate.type.EntityType) { + org.hibernate.type.EntityType entityType = (org.hibernate.type.EntityType) propertyType; + Type idType = entityType.getIdentifierOrUniqueKeyType(entityPersister.getFactory()); + String attributeSubName = attributeName.substring(dotIndex + 1); + if (idType instanceof CompositeType && ((CompositeType) idType).isEmbedded()) { + CompositeType idClassType = (CompositeType) idType; + int propertyIndex = Arrays.asList(idClassType.getPropertyNames()).indexOf(attributeSubName); + if (propertyIndex != -1) { + return idClassType.getSubtypes()[propertyIndex]; + } + } else if (attributeSubName.equals(entityType.getIdentifierOrUniqueKeyPropertyName(entityPersister.getFactory()))) { + return idType; + } + } + } + throw ex; + } + } + @Override public boolean containsEntity(EntityManager em, Class entityClass, Object id) { SessionImplementor session = em.unwrap(SessionImplementor.class); @@ -1548,7 +1615,7 @@ public Map getJoinMappingPropertyNames(EntityType owner, Stri propertyType = componentType.getSubtypes()[componentType.getPropertyIndex(propertyParts[propertyParts.length - 1])]; } else { - propertyType = entityPersister.getPropertyType(attributeName); + propertyType = getPropertyType(entityPersister, attributeName); } List identifierOrUniqueKeyPropertyNames = new ArrayList<>(); @@ -1573,7 +1640,7 @@ public Map getJoinMappingPropertyNames(EntityType owner, Stri } } else { AbstractEntityPersister elementPersister = (AbstractEntityPersister) entityPersisters.get(((org.hibernate.type.EntityType) elementType).getAssociatedEntityName()); - collectPropertyNames(sourceIdentifierOrUniqueKeyPropertyNames, null, elementPersister.getPropertyType(mappedBy), factory); + collectPropertyNames(sourceIdentifierOrUniqueKeyPropertyNames, null, getPropertyType(elementPersister, mappedBy), factory); // if (sourceIdentifierOrUniqueKeyPropertyNames.size() != identifierOrUniqueKeyPropertyNames.size()) { // // We have a inverse map or inverse indexed list here // // Maybe at some point we can determine the index property name mapping as well @@ -1585,10 +1652,10 @@ public Map getJoinMappingPropertyNames(EntityType owner, Stri } else { AbstractEntityPersister elementPersister = (AbstractEntityPersister) entityPersisters.get(((org.hibernate.type.EntityType) elementType).getAssociatedEntityName()); for (String targetAttributeName : targetAttributeNames) { - collectPropertyNames(identifierOrUniqueKeyPropertyNames, targetAttributeName, elementPersister.getPropertyType(targetAttributeName), factory); + collectPropertyNames(identifierOrUniqueKeyPropertyNames, targetAttributeName, getPropertyType(elementPersister, targetAttributeName), factory); } for (String idAttributeName : joinTable.getIdAttributeNames()) { - collectPropertyNames(sourceIdentifierOrUniqueKeyPropertyNames, idAttributeName, entityPersister.getPropertyType(idAttributeName), factory); + collectPropertyNames(sourceIdentifierOrUniqueKeyPropertyNames, idAttributeName, getPropertyType(entityPersister, idAttributeName), factory); } } } @@ -1610,7 +1677,7 @@ public Map getJoinMappingPropertyNames(EntityType owner, Stri if (mappedBy == null || mappedBy.isEmpty()) { throw new IllegalArgumentException("One-to-one using natural key is unsupported!"); } else { - collectPropertyNames(sourceIdentifierOrUniqueKeyPropertyNames, null, elementPersister.getPropertyType(mappedBy), factory); + collectPropertyNames(sourceIdentifierOrUniqueKeyPropertyNames, null, getPropertyType(elementPersister, mappedBy), factory); } } } @@ -1658,7 +1725,7 @@ public boolean supportsEnumLiteral(ManagedType ownerType, String attributeNam if (ownerType instanceof EntityType) { AbstractEntityPersister entityPersister = getEntityPersister(ownerType); Type propertyType; - propertyType = entityPersister.getPropertyType(attributeName); + propertyType = getPropertyType(entityPersister, attributeName); if (propertyType instanceof CollectionType) { CollectionPersister collectionPersister = entityPersister.getFactory().getCollectionPersister(((CollectionType) propertyType).getRole()); if (key) {