From e48ec42be6fc174f57819d035d3102a5726eeaf5 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 22 Feb 2011 20:53:36 +0100 Subject: [PATCH] DATAJPA-28 - Added support for XML based metadata. Added IsNewAware and IdAware implementation that uses JPA 2.0 meta model API to find the id field. Using that instead of JpaAnnotationEntityInformation to be able to use XML based entity mapping as well. Changed mapping of Role sample domain class to XML and created integration test to verify XML metadata is considered as well. --- .../JpaAnnotationEntityInformation.java | 56 ------ .../JpaMetamodelEntityInformation.java | 113 +++++++++++ .../support/JpaRepositorySupport.java | 56 ------ .../support/SimpleJpaRepository.java | 46 ++++- .../data/jpa/domain/sample/Role.java | 15 +- .../RoleRepositoryIntegrationTests.java | 68 +++++++ ...aAnnotationEntityInformationUnitTests.java | 188 ------------------ src/test/resources/META-INF/orm.xml | 9 + 8 files changed, 236 insertions(+), 315 deletions(-) delete mode 100644 src/main/java/org/springframework/data/jpa/repository/support/JpaAnnotationEntityInformation.java create mode 100644 src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java delete mode 100644 src/main/java/org/springframework/data/jpa/repository/support/JpaRepositorySupport.java create mode 100644 src/test/java/org/springframework/data/jpa/repository/RoleRepositoryIntegrationTests.java delete mode 100644 src/test/java/org/springframework/data/jpa/repository/support/JpaAnnotationEntityInformationUnitTests.java diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaAnnotationEntityInformation.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaAnnotationEntityInformation.java deleted file mode 100644 index 452ca79242..0000000000 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaAnnotationEntityInformation.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2008-2011 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 - * - * http://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.repository.support; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.Id; - -import org.springframework.data.repository.support.IsNewAware; -import org.springframework.data.repository.support.ReflectiveEntityInformationSupport; -import org.springframework.util.Assert; - - -/** - * {@link IsNewAware} implementation that reflectively checks a {@link Field} or - * {@link Method} annotated with {@link Id}. - * - * @author Oliver Gierke - */ -public class JpaAnnotationEntityInformation extends - ReflectiveEntityInformationSupport { - - /** - * Creates a new {@link JpaAnnotationEntityInformation} by inspecting the - * given class for a {@link Field} or {@link Method} for and {@link Id} - * annotation. - * - * @param domainClass not {@literal null}, must be annotated with - * {@link Entity} and carry an anootation defining the id - * property. - */ - @SuppressWarnings("unchecked") - public JpaAnnotationEntityInformation(Class domainClass) { - - super(domainClass, Id.class, EmbeddedId.class); - - Assert.isTrue(domainClass.isAnnotationPresent(Entity.class), - "Given domain class was not annotated with @Entity!"); - } -} \ No newline at end of file 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 new file mode 100644 index 0000000000..66782b70ed --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java @@ -0,0 +1,113 @@ +/* + * Copyright 2008-2011 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 + * + * http://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.repository.support; + +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; + +import javax.persistence.metamodel.EntityType; +import javax.persistence.metamodel.Metamodel; +import javax.persistence.metamodel.SingularAttribute; + +import org.springframework.data.repository.support.IdAware; +import org.springframework.data.repository.support.IsNewAware; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + + +/** + * Implementation of {@link IsNewAware} and {@link IdAware} that uses JPA + * {@link Metamodel} to find the domain class' id field. + * + * @author Oliver Gierke + */ +public class JpaMetamodelEntityInformation implements IsNewAware, IdAware { + + private final Member member; + + + /** + * Creates a new {@link JpaMetamodelEntityInformation} for the given domain + * class and {@link Metamodel}. + * + * @param domainClass + * @param metamodel + */ + public JpaMetamodelEntityInformation(Class domainClass, + Metamodel metamodel) { + + Assert.notNull(domainClass); + Assert.notNull(metamodel); + + EntityType type = metamodel.entity(domainClass); + + SingularAttribute idAttribute = + type.getId(type.getIdType().getJavaType()); + this.member = idAttribute.getJavaMember(); + } + + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.support.IdAware#getId(java.lang.Object + * ) + */ + public Object getId(Object entity) { + + return getMemberValue(member, entity); + } + + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.support.IsNewAware#isNew(java.lang + * .Object) + */ + public boolean isNew(Object entity) { + + return getId(entity) == null; + } + + + /** + * Returns the value of the given {@link Member} of the given {@link Object} + * . + * + * @param member + * @param source + * @return + */ + private static Object getMemberValue(Member member, Object source) { + + if (member instanceof Field) { + Field field = (Field) member; + ReflectionUtils.makeAccessible(field); + return ReflectionUtils.getField(field, source); + } else if (member instanceof Method) { + Method method = (Method) member; + ReflectionUtils.makeAccessible(method); + return ReflectionUtils.invokeMethod(method, source); + } + + throw new IllegalArgumentException( + "Given member is neither Field nor Method!"); + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositorySupport.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositorySupport.java deleted file mode 100644 index 2fe7a443f5..0000000000 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositorySupport.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2008-2011 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 - * - * http://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.repository.support; - -import java.io.Serializable; - -import org.springframework.data.domain.Persistable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.repository.support.IsNewAware; -import org.springframework.data.repository.support.PersistableEntityInformation; -import org.springframework.data.repository.support.RepositorySupport; - - -/** - * Base class to implement JPA CRUD repository implementations. Will use a - * {@link JpaAnnotationEntityInformation} by default but prefer a - * {@link PersistableEntityInformation} in case the type to be handled - * implements {@link Persistable}. - * - * @author Oliver Gierke - */ -public abstract class JpaRepositorySupport extends - RepositorySupport implements JpaRepository { - - /** - * @param domainClass - */ - public JpaRepositorySupport(Class domainClass) { - - super(domainClass); - } - - - @Override - protected IsNewAware createIsNewStrategy(Class domainClass) { - - if (Persistable.class.isAssignableFrom(domainClass)) { - return new PersistableEntityInformation(); - } else { - return new JpaAnnotationEntityInformation(domainClass); - } - } -} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index 06a3d6b00a..6fb6d6b551 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -22,6 +22,7 @@ import java.util.List; import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; import javax.persistence.NoResultException; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; @@ -32,9 +33,14 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Persistable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.Repository; +import org.springframework.data.repository.support.IsNewAware; +import org.springframework.data.repository.support.PersistableEntityInformation; +import org.springframework.data.repository.support.RepositorySupport; import org.springframework.util.Assert; @@ -49,7 +55,7 @@ */ @org.springframework.stereotype.Repository public class SimpleJpaRepository extends - JpaRepositorySupport { + RepositorySupport implements JpaRepository { private final EntityManager em; private final PersistenceProvider provider; @@ -355,7 +361,45 @@ public void flush() { } + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.jpa.repository.support.JpaRepositorySupport# + * createIsNewStrategy(java.lang.Class) + */ + @Override + protected final IsNewAware createIsNewStrategy(Class domainClass) { + + return createIsNewStrategy(domainClass, em); + } + + /** + * Creates a new {@link IsNewAware} instance for the given domain class and + * {@link EntityManager}. + * + * @param domainClass + * @param em + * @return + */ + protected IsNewAware createIsNewStrategy(Class domainClass, + EntityManager em) { + + if (Persistable.class.isAssignableFrom(domainClass)) { + return new PersistableEntityInformation(); + } else { + EntityManagerFactory emf = em.getEntityManagerFactory(); + return new JpaMetamodelEntityInformation(domainClass, + emf.getMetamodel()); + } + } + + + /** + * Reads the given {@link TypedQuery} into a {@link Page} applying the given + * {@link Pageable} and {@link Specification}. + * * @param query * @param spec * @param pageable diff --git a/src/test/java/org/springframework/data/jpa/domain/sample/Role.java b/src/test/java/org/springframework/data/jpa/domain/sample/Role.java index 86f4e142c2..1b8f3f0e27 100644 --- a/src/test/java/org/springframework/data/jpa/domain/sample/Role.java +++ b/src/test/java/org/springframework/data/jpa/domain/sample/Role.java @@ -15,30 +15,17 @@ */ package org.springframework.data.jpa.domain.sample; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; - - /** - * Example implementation of the very basic {@code Persistable} interface. The - * id type is matching the typisation of the interface. - * {@code Persitsable#isNew()} is implemented regarding the id as flag. + * Sample domain class representing roles. Mapped with XML. * * @author Oliver Gierke */ -@Entity public class Role { private static final long serialVersionUID = -8832631113344035104L; - private static final String PREFIX = "ROLE_"; - @Id - @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; - private String name; diff --git a/src/test/java/org/springframework/data/jpa/repository/RoleRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/RoleRepositoryIntegrationTests.java new file mode 100644 index 0000000000..1374c7bedf --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/RoleRepositoryIntegrationTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2011 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 + * + * http://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.repository; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.domain.sample.Role; +import org.springframework.data.jpa.repository.sample.RoleRepository; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.transaction.annotation.Transactional; + + +/** + * Integration tests for {@link RoleRepository}. + * + * @author Oliver Gierke + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "classpath:application-context.xml" }) +@Transactional +public class RoleRepositoryIntegrationTests { + + @Autowired + RoleRepository repository; + + + @Test + public void createsRole() throws Exception { + + Role reference = new Role("ADMIN"); + Role result = repository.save(reference); + assertThat(result, is(reference)); + } + + + @Test + public void updatesRole() throws Exception { + + Role reference = new Role("ADMIN"); + Role result = repository.save(reference); + assertThat(result, is(reference)); + + // Change role name + ReflectionTestUtils.setField(reference, "name", "USER"); + repository.save(reference); + + assertThat(repository.findById(result.getId()), is(reference)); + } +} diff --git a/src/test/java/org/springframework/data/jpa/repository/support/JpaAnnotationEntityInformationUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/JpaAnnotationEntityInformationUnitTests.java deleted file mode 100644 index d3a769c7c8..0000000000 --- a/src/test/java/org/springframework/data/jpa/repository/support/JpaAnnotationEntityInformationUnitTests.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright 2008-2011 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 - * - * http://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.repository.support; - -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; - -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.Id; - -import org.junit.Test; -import org.springframework.data.repository.support.IdAware; -import org.springframework.data.repository.support.IsNewAware; - - -/** - * Unit test for various implementations of {@link IsNewAware}. - * - * @author Oliver Gierke - */ -public class JpaAnnotationEntityInformationUnitTests { - - @Test(expected = IllegalArgumentException.class) - public void rejectsNullAsDomainClass() throws Exception { - - new JpaAnnotationEntityInformation(null); - } - - - @Test(expected = IllegalArgumentException.class) - public void rejectsNonEntityClasses() throws Exception { - - new JpaAnnotationEntityInformation(NotAnnotatedEntity.class); - } - - - @Test(expected = IllegalArgumentException.class) - public void rejectsEntityWithMissingIdAnnotation() throws Exception { - - new JpaAnnotationEntityInformation(EntityWithOutIdAnnotation.class); - } - - - @Test - public void detectsFieldAnnotatedIdCorrectly() throws Exception { - - JpaAnnotationEntityInformation info = - new JpaAnnotationEntityInformation(FieldAnnotatedEntity.class); - - assertNewAndNoId(info, new FieldAnnotatedEntity(null)); - assertNotNewAndId(info, new FieldAnnotatedEntity(1L), 1L); - } - - - @Test - public void detectsEmbeddedIdFieldAnnotatedIdCorrectly() throws Exception { - - JpaAnnotationEntityInformation info = - new JpaAnnotationEntityInformation( - EmbeddedIdFieldAnnotatedEntity.class); - - assertNewAndNoId(info, new EmbeddedIdFieldAnnotatedEntity(null)); - assertNotNewAndId(info, new EmbeddedIdFieldAnnotatedEntity(1L), 1L); - } - - - @Test - public void detectsMethodAnnotatedIdCorrectly() throws Exception { - - JpaAnnotationEntityInformation info = - new JpaAnnotationEntityInformation(MethodAnnotatedEntity.class); - - assertNewAndNoId(info, new MethodAnnotatedEntity()); - - MethodAnnotatedEntity entity = new MethodAnnotatedEntity(); - entity.id = 1L; - assertNotNewAndId(info, entity, 1L); - } - - - @Test - public void detectsEmbeddedIdMethodAnnotatedIdCorrectly() throws Exception { - - JpaAnnotationEntityInformation strategy = - new JpaAnnotationEntityInformation( - EmbeddedIdMethodAnnotatedEntity.class); - - assertNewAndNoId(strategy, new EmbeddedIdMethodAnnotatedEntity()); - - EmbeddedIdMethodAnnotatedEntity entity = - new EmbeddedIdMethodAnnotatedEntity(); - entity.id = 1L; - assertNotNewAndId(strategy, entity, 1L); - } - - - private void assertNewAndNoId(T info, - Object entity) { - - assertThat(info.isNew(entity), is(true)); - assertThat(info.getId(entity), is(nullValue())); - } - - - private void assertNotNewAndId(T info, - Object entity, Object id) { - - assertThat(info.isNew(entity), is(false)); - assertThat(info.getId(entity), is(id)); - } - - @Entity - static class FieldAnnotatedEntity { - - @Id - Long id; - - - public FieldAnnotatedEntity(Long id) { - - this.id = id; - } - } - - @Entity - static class EmbeddedIdFieldAnnotatedEntity { - - @EmbeddedId - Long id; - - - public EmbeddedIdFieldAnnotatedEntity(Long id) { - - this.id = id; - } - } - - @Entity - static class MethodAnnotatedEntity { - - private Long id; - - - @Id - public Long getId() { - - return id; - } - } - - @Entity - static class EmbeddedIdMethodAnnotatedEntity { - - private Long id; - - - @EmbeddedId - public Long getId() { - - return id; - } - } - - static class NotAnnotatedEntity { - - @Id - public Long id; - } - - @Entity - static class EntityWithOutIdAnnotation { - - } -} diff --git a/src/test/resources/META-INF/orm.xml b/src/test/resources/META-INF/orm.xml index e74994d4c2..a6f3f5b592 100644 --- a/src/test/resources/META-INF/orm.xml +++ b/src/test/resources/META-INF/orm.xml @@ -13,4 +13,13 @@ SELECT u FROM User u WHERE u.lastname = ?1 + + + + + + + + +