DATAJPA-8 - Introduced support for QueryDsl specifications
Extracted methods taking a Specification from JpaRepository into JpaSpecificationExecutor. Introduced QueryDslPredicateExecutor that takes QueryDsl Predicate objects to execute them. Introduced QueryDsl specific subclass to execute Predicate instances. Extended JpaRepositoryFactory to rather use the QueryDsl specific implementation if the actual user repository implements QueryDslPredicateExecutor. Added test case to ensure detection of query class. Added QueryDslRepositorySupport to ease implementation of repositories. Added constructor to QueryDslJpaRepository to take an EntityPathResolver to allow plugging in a different implementation. Extracted actual EntityPath resolution tests into unit test for SimpleEntityPathResolver. Check for QueryDsl classpath presence to prevent ClassNotFoundExceptions.
This commit is contained in:
@@ -25,6 +25,7 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.sample.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.jpa.repository.QueryHints;
|
||||
@@ -39,7 +40,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface UserRepository extends JpaRepository<User, Integer>,
|
||||
UserRepositoryCustom {
|
||||
JpaSpecificationExecutor<User>, UserRepositoryCustom {
|
||||
|
||||
/**
|
||||
* Retrieve users by their lastname. The finder
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.jpa.repository.support;
|
||||
|
||||
import static junit.framework.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
@@ -27,10 +28,13 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.data.domain.Persistable;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.data.jpa.domain.sample.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.QueryDslPredicateExecutor;
|
||||
import org.springframework.data.jpa.repository.custom.CustomGenericJpaRepositoryFactory;
|
||||
import org.springframework.data.jpa.repository.custom.UserCustomExtendedRepository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
||||
/**
|
||||
@@ -46,7 +50,7 @@ public class JpaRepositoryFactoryUnitTests {
|
||||
@Mock
|
||||
EntityManager entityManager;
|
||||
@Mock
|
||||
JpaEntityInformation<Object, Serializable> metadata;
|
||||
JpaEntityInformation metadata;
|
||||
|
||||
|
||||
@Before
|
||||
@@ -60,7 +64,7 @@ public class JpaRepositoryFactoryUnitTests {
|
||||
public <T, ID extends Serializable> JpaEntityInformation<T, ID> getEntityInformation(
|
||||
Class<T> domainClass) {
|
||||
|
||||
return (JpaEntityInformation<T, ID>) metadata;
|
||||
return metadata;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -134,7 +138,7 @@ public class JpaRepositoryFactoryUnitTests {
|
||||
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void createsProxyWithCustomBaseClass() throws Exception {
|
||||
public void createsProxyWithCustomBaseClass() {
|
||||
|
||||
JpaRepositoryFactory factory =
|
||||
new CustomGenericJpaRepositoryFactory(entityManager);
|
||||
@@ -144,9 +148,25 @@ public class JpaRepositoryFactoryUnitTests {
|
||||
repository.customMethod(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void usesQueryDslRepositoryIfInterfaceImplementsExecutor() {
|
||||
|
||||
when(metadata.getJavaType()).thenReturn(User.class);
|
||||
assertEquals(QueryDslJpaRepository.class,
|
||||
factory.getRepositoryBaseClass(QueryDslSampleRepository.class));
|
||||
|
||||
QueryDslSampleRepository repository =
|
||||
factory.getRepository(QueryDslSampleRepository.class);
|
||||
assertEquals(QueryDslJpaRepository.class,
|
||||
((Advised) repository).getTargetClass());
|
||||
}
|
||||
|
||||
private interface SimpleSampleRepository extends
|
||||
JpaRepository<User, Integer> {
|
||||
|
||||
@Transactional
|
||||
User findOne(Integer id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,13 +206,8 @@ public class JpaRepositoryFactoryUnitTests {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to make the factory use {@link PersistableMetadata} .
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private static abstract class User implements Persistable<Long> {
|
||||
private interface QueryDslSampleRepository extends SimpleSampleRepository,
|
||||
QueryDslPredicateExecutor<User> {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2011 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.support;
|
||||
|
||||
/**
|
||||
* Stub for a generated QueryDsl query class.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class QSimpleEntityPathResolverUnitTests_Sample {
|
||||
|
||||
public QSimpleEntityPathResolverUnitTests_Sample field;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2008-2011 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.support;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.data.jpa.domain.sample.QUser;
|
||||
import org.springframework.data.jpa.domain.sample.User;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.mysema.query.types.expr.BooleanExpression;
|
||||
import com.mysema.query.types.path.PathBuilder;
|
||||
import com.mysema.query.types.path.PathBuilderFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Integration test for {@link QueryDslJpaRepository}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration({ "classpath:infrastructure.xml" })
|
||||
@Transactional
|
||||
public class QueryDslJpaRepositoryTests {
|
||||
|
||||
@PersistenceContext
|
||||
EntityManager em;
|
||||
|
||||
QueryDslJpaRepository<User, Integer> repository;
|
||||
QUser user = new QUser("user");
|
||||
User dave, carter;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
JpaEntityInformation<User, Integer> information =
|
||||
new JpaMetamodelEntityInformation<User, Integer>(User.class,
|
||||
em.getMetamodel());
|
||||
|
||||
repository = new QueryDslJpaRepository<User, Integer>(information, em);
|
||||
dave =
|
||||
repository.save(new User("Dave", "Matthews",
|
||||
"dave@matthews.com"));
|
||||
carter =
|
||||
repository.save(new User("Carter", "Beauford",
|
||||
"carter@beauford.com"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void executesPredicatesCorrectly() throws Exception {
|
||||
|
||||
BooleanExpression isCalledDave = user.firstname.eq("Dave");
|
||||
BooleanExpression isBeauford = user.lastname.eq("Beauford");
|
||||
|
||||
List<User> result = repository.findAll(isCalledDave.or(isBeauford));
|
||||
|
||||
assertThat(result.size(), is(2));
|
||||
assertThat(result, hasItems(carter, dave));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void executesStringBasedPredicatesCorrectly() throws Exception {
|
||||
|
||||
PathBuilder<User> builder = new PathBuilderFactory().create(User.class);
|
||||
|
||||
BooleanExpression isCalledDave =
|
||||
builder.getString("firstname").eq("Dave");
|
||||
BooleanExpression isBeauford =
|
||||
builder.getString("lastname").eq("Beauford");
|
||||
|
||||
List<User> result = repository.findAll(isCalledDave.or(isBeauford));
|
||||
|
||||
assertThat(result.size(), is(2));
|
||||
assertThat(result, hasItems(carter, dave));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package org.springframework.data.jpa.repository.support;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.data.jpa.domain.sample.QUser;
|
||||
import org.springframework.data.jpa.domain.sample.User;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
||||
/**
|
||||
* Integration test for {@link QueryDslRepositorySupport}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration({ "classpath:infrastructure.xml" })
|
||||
@Transactional
|
||||
public class QueryDslRepositorySupportTests {
|
||||
|
||||
@PersistenceContext
|
||||
EntityManager em;
|
||||
|
||||
UserRepository repository;
|
||||
User dave, carter;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
||||
dave = new User("Dave", "Matthews", "dave@matthews.com");
|
||||
em.persist(dave);
|
||||
|
||||
carter = new User("Carter", "Beauford", "carter@beauford.com");
|
||||
em.persist(carter);
|
||||
|
||||
UserRepositoryImpl repository = new UserRepositoryImpl();
|
||||
repository.setEntityManager(em);
|
||||
repository.validate();
|
||||
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void readsUsersCorrectly() throws Exception {
|
||||
|
||||
List<User> result = repository.findUsersByLastname("Matthews");
|
||||
assertThat(result.size(), is(1));
|
||||
assertThat(result.get(0), is(dave));
|
||||
|
||||
result = repository.findUsersByLastname("Beauford");
|
||||
assertThat(result.size(), is(1));
|
||||
assertThat(result.get(0), is(carter));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void updatesUsersCorrectly() throws Exception {
|
||||
|
||||
long updates = repository.updateLastnamesTo("Foo");
|
||||
assertThat(updates, is(2L));
|
||||
|
||||
List<User> result = repository.findUsersByLastname("Matthews");
|
||||
assertThat(result.size(), is(0));
|
||||
|
||||
result = repository.findUsersByLastname("Beauford");
|
||||
assertThat(result.size(), is(0));
|
||||
|
||||
result = repository.findUsersByLastname("Foo");
|
||||
assertThat(result.size(), is(2));
|
||||
assertThat(result, hasItems(dave, carter));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void deletesAllWithLastnameCorrectly() throws Exception {
|
||||
|
||||
long updates = repository.deleteAllWithLastname("Matthews");
|
||||
assertThat(updates, is(1L));
|
||||
|
||||
List<User> result = repository.findUsersByLastname("Matthews");
|
||||
assertThat(result.size(), is(0));
|
||||
|
||||
result = repository.findUsersByLastname("Beauford");
|
||||
assertThat(result.size(), is(1));
|
||||
assertThat(result.get(0), is(carter));
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsUnsetEntityManager() throws Exception {
|
||||
|
||||
UserRepositoryImpl repositoryImpl = new UserRepositoryImpl();
|
||||
repositoryImpl.validate();
|
||||
}
|
||||
|
||||
private static interface UserRepository {
|
||||
|
||||
List<User> findUsersByLastname(String firstname);
|
||||
|
||||
|
||||
long updateLastnamesTo(String lastname);
|
||||
|
||||
|
||||
long deleteAllWithLastname(String lastname);
|
||||
}
|
||||
|
||||
private static class UserRepositoryImpl extends QueryDslRepositorySupport
|
||||
implements UserRepository {
|
||||
|
||||
private static final QUser user = QUser.user;
|
||||
|
||||
|
||||
public List<User> findUsersByLastname(String lastname) {
|
||||
|
||||
return from(user).where(user.lastname.eq(lastname)).list(user);
|
||||
}
|
||||
|
||||
|
||||
public long updateLastnamesTo(String lastname) {
|
||||
|
||||
return update(user).set(user.lastname, lastname).execute();
|
||||
}
|
||||
|
||||
|
||||
public long deleteAllWithLastname(String lastname) {
|
||||
|
||||
return delete(user).where(user.lastname.eq(lastname)).execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2008-2011 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.support;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.jpa.domain.sample.QUser;
|
||||
import org.springframework.data.jpa.domain.sample.User;
|
||||
import org.springframework.data.jpa.repository.support.QueryDslJpaRepository.EntityPathResolver;
|
||||
import org.springframework.data.jpa.repository.support.QueryDslJpaRepository.SimpleEntityPathResolver;
|
||||
import org.springframework.data.jpa.repository.util.JpaClassUtilsUnitTests.NamedUser;
|
||||
import org.springframework.data.jpa.repository.util.QJpaClassUtilsUnitTests_NamedUser;
|
||||
|
||||
|
||||
/**
|
||||
* Unit test for {@link SimpleEntityPathResolver}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class SimpleEntityPathResolverUnitTests {
|
||||
|
||||
EntityPathResolver resolver =
|
||||
QueryDslJpaRepository.SimpleEntityPathResolver.INSTANCE;
|
||||
|
||||
|
||||
@Test
|
||||
public void createsRepositoryFromDomainClassCorrectly() throws Exception {
|
||||
|
||||
assertThat(resolver.createPath(User.class), is(QUser.class));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void resolvesEntityPathForInnerClassCorrectly() throws Exception {
|
||||
|
||||
assertThat(resolver.createPath(NamedUser.class),
|
||||
is(QJpaClassUtilsUnitTests_NamedUser.class));
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void rejectsFoundClassWithoutStaticFieldOfSameType()
|
||||
throws Exception {
|
||||
|
||||
resolver.createPath(Sample.class);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsClassWithoutQueryClassConfrmingToTheNamingScheme()
|
||||
throws Exception {
|
||||
|
||||
resolver.createPath(QSimpleEntityPathResolverUnitTests_Sample.class);
|
||||
}
|
||||
|
||||
static class Sample {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public class JpaClassUtilsUnitTests {
|
||||
}
|
||||
|
||||
@Entity(name = "AnotherNamedUser")
|
||||
static class NamedUser {
|
||||
public static class NamedUser {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user