Skip to content

Instantly share code, notes, and snippets.

@brunnels
Last active November 9, 2021 05:10
Show Gist options
  • Save brunnels/ab980094e0f7c5a5d5f65eff0b5f5613 to your computer and use it in GitHub Desktop.
Save brunnels/ab980094e0f7c5a5d5f65eff0b5f5613 to your computer and use it in GitHub Desktop.
Generic REST Query Language with RSQL for Spring Data JPA
package org.kraven.repository;
import org.kraven.domain.DeviceData;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* Spring Data JPA repository for the DeviceData entity.
*/
@SuppressWarnings("unused")
public interface DeviceDataRepository extends JpaRepository<DeviceData,Long>, JpaSpecificationExecutor<DeviceData> {
}
package org.kraven.repository.specification.util;
import cz.jirutka.rsql.parser.ast.ComparisonOperator;
import cz.jirutka.rsql.parser.ast.RSQLOperators;
/**
* Created by brunnels on 10/26/2016.
*/
public enum RsqlSearchOperation {
EQUAL(RSQLOperators.EQUAL),
NOT_EQUAL(RSQLOperators.NOT_EQUAL),
GREATER_THAN(RSQLOperators.GREATER_THAN),
GREATER_THAN_OR_EQUAL(RSQLOperators.GREATER_THAN_OR_EQUAL),
LESS_THAN(RSQLOperators.LESS_THAN),
LESS_THAN_OR_EQUAL(RSQLOperators.LESS_THAN_OR_EQUAL),
IN(RSQLOperators.IN),
NOT_IN(RSQLOperators.NOT_IN);
private ComparisonOperator operator;
private RsqlSearchOperation(final ComparisonOperator operator) {
this.operator = operator;
}
public static RsqlSearchOperation getSimpleOperator(final ComparisonOperator operator) {
for (final RsqlSearchOperation operation : values()) {
if (operation.getOperator() == operator) {
return operation;
}
}
return null;
}
public ComparisonOperator getOperator() {
return operator;
}
}
package org.kraven.repository.specification;
import cz.jirutka.rsql.parser.ast.ComparisonOperator;
import org.kraven.repository.specification.util.RsqlSearchOperation;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;
/**
* Created by brunnels on 10/26/2016.
*/
public class RsqlSpecification<T> implements Specification<T> {
private String property;
private ComparisonOperator operator;
private List<String> arguments;
public RsqlSpecification(final String property, final ComparisonOperator operator, final List<String> arguments) {
super();
this.property = property;
this.operator = operator;
this.arguments = arguments;
}
@Override
public Predicate toPredicate(final Root<T> root, final CriteriaQuery<?> query, final CriteriaBuilder builder) {
final List<Object> args = castArguments(root);
final Object argument = args.get(0);
RsqlSearchOperation oper = RsqlSearchOperation.getSimpleOperator(operator);
if(oper != null) {
switch (oper) {
case EQUAL: {
if (argument instanceof String) {
return builder.like(root.<String>get(property), argument.toString().replace('*', '%'));
} else if (argument == null) {
return builder.isNull(root.get(property));
} else {
return builder.equal(root.get(property), argument);
}
}
case NOT_EQUAL: {
if (argument instanceof String) {
return builder.notLike(root.<String>get(property), argument.toString().replace('*', '%'));
} else if (argument == null) {
return builder.isNotNull(root.get(property));
} else {
return builder.notEqual(root.get(property), argument);
}
}
case GREATER_THAN: {
return builder.greaterThan(root.<String>get(property), argument.toString());
}
case GREATER_THAN_OR_EQUAL: {
return builder.greaterThanOrEqualTo(root.<String>get(property), argument.toString());
}
case LESS_THAN: {
return builder.lessThan(root.<String>get(property), argument.toString());
}
case LESS_THAN_OR_EQUAL: {
return builder.lessThanOrEqualTo(root.<String>get(property), argument.toString());
}
case IN:
return root.get(property).in(args);
case NOT_IN:
return builder.not(root.get(property).in(args));
}
}
return null;
}
// === private
private List<Object> castArguments(final Root<T> root) {
final List<Object> args = new ArrayList<Object>();
final Class<? extends Object> type = root.get(property).getJavaType();
for (final String argument : arguments) {
if (type.equals(Integer.class)) {
args.add(Integer.parseInt(argument));
} else if (type.equals(Long.class)) {
args.add(Long.parseLong(argument));
} else {
args.add(argument);
}
}
return args;
}
}
package org.kraven.repository.specification;
import cz.jirutka.rsql.parser.ast.ComparisonNode;
import cz.jirutka.rsql.parser.ast.LogicalNode;
import cz.jirutka.rsql.parser.ast.LogicalOperator;
import cz.jirutka.rsql.parser.ast.Node;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.domain.Specifications;
import java.util.ArrayList;
import java.util.List;
/**
* Created by brunnels on 10/26/2016.
*/
public class RsqlSpecificationBuilder<T> {
public Specifications<T> createSpecification(final Node node) {
if (node instanceof LogicalNode) {
return createSpecification((LogicalNode) node);
}
if (node instanceof ComparisonNode) {
return createSpecification((ComparisonNode) node);
}
return null;
}
public Specifications<T> createSpecification(final LogicalNode logicalNode) {
final List<Specifications<T>> specs = new ArrayList<Specifications<T>>();
Specifications<T> temp;
for (final Node node : logicalNode.getChildren()) {
temp = createSpecification(node);
if (temp != null) {
specs.add(temp);
}
}
Specifications<T> result = specs.get(0);
if (logicalNode.getOperator() == LogicalOperator.AND) {
for (int i = 1; i < specs.size(); i++) {
result = Specifications.where(result).and(specs.get(i));
}
} else if (logicalNode.getOperator() == LogicalOperator.OR) {
for (int i = 1; i < specs.size(); i++) {
result = Specifications.where(result).or(specs.get(i));
}
}
return result;
}
public Specifications<T> createSpecification(final ComparisonNode comparisonNode) {
Specification<T> spec = new RsqlSpecification<T>(comparisonNode.getSelector(), comparisonNode.getOperator(), comparisonNode.getArguments());
final Specifications<T> result = Specifications.where(spec);
return result;
}
}
package org.kraven.repository.specification.util;
import cz.jirutka.rsql.parser.ast.AndNode;
import cz.jirutka.rsql.parser.ast.ComparisonNode;
import cz.jirutka.rsql.parser.ast.OrNode;
import cz.jirutka.rsql.parser.ast.RSQLVisitor;
import org.kraven.repository.specification.RsqlSpecificationBuilder;
import org.springframework.data.jpa.domain.Specification;
/**
* Created by brunnels on 10/26/2016.
*/
public class RsqlVisitor<T> implements RSQLVisitor<Specification<T>, Void> {
private RsqlSpecificationBuilder<T> builder;
public RsqlVisitor() {
builder = new RsqlSpecificationBuilder<T>();
}
@Override
public Specification<T> visit(final AndNode node, final Void param) {
return builder.createSpecification(node);
}
@Override
public Specification<T> visit(final OrNode node, final Void param) {
return builder.createSpecification(node);
}
@Override
public Specification<T> visit(final ComparisonNode node, final Void params) {
return builder.createSpecification(node);
}
}
/**
* Service Implementation for managing DeviceData.
*/
@Service
@Transactional
public class DeviceDataService {
private final Logger log = LoggerFactory.getLogger(DeviceDataService.class);
@Inject
private DeviceDataRepository deviceDataRepository;
@Inject
private DeviceDataMapper deviceDataMapper;
/**
* Get all the deviceData.
*
* @search the RSQL search string
* @param pageable the pagination information
* @return the list of entities
*/
@Transactional(readOnly = true)
public Page<DeviceDataDTO> findAll(Pageable pageable, String search) {
log.debug("Request to get all DeviceData");
Page<DeviceData> result;
if(search != null) {
final Node rootNode = new RSQLParser().parse(search);
Specification<DeviceData> spec = rootNode.accept(new RsqlVisitor<DeviceData>());
result = deviceDataRepository.findAll(spec, pageable);
}
else {
result = deviceDataRepository.findAll(pageable);
}
return result.map(deviceData -> deviceDataMapper.deviceDataToDeviceDataDTO(deviceData));
}
}
@brunnels
Copy link
Author

Generic REST Query Language with RSQL using Spring Data JPA Repositories.

Based on REST Query Language with RSQL and utilizing rsql-parser library.

Maven Dependency for rsql-query

<dependency>
    <groupId>cz.jirutka.rsql</groupId>
    <artifactId>rsql-parser</artifactId>
    <version>2.0.0</version>
</dependency>

@cycorax12
Copy link

cycorax12 commented Nov 9, 2021

Do we have anything for mybatis ? Most of Rest controller query search to criteria are mostly for JPA.

I want to use it with MyBatis Example Criterion. Any suggestions ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment