diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java index a359997a77..09deda526a 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java @@ -63,8 +63,7 @@ public JpaMetamodelMappingContext(Set models) { @Override protected JpaPersistentEntityImpl createPersistentEntity(TypeInformation typeInformation) { - return new JpaPersistentEntityImpl<>(typeInformation, persistenceProvider, - models.getRequiredMetamodel(typeInformation)); + return new JpaPersistentEntityImpl<>(typeInformation, models.getRequiredMetamodel(typeInformation)); } @Override diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java index 9dc1b8c648..857f4a094e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java @@ -21,6 +21,7 @@ import org.springframework.data.annotation.Version; import org.springframework.data.core.TypeInformation; +import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.provider.ProxyIdAccessor; import org.springframework.data.jpa.util.JpaMetamodel; import org.springframework.data.mapping.IdentifierAccessor; @@ -36,6 +37,7 @@ * @author Christoph Strobl * @author Mark Paluch * @author Michael J. Simons + * @author Gieun Nam * @since 1.3 */ class JpaPersistentEntityImpl extends BasicPersistentEntity @@ -45,23 +47,21 @@ class JpaPersistentEntityImpl extends BasicPersistentEntity information, ProxyIdAccessor proxyIdAccessor, - JpaMetamodel metamodel) { + public JpaPersistentEntityImpl(TypeInformation information, JpaMetamodel metamodel) { super(information, null); - - this.proxyIdAccessor = proxyIdAccessor; this.metamodel = metamodel; + this.persistenceProvider = PersistenceProvider.fromMetamodel(metamodel.getMetamodel()); } @Override @@ -71,7 +71,7 @@ public JpaPersistentEntityImpl(TypeInformation information, ProxyIdAccessor p @Override public IdentifierAccessor getIdentifierAccessor(Object bean) { - return new JpaProxyAwareIdentifierAccessor(this, bean, proxyIdAccessor); + return new JpaProxyAwareIdentifierAccessor(this, bean, persistenceProvider); } @Override @@ -98,7 +98,7 @@ JpaMetamodel getMetamodel() { private static class JpaProxyAwareIdentifierAccessor extends IdPropertyIdentifierAccessor { private final Object bean; - private final ProxyIdAccessor proxyIdAccessor; + private final PersistenceProvider persistenceProvider; /** * Creates a new {@link JpaProxyAwareIdentifierAccessor} for the given {@link JpaPersistentEntity}, target bean and @@ -106,24 +106,24 @@ private static class JpaProxyAwareIdentifierAccessor extends IdPropertyIdentifie * * @param entity must not be {@literal null}. * @param bean must not be {@literal null}. - * @param proxyIdAccessor must not be {@literal null}. + * @param persistenceProvider must not be {@literal null}. */ - JpaProxyAwareIdentifierAccessor(JpaPersistentEntity entity, Object bean, ProxyIdAccessor proxyIdAccessor) { + JpaProxyAwareIdentifierAccessor(JpaPersistentEntity entity, Object bean, PersistenceProvider persistenceProvider) { super(entity, bean); - Assert.notNull(proxyIdAccessor, "Proxy identifier accessor must not be null"); + Assert.notNull(persistenceProvider, "Proxy identifier accessor must not be null"); - this.proxyIdAccessor = proxyIdAccessor; + this.persistenceProvider = persistenceProvider; this.bean = bean; } @Override public @Nullable Object getIdentifier() { - return proxyIdAccessor.shouldUseAccessorFor(bean) // - ? proxyIdAccessor.getIdentifierFrom(bean)// + return persistenceProvider.shouldUseAccessorFor(bean) // + ? persistenceProvider.getIdentifierFrom(bean)// : super.getIdentifier(); } } -} +} \ No newline at end of file diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityInformation.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityInformation.java new file mode 100644 index 0000000000..7933efa4f4 --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityInformation.java @@ -0,0 +1,59 @@ +/* + * Copyright 2026-present the original author or authors. + * + * 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 + * + * https://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 org.springframework.data.jpa.mapping; + + +import org.springframework.data.mapping.IdentifierAccessor; +import org.springframework.util.Assert; + +/** + * Implementation of {@link JpaPersistentEntityInformation}. + * + * @author Gieun Nam + * @since 4.1 + */ +public class JpaPersistentEntityInformation { + private final JpaPersistentEntity entityMetadata; + + public JpaPersistentEntityInformation(JpaPersistentEntity entityMetadata) { + Assert.notNull(entityMetadata, "JpaPersistentEntity must not be null"); + this.entityMetadata = entityMetadata; + } + + public ID getId(T entity) { + Assert.notNull(entity, "Entity must not be null"); + + IdentifierAccessor accessor = entityMetadata.getIdentifierAccessor(entity); + return (ID) accessor.getIdentifier(); + } + + public Class getIdType() { + return (Class) entityMetadata.getRequiredIdProperty().getType(); + } + + public boolean isNew(T entity) { + return entityMetadata.isNew(entity); + } + + public boolean hasCompositeId() { + JpaPersistentProperty idProperty = entityMetadata.getRequiredIdProperty(); + return idProperty.isEmbeddable(); + } + + public Class getIdAttribute() { + return entityMetadata.getRequiredIdProperty().getActualType(); + } +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java index b664ae5cfa..526d294a32 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java @@ -154,4 +154,8 @@ static void clear() { .filter(SingularAttribute::isId) // .findFirst(); } + + public Metamodel getMetamodel() { + return metamodel; + } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/mapping/JpaPersistentEntityInformationUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/mapping/JpaPersistentEntityInformationUnitTests.java new file mode 100644 index 0000000000..f0fd549407 --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/mapping/JpaPersistentEntityInformationUnitTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2026-present the original author or authors. + * + * 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 + * + * https://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 org.springframework.data.jpa.mapping; + +import jakarta.persistence.metamodel.Metamodel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Unit tests for {@link JpaPersistentEntityInformation}. + * + * @author Gieun Nam + */ +class JpaPersistentEntityInformationUnitTests { + private JpaMetamodelMappingContext mappingContext; + @BeforeEach + void setUp() { + Metamodel metamodel = mock(Metamodel.class); + this.mappingContext = new JpaMetamodelMappingContext(Collections.singleton(metamodel)); + } + + @Test // GH-4037 + void validationScenario() { + JpaPersistentEntity metaData = mappingContext.getRequiredPersistentEntity(User.class); + + JpaPersistentEntityInformation entityInfo = + new JpaPersistentEntityInformation<>((JpaPersistentEntity) metaData); + + User user = new User(); + user.id = 77L; + + assertThat(entityInfo.getId(user)).isEqualTo(77L); + + assertThat(entityInfo.getIdType()).isEqualTo(Long.class); + + assertThat(entityInfo.isNew(user)).isFalse(); + + assertThat(entityInfo.hasCompositeId()).isFalse(); + + assertThat(entityInfo.getIdAttribute()).isEqualTo(Long.class); + } + + @Test + void identifiesCompositeIdCorrectly() { + JpaPersistentEntity metaData = mappingContext.getRequiredPersistentEntity(Order.class); + JpaPersistentEntityInformation entityInfo = + new JpaPersistentEntityInformation<>((JpaPersistentEntity) metaData); + + assertThat(entityInfo.hasCompositeId()).isTrue(); + + assertThat(entityInfo.getIdAttribute()).isEqualTo(OrderId.class); + } + + // Test Entities + static class User { + @jakarta.persistence.Id Long id; + } + + static class Order { + @jakarta.persistence.EmbeddedId OrderId id; + } + + @jakarta.persistence.Embeddable + static class OrderId implements java.io.Serializable { + Long orderId; + Long userId; + } +} \ No newline at end of file