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 org.baeldung.persistence.model.User;
|
||||
import org.baeldung.web.util.SearchCriteria;
|
||||
import org.baeldung.web.util.SpecSearchCriteria;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
public class UserSpecification implements Specification<User> {
|
||||
|
||||
private final SearchCriteria criteria;
|
||||
private SpecSearchCriteria criteria;
|
||||
|
||||
public UserSpecification(final SearchCriteria criteria) {
|
||||
public UserSpecification(final SpecSearchCriteria criteria) {
|
||||
super();
|
||||
this.criteria = criteria;
|
||||
}
|
||||
|
||||
public SearchCriteria getCriteria() {
|
||||
public SpecSearchCriteria getCriteria() {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate toPredicate(final Root<User> root, final CriteriaQuery<?> query, final CriteriaBuilder builder) {
|
||||
if (criteria.getOperation().equalsIgnoreCase(">")) {
|
||||
return builder.greaterThanOrEqualTo(root.<String> get(criteria.getKey()), criteria.getValue().toString());
|
||||
} else if (criteria.getOperation().equalsIgnoreCase("<")) {
|
||||
return builder.lessThanOrEqualTo(root.<String> get(criteria.getKey()), criteria.getValue().toString());
|
||||
} else if (criteria.getOperation().equalsIgnoreCase(":")) {
|
||||
if (root.get(criteria.getKey()).getJavaType() == String.class) {
|
||||
return builder.like(root.<String> get(criteria.getKey()), "%" + criteria.getValue() + "%");
|
||||
} else {
|
||||
switch (criteria.getOperation()) {
|
||||
case EQUALITY:
|
||||
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());
|
||||
case LIKE:
|
||||
return builder.like(root.<String> get(criteria.getKey()), criteria.getValue().toString());
|
||||
case STARTS_WITH:
|
||||
return builder.like(root.<String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,22 +4,39 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
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.Specifications;
|
||||
|
||||
public final class UserSpecificationsBuilder {
|
||||
|
||||
private final List<SearchCriteria> params;
|
||||
private final List<SpecSearchCriteria> params;
|
||||
|
||||
public UserSpecificationsBuilder() {
|
||||
params = new ArrayList<SearchCriteria>();
|
||||
params = new ArrayList<SpecSearchCriteria>();
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
public final UserSpecificationsBuilder with(final String key, final String operation, final Object value) {
|
||||
params.add(new SearchCriteria(key, operation, value));
|
||||
public final UserSpecificationsBuilder with(final String key, final String operation, final Object value, final String prefix, final String suffix) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -29,7 +46,7 @@ public final class UserSpecificationsBuilder {
|
|||
}
|
||||
|
||||
final List<Specification<User>> specs = new ArrayList<Specification<User>>();
|
||||
for (final SearchCriteria param : params) {
|
||||
for (final SpecSearchCriteria param : params) {
|
||||
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.User;
|
||||
import org.baeldung.web.util.SearchCriteria;
|
||||
import org.baeldung.web.util.SearchOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
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.ResponseStatus;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.mysema.query.types.expr.BooleanExpression;
|
||||
|
||||
|
@ -63,10 +65,11 @@ public class UserController {
|
|||
@ResponseBody
|
||||
public List<User> findAllBySpecification(@RequestParam(value = "search") final String search) {
|
||||
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 + ",");
|
||||
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();
|
||||
|
|
|
@ -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.model.User;
|
||||
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.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
@ -25,7 +26,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
@ContextConfiguration(classes = { PersistenceConfig.class })
|
||||
@Transactional
|
||||
@TransactionConfiguration
|
||||
public class JPASpecificationsTest {
|
||||
public class JPASpecificationTest {
|
||||
|
||||
@Autowired
|
||||
private UserRepository repository;
|
||||
|
@ -51,19 +52,10 @@ public class JPASpecificationsTest {
|
|||
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
|
||||
public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {
|
||||
final UserSpecification spec = new UserSpecification(new SearchCriteria("firstName", ":", "john"));
|
||||
final UserSpecification spec1 = new UserSpecification(new SearchCriteria("lastName", ":", "doe"));
|
||||
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.EQUALITY, "john"));
|
||||
final UserSpecification spec1 = new UserSpecification(new SpecSearchCriteria("lastName", SearchOperation.EQUALITY, "doe"));
|
||||
final List<User> results = repository.findAll(Specifications.where(spec).and(spec1));
|
||||
|
||||
assertThat(userJohn, isIn(results));
|
||||
|
@ -71,31 +63,57 @@ public class JPASpecificationsTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void givenLastAndAge_whenGettingListOfUsers_thenCorrect() {
|
||||
final UserSpecification spec = new UserSpecification(new SearchCriteria("age", ">", "25"));
|
||||
final UserSpecification spec1 = new UserSpecification(new SearchCriteria("lastName", ":", "doe"));
|
||||
final List<User> results = repository.findAll(Specifications.where(spec).and(spec1));
|
||||
public void givenFirstNameInverse_whenGettingListOfUsers_thenCorrect() {
|
||||
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.NEGATION, "john"));
|
||||
final List<User> results = repository.findAll(Specifications.where(spec));
|
||||
|
||||
assertThat(userTom, isIn(results));
|
||||
assertThat(userJohn, not(isIn(results)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() {
|
||||
final UserSpecification spec = new UserSpecification(new SearchCriteria("firstName", ":", "Adam"));
|
||||
final UserSpecification spec1 = new UserSpecification(new SearchCriteria("lastName", ":", "Fox"));
|
||||
final List<User> results = repository.findAll(Specifications.where(spec).and(spec1));
|
||||
public void givenMinAge_whenGettingListOfUsers_thenCorrect() {
|
||||
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.GREATER_THAN, "25"));
|
||||
final List<User> results = repository.findAll(Specifications.where(spec));
|
||||
|
||||
assertThat(userTom, isIn(results));
|
||||
assertThat(userJohn, not(isIn(results)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect() {
|
||||
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.STARTS_WITH, "jo"));
|
||||
final List<User> results = repository.findAll(spec);
|
||||
|
||||
assertThat(userJohn, isIn(results));
|
||||
assertThat(userTom, not(isIn(results)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenPartialFirst_whenGettingListOfUsers_thenCorrect() {
|
||||
final UserSpecification spec = new UserSpecification(new SearchCriteria("firstName", ":", "jo"));
|
||||
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