Skip to content

Commit

Permalink
ListArrayTypeDescriptor doesn't support Spring JPA Projections #562
Browse files Browse the repository at this point in the history
  • Loading branch information
SergeiKhmelevSPA authored and vladmihalcea committed Feb 16, 2023
1 parent 96335e4 commit 9232995
Show file tree
Hide file tree
Showing 15 changed files with 486 additions and 8 deletions.
Expand Up @@ -149,7 +149,7 @@ public void setParameterValues(Properties parameters) {
}

private Collection newPropertyCollectionInstance() {
if(List.class.isAssignableFrom(propertyClass)) {
if (propertyClass == null || List.class.isAssignableFrom(propertyClass)) {
return new ArrayList();
} else if(Set.class.isAssignableFrom(propertyClass)) {
return new LinkedHashSet();
Expand Down
Expand Up @@ -151,7 +151,7 @@ public void setParameterValues(Properties parameters) {
}

private Collection newPropertyCollectionInstance() {
if(List.class.isAssignableFrom(propertyClass)) {
if (propertyClass == null || List.class.isAssignableFrom(propertyClass)) {
return new ArrayList();
} else if(Set.class.isAssignableFrom(propertyClass)) {
return new LinkedHashSet();
Expand Down
@@ -0,0 +1,33 @@
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<Post, Long> {

@Query(
value = "select p.title as title, array_agg(p.slug) as slugs " +
"from Post p " +
"group by p.title",
nativeQuery = true)
@Nullable
List<TestProjection> findAllSlugGroupedByTitle();


interface TestProjection {
@Nullable
String getTitle();

@Nullable
List<String> getSlugs();
}
}
@@ -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 SpringDataProjectionConfiguration 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
)
);
}
}
@@ -0,0 +1,75 @@
package io.hypersistence.utils.spring.repo.projection;

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 static org.junit.Assert.assertEquals;

/**
* @author Vlad Mihalcea
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringDataProjectionConfiguration.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class SpringDataProjectionTest {

protected final Logger LOGGER = LoggerFactory.getLogger(getClass());

@Autowired
private TransactionTemplate transactionTemplate;

@Autowired
private PostRepository postRepository;

@PersistenceContext
private EntityManager entityManager;

@Test
public void myTest() {
transactionTemplate.execute((TransactionCallback<Void>) 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;
});

List<PostRepository.TestProjection> postsSummary = transactionTemplate.execute(transactionStatus ->
postRepository.findAllSlugGroupedByTitle()
);

// then
PostRepository.TestProjection result = postsSummary.get(0);
assertEquals("test title", result.getTitle());

List<String> expectedSlugs = new ArrayList<>();
expectedSlugs.add("slug1");
expectedSlugs.add("slug2");
assertEquals(expectedSlugs, result.getSlugs());
}
}

Expand Up @@ -151,7 +151,7 @@ public void setParameterValues(Properties parameters) {
}

private Collection newPropertyCollectionInstance() {
if(List.class.isAssignableFrom(propertyClass)) {
if (propertyClass == null || List.class.isAssignableFrom(propertyClass)) {
return new ArrayList();
} else if(Set.class.isAssignableFrom(propertyClass)) {
return new LinkedHashSet();
Expand Down
@@ -0,0 +1,33 @@
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<Post, Long> {

@Query(
value = "select p.title as title, array_agg(p.slug) as slugs " +
"from Post p " +
"group by p.title",
nativeQuery = true)
@Nullable
List<TestProjection> findAllSlugGroupedByTitle();


interface TestProjection {
@Nullable
String getTitle();

@Nullable
List<String> getSlugs();
}
}
@@ -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 SpringDataProjectionConfiguration 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
)
);
}
}
@@ -0,0 +1,75 @@
package io.hypersistence.utils.spring.repo.projection;

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 static org.junit.Assert.assertEquals;

/**
* @author Vlad Mihalcea
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringDataProjectionConfiguration.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class SpringDataProjectionTest {

protected final Logger LOGGER = LoggerFactory.getLogger(getClass());

@Autowired
private TransactionTemplate transactionTemplate;

@Autowired
private PostRepository postRepository;

@PersistenceContext
private EntityManager entityManager;

@Test
public void myTest() {
transactionTemplate.execute((TransactionCallback<Void>) 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;
});

List<PostRepository.TestProjection> postsSummary = transactionTemplate.execute(transactionStatus ->
postRepository.findAllSlugGroupedByTitle()
);

// then
PostRepository.TestProjection result = postsSummary.get(0);
assertEquals("test title", result.getTitle());

List<String> expectedSlugs = new ArrayList<>();
expectedSlugs.add("slug1");
expectedSlugs.add("slug2");
assertEquals(expectedSlugs, result.getSlugs());
}
}

@@ -1,10 +1,13 @@
package io.hypersistence.utils.hibernate.type.array.internal;

import io.hypersistence.utils.hibernate.type.array.ListArrayType;
import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.AbstractClassJavaType;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
import org.hibernate.type.descriptor.java.*;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.usertype.DynamicParameterizedType;

import java.sql.Array;
Expand All @@ -18,7 +21,7 @@
* @author Vlad Mihalcea
*/
public abstract class AbstractArrayTypeDescriptor<T>
extends AbstractClassJavaType<T> implements DynamicParameterizedType {
extends AbstractClassJavaType<T> implements DynamicParameterizedType, BasicPluralJavaType {

private Class<T> arrayObjectClass;

Expand Down Expand Up @@ -96,6 +99,16 @@ public <X> T wrap(X value, WrapperOptions options) {
return (T) value;
}

@Override
public JavaType getElementJavaType() {
return null;
}

@Override
public BasicType<?> resolveType(TypeConfiguration typeConfiguration, Dialect dialect, BasicType elementType, ColumnTypeInformation columnTypeInformation) {
return null;
}

protected String getSqlArrayType() {
return sqlArrayType;
}
Expand Down
Expand Up @@ -152,7 +152,7 @@ public void setParameterValues(Properties parameters) {
}

private Collection newPropertyCollectionInstance() {
if(List.class.isAssignableFrom(propertyClass)) {
if (propertyClass == null || List.class.isAssignableFrom(propertyClass)) {
return new ArrayList();
} else if(Set.class.isAssignableFrom(propertyClass)) {
return new LinkedHashSet();
Expand Down

0 comments on commit 9232995

Please sign in to comment.