DATAJPA-484 - Improved registration of JpaMetamodelMappingContext.

Instead of creating an individual instance of JpaMetamodelMappingContext per repository we now register a unique instance with access to the metamodel of the EntityManager the repositories use under "jpaMappingContext".

Weakened the contract in JpaPersistentEntityImpl to allow multiple @Id properties (in case @IdClass is used). The mapping context now also allows looking up of embeddable types as they're considered entities in the context of Spring Data mapping metadata.
This commit is contained in:
Oliver Gierke
2014-03-03 11:38:45 +01:00
parent 26c64eca0f
commit bc9ee616ef
6 changed files with 194 additions and 51 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2014 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.
@@ -38,4 +38,13 @@ class JpaPersistentEntityImpl<T> extends BasicPersistentEntity<T, JpaPersistentP
public JpaPersistentEntityImpl(TypeInformation<T> information, Comparator<JpaPersistentProperty> comparator) {
super(information, comparator);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.BasicPersistentEntity#returnPropertyIfBetterIdPropertyCandidateOrNull(org.springframework.data.mapping.PersistentProperty)
*/
@Override
protected JpaPersistentProperty returnPropertyIfBetterIdPropertyCandidateOrNull(JpaPersistentProperty property) {
return property.isIdProperty() ? property : null;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2014 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,6 +23,8 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.EmbeddedId;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
@@ -30,8 +32,6 @@ import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Transient;
import javax.persistence.metamodel.EmbeddableType;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.Metamodel;
import org.springframework.data.mapping.Association;
@@ -58,6 +58,7 @@ class JpaPersistentPropertyImpl extends AnnotationBasedPersistentProperty<JpaPer
annotations.add(OneToOne.class);
annotations.add(ManyToMany.class);
annotations.add(ManyToOne.class);
annotations.add(Embedded.class);
ASSOCIATION_ANNOTATIONS = Collections.unmodifiableSet(annotations);
@@ -110,8 +111,8 @@ class JpaPersistentPropertyImpl extends AnnotationBasedPersistentProperty<JpaPer
public boolean isEntity() {
try {
ManagedType<?> type = metamodel.managedType(getType());
return !(type instanceof EmbeddableType);
metamodel.managedType(getType());
return true;
} catch (IllegalArgumentException o_O) {
return false;
}
@@ -130,6 +131,10 @@ class JpaPersistentPropertyImpl extends AnnotationBasedPersistentProperty<JpaPer
}
}
if (getType().isAnnotationPresent(Embeddable.class)) {
return true;
}
return false;
}

View File

