diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityMetadata.java similarity index 74% rename from src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java rename to src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityMetadata.java index 66782b70e..3b9dc6385 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityMetadata.java @@ -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 extends AbstractEntityMetadata { 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 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} * . diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java index 45bac7798..7a26ff261 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java @@ -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 RepositorySupport getTargetRepository( - Class 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 * @param - * @param domainClass * @param entityManager - * @see #getTargetRepository(Class, Class) + * @see #getTargetRepository(RepositoryMetadata) * @return */ - protected RepositorySupport getTargetRepository( - Class domainClass, Class repositoryInterface, + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected Object getTargetRepository(RepositoryMetadata metadata, EntityManager entityManager) { - return new SimpleJpaRepository(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 getRepositoryClass( - Class repositoryInterface) { + protected Class getRepositoryBaseClass(Class repositoryInterface) { return SimpleJpaRepository.class; } 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 6fb6d6b55..6119a67c1 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,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 the type of the entity's identifier */ @org.springframework.stereotype.Repository -public class SimpleJpaRepository extends - RepositorySupport implements JpaRepository { +public class SimpleJpaRepository implements + JpaRepository { + private final EntityMetadata entityInformation; private final EntityManager em; private final PersistenceProvider provider; @@ -65,19 +62,26 @@ public class SimpleJpaRepository extends * Creates a new {@link SimpleJpaRepository} to manage objects of the given * domain type. * - * @param domainClass + * @param entityInformation * @param entityManager */ - public SimpleJpaRepository(Class domainClass, EntityManager entityManager) { - - super(domainClass); + public SimpleJpaRepository(EntityMetadata entityInformation, + EntityManager entityManager) { + Assert.notNull(entityInformation); Assert.notNull(entityManager); + this.entityInformation = entityInformation; this.em = entityManager; this.provider = PersistenceProvider.fromEntityManager(entityManager); } + private Class getDomainClass() { + + return entityInformation.getJavaType(); + } + + private String getDeleteAllQueryString() { return getQueryString(DELETE_ALL_QUERY_STRING, getDomainClass()); @@ -94,22 +98,6 @@ public class SimpleJpaRepository extends } - /** - * Factory method to create {@link SimpleJpaRepository} instances. - * - * @param domainClass the domain class to handle - * @param entityManager the {@link EntityManager} backing the repository - * @param the type of the entity to handle - * @param the type of the entity's identifier - * @return - */ - public static Repository create( - Class domainClass, EntityManager entityManager) { - - return new SimpleJpaRepository(domainClass, entityManager); - } - - /* * (non-Javadoc) * @@ -303,7 +291,7 @@ public class SimpleJpaRepository 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 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}. diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 3dc007164..8508945ea 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -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 ---------------------------------------- diff --git a/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepository.java b/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepository.java index a7d68b719..ccf822904 100644 --- a/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepository.java @@ -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 extends * @param domainClass * @param entityManager */ - public CustomGenericJpaRepository(Class domainClass, + public CustomGenericJpaRepository(EntityMetadata metadata, EntityManager entityManager) { - super(domainClass, entityManager); + super(metadata, entityManager); } diff --git a/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepositoryFactory.java b/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepositoryFactory.java index b49cd4c16..93d41f310 100644 --- a/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepositoryFactory.java +++ b/src/test/java/org/springframework/data/jpa/repository/custom/CustomGenericJpaRepositoryFactory.java @@ -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 RepositorySupport getTargetRepository( - Class domainClass, Class repositoryInterface, EntityManager em) { + @SuppressWarnings("unchecked") + protected Object getTargetRepository(RepositoryMetadata metadata, + EntityManager em) { - return new CustomGenericJpaRepository(domainClass, em); + EntityMetadata entityMetadata = mock(EntityMetadata.class); + when(entityMetadata.getJavaType()).thenReturn( + (Class) metadata.getDomainClass()); + return new CustomGenericJpaRepository( + 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 getRepositoryClass( - Class repositoryInterface) { + protected Class getRepositoryBaseClass(Class repositoryInterface) { return CustomGenericJpaRepository.class; } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefUnitTests.java index 5f1c705a4..8a1a7219d 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefUnitTests.java @@ -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>() { - - 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; } } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java index 536cca1d5..68e3422ce 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBeanUnitTests.java @@ -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 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 { } /** - * 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, - SampleCustomRepository { + @SuppressWarnings("serial") + private static abstract class User implements Persistable { } } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryUnitTests.java index b6b838034..3c881733e 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryUnitTests.java @@ -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 { + + } } diff --git a/src/test/resources/multiple-entity-manager-context.xml b/src/test/resources/multiple-entity-manager-context.xml index 178549ea5..916e4a036 100644 --- a/src/test/resources/multiple-entity-manager-context.xml +++ b/src/test/resources/multiple-entity-manager-context.xml @@ -7,27 +7,15 @@ http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> - - + - - + - - - - - - - - - -