Skip to content

Commit c3ffa47

Browse files
committed
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.
1 parent f173a6f commit c3ffa47

File tree

7 files changed

+91
-37
lines changed

7 files changed

+91
-37
lines changed

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

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2011-2014 the original author or authors.
2+
* Copyright 2011-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -66,6 +66,7 @@ public JpaMetamodelEntityInformation(Class<T> domainClass, Metamodel metamodel)
6666
this.metamodel = metamodel;
6767

6868
ManagedType<T> type = metamodel.managedType(domainClass);
69+
6970
if (type == null) {
7071
throw new IllegalArgumentException("The given domain class can not be found in the given Metamodel!");
7172
}
@@ -76,8 +77,10 @@ public JpaMetamodelEntityInformation(Class<T> domainClass, Metamodel metamodel)
7677
throw new IllegalArgumentException("The given domain class does not contain an id attribute!");
7778
}
7879

79-
this.idMetadata = new IdMetadata<T>((IdentifiableType<T>) type);
80-
this.versionAttribute = findVersionAttribute(type);
80+
IdentifiableType<T> identifiableType = (IdentifiableType<T>) type;
81+
82+
this.idMetadata = new IdMetadata<T>(identifiableType);
83+
this.versionAttribute = findVersionAttribute(identifiableType, metamodel);
8184
}
8285

8386
/*
@@ -93,9 +96,18 @@ public String getEntityName() {
9396
* Returns the version attribute of the given {@link ManagedType} or {@literal null} if none available.
9497
*
9598
* @param type must not be {@literal null}.
99+
* @param metamodel must not be {@literal null}.
96100
* @return
97101
*/
98-
private static <T> SingularAttribute<? super T, ?> findVersionAttribute(ManagedType<T> type) {
102+
@SuppressWarnings("unchecked")
103+
private static <T> SingularAttribute<? super T, ?> findVersionAttribute(IdentifiableType<T> type,
104+
Metamodel metamodel) {
105+
106+
try {
107+
return type.getVersion(Object.class);
108+
} catch (IllegalArgumentException o_O) {
109+
// Needs workarounds as the method is implemented with a strict type check on e.g. Hibernate < 4.3
110+
}
99111

100112
Set<SingularAttribute<? super T, ?>> attributes = type.getSingularAttributes();
101113

@@ -105,7 +117,21 @@ public String getEntityName() {
105117
}
106118
}
107119

108-
return null;
120+
Class<?> superType = type.getJavaType().getSuperclass();
121+
122+
try {
123+
124+
ManagedType<?> managedSuperType = metamodel.managedType(superType);
125+
126+
if (!(managedSuperType instanceof IdentifiableType)) {
127+
return null;
128+
}
129+
130+
return (SingularAttribute<? super T, ?>) findVersionAttribute((IdentifiableType<T>) managedSuperType, metamodel);
131+
132+
} catch (IllegalArgumentException o_O) {
133+
return null;
134+
}
109135
}
110136