@@ -15,27 +15,32 @@
*/
package org.springframework.data.jpa.repository.config;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceUnit;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.Metamodel;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext;
import org.springframework.data.jpa.repository.support.EntityManagerBeanDefinitionRegistrarPostProcessor;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
import org.springframework.data.repository.config.RepositoryConfigurationSource;
import org.springframework.data.repository.config.XmlRepositoryConfigurationSource;
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import org.springframework.util.Assert;
/**
* JPA specific configuration extension parsing custom attributes from the XML namespace and
@@ -53,6 +58,7 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi
private static final Class<?> PAB_POST_PROCESSOR = PersistenceAnnotationBeanPostProcessor.class;
private static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";
private static final String JPA_MAPPING_CONTEXT_BEAN_NAME = "jpaMapppingContext";
/*
* (non-Javadoc)
@@ -73,40 +79,32 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config14.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config14.XmlRepositoryConfigurationSource)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.RepositoryConfigurationSource)
*/
@Override
public void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource config) {
public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) {
Element element = config.getElement();
String transactionManagerRef = source.getAttribute("transactionManagerRef");
builder.addPropertyValue("transactionManager",
transactionManagerRef == null ? DEFAULT_TRANSACTION_MANAGER_BEAN_NAME : transactionManagerRef);
postProcess(builder, element.getAttribute("transaction-manager-ref"),
element.getAttribute("entity-manager-factory-ref"), config.getSource());
}
String entityManagerFactoryRef = getEntityManagerFactoryRef(source);
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource)
*/
@Override
public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) {
AnnotationAttributes attributes = config.getAttributes();
postProcess(builder, attributes.getString("transactionManagerRef"),
attributes.getString("entityManagerFactoryRef"), config.getSource());
}
private void postProcess(BeanDefinitionBuilder builder, String transactionManagerRef, String entityManagerRef,
Object source) {
transactionManagerRef = StringUtils.hasText(transactionManagerRef) ? transactionManagerRef
: DEFAULT_TRANSACTION_MANAGER_BEAN_NAME;
builder.addPropertyValue("transactionManager", transactionManagerRef);
if (StringUtils.hasText(entityManagerRef)) {
builder.addPropertyValue("entityManager", getEntityManagerBeanDefinitionFor(entityManagerRef, source));
if (entityManagerFactoryRef != null) {
builder.addPropertyValue("entityManager", getEntityManagerBeanDefinitionFor(entityManagerFactoryRef, source));
}
builder.addPropertyReference("mappingContext", JPA_MAPPING_CONTEXT_BEAN_NAME);
}
/**
* @param source
* @return
*/
private String getEntityManagerFactoryRef(RepositoryConfigurationSource source) {
String entityManagerFactoryRef = source.getAttribute("entityManagerFactoryRef");
return entityManagerFactoryRef == null ? "entityManagerFactory" : entityManagerFactoryRef;
}
/**
@@ -143,10 +141,87 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi
registerWithSourceAndGeneratedBeanName(registry, new RootBeanDefinition(
EntityManagerBeanDefinitionRegistrarPostProcessor.class), source);
BeanDefinition entityManagerBeanDefinitionFor = getEntityManagerBeanDefinitionFor(
getEntityManagerFactoryRef(configurationSource), source);
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.rootBeanDefinition(JpaMetamodelMappingContextFactoryBean.class);
builder.addPropertyValue("entityManager", entityManagerBeanDefinitionFor);
AbstractBeanDefinition definition = builder.getBeanDefinition();
definition.setSource(source);
registry.registerBeanDefinition(JPA_MAPPING_CONTEXT_BEAN_NAME, definition);
if (!hasBean(PAB_POST_PROCESSOR, registry)
&& !registry.containsBeanDefinition(AnnotationConfigUtils.PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
registerWithSourceAndGeneratedBeanName(registry, new RootBeanDefinition(PAB_POST_PROCESSOR), source);
}
}
/**
* {@link FactoryBean} to setup {@link JpaMetamodelMappingContext} instances from Spring configuration.
*
* @author Oliver Gierke
* @since 1.6
*/
static class JpaMetamodelMappingContextFactoryBean extends AbstractFactoryBean<JpaMetamodelMappingContext> {
private EntityManager entityManager;
/**
* Configures the {@link EntityManager} to use to create the {@link JpaMetamodelMappingContext}.
*
* @param entityManager must not be {@literal null}.
*/
public void setEntityManager(EntityManager entityManager) {
Assert.notNull(entityManager, "EntityManager must not be null!");
this.entityManager = entityManager;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.AbstractFactoryBean#getObjectType()
*/
@Override
public Class<?> getObjectType() {
return JpaMetamodelMappingContext.class;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.AbstractFactoryBean#createInstance()
*/
@Override
protected JpaMetamodelMappingContext createInstance() throws Exception {
Metamodel metamodel = entityManager.getMetamodel();
Set<ManagedType<?>> managedTypes = metamodel.getManagedTypes();
Set<Class<?>> entitySources = new HashSet<Class<?>>(managedTypes.size());
for (ManagedType<?> type : managedTypes) {
entitySources.add(type.getJavaType());
}
JpaMetamodelMappingContext context = new JpaMetamodelMappingContext(metamodel);
context.setInitialEntitySet(entitySources);
context.initialize();
return context;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.AbstractFactoryBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(entityManager, "EntityManager must not be null!");
super.afterPropertiesSet();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2012 the original author or authors.
* Copyright 2008-2014 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.
@@ -20,7 +20,7 @@ import java.io.Serializable;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport;
@@ -47,7 +47,15 @@ public class JpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends
@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
setMappingContext(new JpaMetamodelMappingContext(entityManager.getMetamodel()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#setMappingContext(org.springframework.data.mapping.context.MappingContext)
*/
@Override
public void setMappingContext(MappingContext<?, ?> mappingContext) {
super.setMappingContext(mappingContext);
}
/*

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 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.
@@ -18,6 +18,8 @@ package org.springframework.data.jpa.mapping;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.OneToOne;
import javax.persistence.Transient;
import javax.persistence.metamodel.Metamodel;
@@ -66,9 +68,44 @@ public class JpaPersistentPropertyImplUnitTests {
assertThat(entity.getPersistentProperty("transientProp"), is(nullValue()));
}
/**
* @see DATAJPA-484
*/
@Test
public void considersEmbeddableAnEntity() {
assertThat(context.getPersistentEntity(SampleEmbeddable.class), is(notNullValue()));
}
/**
* @see DATAJPA-484
*/
@Test
public void considersEmbeddablePropertyAnAssociation() {
assertThat(entity.getPersistentProperty("embeddable").isAssociation(), is(true));
}
/**
* @see DATAJPA-484
*/
@Test
public void considersEmbeddedPropertyAnAssociation() {
assertThat(entity.getPersistentProperty("embedded").isAssociation(), is(true));
}
static class Sample {
@OneToOne Sample other;
@Transient String transientProp;
SampleEmbeddable embeddable;
@Embedded SampleEmbedded embedded;
}
@Embeddable
static class SampleEmbeddable {
}
static class SampleEmbedded {
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2013 the original author or authors.
* Copyright 2008-2014 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.
@@ -20,6 +20,7 @@ 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.mapping.JpaMetamodelMappingContext;
import org.springframework.data.jpa.repository.sample.AuditableUserRepository;
import org.springframework.data.jpa.repository.sample.RoleRepository;
import org.springframework.data.jpa.repository.sample.UserRepository;
@@ -33,14 +34,11 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
public abstract class AbstractRepositoryConfigTests {
@Autowired(required = false)
UserRepository userRepository;
@Autowired(required = false) UserRepository userRepository;
@Autowired(required = false) RoleRepository roleRepository;
@Autowired(required = false) AuditableUserRepository auditableUserRepository;
@Autowired(required = false)
RoleRepository roleRepository;
@Autowired(required = false)
AuditableUserRepository auditableUserRepository;
@Autowired JpaMetamodelMappingContext mappingContext;
/**
* Asserts that context creation detects 3 repository beans.
@@ -53,6 +51,9 @@ public abstract class AbstractRepositoryConfigTests {
assertNotNull(auditableUserRepository);
}
/**
* @see DATAJPA-330
*/
@Test
public void repositoriesHaveExceptionTranslationApplied() {
@@ -60,4 +61,12 @@ public abstract class AbstractRepositoryConfigTests {
JpaRepositoriesRegistrarIntegrationTests.assertExceptionTranslationActive(roleRepository);
JpaRepositoriesRegistrarIntegrationTests.assertExceptionTranslationActive(auditableUserRepository);
}
/**
* @see DATAJPA-???
*/
@Test
public void exposesJpaMappingContext() {
assertNotNull(mappingContext);
}
}