diff --git a/src/main/java/org/springframework/data/jpa/projection/CollectionAwareProjectionFactory.java b/src/main/java/org/springframework/data/jpa/projection/CollectionAwareProjectionFactory.java new file mode 100644 index 000000000..08969d81b --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/projection/CollectionAwareProjectionFactory.java @@ -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())); + } + } +} diff --git a/src/main/java/org/springframework/data/jpa/projection/CollectionAwareProjectionFactory.java.orig b/src/main/java/org/springframework/data/jpa/projection/CollectionAwareProjectionFactory.java.orig new file mode 100644 index 000000000..25bb1f4b3 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/projection/CollectionAwareProjectionFactory.java.orig @@ -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())); + } + } +} 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 a4a2bfa84..172fb86b0 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 @@ -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. * diff --git a/src/test/java/org/springframework/data/jpa/repository/projections/ProjectionsIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/projections/ProjectionsIntegrationTests.java new file mode 100644 index 000000000..11a3fd45e --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/projections/ProjectionsIntegrationTests.java @@ -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 subs = new ArrayList(); + + String otherAttribute; + } + + @Data + @Entity + @Table(name = "SubEntity") + static class SubEntity { + + @GeneratedValue @Id Long id; + String name; + String otherAttribute; + } + + interface DummyEntityProjection { + + String getName(); + + List getSubs(); + } + + interface SubEntityProjection { + String getName(); + } + + interface DummyEntityWithCollectionRepository extends JpaRepository { + List 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); + } + } +} diff --git a/src/test/resources/simple-persistence/simple-persistence.xml b/src/test/resources/simple-persistence/simple-persistence.xml new file mode 100644 index 000000000..adc69aa86 --- /dev/null +++ b/src/test/resources/simple-persistence/simple-persistence.xml @@ -0,0 +1,8 @@ + + + + + true + + +