DATAJPA-1173 - Projections containing collections are considered open.
It is not reasonably possible to create a JPA query that creates tuples with collections of referenced entities. Therefore projections that contain attributes of type Map or Collection are now considered open. We now use a custom ProjectionFactory to achieve this. The added integration test does not depend on the existing integration tests or their infrastructure and uses java configuration instead of XML. This reduces the setup to what actually is needed for this test. Original pull request: #218. Related issue: DATACMNS-1158.
This commit is contained in:
committed by
Oliver Gierke
parent
f1bbc62368
commit
f999800e5d
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2017 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.projection;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.projection.ProjectionInformation;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.data.projection.ProjectionFactory} considering projections containing collections or
|
||||
* maps to be open.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class CollectionAwareProjectionFactory extends SpelAwareProxyProjectionFactory {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.projection.SpelAwareProxyProjectionFactory#getProjectionInformation(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public ProjectionInformation getProjectionInformation(Class<?> projectionType) {
|
||||
return new CollectionAwareProjectionInformation(projectionType);
|
||||
}
|
||||
|
||||
private static class CollectionAwareProjectionInformation extends SpelAwareProjectionInformation {
|
||||
|
||||
CollectionAwareProjectionInformation(Class<?> projectionType) {
|
||||
super(projectionType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.projection.SpelAwareProxyProjectionFactory.SpelAwareProjectionInformation#isInputProperty(java.beans.PropertyDescriptor)
|
||||
*/
|
||||
@Override
|
||||
protected boolean isInputProperty(PropertyDescriptor descriptor) {
|
||||
|
||||
if (!super.isInputProperty(descriptor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !(Collection.class.isAssignableFrom(descriptor.getPropertyType()) //
|
||||
|| Map.class.isAssignableFrom(descriptor.getPropertyType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2017 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.projection;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.projection.ProjectionInformation;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.data.projection.ProjectionFactory} considering projections containing collections or
|
||||
* maps to be open.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class CollectionAwareProjectionFactory extends SpelAwareProxyProjectionFactory {
|
||||
|
||||
<<<<<<< HEAD
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.projection.SpelAwareProxyProjectionFactory#getProjectionInformation(java.lang.Class)
|
||||
=======
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.projection.SpelAwareProxyProjectionFactory#createProjectionInformation(java.lang.Class)
|
||||
>>>>>>> bc828b9a... DATAJPA-1173 - Polishing.
|
||||
*/
|
||||
@Override
|
||||
public ProjectionInformation getProjectionInformation(Class<?> projectionType) {
|
||||
return new CollectionAwareProjectionInformation(projectionType);
|
||||
}
|
||||
|
||||
private static class CollectionAwareProjectionInformation extends SpelAwareProjectionInformation {
|
||||
|
||||
CollectionAwareProjectionInformation(Class<?> projectionType) {
|
||||
super(projectionType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.projection.SpelAwareProxyProjectionFactory.SpelAwareProjectionInformation#isInputProperty(java.beans.PropertyDescriptor)
|
||||
*/
|
||||
@Override
|
||||
protected boolean isInputProperty(PropertyDescriptor descriptor) {
|
||||
|
||||
if (!super.isInputProperty(descriptor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !(Collection.class.isAssignableFrom(descriptor.getPropertyType()) //
|
||||
|| Map.class.isAssignableFrom(descriptor.getPropertyType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,14 @@ import java.io.Serializable;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.data.jpa.projection.CollectionAwareProjectionFactory;
|
||||
import org.springframework.data.jpa.provider.PersistenceProvider;
|
||||
import org.springframework.data.jpa.provider.QueryExtractor;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
@@ -39,6 +43,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class JpaRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
@@ -68,6 +73,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
|
||||
*/
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
|
||||
super.setBeanClassLoader(classLoader);
|
||||
this.crudMethodMetadataPostProcessor.setBeanClassLoader(classLoader);
|
||||
}
|
||||
@@ -119,6 +125,20 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getProjectionFactory(java.lang.ClassLoader, org.springframework.beans.factory.BeanFactory)
|
||||
*/
|
||||
@Override
|
||||
protected ProjectionFactory getProjectionFactory(ClassLoader classLoader, BeanFactory beanFactory) {
|
||||
|
||||
CollectionAwareProjectionFactory factory = new CollectionAwareProjectionFactory();
|
||||
factory.setBeanClassLoader(classLoader);
|
||||
factory.setBeanFactory(beanFactory);
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given repository interface requires a QueryDsl specific implementation to be chosen.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2017 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.projections;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.data.jpa.repository.projections.ProjectionsIntegrationTests.Config;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Integration tests for the behavior of projections.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@Transactional
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = Config.class)
|
||||
public class ProjectionsIntegrationTests {
|
||||
|
||||
@Autowired DummyEntityWithCollectionRepository repository;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
||||
DummyEntityWithCollection entity = new DummyEntityWithCollection();
|
||||
entity.setName("A Name");
|
||||
entity.getSubs().add(createSubEntity(1));
|
||||
entity.getSubs().add(createSubEntity(2));
|
||||
|
||||
repository.save(entity);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1173
|
||||
public void findAllFindsTheSingleEntity() {
|
||||
assertThat(repository.findAll(), hasSize(1));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1173
|
||||
public void findAllProjectedFindsTheSingleEntity() {
|
||||
assertThat(repository.findAllProjectedBy(), hasSize(1));
|
||||
}
|
||||
|
||||
private SubEntity createSubEntity(int index) {
|
||||
|
||||
SubEntity entity = new SubEntity();
|
||||
entity.setName("sub-" + index);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Entity(name = "Dummy")
|
||||
@Table(name = "DummyEntity")
|
||||
static class DummyEntityWithCollection {
|
||||
|
||||
@GeneratedValue @Id //
|
||||
Long id;
|
||||
|
||||
String name;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "subs") //
|
||||
List<SubEntity> subs = new ArrayList<SubEntity>();
|
||||
|
||||
String otherAttribute;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Entity
|
||||
@Table(name = "SubEntity")
|
||||
static class SubEntity {
|
||||
|
||||
@GeneratedValue @Id Long id;
|
||||
String name;
|
||||
String otherAttribute;
|
||||
}
|
||||
|
||||
interface DummyEntityProjection {
|
||||
|
||||
String getName();
|
||||
|
||||
List<SubEntityProjection> getSubs();
|
||||
}
|
||||
|
||||
interface SubEntityProjection {
|
||||
String getName();
|
||||
}
|
||||
|
||||
interface DummyEntityWithCollectionRepository extends JpaRepository<DummyEntityWithCollection, Long> {
|
||||
List<DummyEntityProjection> findAllProjectedBy();
|
||||
}
|
||||
|
||||
@EnableJpaRepositories(considerNestedRepositories = true)
|
||||
@EnableTransactionManagement
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
DataSource dataSource() {
|
||||
|
||||
return new EmbeddedDatabaseBuilder() //
|
||||
.generateUniqueName(true) //
|
||||
.setType(EmbeddedDatabaseType.HSQL) //
|
||||
.setScriptEncoding("UTF-8") //
|
||||
.ignoreFailedDrops(true) //
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
AbstractEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
|
||||
|
||||
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
|
||||
factoryBean.setDataSource(dataSource);
|
||||
factoryBean.setPersistenceUnitRootLocation("simple-persistence");
|
||||
factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
|
||||
|
||||
factoryBean.setPackagesToScan(this.getClass().getPackage().getName());
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("hibernate.hbm2ddl.auto", "create");
|
||||
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
|
||||
factoryBean.setJpaProperties(properties);
|
||||
|
||||
return factoryBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
|
||||
return new JpaTransactionManager(emf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
|
||||
|
||||
<persistence-unit name="xxx">
|
||||
<exclude-unlisted-classes>true</exclude-unlisted-classes>
|
||||
</persistence-unit>
|
||||
|
||||
</persistence>
|
||||
Reference in New Issue
Block a user