DATAJPA-820 - Try to discover more version properties on superclasses.

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.
This commit is contained in:
Oliver Gierke
2015-11-06 11:29:29 +01:00
parent b3324a0e7d
commit 3f7fad1a6a
7 changed files with 91 additions and 37 deletions

View File

@@ -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<T, ID extends Serializable> extends J
this.metamodel = metamodel;
ManagedType<T> 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<T, ID extends Serializable> extends J
throw new IllegalArgumentException("The given domain class does not contain an id attribute!");
}
this.idMetadata = new IdMetadata<T>((IdentifiableType<T>) type);
this.versionAttribute = findVersionAttribute(type);
IdentifiableType<T> identifiableType = (IdentifiableType<T>) type;
this.idMetadata = new IdMetadata<T>(identifiableType);
this.versionAttribute = findVersionAttribute(identifiableType, metamodel);
}
/*
@@ -93,9 +96,18 @@ public class JpaMetamodelEntityInformation<T, ID extends Serializable> 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 <T> SingularAttribute<? super T, ?> findVersionAttribute(ManagedType<T> type) {
@SuppressWarnings("unchecked")
private static <T> SingularAttribute<? super T, ?> findVersionAttribute(IdentifiableType<T> 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<SingularAttribute<? super T, ?>> attributes = type.getSingularAttributes();
@@ -105,7 +117,21 @@ public class JpaMetamodelEntityInformation<T, ID extends Serializable> extends J
}
}
return null;
Class<?> superType = type.getJavaType().getSuperclass();
try {
ManagedType<?> managedSuperType = metamodel.managedType(superType);
if (!(managedSuperType instanceof IdentifiableType)) {
return null;
}
return (SingularAttribute<? super T, ?>) findVersionAttribute((IdentifiableType<T>) managedSuperType, metamodel);
} catch (IllegalArgumentException o_O) {
return null;
}
}
/*
@@ -219,8 +245,8 @@ public class JpaMetamodelEntityInformation<T, ID extends Serializable> extends J
public IdMetadata(IdentifiableType<T> source) {
this.type = source;
this.attributes = (Set<SingularAttribute<? super T, ?>>) (source.hasSingleIdAttribute() ? Collections
.singleton(source.getId(source.getIdType().getJavaType())) : source.getIdClassAttributes());
this.attributes = (Set<SingularAttribute<? super T, ?>>) (source.hasSingleIdAttribute()
? Collections.singleton(source.getId(source.getIdType().getJavaType())) : source.getIdClassAttributes());
}
public boolean hasSimpleId() {
@@ -275,8 +301,8 @@ public class JpaMetamodelEntityInformation<T, ID extends Serializable> extends J
*
* @author Thomas Darimont
*/
private static class IdentifierDerivingDirectFieldAccessFallbackBeanWrapper extends
DirectFieldAccessFallbackBeanWrapper {
private static class IdentifierDerivingDirectFieldAccessFallbackBeanWrapper
extends DirectFieldAccessFallbackBeanWrapper {
private final Metamodel metamodel;

View File

@@ -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;
}

View File

@@ -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";

View File

@@ -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<PersistableWithIdClass, ?> information = getEntityInformation(PersistableWithIdClass.class, em);
JpaEntityInformation<PersistableWithIdClass, ?> 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<ConcreteType1, ?> information = getEntityInformation(ConcreteType1.class, em);
assertThat(ReflectionTestUtils.getField(information, "versionAttribute"), is(notNullValue()));
}
protected String getMetadadataPersitenceUnitName() {
return "metadata";
}

View File

@@ -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<PersistableWithIdClass> type;
@Mock
SingularAttribute<PersistableWithIdClass, ?> first, second;
@Mock IdentifiableType<PersistableWithIdClass> type;
@Mock SingularAttribute<PersistableWithIdClass, ?> 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);

View File

@@ -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<Foo> type;
@Mock EntityType<Foo> 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);
}

View File

@@ -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";