diff --git a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index 127291188..73e62ed98 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -456,7 +456,12 @@ public class SimpleJpaRepository implements JpaRepos CriteriaQuery query = builder.createQuery(Long.class); Root root = applySpecificationToCriteria(spec, query); - query.select(builder.count(root)); + + if (query.isDistinct()) { + query.select(builder.countDistinct(root)); + } else { + query.select(builder.count(root)); + } return em.createQuery(query); } diff --git a/src/test/java/org/springframework/data/jpa/domain/sample/Child.java b/src/test/java/org/springframework/data/jpa/domain/sample/Child.java new file mode 100644 index 000000000..974c33198 --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/domain/sample/Child.java @@ -0,0 +1,49 @@ +/* + * Copyright 2013 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.domain.sample; + +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; + +@Entity +public class Child { + + @Id + @GeneratedValue + Long id; + + @ManyToMany(mappedBy = "children") + Set parents = new HashSet(); + + /** + * @param parent + */ + public Child add(Parent parent) { + + this.parents.add(parent); + + if (!parent.children.contains(this)) { + parent.add(this); + } + + return this; + } +} diff --git a/src/test/java/org/springframework/data/jpa/domain/sample/Parent.java b/src/test/java/org/springframework/data/jpa/domain/sample/Parent.java new file mode 100644 index 000000000..6c18d176a --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/domain/sample/Parent.java @@ -0,0 +1,49 @@ +/* + * Copyright 2013 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.domain.sample; + +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; + +@Entity +public class Parent { + + @Id + @GeneratedValue + Long id; + + static final long serialVersionUID = -89717120680485957L; + + @ManyToMany(cascade = CascadeType.ALL) + Set children = new HashSet(); + + public Parent add(Child child) { + + this.children.add(child); + + if (!child.parents.contains(this)) { + child.add(this); + } + + return this; + } +} diff --git a/src/test/java/org/springframework/data/jpa/repository/EclipseLinkParentRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/EclipseLinkParentRepositoryIntegrationTests.java new file mode 100644 index 000000000..6cac457ff --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/EclipseLinkParentRepositoryIntegrationTests.java @@ -0,0 +1,23 @@ +/* + * Copyright 2013 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; + +import org.springframework.test.context.ContextConfiguration; + +@ContextConfiguration("classpath:eclipselink.xml") +public class EclipseLinkParentRepositoryIntegrationTests extends ParentRepositoryIntegrationTests { + +} diff --git a/src/test/java/org/springframework/data/jpa/repository/OpenJpaParentRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/OpenJpaParentRepositoryIntegrationTests.java new file mode 100644 index 000000000..9f01411c2 --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/OpenJpaParentRepositoryIntegrationTests.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013 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; + +import org.junit.Ignore; +import org.springframework.test.context.ContextConfiguration; + +@ContextConfiguration("classpath:openjpa.xml") +public class OpenJpaParentRepositoryIntegrationTests extends ParentRepositoryIntegrationTests { + + @Override + @Ignore + public void testWithJoin() throws Exception { + + } +} diff --git a/src/test/java/org/springframework/data/jpa/repository/ParentRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/ParentRepositoryIntegrationTests.java new file mode 100644 index 000000000..dc76fac4a --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/ParentRepositoryIntegrationTests.java @@ -0,0 +1,108 @@ +/* + * Copyright 2013 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; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.util.List; +import java.util.Set; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.domain.sample.Child; +import org.springframework.data.jpa.domain.sample.Parent; +import org.springframework.data.jpa.repository.sample.ParentRepository; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:config/namespace-application-context.xml") +public class ParentRepositoryIntegrationTests { + + @Autowired + ParentRepository repository; + + @Before + public void setUp() { + + repository.save(new Parent().add(new Child())); + repository.save(new Parent().add(new Child()).add(new Child())); + repository.save(new Parent().add(new Child())); + repository.save(new Parent()); + repository.flush(); + } + + @Test + public void testWithoutJoin() throws Exception { + + Page page = repository.findAll(new Specification() { + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + Path> childrenPath = root.get("children"); + query.distinct(true); + return cb.isNotEmpty(childrenPath); + } + }, new PageRequest(0, 5, new Sort(Sort.Direction.ASC, "id"))); + + List content = page.getContent(); + + assertThat(content.size(), is(3)); + assertThat(page.getSize(), is(5)); + assertThat(page.getNumber(), is(0)); + assertThat(page.getTotalElements(), is(3L)); + assertThat(page.getTotalPages(), is(1)); + } + + @Test + public void testWithJoin() throws Exception { + Page page = repository.findAll(new Specification() { + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + Path> childrenPath = root.get("children"); + root.join("children"); + // we are interesting in distinct items, especially when join presents in query + query.distinct(true); + return cb.isNotEmpty(childrenPath); + } + }, new PageRequest(0, 5, new Sort(Sort.Direction.ASC, "id"))); + + List content = page.getContent(); + + // according to the initial setup there should be + // 3 parents which children collection is not empty + assertThat(content.size(), is(3)); + assertThat(page.getSize(), is(5)); + assertThat(page.getNumber(), is(0)); + + // we get here wrong total elements number since + // count query doesn't take into account the distinct marker of query + assertThat(page.getTotalElements(), is(3L)); + assertThat(page.getTotalPages(), is(1)); + } +} diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/ParentRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/ParentRepository.java new file mode 100644 index 000000000..33bf6def7 --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/sample/ParentRepository.java @@ -0,0 +1,23 @@ +/* + * Copyright 2013 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.sample; + +import org.springframework.data.jpa.domain.sample.Parent; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface ParentRepository extends JpaRepository, JpaSpecificationExecutor { +} diff --git a/src/test/resources/META-INF/persistence.xml b/src/test/resources/META-INF/persistence.xml index e2c1924a4..fda60fa7c 100644 --- a/src/test/resources/META-INF/persistence.xml +++ b/src/test/resources/META-INF/persistence.xml @@ -2,13 +2,15 @@ org.springframework.data.jpa.domain.AbstractPersistable - org.springframework.data.jpa.domain.sample.User - org.springframework.data.jpa.domain.sample.SpecialUser - org.springframework.data.jpa.domain.sample.Role - org.springframework.data.jpa.domain.sample.Account org.springframework.data.jpa.domain.AbstractAuditable + org.springframework.data.jpa.domain.sample.Account org.springframework.data.jpa.domain.sample.AuditableUser org.springframework.data.jpa.domain.sample.AuditableRole + org.springframework.data.jpa.domain.sample.Parent + org.springframework.data.jpa.domain.sample.Child + org.springframework.data.jpa.domain.sample.Role + org.springframework.data.jpa.domain.sample.SpecialUser + org.springframework.data.jpa.domain.sample.User org.springframework.data.jpa.domain.sample.SampleEntity org.springframework.data.jpa.domain.sample.SampleEntityPK org.springframework.data.jpa.domain.sample.SampleWithIdClass