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 452ca7924..000000000 --- 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 000000000..66782b70e --- /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 2fe7a443f..000000000 --- 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 06a3d6b00..6fb6d6b55 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.ArrayList; 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 javax.persistence.criteria.Root; 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 @@ import org.springframework.util.Assert; */ @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 class SimpleJpaRepository extends } + /* + * (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 86f4e142c..1b8f3f0e2 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 000000000..1374c7bed --- /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 d3a769c7c..000000000 --- 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 e74994d4c..a6f3f5b59 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 + + + + + + + + +