DATAJPA-287 - Count query execution now considers distinct flag of query.
If the implementation of a Specification sets the distinct flag, we now create a count query that is honoring the distinct flag as well.
This commit is contained in:
@@ -456,7 +456,12 @@ public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepos
|
||||
CriteriaQuery<Long> query = builder.createQuery(Long.class);
|
||||
|
||||
Root<T> 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);
|
||||
}
|
||||
|
||||
@@ -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<Parent> parents = new HashSet<Parent>();
|
||||
|
||||
/**
|
||||
* @param parent
|
||||
*/
|
||||
public Child add(Parent parent) {
|
||||
|
||||
this.parents.add(parent);
|
||||
|
||||
if (!parent.children.contains(this)) {
|
||||
parent.add(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -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<Child> children = new HashSet<Child>();
|
||||
|
||||
public Parent add(Child child) {
|
||||
|
||||
this.children.add(child);
|
||||
|
||||
if (!child.parents.contains(this)) {
|
||||
child.add(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<Parent> page = repository.findAll(new Specification<Parent>() {
|
||||
public Predicate toPredicate(Root<Parent> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
|
||||
Path<Set<Child>> childrenPath = root.get("children");
|
||||
query.distinct(true);
|
||||
return cb.isNotEmpty(childrenPath);
|
||||
}
|
||||
}, new PageRequest(0, 5, new Sort(Sort.Direction.ASC, "id")));
|
||||
|
||||
List<Parent> 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<Parent> page = repository.findAll(new Specification<Parent>() {
|
||||
public Predicate toPredicate(Root<Parent> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
|
||||
Path<Set<Child>> 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<Parent> 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));
|
||||
}
|
||||
}
|
||||
@@ -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<Parent, Long>, JpaSpecificationExecutor<Parent> {
|
||||
}
|
||||
@@ -2,13 +2,15 @@
|
||||
<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="default">
|
||||
<class>org.springframework.data.jpa.domain.AbstractPersistable</class>
|
||||
<class>org.springframework.data.jpa.domain.sample.User</class>
|
||||
<class>org.springframework.data.jpa.domain.sample.SpecialUser</class>
|
||||
<class>org.springframework.data.jpa.domain.sample.Role</class>
|
||||
<class>org.springframework.data.jpa.domain.sample.Account</class>
|
||||
<class>org.springframework.data.jpa.domain.AbstractAuditable</class>
|
||||
<class>org.springframework.data.jpa.domain.sample.Account</class>
|
||||
<class>org.springframework.data.jpa.domain.sample.AuditableUser</class>
|
||||
<class>org.springframework.data.jpa.domain.sample.AuditableRole</class>
|
||||
<class>org.springframework.data.jpa.domain.sample.Parent</class>
|
||||
<class>org.springframework.data.jpa.domain.sample.Child</class>
|
||||
<class>org.springframework.data.jpa.domain.sample.Role</class>
|
||||
<class>org.springframework.data.jpa.domain.sample.SpecialUser</class>
|
||||
<class>org.springframework.data.jpa.domain.sample.User</class>
|
||||
<class>org.springframework.data.jpa.domain.sample.SampleEntity</class>
|
||||
<class>org.springframework.data.jpa.domain.sample.SampleEntityPK</class>
|
||||
<class>org.springframework.data.jpa.domain.sample.SampleWithIdClass</class>
|
||||
|
||||
Reference in New Issue
Block a user