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.
This commit is contained in:
Oliver Gierke
2011-02-22 20:53:36 +01:00
parent 14a69eac43
commit e48ec42be6
8 changed files with 236 additions and 315 deletions

View File

@@ -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!");
}
}

View File

@@ -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!");
}
}

View File

@@ -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<T, ID extends Serializable> extends
RepositorySupport<T, ID> implements JpaRepository<T, ID> {
/**
* @param domainClass
*/
public JpaRepositorySupport(Class<T> domainClass) {
super(domainClass);
}
@Override
protected IsNewAware createIsNewStrategy(Class<?> domainClass) {
if (Persistable.class.isAssignableFrom(domainClass)) {
return new PersistableEntityInformation();
} else {
return new JpaAnnotationEntityInformation(domainClass);
}
}
}

View File

@@ -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<T, ID extends Serializable> extends
JpaRepositorySupport<T, ID> {
RepositorySupport<T, ID> implements JpaRepository<T, ID> {
private final EntityManager em;
private final PersistenceProvider provider;
@@ -355,7 +361,45 @@ public class SimpleJpaRepository<T, ID extends Serializable> 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

View File

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

View File

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

View File

@@ -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 <T extends IdAware & IsNewAware> void assertNewAndNoId(T info,
Object entity) {
assertThat(info.isNew(entity), is(true));
assertThat(info.getId(entity), is(nullValue()));
}
private <T extends IdAware & IsNewAware> 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 {
}
}

View File

@@ -13,4 +13,13 @@
<query>SELECT u FROM User u WHERE u.lastname = ?1</query>
</named-query>
<entity class="org.springframework.data.jpa.domain.sample.Role" access="FIELD">
<attributes>
<id name="id">
<generated-value strategy="AUTO" />
</id>
<basic name="name" />
</attributes>
</entity>
</entity-mappings>