From 7e39ae3490fa81bcf73d9c672954172f4d16687d Mon Sep 17 00:00:00 2001 From: Sergei Khmelev Date: Mon, 30 Jan 2023 10:34:44 +0100 Subject: [PATCH] ListArrayTypeDescriptor doesn't support Spring JPA Projections #562 --- .../internal/ListArrayTypeDescriptor.java | 4 + .../repo/projection/PostRepository.java | 31 ++++++++ ...jectionSpringDataJPABaseConfiguration.java | 45 +++++++++++ .../ProjectionSpringDataJPABaseTest.java | 79 +++++++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 hypersistence-utils-hibernate-55/src/test/java/io/hypersistence/utils/spring/repo/projection/PostRepository.java create mode 100644 hypersistence-utils-hibernate-55/src/test/java/io/hypersistence/utils/spring/repo/projection/ProjectionSpringDataJPABaseConfiguration.java create mode 100644 hypersistence-utils-hibernate-55/src/test/java/io/hypersistence/utils/spring/repo/projection/ProjectionSpringDataJPABaseTest.java diff --git a/hypersistence-utils-hibernate-55/src/main/java/io/hypersistence/utils/hibernate/type/array/internal/ListArrayTypeDescriptor.java b/hypersistence-utils-hibernate-55/src/main/java/io/hypersistence/utils/hibernate/type/array/internal/ListArrayTypeDescriptor.java index a4149ae76..a773a0959 100644 --- a/hypersistence-utils-hibernate-55/src/main/java/io/hypersistence/utils/hibernate/type/array/internal/ListArrayTypeDescriptor.java +++ b/hypersistence-utils-hibernate-55/src/main/java/io/hypersistence/utils/hibernate/type/array/internal/ListArrayTypeDescriptor.java @@ -151,6 +151,10 @@ public void setParameterValues(Properties parameters) { } private Collection newPropertyCollectionInstance() { + // propertyClass is not defind for the projection + if(propertyClass == null){ + return new ArrayList(); + } if(List.class.isAssignableFrom(propertyClass)) { return new ArrayList(); } else if(Set.class.isAssignableFrom(propertyClass)) { diff --git a/hypersistence-utils-hibernate-55/src/test/java/io/hypersistence/utils/spring/repo/projection/PostRepository.java b/hypersistence-utils-hibernate-55/src/test/java/io/hypersistence/utils/spring/repo/projection/PostRepository.java new file mode 100644 index 000000000..3bff77658 --- /dev/null +++ b/hypersistence-utils-hibernate-55/src/test/java/io/hypersistence/utils/spring/repo/projection/PostRepository.java @@ -0,0 +1,31 @@ +package io.hypersistence.utils.spring.repo.projection; + +import io.hypersistence.utils.spring.domain.Post; +import io.hypersistence.utils.spring.repository.BaseJpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import javax.annotation.Nullable; +import java.util.List; + +/** + * @author Vlad Mihalcea + */ +@Repository +public interface PostRepository extends BaseJpaRepository { + + @Query(value = "select p.title as title, array_agg(p.slug) as slugs " + + "from Post p " + + "group by p.title", + nativeQuery = true) + @Nullable + List findAllSlugGroupedByTitle(); + + + interface TestProjection { + @Nullable + String getTitle(); + @Nullable + List getSlugs(); + } +} diff --git a/hypersistence-utils-hibernate-55/src/test/java/io/hypersistence/utils/spring/repo/projection/ProjectionSpringDataJPABaseConfiguration.java b/hypersistence-utils-hibernate-55/src/test/java/io/hypersistence/utils/spring/repo/projection/ProjectionSpringDataJPABaseConfiguration.java new file mode 100644 index 000000000..800188335 --- /dev/null +++ b/hypersistence-utils-hibernate-55/src/test/java/io/hypersistence/utils/spring/repo/projection/ProjectionSpringDataJPABaseConfiguration.java @@ -0,0 +1,45 @@ +package io.hypersistence.utils.spring.repo.projection; + +import io.hypersistence.utils.hibernate.type.array.ListArrayType; +import io.hypersistence.utils.spring.config.AbstractSpringDataJPAConfiguration; +import io.hypersistence.utils.spring.domain.Post; +import io.hypersistence.utils.spring.repository.BaseJpaRepositoryImpl; +import org.hibernate.boot.spi.MetadataBuilderContributor; +import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +import java.util.Properties; + +/** + * @author Vlad Mihalcea + */ +@ComponentScan( + basePackages = { + "io.hypersistence.utils.spring.repo.projection", + } +) +@EnableJpaRepositories( + value = "io.hypersistence.utils.spring.repo.projection", + repositoryBaseClass = BaseJpaRepositoryImpl.class +) +public class ProjectionSpringDataJPABaseConfiguration extends AbstractSpringDataJPAConfiguration { + + @Override + protected String packageToScan() { + return Post.class.getPackage().getName(); + } + + @Override + protected void additionalProperties(Properties properties) { + properties.put("hibernate.jdbc.batch_size", "100"); + properties.put("hibernate.order_inserts", "true"); + + properties.put( + EntityManagerFactoryBuilderImpl.METADATA_BUILDER_CONTRIBUTOR, + (MetadataBuilderContributor) metadataBuilder -> metadataBuilder.applyBasicType( + ListArrayType.INSTANCE + ) + ); + } +} diff --git a/hypersistence-utils-hibernate-55/src/test/java/io/hypersistence/utils/spring/repo/projection/ProjectionSpringDataJPABaseTest.java b/hypersistence-utils-hibernate-55/src/test/java/io/hypersistence/utils/spring/repo/projection/ProjectionSpringDataJPABaseTest.java new file mode 100644 index 000000000..448316736 --- /dev/null +++ b/hypersistence-utils-hibernate-55/src/test/java/io/hypersistence/utils/spring/repo/projection/ProjectionSpringDataJPABaseTest.java @@ -0,0 +1,79 @@ +package io.hypersistence.utils.spring.repo.projection; + +import io.hypersistence.utils.hibernate.type.util.Configuration; +import io.hypersistence.utils.spring.domain.Post; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = ProjectionSpringDataJPABaseConfiguration.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +public class ProjectionSpringDataJPABaseTest { + + protected final Logger LOGGER = LoggerFactory.getLogger(getClass()); + + @Autowired + private TransactionTemplate transactionTemplate; + + @Autowired + private PostRepository postRepository; + + @PersistenceContext + private EntityManager entityManager; + + @Test + public void myTest() { + // given + transactionTemplate.execute((TransactionCallback) transactionStatus -> { + postRepository.persist( + new Post() + .setId(1L) + .setTitle("test title") + .setSlug("slug1") + ); + + postRepository.persistAndFlush( + new Post() + .setId(2L) + .setTitle("test title") + .setSlug("slug2") + ); + + return null; + }); + + // when + List postsSummary = transactionTemplate.execute(transactionStatus -> + postRepository.findAllSlugGroupedByTitle() + ); + + // then + PostRepository.TestProjection result = postsSummary.get(0); + assertEquals("test title", result.getTitle()); + + List expectedSlugs = new ArrayList<>(); + expectedSlugs.add("slug1"); + expectedSlugs.add("slug2"); + assertEquals(expectedSlugs, result.getSlugs()); + } +} +