111137
/*
@@ -219,8 +245,8 @@ private static class IdMetadata<T> implements Iterable<SingularAttribute<? super
219245
public IdMetadata(IdentifiableType<T> source) {
220246

221247
this.type = source;
222-
this.attributes = (Set<SingularAttribute<? super T, ?>>) (source.hasSingleIdAttribute() ? Collections
223-
.singleton(source.getId(source.getIdType().getJavaType())) : source.getIdClassAttributes());
248+
this.attributes = (Set<SingularAttribute<? super T, ?>>) (source.hasSingleIdAttribute()
249+
? Collections.singleton(source.getId(source.getIdType().getJavaType())) : source.getIdClassAttributes());
224250
}
225251

226252
public boolean hasSimpleId() {
@@ -275,8 +301,8 @@ private static Class<?> fallbackIdTypeLookup(IdentifiableType<?> type) {
275301
*
276302
* @author Thomas Darimont
277303
*/
278-
private static class IdentifierDerivingDirectFieldAccessFallbackBeanWrapper extends
279-
DirectFieldAccessFallbackBeanWrapper {
304+
private static class IdentifierDerivingDirectFieldAccessFallbackBeanWrapper
305+
extends DirectFieldAccessFallbackBeanWrapper {
280306

281307
private final Metamodel metamodel;
282308

src/test/java/org/springframework/data/jpa/domain/sample/AbstractMappedType.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013 the original author or authors.
2+
* Copyright 2013-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,19 +18,22 @@
1818
import javax.persistence.GeneratedValue;
1919
import javax.persistence.Id;
2020
import javax.persistence.MappedSuperclass;
21+
import javax.persistence.Version;
2122

2223
/**
2324
* @author Thomas Darimont
25+
* @author Oliver Gierke
2426
*/
2527
@MappedSuperclass
2628
public abstract class AbstractMappedType {
2729

30+
@Id @GeneratedValue Long id;
31+
@Version Long version;
32+
String attribute1;
33+
2834
public AbstractMappedType() {}
2935

3036
public AbstractMappedType(String attribute1) {
3137
this.attribute1 = attribute1;
3238
}
33-
34-
@Id @GeneratedValue Long id;
35-
String attribute1;
3639
}

src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaMetamodelEntityInformationIntegrationTests.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013 the original author or authors.
2+
* Copyright 2013-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,16 +30,16 @@
3030
* @author Oliver Gierke
3131
*/
3232
@ContextConfiguration("classpath:eclipselink.xml")
33-
public class EclipseLinkJpaMetamodelEntityInformationIntegrationTests extends
34-
JpaMetamodelEntityInformationIntegrationTests {
33+
public class EclipseLinkJpaMetamodelEntityInformationIntegrationTests
34+
extends JpaMetamodelEntityInformationIntegrationTests {
3535

3636
/**
3737
* Re-activate test. Change to check for {@link String} as OpenJpa defaults {@link Serializable}s to {@link String}.
3838
*/
3939
@Test
4040
public void reactivatedDetectsIdTypeForMappedSuperclass() {
41-
JpaEntityInformation<?, ?> information = JpaEntityInformationSupport.getEntityInformation(
42-
AbstractPersistable.class, em);
41+
JpaEntityInformation<?, ?> information = JpaEntityInformationSupport.getEntityInformation(AbstractPersistable.class,
42+
em);
4343
assertEquals(String.class, information.getIdType());
4444
}
4545

@@ -61,6 +61,14 @@ public void detectsNewStateForEntityWithPrimitiveId() {}
6161
@Ignore
6262
public void considersEntityWithUnsetCompundIdNew() {}
6363

64+
/**
65+
* Re-activate test for DATAJPA-820.
66+
*/
67+
@Test
68+
public void detectsVersionPropertyOnMappedSuperClass() {
69+
super.detectsVersionPropertyOnMappedSuperClass();
70+
}
71+
6472
@Override
6573
protected String getMetadadataPersitenceUnitName() {
6674
return "metadata_el";

src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationIntegrationTests.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2011-2014 the original author or authors.
2+
* Copyright 2011-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -39,6 +39,7 @@
3939
import org.junit.Test;
4040
import org.junit.runner.RunWith;
4141
import org.springframework.data.jpa.domain.AbstractPersistable;
42+
import org.springframework.data.jpa.domain.sample.ConcreteType1;
4243
import org.springframework.data.jpa.domain.sample.PersistableWithIdClass;
4344
import org.springframework.data.jpa.domain.sample.PersistableWithIdClassPK;
4445
import org.springframework.data.jpa.domain.sample.PrimitiveVersionProperty;
@@ -51,6 +52,7 @@
5152
import org.springframework.data.repository.core.EntityInformation;
5253
import org.springframework.test.context.ContextConfiguration;
5354
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
55+
import org.springframework.test.util.ReflectionTestUtils;
5456

5557
/**
5658
* Integration tests for {@link JpaMetamodelEntityInformation}.
@@ -105,7 +107,8 @@ public void returnsIdInstanceCorrectly() {
105107

106108
PersistableWithIdClass entity = new PersistableWithIdClass(2L, 4L);
107109

108-
JpaEntityInformation<PersistableWithIdClass, ?> information = getEntityInformation(PersistableWithIdClass.class, em);
110+
JpaEntityInformation<PersistableWithIdClass, ?> information = getEntityInformation(PersistableWithIdClass.class,
111+
em);
109112
Object id = information.getId(entity);
110113

111114
assertThat(id, is(instanceOf(PersistableWithIdClassPK.class)));
@@ -260,6 +263,19 @@ public void considersEntityWithNonPrimitiveNonNullIdTypeNotNew() {
260263
assertThat(information.isNew(user), is(false));
261264
}
262265

266+
/**
267+
* @see DATAJPA-820 - Ignored as Hibernate < 4.3 doesn't expose the version property properly if it's declared on the
268+
* superclass.
269+
*/
270+
@Test
271+
@Ignore
272+
public void detectsVersionPropertyOnMappedSuperClass() {
273+
274+
EntityInformation<ConcreteType1, ?> information = getEntityInformation(ConcreteType1.class, em);
275+
276+
assertThat(ReflectionTestUtils.getField(information, "versionAttribute"), is(notNullValue()));
277+
}
278+
263279
protected String getMetadadataPersitenceUnitName() {
264280
return "metadata";
265281
}

src/test/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformationUnitTests.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,12 @@
4545
@RunWith(MockitoJUnitRunner.class)
4646
public class JpaMetamodelEntityInformationUnitTests {
4747

48-
@Mock
49-
Metamodel metamodel;
48+
@Mock Metamodel metamodel;
5049

51-
@Mock
52-
IdentifiableType<PersistableWithIdClass> type;
53-
@Mock
54-
SingularAttribute<PersistableWithIdClass, ?> first, second;
50+
@Mock IdentifiableType<PersistableWithIdClass> type;
51+
@Mock SingularAttribute<PersistableWithIdClass, ?> first, second;
5552

56-
@Mock
57-
@SuppressWarnings("rawtypes")
58-
Type idType;
53+
@Mock @SuppressWarnings("rawtypes") Type idType;
5954

6055
@Before
6156
@SuppressWarnings("unchecked")
@@ -68,6 +63,7 @@ public void setUp() {
6863

6964
when(type.getIdClassAttributes()).thenReturn(attributes);
7065

66+
when(metamodel.managedType(Object.class)).thenThrow(IllegalArgumentException.class);
7167
when(metamodel.managedType(PersistableWithIdClass.class)).thenReturn(type);
7268

7369
when(type.getIdType()).thenReturn(idType);

src/test/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformationUnitTests.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,17 @@
3939
@RunWith(MockitoJUnitRunner.class)
4040
public class JpaPersistableEntityInformationUnitTests {
4141

42-
@Mock
43-
Metamodel metamodel;
42+
@Mock Metamodel metamodel;
4443

45-
@Mock
46-
EntityType<Foo> type;
44+
@Mock EntityType<Foo> type;
4745

48-
@Mock
49-
@SuppressWarnings("rawtypes")
50-
Type idType;
46+
@Mock @SuppressWarnings("rawtypes") Type idType;
5147

5248
@Before
5349
@SuppressWarnings("unchecked")
5450
public void setUp() {
5551

52+
when(metamodel.managedType(Object.class)).thenThrow(IllegalArgumentException.class);
5653
when(metamodel.managedType(Foo.class)).thenReturn(type);
5754
when(type.getIdType()).thenReturn(idType);
5855
}

src/test/java/org/springframework/data/jpa/repository/support/OpenJpaMetamodelEntityInformationIntegrationTests.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013 the original author or authors.
2+
* Copyright 2013-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -42,6 +42,14 @@ public void reactivatedDetectsIdTypeForMappedSuperclass() {
4242
@Ignore
4343
public void findsIdClassOnMappedSuperclass() {}
4444

45+
/**
46+
* Re-activate test for DATAJPA-820.
47+
*/
48+
@Test
49+
public void detectsVersionPropertyOnMappedSuperClass() {
50+
super.detectsVersionPropertyOnMappedSuperClass();
51+
}
52+
4553
@Override
4654
protected String getMetadadataPersitenceUnitName() {
4755
return "metadata_oj";

0 commit comments

Comments
 (0)