Skip to content

Commit cb6c74f

Browse files
committed
Simplify accessing the ID of an entity.
Delegate ID access to the JPA provider. Closes #1854.
1 parent f2e7bdd commit cb6c74f

15 files changed

+277
-249
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupport.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.jpa.repository.support;
1717

1818
import jakarta.persistence.EntityManager;
19+
import jakarta.persistence.PersistenceUnitUtil;
1920
import jakarta.persistence.metamodel.Metamodel;
2021

2122
import org.springframework.data.domain.Persistable;
@@ -29,6 +30,7 @@
2930
*
3031
* @author Oliver Gierke
3132
* @author Mark Paluch
33+
* @author Greg Turnquist
3234
*/
3335
public abstract class JpaEntityInformationSupport<T, ID> extends AbstractEntityInformation<T, ID>
3436
implements JpaEntityInformation<T, ID> {
@@ -59,11 +61,12 @@ public JpaEntityInformationSupport(Class<T> domainClass) {
5961
Assert.notNull(em, "EntityManager must not be null");
6062

6163
Metamodel metamodel = em.getMetamodel();
64+
PersistenceUnitUtil persistenceUnitUtil = em.getEntityManagerFactory().getPersistenceUnitUtil();
6265

6366
if (Persistable.class.isAssignableFrom(domainClass)) {
64-
return new JpaPersistableEntityInformation(domainClass, metamodel);
67+
return new JpaPersistableEntityInformation(domainClass, metamodel, persistenceUnitUtil);
6568
} else {
66-
return new JpaMetamodelEntityInformation(domainClass, metamodel);
69+
return new JpaMetamodelEntityInformation(domainClass, metamodel, persistenceUnitUtil);
6770
}
6871
}
6972

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java

Lines changed: 26 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,28 @@
1515
*/
1616
package org.springframework.data.jpa.repository.support;
1717

18-
import java.util.ArrayList;
19-
import java.util.Collections;
20-
import java.util.Iterator;
21-
import java.util.List;
22-
import java.util.Optional;
23-
import java.util.Set;
24-
2518
import jakarta.persistence.IdClass;
19+
import jakarta.persistence.PersistenceUnitUtil;
2620
import jakarta.persistence.metamodel.Attribute;
2721
import jakarta.persistence.metamodel.EntityType;
2822
import jakarta.persistence.metamodel.IdentifiableType;
2923
import jakarta.persistence.metamodel.ManagedType;
3024
import jakarta.persistence.metamodel.Metamodel;
3125
import jakarta.persistence.metamodel.SingularAttribute;
3226
import jakarta.persistence.metamodel.Type;
33-
import jakarta.persistence.metamodel.Type.PersistenceType;
27+
28+
import java.util.ArrayList;
29+
import java.util.Collections;
30+
import java.util.Iterator;
31+
import java.util.List;
32+
import java.util.Optional;
33+
import java.util.Set;
3434

3535
import org.springframework.beans.BeanWrapper;
36-
import org.springframework.beans.BeanWrapperImpl;
3736
import org.springframework.core.annotation.AnnotationUtils;
3837
import org.springframework.data.jpa.provider.PersistenceProvider;
3938
import org.springframework.data.jpa.util.JpaMetamodel;
4039
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
41-
import org.springframework.data.util.ProxyUtils;
4240
import org.springframework.lang.Nullable;
4341
import org.springframework.util.Assert;
4442

@@ -51,21 +49,25 @@
5149
* @author Christoph Strobl
5250
* @author Mark Paluch
5351
* @author Jens Schauder
52+
* @author Greg Turnquist
5453
*/
5554
public class JpaMetamodelEntityInformation<T, ID> extends JpaEntityInformationSupport<T, ID> {
5655

5756
private final IdMetadata<T> idMetadata;
5857
private final Optional<SingularAttribute<? super T, ?>> versionAttribute;
5958
private final Metamodel metamodel;
6059
private final @Nullable String entityName;
60+
private final PersistenceUnitUtil persistenceUnitUtil;
6161

6262
/**
6363
* Creates a new {@link JpaMetamodelEntityInformation} for the given domain class and {@link Metamodel}.
64-
*
64+
*
6565
* @param domainClass must not be {@literal null}.
6666
* @param metamodel must not be {@literal null}.
67+
* @param persistenceUnitUtil must not be {@literal null}.
6768
*/
68-
public JpaMetamodelEntityInformation(Class<T> domainClass, Metamodel metamodel) {
69+
public JpaMetamodelEntityInformation(Class<T> domainClass, Metamodel metamodel,
70+
PersistenceUnitUtil persistenceUnitUtil) {
6971

7072
super(domainClass);
7173

@@ -88,6 +90,9 @@ public JpaMetamodelEntityInformation(Class<T> domainClass, Metamodel metamodel)
8890

8991
this.idMetadata = new IdMetadata<>(identifiableType, PersistenceProvider.fromMetamodel(metamodel));
9092
this.versionAttribute = findVersionAttribute(identifiableType, metamodel);
93+
94+
Assert.notNull(persistenceUnitUtil, "PersistenceUnitUtil must not be null");
95+
this.persistenceUnitUtil = persistenceUnitUtil;
9196
}
9297

9398
@Override
@@ -147,27 +152,25 @@ public ID getId(T entity) {
147152
return (ID) persistenceProvider.getIdentifierFrom(entity);
148153
}
149154

150-
// if not a proxy use Spring mechanics to access the id.
151-
BeanWrapper entityWrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
152-
155+
// If it's a simple type, then immediately delegate to the provider
153156
if (idMetadata.hasSimpleId()) {
154-
return (ID) entityWrapper.getPropertyValue(idMetadata.getSimpleIdAttribute().getName());
157+
return (ID) persistenceUnitUtil.getIdentifier(entity);
155158
}
156159

157-
BeanWrapper idWrapper = new IdentifierDerivingDirectFieldAccessFallbackBeanWrapper(idMetadata.getType(), metamodel);
160+
// otherwise, check if the complex id type has any partially filled fields
161+
BeanWrapper entityWrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
158162
boolean partialIdValueFound = false;
159163

160164
for (SingularAttribute<? super T, ?> attribute : idMetadata) {
165+
161166
Object propertyValue = entityWrapper.getPropertyValue(attribute.getName());
162167

163168
if (propertyValue != null) {
164169
partialIdValueFound = true;
165170
}
166-
167-
idWrapper.setPropertyValue(attribute.getName(), propertyValue);
168171
}
169172

170-
return partialIdValueFound ? (ID) idWrapper.getWrappedInstance() : null;
173+
return partialIdValueFound ? (ID) persistenceUnitUtil.getIdentifier(entity) : null;
171174
}
172175

173176
@Override
@@ -209,7 +212,7 @@ public Object getCompositeIdAttributeValue(Object id, String idAttribute) {
209212
@Override
210213
public boolean isNew(T entity) {
211214

212-
if (!versionAttribute.isPresent()
215+
if (versionAttribute.isEmpty()
213216
|| versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
214217
return super.isNew(entity);
215218
}
@@ -237,9 +240,9 @@ private static class IdMetadata<T> implements Iterable<SingularAttribute<? super
237240

238241
this.type = source;
239242
this.idClassAttributes = persistenceProvider.getIdClassAttributes(source);
240-
this.attributes = (Set<SingularAttribute<? super T, ?>>) (source.hasSingleIdAttribute()
243+
this.attributes = source.hasSingleIdAttribute()
241244
? Collections.singleton(source.getId(source.getIdType().getJavaType()))
242-
: source.getIdClassAttributes());
245+
: source.getIdClassAttributes();
243246
}
244247

245248
boolean hasSimpleId() {
@@ -298,121 +301,4 @@ private static Class<?> lookupIdClass(IdentifiableType<?> type) {
298301
return attributes.iterator();
299302
}
300303
}
301-
302-
/**
303-
* Custom extension of {@link DirectFieldAccessFallbackBeanWrapper} that allows to derive the identifier if composite
304-
* keys with complex key attribute types (e.g. types that are annotated with {@code @Entity} themselves) are used.
305-
*
306-
* @author Thomas Darimont
307-
*/
308-
private static class IdentifierDerivingDirectFieldAccessFallbackBeanWrapper
309-
extends DirectFieldAccessFallbackBeanWrapper {
310-
311-
private final Metamodel metamodel;
312-
private final JpaMetamodel jpaMetamodel;
313-
314-
IdentifierDerivingDirectFieldAccessFallbackBeanWrapper(Class<?> type, Metamodel metamodel) {
315-
super(type);
316-
this.metamodel = metamodel;
317-
this.jpaMetamodel = JpaMetamodel.of(metamodel);
318-
}
319-
320-
/**
321-
* In addition to the functionality described in {@link BeanWrapperImpl} it is checked whether we have a nested
322-
* entity that is part of the id key. If this is the case, we need to derive the identifier of the nested entity.
323-
*/
324-
@Override
325-
@SuppressWarnings({ "unchecked", "rawtypes" })
326-
public void setPropertyValue(String propertyName, @Nullable Object value) {
327-
328-
if (!isIdentifierDerivationNecessary(value)) {
329-
super.setPropertyValue(propertyName, value);
330-
return;
331-
}
332-
333-
// Derive the identifier from the nested entity that is part of the composite key.
334-
JpaMetamodelEntityInformation nestedEntityInformation = new JpaMetamodelEntityInformation(
335-
ProxyUtils.getUserClass(value), this.metamodel);
336-
337-
if (!nestedEntityInformation.getJavaType().isAnnotationPresent(IdClass.class)) {
338-
339-
Object nestedIdPropertyValue = new DirectFieldAccessFallbackBeanWrapper(value)
340-
.getPropertyValue(nestedEntityInformation.getRequiredIdAttribute().getName());
341-
super.setPropertyValue(propertyName, nestedIdPropertyValue);
342-
return;
343-
}
344-
345-
// We have an IdClass property, we need to inspect the current value in order to map potentially multiple id
346-
// properties correctly.
347-
348-
BeanWrapper sourceIdValueWrapper = new DirectFieldAccessFallbackBeanWrapper(value);
349-
BeanWrapper targetIdClassTypeWrapper = new BeanWrapperImpl(nestedEntityInformation.getIdType());
350-
351-
for (String idAttributeName : (Iterable<String>) nestedEntityInformation.getIdAttributeNames()) {
352-
targetIdClassTypeWrapper.setPropertyValue(idAttributeName,
353-
extractActualIdPropertyValue(sourceIdValueWrapper, idAttributeName));
354-
}
355-
356-
super.setPropertyValue(propertyName, targetIdClassTypeWrapper.getWrappedInstance());
357-
}
358-
359-
@Nullable
360-
private Object extractActualIdPropertyValue(BeanWrapper sourceIdValueWrapper, String idAttributeName) {
361-
362-
Object idPropertyValue = sourceIdValueWrapper.getPropertyValue(idAttributeName);
363-
364-
if (idPropertyValue != null) {
365-
366-
Class<?> idPropertyValueType = idPropertyValue.getClass();
367-
368-
if (!jpaMetamodel.isJpaManaged(idPropertyValueType)) {
369-
return idPropertyValue;
370-
}
371-
372-
return new DirectFieldAccessFallbackBeanWrapper(idPropertyValue)
373-
.getPropertyValue(tryFindSingularIdAttributeNameOrUseFallback(idPropertyValueType, idAttributeName));
374-
}
375-
376-
return null;
377-
}
378-
379-
private String tryFindSingularIdAttributeNameOrUseFallback(Class<?> idPropertyValueType,
380-
String fallbackIdTypePropertyName) {
381-
382-
ManagedType<?> idPropertyType = metamodel.managedType(idPropertyValueType);
383-
for (SingularAttribute<?, ?> sa : idPropertyType.getSingularAttributes()) {
384-
if (sa.isId()) {
385-
return sa.getName();
386-
}
387-
}
388-
389-
return fallbackIdTypePropertyName;
390-
}
391-
392-
/**
393-
* @param value
394-
* @return {@literal true} if the given value is not {@literal null} and a mapped persistable entity otherwise
395-
* {@literal false}
396-
*/
397-
private boolean isIdentifierDerivationNecessary(@Nullable Object value) {
398-
399-
if (value == null) {
400-
return false;
401-
}
402-
403-
Class<?> userClass = ProxyUtils.getUserClass(value);
404-
405-
if (!this.jpaMetamodel.isJpaManaged(userClass)) {
406-
return false;
407-
}
408-
409-
ManagedType<?> managedType = this.metamodel.managedType(userClass);
410-
411-
if (managedType == null) {
412-
throw new IllegalStateException("ManagedType must not be null; We checked that it exists before.");
413-
}
414-
415-
return managedType.getPersistenceType() == PersistenceType.ENTITY;
416-
}
417-
}
418304
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformation.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.jpa.repository.support;
1717

18+
import jakarta.persistence.PersistenceUnitUtil;
1819
import jakarta.persistence.metamodel.Metamodel;
1920

2021
import org.springframework.data.domain.Persistable;
@@ -32,12 +33,14 @@ public class JpaPersistableEntityInformation<T extends Persistable<ID>, ID>
3233

3334
/**
3435
* Creates a new {@link JpaPersistableEntityInformation} for the given domain class and {@link Metamodel}.
35-
*
36+
*
3637
* @param domainClass must not be {@literal null}.
3738
* @param metamodel must not be {@literal null}.
39+
* @param persistenceUnitUtil must not be {@literal null}.
3840
*/
39-
public JpaPersistableEntityInformation(Class<T> domainClass, Metamodel metamodel) {
40-
super(domainClass, metamodel);
41+
public JpaPersistableEntityInformation(Class<T> domainClass, Metamodel metamodel,
42+
PersistenceUnitUtil persistenceUnitUtil) {
43+
super(domainClass, metamodel, persistenceUnitUtil);
4144
}
4245

4346
@Override

0 commit comments

Comments
 (0)