JPA specifications more operations
This commit is contained in:
parent
c5081d8c28
commit
c16add062a
|
@ -6,36 +6,44 @@ import javax.persistence.criteria.Predicate;
|
||||||
import javax.persistence.criteria.Root;
|
import javax.persistence.criteria.Root;
|
||||||
|
|
||||||
import org.baeldung.persistence.model.User;
|
import org.baeldung.persistence.model.User;
|
||||||
import org.baeldung.web.util.SearchCriteria;
|
import org.baeldung.web.util.SpecSearchCriteria;
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
|
||||||
public class UserSpecification implements Specification<User> {
|
public class UserSpecification implements Specification<User> {
|
||||||
|
|
||||||
private final SearchCriteria criteria;
|
private SpecSearchCriteria criteria;
|
||||||
|
|
||||||
public UserSpecification(final SearchCriteria criteria) {
|
public UserSpecification(final SpecSearchCriteria criteria) {
|
||||||
super();
|
super();
|
||||||
this.criteria = criteria;
|
this.criteria = criteria;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SearchCriteria getCriteria() {
|
public SpecSearchCriteria getCriteria() {
|
||||||
return criteria;
|
return criteria;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Predicate toPredicate(final Root<User> root, final CriteriaQuery<?> query, final CriteriaBuilder builder) {
|
public Predicate toPredicate(final Root<User> root, final CriteriaQuery<?> query, final CriteriaBuilder builder) {
|
||||||
if (criteria.getOperation().equalsIgnoreCase(">")) {
|
switch (criteria.getOperation()) {
|
||||||
return builder.greaterThanOrEqualTo(root.<String> get(criteria.getKey()), criteria.getValue().toString());
|
case EQUALITY:
|
||||||
} else if (criteria.getOperation().equalsIgnoreCase("<")) {
|
return builder.equal(root.get(criteria.getKey()), criteria.getValue());
|
||||||
|
case NEGATION:
|
||||||
|
return builder.notEqual(root.get(criteria.getKey()), criteria.getValue());
|
||||||
|
case GREATER_THAN:
|
||||||
|
return builder.greaterThan(root.<String> get(criteria.getKey()), criteria.getValue().toString());
|
||||||
|
case LESS_THAN:
|
||||||
return builder.lessThanOrEqualTo(root.<String> get(criteria.getKey()), criteria.getValue().toString());
|
return builder.lessThanOrEqualTo(root.<String> get(criteria.getKey()), criteria.getValue().toString());
|
||||||
} else if (criteria.getOperation().equalsIgnoreCase(":")) {
|
case LIKE:
|
||||||
if (root.get(criteria.getKey()).getJavaType() == String.class) {
|
return builder.like(root.<String> get(criteria.getKey()), criteria.getValue().toString());
|
||||||
return builder.like(root.<String> get(criteria.getKey()), "%" + criteria.getValue() + "%");
|
case STARTS_WITH:
|
||||||
} else {
|
return builder.like(root.<String> get(criteria.getKey()), criteria.getValue() + "%");
|
||||||
return builder.equal(root.get(criteria.getKey()), criteria.getValue());
|
case ENDS_WITH:
|
||||||
}
|
return builder.like(root.<String> get(criteria.getKey()), "%" + criteria.getValue());
|
||||||
|
case CONTAINS:
|
||||||
|
return builder.like(root.<String> get(criteria.getKey()), "%" + criteria.getValue() + "%");
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,22 +4,39 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.baeldung.persistence.model.User;
|
import org.baeldung.persistence.model.User;
|
||||||
import org.baeldung.web.util.SearchCriteria;
|
import org.baeldung.web.util.SearchOperation;
|
||||||
|
import org.baeldung.web.util.SpecSearchCriteria;
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
import org.springframework.data.jpa.domain.Specifications;
|
import org.springframework.data.jpa.domain.Specifications;
|
||||||
|
|
||||||
public final class UserSpecificationsBuilder {
|
public final class UserSpecificationsBuilder {
|
||||||
|
|
||||||
private final List<SearchCriteria> params;
|
private final List<SpecSearchCriteria> params;
|
||||||
|
|
||||||
public UserSpecificationsBuilder() {
|
public UserSpecificationsBuilder() {
|
||||||
params = new ArrayList<SearchCriteria>();
|
params = new ArrayList<SpecSearchCriteria>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// API
|
// API
|
||||||
|
|
||||||
public final UserSpecificationsBuilder with(final String key, final String operation, final Object value) {
|
public final UserSpecificationsBuilder with(final String key, final String operation, final Object value, final String prefix, final String suffix) {
|
||||||
params.add(new SearchCriteria(key, operation, value));
|
SearchOperation op = SearchOperation.getSimpleOperation(operation.charAt(0));
|
||||||
|
if (op != null) {
|
||||||
|
if (op == SearchOperation.EQUALITY) // the operation may be complex operation
|
||||||
|
{
|
||||||
|
final boolean startWithAsterisk = prefix.contains("*");
|
||||||
|
final boolean endWithAsterisk = suffix.contains("*");
|
||||||
|
|
||||||
|
if (startWithAsterisk && endWithAsterisk) {
|
||||||
|
op = SearchOperation.CONTAINS;
|
||||||
|
} else if (startWithAsterisk) {
|
||||||
|
op = SearchOperation.ENDS_WITH;
|
||||||
|
} else if (endWithAsterisk) {
|
||||||
|
op = SearchOperation.STARTS_WITH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
params.add(new SpecSearchCriteria(key, op, value));
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +46,7 @@ public final class UserSpecificationsBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Specification<User>> specs = new ArrayList<Specification<User>>();
|
final List<Specification<User>> specs = new ArrayList<Specification<User>>();
|
||||||
for (final SearchCriteria param : params) {
|
for (final SpecSearchCriteria param : params) {
|
||||||
specs.add(new UserSpecification(param));
|
specs.add(new UserSpecification(param));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.baeldung.persistence.dao.UserSpecificationsBuilder;
|
||||||
import org.baeldung.persistence.model.MyUser;
|
import org.baeldung.persistence.model.MyUser;
|
||||||
import org.baeldung.persistence.model.User;
|
import org.baeldung.persistence.model.User;
|
||||||
import org.baeldung.web.util.SearchCriteria;
|
import org.baeldung.web.util.SearchCriteria;
|
||||||
|
import org.baeldung.web.util.SearchOperation;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
@ -24,6 +25,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.mysema.query.types.expr.BooleanExpression;
|
import com.mysema.query.types.expr.BooleanExpression;
|
||||||
|
|
||||||
|
@ -63,10 +65,11 @@ public class UserController {
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public List<User> findAllBySpecification(@RequestParam(value = "search") final String search) {
|
public List<User> findAllBySpecification(@RequestParam(value = "search") final String search) {
|
||||||
final UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
|
final UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
|
||||||
final Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
|
final String operationSetExper = Joiner.on("|").join(SearchOperation.SIMPLE_OPERATION_SET);
|
||||||
|
final Pattern pattern = Pattern.compile("(\\w+?)(" + operationSetExper + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),");
|
||||||
final Matcher matcher = pattern.matcher(search + ",");
|
final Matcher matcher = pattern.matcher(search + ",");
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
builder.with(matcher.group(1), matcher.group(2), matcher.group(3));
|
builder.with(matcher.group(1), matcher.group(2), matcher.group(4), matcher.group(3), matcher.group(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
final Specification<User> spec = builder.build();
|
final Specification<User> spec = builder.build();
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.baeldung.web.util;
|
||||||
|
|
||||||
|
public enum SearchOperation {
|
||||||
|
EQUALITY, NEGATION, GREATER_THAN, LESS_THAN, LIKE, STARTS_WITH, ENDS_WITH, CONTAINS;
|
||||||
|
|
||||||
|
public static final String[] SIMPLE_OPERATION_SET = { ":", "!", ">", "<", "~" };
|
||||||
|
|
||||||
|
public static SearchOperation getSimpleOperation(final char input) {
|
||||||
|
switch (input) {
|
||||||
|
case ':':
|
||||||
|
return EQUALITY;
|
||||||
|
case '!':
|
||||||
|
return NEGATION;
|
||||||
|
case '>':
|
||||||
|
return GREATER_THAN;
|
||||||
|
case '<':
|
||||||
|
return LESS_THAN;
|
||||||
|
case '~':
|
||||||
|
return LIKE;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package org.baeldung.web.util;
|
||||||
|
|
||||||
|
public class SpecSearchCriteria {
|
||||||
|
|
||||||
|
private String key;
|
||||||
|
private SearchOperation operation;
|
||||||
|
private Object value;
|
||||||
|
|
||||||
|
public SpecSearchCriteria() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpecSearchCriteria(final String key, final SearchOperation operation, final Object value) {
|
||||||
|
super();
|
||||||
|
this.key = key;
|
||||||
|
this.operation = operation;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(final String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchOperation getOperation() {
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOperation(final SearchOperation operation) {
|
||||||
|
this.operation = operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(final Object value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -10,7 +10,8 @@ import org.baeldung.persistence.dao.UserRepository;
|
||||||
import org.baeldung.persistence.dao.UserSpecification;
|
import org.baeldung.persistence.dao.UserSpecification;
|
||||||
import org.baeldung.persistence.model.User;
|
import org.baeldung.persistence.model.User;
|
||||||
import org.baeldung.spring.PersistenceConfig;
|
import org.baeldung.spring.PersistenceConfig;
|
||||||
import org.baeldung.web.util.SearchCriteria;
|
import org.baeldung.web.util.SearchOperation;
|
||||||
|
import org.baeldung.web.util.SpecSearchCriteria;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
@ -25,7 +26,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||||
@ContextConfiguration(classes = { PersistenceConfig.class })
|
@ContextConfiguration(classes = { PersistenceConfig.class })
|
||||||
@Transactional
|
@Transactional
|
||||||
@TransactionConfiguration
|
@TransactionConfiguration
|
||||||
public class JPASpecificationsTest {
|
public class JPASpecificationTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserRepository repository;
|
private UserRepository repository;
|
||||||
|
@ -51,19 +52,10 @@ public class JPASpecificationsTest {
|
||||||
repository.save(userTom);
|
repository.save(userTom);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void givenLast_whenGettingListOfUsers_thenCorrect() {
|
|
||||||
final UserSpecification spec = new UserSpecification(new SearchCriteria("lastName", ":", "doe"));
|
|
||||||
final List<User> results = repository.findAll(spec);
|
|
||||||
|
|
||||||
assertThat(userJohn, isIn(results));
|
|
||||||
assertThat(userTom, isIn(results));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {
|
public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {
|
||||||
final UserSpecification spec = new UserSpecification(new SearchCriteria("firstName", ":", "john"));
|
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.EQUALITY, "john"));
|
||||||
final UserSpecification spec1 = new UserSpecification(new SearchCriteria("lastName", ":", "doe"));
|
final UserSpecification spec1 = new UserSpecification(new SpecSearchCriteria("lastName", SearchOperation.EQUALITY, "doe"));
|
||||||
final List<User> results = repository.findAll(Specifications.where(spec).and(spec1));
|
final List<User> results = repository.findAll(Specifications.where(spec).and(spec1));
|
||||||
|
|
||||||
assertThat(userJohn, isIn(results));
|
assertThat(userJohn, isIn(results));
|
||||||
|
@ -71,31 +63,57 @@ public class JPASpecificationsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void givenLastAndAge_whenGettingListOfUsers_thenCorrect() {
|
public void givenFirstNameInverse_whenGettingListOfUsers_thenCorrect() {
|
||||||
final UserSpecification spec = new UserSpecification(new SearchCriteria("age", ">", "25"));
|
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.NEGATION, "john"));
|
||||||
final UserSpecification spec1 = new UserSpecification(new SearchCriteria("lastName", ":", "doe"));
|
final List<User> results = repository.findAll(Specifications.where(spec));
|
||||||
final List<User> results = repository.findAll(Specifications.where(spec).and(spec1));
|
|
||||||
|
|
||||||
assertThat(userTom, isIn(results));
|
assertThat(userTom, isIn(results));
|
||||||
assertThat(userJohn, not(isIn(results)));
|
assertThat(userJohn, not(isIn(results)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() {
|
public void givenMinAge_whenGettingListOfUsers_thenCorrect() {
|
||||||
final UserSpecification spec = new UserSpecification(new SearchCriteria("firstName", ":", "Adam"));
|
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.GREATER_THAN, "25"));
|
||||||
final UserSpecification spec1 = new UserSpecification(new SearchCriteria("lastName", ":", "Fox"));
|
final List<User> results = repository.findAll(Specifications.where(spec));
|
||||||
final List<User> results = repository.findAll(Specifications.where(spec).and(spec1));
|
|
||||||
|
|
||||||
|
assertThat(userTom, isIn(results));
|
||||||
assertThat(userJohn, not(isIn(results)));
|
assertThat(userJohn, not(isIn(results)));
|
||||||
assertThat(userTom, not(isIn(results)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void givenPartialFirst_whenGettingListOfUsers_thenCorrect() {
|
public void givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect() {
|
||||||
final UserSpecification spec = new UserSpecification(new SearchCriteria("firstName", ":", "jo"));
|
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.STARTS_WITH, "jo"));
|
||||||
final List<User> results = repository.findAll(spec);
|
final List<User> results = repository.findAll(spec);
|
||||||
|
|
||||||
assertThat(userJohn, isIn(results));
|
assertThat(userJohn, isIn(results));
|
||||||
assertThat(userTom, not(isIn(results)));
|
assertThat(userTom, not(isIn(results)));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Test
|
||||||
|
public void givenFirstNameSuffix_whenGettingListOfUsers_thenCorrect() {
|
||||||
|
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.ENDS_WITH, "n"));
|
||||||
|
final List<User> results = repository.findAll(spec);
|
||||||
|
|
||||||
|
assertThat(userJohn, isIn(results));
|
||||||
|
assertThat(userTom, not(isIn(results)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenFirstNameSubstring_whenGettingListOfUsers_thenCorrect() {
|
||||||
|
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.CONTAINS, "oh"));
|
||||||
|
final List<User> results = repository.findAll(spec);
|
||||||
|
|
||||||
|
assertThat(userJohn, isIn(results));
|
||||||
|
assertThat(userTom, not(isIn(results)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenAgeRange_whenGettingListOfUsers_thenCorrect() {
|
||||||
|
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.GREATER_THAN, "20"));
|
||||||
|
final UserSpecification spec1 = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.LESS_THAN, "25"));
|
||||||
|
final List<User> results = repository.findAll(Specifications.where(spec).and(spec1));
|
||||||
|
|
||||||
|
assertThat(userJohn, isIn(results));
|
||||||
|
assertThat(userTom, not(isIn(results)));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue