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
89f09a547d
commit
e8240b7af5
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
public class CollectionAwareProjectionFactory extends SpelAwareProxyProjectionFactory {
|
||||
|
||||
@Override
|
||||
protected ProjectionInformation createProjectionInformation(Class<?> projectionType) {
|
||||
return new CollectionAwareProjectionInformation(projectionType);
|
||||
}
|
||||
|
||||
private class CollectionAwareProjectionInformation extends SpelAwareProjectionInformation {
|
||||
|
||||
CollectionAwareProjectionInformation(Class<?> projectionType) {
|
||||
super(projectionType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isInputProperty(PropertyDescriptor descriptor) {
|
||||
|
||||
if (!super.isInputProperty(descriptor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isMapOrCollection = //
|
||||
Collection.class.isAssignableFrom(descriptor.getPropertyType()) //
|
||||
|| Map.class.isAssignableFrom(descriptor.getPropertyType());
|
||||
|
||||
return !isMapOrCollection;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,14 @@ import java.util.Optional;
|
||||
|
||||
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;
|
||||
@@ -42,6 +46,7 @@ import org.springframework.util.Assert;
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class JpaRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
@@ -118,6 +123,16 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
|
||||
}
|
||||
}
|
||||
|
||||
@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,173 @@
|
||||
/*
|
||||
* 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.assertj.core.api.Assertions.*;
|
||||
|
||||
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;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 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<>();
|
||||
|
||||
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