Skip to content

Commit 9232995

Browse files
SergeiKhmelevSPAvladmihalcea
authored andcommitted
ListArrayTypeDescriptor doesn't support Spring JPA Projections #562
1 parent 96335e4 commit 9232995

File tree

15 files changed

+486
-8
lines changed

15 files changed

+486
-8
lines changed

hypersistence-utils-hibernate-5/src/main/java/io/hypersistence/utils/hibernate/type/array/internal/ListArrayTypeDescriptor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ public void setParameterValues(Properties parameters) {
149149
}
150150

151151
private Collection newPropertyCollectionInstance() {
152-
if(List.class.isAssignableFrom(propertyClass)) {
152+
if (propertyClass == null || List.class.isAssignableFrom(propertyClass)) {
153153
return new ArrayList();
154154
} else if(Set.class.isAssignableFrom(propertyClass)) {
155155
return new LinkedHashSet();

hypersistence-utils-hibernate-52/src/main/java/io/hypersistence/utils/hibernate/type/array/internal/ListArrayTypeDescriptor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public void setParameterValues(Properties parameters) {
151151
}
152152

153153
private Collection newPropertyCollectionInstance() {
154-
if(List.class.isAssignableFrom(propertyClass)) {
154+
if (propertyClass == null || List.class.isAssignableFrom(propertyClass)) {
155155
return new ArrayList();
156156
} else if(Set.class.isAssignableFrom(propertyClass)) {
157157
return new LinkedHashSet();
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.hypersistence.utils.spring.repo.projection;
2+
3+
import io.hypersistence.utils.spring.domain.Post;
4+
import io.hypersistence.utils.spring.repository.BaseJpaRepository;
5+
import org.springframework.data.jpa.repository.Query;
6+
import org.springframework.stereotype.Repository;
7+
8+
import javax.annotation.Nullable;
9+
import java.util.List;
10+
11+
/**
12+
* @author Vlad Mihalcea
13+
*/
14+
@Repository
15+
public interface PostRepository extends BaseJpaRepository<Post, Long> {
16+
17+
@Query(
18+
value = "select p.title as title, array_agg(p.slug) as slugs " +
19+
"from Post p " +
20+
"group by p.title",
21+
nativeQuery = true)
22+
@Nullable
23+
List<TestProjection> findAllSlugGroupedByTitle();
24+
25+
26+
interface TestProjection {
27+
@Nullable
28+
String getTitle();
29+
30+
@Nullable
31+
List<String> getSlugs();
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.hypersistence.utils.spring.repo.projection;
2+
3+
import io.hypersistence.utils.hibernate.type.array.ListArrayType;
4+
import io.hypersistence.utils.spring.config.AbstractSpringDataJPAConfiguration;
5+
import io.hypersistence.utils.spring.domain.Post;
6+
import io.hypersistence.utils.spring.repository.BaseJpaRepositoryImpl;
7+
import org.hibernate.boot.spi.MetadataBuilderContributor;
8+
import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
9+
import org.springframework.context.annotation.ComponentScan;
10+
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
11+
12+
import java.util.Properties;
13+
14+
/**
15+
* @author Vlad Mihalcea
16+
*/
17+
@ComponentScan(
18+
basePackages = {
19+
"io.hypersistence.utils.spring.repo.projection",
20+
}
21+
)
22+
@EnableJpaRepositories(
23+
value = "io.hypersistence.utils.spring.repo.projection",
24+
repositoryBaseClass = BaseJpaRepositoryImpl.class
25+
)
26+
public class SpringDataProjectionConfiguration extends AbstractSpringDataJPAConfiguration {
27+
28+
@Override
29+
protected String packageToScan() {
30+
return Post.class.getPackage().getName();
31+
}
32+
33+
@Override
34+
protected void additionalProperties(Properties properties) {
35+
properties.put("hibernate.jdbc.batch_size", "100");
36+
properties.put("hibernate.order_inserts", "true");
37+
38+
properties.put(
39+
EntityManagerFactoryBuilderImpl.METADATA_BUILDER_CONTRIBUTOR,
40+
(MetadataBuilderContributor) metadataBuilder -> metadataBuilder.applyBasicType(
41+
ListArrayType.INSTANCE
42+
)
43+
);
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package io.hypersistence.utils.spring.repo.projection;
2+
3+
import io.hypersistence.utils.spring.domain.Post;
4+
import org.junit.Test;
5+
import org.junit.runner.RunWith;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
import org.springframework.beans.factory.annotation.Autowired;
9+
import org.springframework.test.annotation.DirtiesContext;
10+
import org.springframework.test.context.ContextConfiguration;
11+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12+
import org.springframework.transaction.support.TransactionCallback;
13+
import org.springframework.transaction.support.TransactionTemplate;
14+
15+
import javax.persistence.EntityManager;
16+
import javax.persistence.PersistenceContext;
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
20+
import static org.junit.Assert.assertEquals;
21+
22+
/**
23+
* @author Vlad Mihalcea
24+
*/
25+
@RunWith(SpringJUnit4ClassRunner.class)
26+
@ContextConfiguration(classes = SpringDataProjectionConfiguration.class)
27+
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
28+
public class SpringDataProjectionTest {
29+
30+
protected final Logger LOGGER = LoggerFactory.getLogger(getClass());
31+
32+
@Autowired
33+
private TransactionTemplate transactionTemplate;
34+
35+
@Autowired
36+
private PostRepository postRepository;
37+
38+
@PersistenceContext
39+
private EntityManager entityManager;
40+
41+
@Test
42+
public void myTest() {
43+
transactionTemplate.execute((TransactionCallback<Void>) transactionStatus -> {
44+
postRepository.persist(
45+
new Post()
46+
.setId(1L)
47+
.setTitle("test title")
48+
.setSlug("slug1")
49+
);
50+
51+
postRepository.persistAndFlush(
52+
new Post()
53+
.setId(2L)
54+
.setTitle("test title")
55+
.setSlug("slug2")
56+
);
57+
58+
return null;
59+
});
60+
61+
List<PostRepository.TestProjection> postsSummary = transactionTemplate.execute(transactionStatus ->
62+
postRepository.findAllSlugGroupedByTitle()
63+
);
64+
65+
// then
66+
PostRepository.TestProjection result = postsSummary.get(0);
67+
assertEquals("test title", result.getTitle());
68+
69+
List<String> expectedSlugs = new ArrayList<>();
70+
expectedSlugs.add("slug1");
71+
expectedSlugs.add("slug2");
72+
assertEquals(expectedSlugs, result.getSlugs());
73+
}
74+
}
75+

hypersistence-utils-hibernate-55/src/main/java/io/hypersistence/utils/hibernate/type/array/internal/ListArrayTypeDescriptor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public void setParameterValues(Properties parameters) {
151151
}
152152

153153
private Collection newPropertyCollectionInstance() {
154-
if(List.class.isAssignableFrom(propertyClass)) {
154+
if (propertyClass == null || List.class.isAssignableFrom(propertyClass)) {
155155
return new ArrayList();
156156
} else if(Set.class.isAssignableFrom(propertyClass)) {
157157
return new LinkedHashSet();
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.hypersistence.utils.spring.repo.projection;
2+
3+
import io.hypersistence.utils.spring.domain.Post;
4+
import io.hypersistence.utils.spring.repository.BaseJpaRepository;
5+
import org.springframework.data.jpa.repository.Query;
6+
import org.springframework.stereotype.Repository;
7+
8+
import javax.annotation.Nullable;
9+
import java.util.List;
10+
11+
/**
12+
* @author Vlad Mihalcea
13+
*/
14+
@Repository
15+
public interface PostRepository extends BaseJpaRepository<Post, Long> {
16+
17+
@Query(
18+
value = "select p.title as title, array_agg(p.slug) as slugs " +
19+
"from Post p " +
20+
"group by p.title",
21+
nativeQuery = true)
22+
@Nullable
23+
List<TestProjection> findAllSlugGroupedByTitle();
24+
25+
26+
interface TestProjection {
27+
@Nullable
28+
String getTitle();
29+
30+
@Nullable
31+
List<String> getSlugs();
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.hypersistence.utils.spring.repo.projection;
2+
3+
import io.hypersistence.utils.hibernate.type.array.ListArrayType;
4+
import io.hypersistence.utils.spring.config.AbstractSpringDataJPAConfiguration;
5+
import io.hypersistence.utils.spring.domain.Post;
6+
import io.hypersistence.utils.spring.repository.BaseJpaRepositoryImpl;
7+
import org.hibernate.boot.spi.MetadataBuilderContributor;
8+
import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
9+
import org.springframework.context.annotation.ComponentScan;
10+
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
11+
12+
import java.util.Properties;
13+
14+
/**
15+
* @author Vlad Mihalcea
16+
*/
17+
@ComponentScan(
18+
basePackages = {
19+
"io.hypersistence.utils.spring.repo.projection",
20+
}
21+
)
22+
@EnableJpaRepositories(
23+
value = "io.hypersistence.utils.spring.repo.projection",
24+
repositoryBaseClass = BaseJpaRepositoryImpl.class
25+
)
26+
public class SpringDataProjectionConfiguration extends AbstractSpringDataJPAConfiguration {
27+
28+
@Override
29+
protected String packageToScan() {
30+
return Post.class.getPackage().getName();
31+
}
32+
33+
@Override
34+
protected void additionalProperties(Properties properties) {
35+
properties.put("hibernate.jdbc.batch_size", "100");
36+
properties.put("hibernate.order_inserts", "true");
37+
38+
properties.put(
39+
EntityManagerFactoryBuilderImpl.METADATA_BUILDER_CONTRIBUTOR,
40+
(MetadataBuilderContributor) metadataBuilder -> metadataBuilder.applyBasicType(
41+
ListArrayType.INSTANCE
42+
)
43+
);
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package io.hypersistence.utils.spring.repo.projection;
2+
3+
import io.hypersistence.utils.spring.domain.Post;
4+
import org.junit.Test;
5+
import org.junit.runner.RunWith;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
import org.springframework.beans.factory.annotation.Autowired;
9+
import org.springframework.test.annotation.DirtiesContext;
10+
import org.springframework.test.context.ContextConfiguration;
11+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12+
import org.springframework.transaction.support.TransactionCallback;
13+
import org.springframework.transaction.support.TransactionTemplate;
14+
15+
import javax.persistence.EntityManager;
16+
import javax.persistence.PersistenceContext;
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
20+
import static org.junit.Assert.assertEquals;
21+
22+
/**
23+
* @author Vlad Mihalcea
24+
*/
25+
@RunWith(SpringJUnit4ClassRunner.class)
26+
@ContextConfiguration(classes = SpringDataProjectionConfiguration.class)
27+
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
28+
public class SpringDataProjectionTest {
29+
30+
protected final Logger LOGGER = LoggerFactory.getLogger(getClass());
31+
32+
@Autowired
33+
private TransactionTemplate transactionTemplate;
34+
35+
@Autowired
36+
private PostRepository postRepository;
37+
38+
@PersistenceContext
39+
private EntityManager entityManager;
40+
41+
@Test
42+
public void myTest() {
43+
transactionTemplate.execute((TransactionCallback<Void>) transactionStatus -> {
44+
postRepository.persist(
45+
new Post()
46+
.setId(1L)
47+
.setTitle("test title")
48+
.setSlug("slug1")
49+
);
50+
51+
postRepository.persistAndFlush(
52+
new Post()
53+
.setId(2L)
54+
.setTitle("test title")
55+
.setSlug("slug2")
56+
);
57+
58+
return null;
59+
});
60+
61+
List<PostRepository.TestProjection> postsSummary = transactionTemplate.execute(transactionStatus ->
62+
postRepository.findAllSlugGroupedByTitle()
63+
);
64+
65+
// then
66+
PostRepository.TestProjection result = postsSummary.get(0);
67+
assertEquals("test title", result.getTitle());
68+
69+
List<String> expectedSlugs = new ArrayList<>();
70+
expectedSlugs.add("slug1");
71+
expectedSlugs.add("slug2");
72+
assertEquals(expectedSlugs, result.getSlugs());
73+
}
74+
}
75+

hypersistence-utils-hibernate-60/src/main/java/io/hypersistence/utils/hibernate/type/array/internal/AbstractArrayTypeDescriptor.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package io.hypersistence.utils.hibernate.type.array.internal;
22

3+
import io.hypersistence.utils.hibernate.type.array.ListArrayType;
34
import org.hibernate.HibernateException;
5+
import org.hibernate.dialect.Dialect;
6+
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
7+
import org.hibernate.type.BasicType;
48
import org.hibernate.type.descriptor.WrapperOptions;
5-
import org.hibernate.type.descriptor.java.AbstractClassJavaType;
6-
import org.hibernate.type.descriptor.java.MutabilityPlan;
7-
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
9+
import org.hibernate.type.descriptor.java.*;
10+
import org.hibernate.type.spi.TypeConfiguration;
811
import org.hibernate.usertype.DynamicParameterizedType;
912

1013
import java.sql.Array;
@@ -18,7 +21,7 @@
1821
* @author Vlad Mihalcea
1922
*/
2023
public abstract class AbstractArrayTypeDescriptor<T>
21-
extends AbstractClassJavaType<T> implements DynamicParameterizedType {
24+
extends AbstractClassJavaType<T> implements DynamicParameterizedType, BasicPluralJavaType {
2225

2326
private Class<T> arrayObjectClass;
2427

@@ -96,6 +99,16 @@ public <X> T wrap(X value, WrapperOptions options) {
9699
return (T) value;
97100
}
98101

102+
@Override
103+
public JavaType getElementJavaType() {
104+
return null;
105+
}
106+
107+
@Override
108+
public BasicType<?> resolveType(TypeConfiguration typeConfiguration, Dialect dialect, BasicType elementType, ColumnTypeInformation columnTypeInformation) {
109+
return null;
110+
}
111+
99112
protected String getSqlArrayType() {
100113
return sqlArrayType;
101114
}

0 commit comments

Comments
 (0)