From 3f7fad1a6a06fe543ce00eaf0eb3ae1512f43c4c Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 6 Nov 2015 11:29:29 +0100 Subject: [PATCH] DATAJPA-820 - Try to discover more version properties on superclasses. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tweaked the lookup of a version property on a mapped superclass. Hibernate doesn't expose itself as being very supportive in that: on versions below 4.3 the method primarily intended to look it up (IdentifiableType.getVersion(…)) expects you to hand in exactly the type of the property you're trying to find in the first place. Awesome, not. If this fails, we now explicitly traverse the singular attributes and recursively traverse super types. Unfortunately, on the Hibernate version broken as defined above, the check for attribute.isVersion() fails even for the version property as the implementation holds all singular attributes with one for the version property which is not marked as such. tl;dr; - everyone trying to use @Version on a mapped superclass and a primitive identifier in an entity on Hibernate 4.3 will still have to implement Persistable to make sure EntityManager.persist(…) is used for new entities. --- .../JpaMetamodelEntityInformation.java | 44 +++++++++++++++---- .../jpa/domain/sample/AbstractMappedType.java | 11 +++-- ...odelEntityInformationIntegrationTests.java | 18 +++++--- ...odelEntityInformationIntegrationTests.java | 20 ++++++++- ...paMetamodelEntityInformationUnitTests.java | 14 +++--- ...PersistableEntityInformationUnitTests.java | 11 ++--- ...odelEntityInformationIntegrationTests.java | 10 ++++- 7 files changed, 91 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java index a2e6586be..28cfd4033 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2015 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. @@ -66,6 +66,7 @@ public class JpaMetamodelEntityInformation extends J this.metamodel = metamodel; ManagedType type = metamodel.managedType(domainClass); + if (type == null) { throw new IllegalArgumentException("The given domain class can not be found in the given Metamodel!"); } @@ -76,8 +77,10 @@ public class JpaMetamodelEntityInformation extends J throw new IllegalArgumentException("The given domain class does not contain an id attribute!"); } - this.idMetadata = new IdMetadata((IdentifiableType) type); - this.versionAttribute = findVersionAttribute(type); + IdentifiableType identifiableType = (IdentifiableType) type; + + this.idMetadata = new IdMetadata(identifiableType); + this.versionAttribute = findVersionAttribute(identifiableType, metamodel); } /* @@ -93,9 +96,18 @@ public class JpaMetamodelEntityInformation extends J * Returns the version attribute of the given {@link ManagedType} or {@literal null} if none available. * * @param type must not be {@literal null}. + * @param metamodel must not be {@literal null}. * @return */ - private static SingularAttribute findVersionAttribute(ManagedType type) { + @SuppressWarnings("unchecked") + private static SingularAttribute findVersionAttribute(IdentifiableType type, + Metamodel metamodel) { + + try { + return type.getVersion(Object.class); + } catch (IllegalArgumentException o_O) { + // Needs workarounds as the method is implemented with a strict type check on e.g. Hibernate < 4.3 + } Set> attributes = type.getSingularAttributes(); @@ -105,7 +117,21 @@ public class JpaMetamodelEntityInformation extends J } } - return null; + Class superType = type.getJavaType().getSuperclass(); + + try { + + ManagedType managedSuperType = metamodel.managedType(superType); + + if (!(managedSuperType instanceof IdentifiableType)) { + return null; + } + + return (SingularAttribute) findVersionAttribute((IdentifiableType) managedSuperType, metamodel); + + } catch (IllegalArgumentException o_O) { + return null; + } } /* @@ -219,8 +245,8 @@ public class JpaMetamodelEntityInformation extends J public IdMetadata(IdentifiableType source) { this.type = source; - this.attributes = (Set>) (source.hasSingleIdAttribute() ? Collections - .singleton(source.getId(source.getIdType().getJavaType())) : source.getIdClassAttributes()); + this.attributes = (Set>) (source.hasSingleIdAttribute() + ? Collections.singleton(source.getId(source.getIdType().getJavaType())) : source.getIdClassAttributes()); } public boolean hasSimpleId() { @@ -275,8 +301,8 @@ public class JpaMetamodelEntityInformation extends J * * @author Thomas Darimont */ - private static class IdentifierDerivingDirectFieldAccessFallbackBeanWrapper extends - DirectFieldAccessFallbackBeanWrapper { + private static class IdentifierDerivingDirectFieldAccessFallbackBeanWrapper + extends DirectFieldAccessFallbackBeanWrapper { private final Metamodel metamodel; diff --git a/src/test/java/org/springframework/data/jpa/domain/sample/AbstractMappedType.java b/src/test/java/org/springframework/data/jpa/domain/sample/AbstractMappedType.java index 07e407922..2bb4096e9 100644 --- a/src/test/java/org/springframework/data/jpa/domain/sample/AbstractMappedType.java +++ b/src/test/java/org/springframework/data/jpa/domain/sample/AbstractMappedType.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2015 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. @@ -18,19 +18,22 @@ package org.springframework.data.jpa.domain.sample; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.MappedSuperclass; +import javax.persistence.Version; /** * @author Thomas Darimont + * @author Oliver Gierke */ @MappedSuperclass public abstract class AbstractMappedType { + @Id @GeneratedValue Long id; + @Version Long version; + String attribute1; + public AbstractMappedType() {} public AbstractMappedType(String attribute1) { this.attribute1 = attribute1; } - - @Id @GeneratedValue Long id; - String attribute1; } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaMetamodelEntityInformationIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaMetamodelEntityInformationIntegrationTests.java index 3590c8600..af06b0a3c 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaMetamodelEntityInformationIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaMetamodelEntityInformationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2015 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. @@ -30,16 +30,16 @@ import org.springframework.test.context.ContextConfiguration; * @author Oliver Gierke */ @ContextConfiguration("classpath:eclipselink.xml") -public class EclipseLinkJpaMetamodelEntityInformationIntegrationTests extends - JpaMetamodelEntityInformationIntegrationTests { +public class EclipseLinkJpaMetamodelEntityInformationIntegrationTests + extends JpaMetamodelEntityInformationIntegrationTests { /** * Re-activate test. Change to check for {@link String} as OpenJpa defaults {@link Serializable}s to {@link String}. */ @Test public void reactivatedDetectsIdTypeForMappedSuperclass() { - JpaEntityInformation information = JpaEntityInformationSupport.getEntityInformation( - AbstractPersistable.class, em); + JpaEntityInformation information = JpaEntityInformationSupport.getEntityInformation(AbstractPersistable.class, + em); assertEquals(String.class, information.getIdType()); } @@ -61,6 +61,14 @@ public class EclipseLinkJpaMetamodelEntityInformationIntegrationTests extends @Ignore public void considersEntityWithUnsetCompundIdNew() {} + /** + * Re-activate test for DATAJPA-820. + */ + @Test + public void detectsVersionPropertyOnMappedSuperClass() { + super.detectsVersionPropertyOnMappedSuperClass(); + } + @Override protected String getMetadadataPersitenceUnitName() { return "metadata_el"; diff --git a/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationIntegrationTests.java index 0c39a5ef8..a9fd92c48 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2015 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. @@ -39,6 +39,7 @@ import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.data.jpa.domain.AbstractPersistable; +import org.springframework.data.jpa.domain.sample.ConcreteType1; import org.springframework.data.jpa.domain.sample.PersistableWithIdClass; import org.springframework.data.jpa.domain.sample.PersistableWithIdClassPK; import org.springframework.data.jpa.domain.sample.PrimitiveVersionProperty; @@ -51,6 +52,7 @@ import org.springframework.data.jpa.domain.sample.VersionedUser; import org.springframework.data.repository.core.EntityInformation; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.util.ReflectionTestUtils; /** * Integration tests for {@link JpaMetamodelEntityInformation}. @@ -105,7 +107,8 @@ public class JpaMetamodelEntityInformationIntegrationTests { PersistableWithIdClass entity = new PersistableWithIdClass(2L, 4L); - JpaEntityInformation information = getEntityInformation(PersistableWithIdClass.class, em); + JpaEntityInformation information = getEntityInformation(PersistableWithIdClass.class, + em); Object id = information.getId(entity); assertThat(id, is(instanceOf(PersistableWithIdClassPK.class))); @@ -260,6 +263,19 @@ public class JpaMetamodelEntityInformationIntegrationTests { assertThat(information.isNew(user), is(false)); } + /** + * @see DATAJPA-820 - Ignored as Hibernate < 4.3 doesn't expose the version property properly if it's declared on the + * superclass. + */ + @Test + @Ignore + public void detectsVersionPropertyOnMappedSuperClass() { + + EntityInformation information = getEntityInformation(ConcreteType1.class, em); + + assertThat(ReflectionTestUtils.getField(information, "versionAttribute"), is(notNullValue())); + } + protected String getMetadadataPersitenceUnitName() { return "metadata"; } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationUnitTests.java index 5bc55bbcb..04c95cca6 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationUnitTests.java @@ -45,17 +45,12 @@ import org.springframework.data.jpa.domain.sample.PersistableWithIdClassPK; @RunWith(MockitoJUnitRunner.class) public class JpaMetamodelEntityInformationUnitTests { - @Mock - Metamodel metamodel; + @Mock Metamodel metamodel; - @Mock - IdentifiableType type; - @Mock - SingularAttribute first, second; + @Mock IdentifiableType type; + @Mock SingularAttribute first, second; - @Mock - @SuppressWarnings("rawtypes") - Type idType; + @Mock @SuppressWarnings("rawtypes") Type idType; @Before @SuppressWarnings("unchecked") @@ -68,6 +63,7 @@ public class JpaMetamodelEntityInformationUnitTests { when(type.getIdClassAttributes()).thenReturn(attributes); + when(metamodel.managedType(Object.class)).thenThrow(IllegalArgumentException.class); when(metamodel.managedType(PersistableWithIdClass.class)).thenReturn(type); when(type.getIdType()).thenReturn(idType); diff --git a/src/test/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformationUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformationUnitTests.java index 0788e3a37..c4ec425fc 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformationUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformationUnitTests.java @@ -39,20 +39,17 @@ import org.springframework.data.repository.core.EntityInformation; @RunWith(MockitoJUnitRunner.class) public class JpaPersistableEntityInformationUnitTests { - @Mock - Metamodel metamodel; + @Mock Metamodel metamodel; - @Mock - EntityType type; + @Mock EntityType type; - @Mock - @SuppressWarnings("rawtypes") - Type idType; + @Mock @SuppressWarnings("rawtypes") Type idType; @Before @SuppressWarnings("unchecked") public void setUp() { + when(metamodel.managedType(Object.class)).thenThrow(IllegalArgumentException.class); when(metamodel.managedType(Foo.class)).thenReturn(type); when(type.getIdType()).thenReturn(idType); } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaMetamodelEntityInformationIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaMetamodelEntityInformationIntegrationTests.java index c1a1a6c92..c6d23d347 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaMetamodelEntityInformationIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaMetamodelEntityInformationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2015 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. @@ -42,6 +42,14 @@ public class OpenJpaMetamodelEntityInformationIntegrationTests extends JpaMetamo @Ignore public void findsIdClassOnMappedSuperclass() {} + /** + * Re-activate test for DATAJPA-820. + */ + @Test + public void detectsVersionPropertyOnMappedSuperClass() { + super.detectsVersionPropertyOnMappedSuperClass(); + } + @Override protected String getMetadadataPersitenceUnitName() { return "metadata_oj";