JPA specifications more operations

This commit is contained in:
DOHA 2015-02-17 20:02:30 +02:00
parent c5081d8c28
commit c16add062a
6 changed files with 161 additions and 47 deletions

View File

@ -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;
}
}
}

View File

@ -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));
}

View File

@ -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();

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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)));
}
}