DATAJPA-32 - Adopted changes for DATACMNS-17.

This commit is contained in:
Oliver Gierke
2011-02-24 17:30:03 +00:00
parent 44979ac246
commit 99a05d5bb0
10 changed files with 128 additions and 247 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2011 the original author or authors.
* 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.
@@ -23,38 +23,35 @@ 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.data.repository.support.AbstractEntityMetadata;
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.
* Implementation of {@link EntityInformation} that uses JPA {@link Metamodel}
* to find the domain class' id field.
*
* @author Oliver Gierke
*/
public class JpaMetamodelEntityInformation implements IsNewAware, IdAware {
public class JpaMetamodelEntityMetadata<T> extends AbstractEntityMetadata<T> {
private final Member member;
/**
* Creates a new {@link JpaMetamodelEntityInformation} for the given domain
* Creates a new {@link JpaMetamodelEntityMetadata} for the given domain
* class and {@link Metamodel}.
*
* @param domainClass
* @param metamodel
*/
public JpaMetamodelEntityInformation(Class<?> domainClass,
Metamodel metamodel) {
public JpaMetamodelEntityMetadata(Class<T> domainClass, Metamodel metamodel) {
super(domainClass);
Assert.notNull(domainClass);
Assert.notNull(metamodel);
EntityType<?> type = metamodel.entity(domainClass);
SingularAttribute<?, ?> idAttribute =
type.getId(type.getIdType().getJavaType());
this.member = idAttribute.getJavaMember();
@@ -74,19 +71,6 @@ public class JpaMetamodelEntityInformation implements IsNewAware, IdAware {
}
/*
* (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}
* .

View File

@@ -15,16 +15,17 @@
*/
package org.springframework.data.jpa.repository.support;
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.domain.Persistable;
import org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy;
import org.springframework.data.jpa.repository.query.QueryExtractor;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.support.EntityMetadata;
import org.springframework.data.repository.support.PersistableEntityMetadata;
import org.springframework.data.repository.support.RepositoryFactorySupport;
import org.springframework.data.repository.support.RepositorySupport;
import org.springframework.data.repository.support.RepositoryMetadata;
import org.springframework.util.Assert;
@@ -60,11 +61,9 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
* getTargetRepository(java.lang.Class)
*/
@Override
protected <T, ID extends Serializable> RepositorySupport<T, ID> getTargetRepository(
Class<T> domainClass, Class<?> repositoryInterface) {
protected Object getTargetRepository(RepositoryMetadata metadata) {
return getTargetRepository(domainClass, repositoryInterface,
entityManager);
return getTargetRepository(metadata, entityManager);
}
@@ -74,16 +73,40 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
*
* @param <T>
* @param <ID>
* @param domainClass
* @param entityManager
* @see #getTargetRepository(Class, Class)
* @see #getTargetRepository(RepositoryMetadata)
* @return
*/
protected <T, ID extends Serializable> RepositorySupport<T, ID> getTargetRepository(
Class<T> domainClass, Class<?> repositoryInterface,
@SuppressWarnings({ "unchecked", "rawtypes" })
protected Object getTargetRepository(RepositoryMetadata metadata,
EntityManager entityManager) {
return new SimpleJpaRepository<T, ID>(domainClass, entityManager);
return new SimpleJpaRepository(createEntityInformation(
metadata.getDomainClass(), entityManager), entityManager);
}
/**
* Creates a new {@link EntityMetadata} instance for the given domain class
* and {@link EntityManager}. Default implementation will use a
* {@link PersistableMetadata} for domain classes implementing
* {@link Persistable} and fall back to the JPA meta model though
* {@link JpaMetamodelEntityInformation} otherwise.
*
* @param domainClass
* @param em
* @return
*/
@SuppressWarnings("unchecked")
protected EntityMetadata<?> createEntityInformation(Class<?> domainClass,
EntityManager em) {
if (Persistable.class.isAssignableFrom(domainClass)) {
return new PersistableEntityMetadata();
} else {
return new JpaMetamodelEntityMetadata(domainClass,
em.getMetamodel());
}
}
@@ -92,12 +115,10 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
*
* @see
* org.springframework.data.repository.support.RepositoryFactorySupport#
* getRepositoryClass(java.lang.Class)
* getRepositoryBaseClass()
*/
@Override
@SuppressWarnings("rawtypes")
protected Class<? extends RepositorySupport> getRepositoryClass(
Class<?> repositoryInterface) {
protected Class<?> getRepositoryBaseClass(Class<?> repositoryInterface) {
return SimpleJpaRepository.class;
}

View File

@@ -22,7 +22,6 @@ 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;
@@ -33,14 +32,11 @@ 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.data.repository.support.EntityMetadata;
import org.springframework.util.Assert;
@@ -54,9 +50,10 @@ import org.springframework.util.Assert;
* @param <ID> the type of the entity's identifier
*/
@org.springframework.stereotype.Repository
public class SimpleJpaRepository<T, ID extends Serializable> extends
RepositorySupport<T, ID> implements JpaRepository<T, ID> {
public class SimpleJpaRepository<T, ID extends Serializable> implements
JpaRepository<T, ID> {
private final EntityMetadata<T> entityInformation;
private final EntityManager em;
private final PersistenceProvider provider;
@@ -65,19 +62,26 @@ public class SimpleJpaRepository<T, ID extends Serializable> extends
* Creates a new {@link SimpleJpaRepository} to manage objects of the given
* domain type.
*
* @param domainClass
* @param entityInformation
* @param entityManager
*/
public SimpleJpaRepository(Class<T> domainClass, EntityManager entityManager) {
super(domainClass);
public SimpleJpaRepository(EntityMetadata<T> entityInformation,
EntityManager entityManager) {
Assert.notNull(entityInformation);
Assert.notNull(entityManager);
this.entityInformation = entityInformation;
this.em = entityManager;
this.provider = PersistenceProvider.fromEntityManager(entityManager);
}
private Class<T> getDomainClass() {
return entityInformation.getJavaType();
}
private String getDeleteAllQueryString() {
return getQueryString(DELETE_ALL_QUERY_STRING, getDomainClass());
@@ -94,22 +98,6 @@ public class SimpleJpaRepository<T, ID extends Serializable> extends
}
/**
* Factory method to create {@link SimpleJpaRepository} instances.
*
* @param domainClass the domain class to handle
* @param entityManager the {@link EntityManager} backing the repository
* @param <T> the type of the entity to handle
* @param <ID> the type of the entity's identifier
* @return
*/
public static <T, ID extends Serializable> Repository<T, ID> create(
Class<T> domainClass, EntityManager entityManager) {
return new SimpleJpaRepository<T, ID>(domainClass, entityManager);
}
/*
* (non-Javadoc)
*
@@ -303,7 +291,7 @@ public class SimpleJpaRepository<T, ID extends Serializable> extends
*/
public T save(T entity) {
if (getIsNewStrategy().isNew(entity)) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
@@ -361,41 +349,6 @@ 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}.

View File

@@ -5,6 +5,7 @@ Changes in version 1.0.0.M2
----------------------------------------
* Added support for 'Distinct' (DATACMNS-15)
* Added support for 'In' and 'NotIn' (DATAJPA-30)
* Adapted new metadata API (DATAJPA-32, DATACMNS-17)
Changes in version 1.0.0.M1 (2011-02-10) - https://jira.springsource.org/browse/DATAJPA/fixforversion/11786
----------------------------------------

View File

@@ -20,6 +20,7 @@ import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.support.EntityMetadata;
/**
@@ -35,10 +36,10 @@ public class CustomGenericJpaRepository<T, ID extends Serializable> extends
* @param domainClass
* @param entityManager
*/
public CustomGenericJpaRepository(Class<T> domainClass,
public CustomGenericJpaRepository(EntityMetadata<T> metadata,
EntityManager entityManager) {
super(domainClass, entityManager);
super(metadata, entityManager);
}

View File

@@ -15,12 +15,15 @@
*/
package org.springframework.data.jpa.repository.custom;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.repository.support.RepositorySupport;
import org.springframework.data.repository.support.EntityMetadata;
import org.springframework.data.repository.support.RepositoryMetadata;
/**
@@ -48,10 +51,15 @@ public class CustomGenericJpaRepositoryFactory extends JpaRepositoryFactory {
* #getTargetRepository(java.lang.Class, javax.persistence.EntityManager)
*/
@Override
protected <T, ID extends Serializable> RepositorySupport<T, ID> getTargetRepository(
Class<T> domainClass, Class<?> repositoryInterface, EntityManager em) {
@SuppressWarnings("unchecked")
protected Object getTargetRepository(RepositoryMetadata metadata,
EntityManager em) {
return new CustomGenericJpaRepository<T, ID>(domainClass, em);
EntityMetadata<Object> entityMetadata = mock(EntityMetadata.class);
when(entityMetadata.getJavaType()).thenReturn(
(Class<Object>) metadata.getDomainClass());
return new CustomGenericJpaRepository<Object, Serializable>(
entityMetadata, em);
}
@@ -59,13 +67,11 @@ public class CustomGenericJpaRepositoryFactory extends JpaRepositoryFactory {
* (non-Javadoc)
*
* @see
* org.springframework.data.jpa.repository.support.GenericJpaRepositoryFactory
* #getRepositoryClass()
* org.springframework.data.repository.support.RepositoryFactorySupport#
* getRepositoryBaseClass()
*/
@Override
@SuppressWarnings("rawtypes")
protected Class<? extends RepositorySupport> getRepositoryClass(
Class<?> repositoryInterface) {
protected Class<?> getRepositoryBaseClass(Class<?> repositoryInterface) {
return CustomGenericJpaRepository.class;
}

View File

@@ -15,24 +15,16 @@
*/
package org.springframework.data.jpa.repository.support;
import static org.mockito.Mockito.*;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import org.hibernate.ejb.HibernateEntityManager;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.jpa.repository.config.AbstractRepositoryConfigTests;
import org.springframework.orm.jpa.EntityManagerFactoryInfo;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
/**
@@ -41,83 +33,40 @@ import org.springframework.test.context.ContextConfiguration;
*
* @author Oliver Gierke
*/
@ContextConfiguration(locations = "classpath:multiple-entity-manager-context.xml")
public class EntityManagerFactoryRefUnitTests extends
AbstractRepositoryConfigTests {
@Autowired
@Qualifier("entityManagerFactory")
EntityManagerFactory first;
@Autowired
@Qualifier("secondEntityManagerFactory")
EntityManagerFactory second;
public class EntityManagerFactoryRefUnitTests {
@Test
public void repositoriesGetTheSecondEntityManagerFactoryInjected()
throws Exception {
public void repositoriesGetTheSecondEntityManagerFactoryInjected2() {
verify(first, never()).createEntityManager();
verify(second, atLeastOnce()).createEntityManager();
XmlBeanFactory factory =
new XmlBeanFactory(new ClassPathResource(
"multiple-entity-manager-context.xml"));
BeanDefinition bean = factory.getBeanDefinition("userRepository");
Object value = getPropertyValue(bean, "entityManager");
assertTrue(value instanceof BeanDefinition);
BeanDefinition emCreator = (BeanDefinition) value;
BeanReference reference = getConstructorBeanReference(emCreator, 0);
assertThat(reference.getBeanName(), is("secondEntityManagerFactory"));
}
/**
* A simple No-Op {@link PersistenceExceptionTranslator} to be configured in
* the test case's config file as it is required.
*
* @author Oliver Gierke
*/
static class NoOpPersistenceExceptionTranslator implements
PersistenceExceptionTranslator {
public DataAccessException translateExceptionIfPossible(
RuntimeException ex) {
private Object getPropertyValue(BeanDefinition definition,
String propertyName) {
return null;
}
return definition.getPropertyValues().getPropertyValue(propertyName)
.getValue();
}
/**
* {@link BeanPostProcessor} to configure the mock
* {@link EntityManagerFactory} instances. {@code entityManagerFactory} is
* configured to be never invoked, {@code secondEntityManagerFactory} is
* configured to be invoked at least once.
*
* @author Oliver Gierke
*/
static class MockPreparingBeanPostProcessor implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean,
String beanName) throws BeansException {
private BeanReference getConstructorBeanReference(
BeanDefinition definition, int index) {
if ("secondEntityManagerFactory".equals(beanName)) {
EntityManagerFactory entityManagerFactory =
(EntityManagerFactory) bean;
EntityManager em = mock(HibernateEntityManager.class);
when(entityManagerFactory.createEntityManager()).thenReturn(em);
EntityManagerFactoryInfo info = (EntityManagerFactoryInfo) bean;
when(info.getEntityManagerInterface()).thenAnswer(
new Answer<Class<?>>() {
public Class<?> answer(InvocationOnMock invocation)
throws Throwable {
return HibernateEntityManager.class;
}
});
}
return bean;
}
public Object postProcessBeforeInitialization(Object bean,
String beanName) throws BeansException {
return bean;
}
Object value =
definition.getConstructorArgumentValues()
.getIndexedArgumentValues().get(index).getValue();
assertTrue(value instanceof BeanReference);
return (BeanReference) value;
}
}

View File

@@ -32,7 +32,7 @@ import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.jpa.domain.sample.User;
import org.springframework.data.domain.Persistable;
import org.springframework.data.jpa.repository.JpaRepository;
@@ -124,50 +124,18 @@ public class JpaRepositoryFactoryBeanUnitTests {
factory.afterPropertiesSet();
}
/**
* Asserts that the factory recognized configured repository classes that
* contain custom method but no custom implementation could be found.
* Furthremore the exception has to contain the name of the repository
* interface as for a large repository configuration it's hard to find out
* where this error occured.
*
* @throws Exception
*/
@Test
public void capturesMissingCustomImplementationAndProvidesInterfacename()
throws Exception {
JpaRepositoryFactoryBean<SampleRepository> factory =
JpaRepositoryFactoryBean.create(SampleRepository.class, entityManager);
try {
factory.afterPropertiesSet();
fail("Expected IllegalArgumentException!");
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage()
.contains(SampleRepository.class.getName()));
}
}
private interface SimpleSampleRepository extends
JpaRepository<User, Integer> {
}
/**
* Sample interface to contain a custom method.
* Helper class to make the factory use {@link PersistableMetadata} .
*
* @author Oliver Gierke
*/
private interface SampleCustomRepository {
void someSampleMethod();
}
private interface SampleRepository extends JpaRepository<User, Integer>,
SampleCustomRepository {
@SuppressWarnings("serial")
private static abstract class User implements Persistable<Long> {
}
}

View File

@@ -26,7 +26,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.jpa.domain.sample.User;
import org.springframework.data.domain.Persistable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.custom.CustomGenericJpaRepositoryFactory;
import org.springframework.data.jpa.repository.custom.UserCustomExtendedRepository;
@@ -176,4 +176,14 @@ public class JpaRepositoryFactoryUnitTests {
SampleCustomRepository {
}
/**
* Helper class to make the factory use {@link PersistableMetadata} .
*
* @author Oliver Gierke
*/
@SuppressWarnings("serial")
private static abstract class User implements Persistable<Long> {
}
}

View File

@@ -7,27 +7,15 @@
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<bean id="entityManagerFactory" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.hibernate.ejb.HibernateEntityManagerFactory" />
<constructor-arg ref="implementEntityManagerFactoryInfo" />
<constructor-arg value="javax.persistence.EntityManagerFactory" />
</bean>
<bean id="secondEntityManagerFactory" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.hibernate.ejb.HibernateEntityManagerFactory" />
<constructor-arg ref="implementEntityManagerFactoryInfo" />
<constructor-arg value="javax.persistence.EntityManagerFactory" />
</bean>
<bean id="settings" class="org.mockito.Mockito" factory-method="withSettings" />
<bean id="implementEntityManagerFactoryInfo" factory-bean="settings" factory-method="extraInterfaces">
<constructor-arg value="org.springframework.orm.jpa.EntityManagerFactoryInfo" />
</bean>
<bean class="org.springframework.data.jpa.repository.support.EntityManagerFactoryRefUnitTests$MockPreparingBeanPostProcessor" />
<bean class="org.springframework.data.jpa.repository.support.EntityManagerFactoryRefUnitTests$NoOpPersistenceExceptionTranslator" />
<jpa:repositories base-package="org.springframework.data.jpa.repository.sample" entity-manager-factory-ref="secondEntityManagerFactory">
<jpa:repository id="userRepository" />
<jpa:repository id="roleRepository" />
<jpa:repository id="auditableUserRepository"/>
</jpa:repositories>
</beans>