All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.data.relational.core.query.Criteria Maven / Gradle / Ivy

There is a newer version: 3.3.2
Show newest version
/*
 * Copyright 2019-2023 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
 *
 *      https://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.relational.core.query;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;

import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.relational.core.sql.IdentifierProcessing;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.util.Pair;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * Central class for creating queries. It follows a fluent API style so that you can easily chain together multiple
 * criteria. Static import of the {@code Criteria.property(…)} method will improve readability as in
 * {@code where(property(…).is(…)}.
 * 

* The Criteria API supports composition with a {@link #empty() NULL object} and a {@link #from(List) static factory * method}. Example usage: * *

 * Criteria.from(Criteria.where("name").is("Foo"), Criteria.from(Criteria.where("age").greaterThan(42)));
 * 
* * rendering: * *
 * WHERE name = 'Foo' AND age > 42
 * 
* * @author Mark Paluch * @author Oliver Drotbohm * @author Roman Chigvintsev * @author Jens Schauder * @since 2.0 */ public class Criteria implements CriteriaDefinition { static final Criteria EMPTY = new Criteria(SqlIdentifier.EMPTY, Comparator.INITIAL, null); private final @Nullable Criteria previous; private final Combinator combinator; private final List group; private final @Nullable SqlIdentifier column; private final @Nullable Comparator comparator; private final @Nullable Object value; private final boolean ignoreCase; private Criteria(SqlIdentifier column, Comparator comparator, @Nullable Object value) { this(null, Combinator.INITIAL, Collections.emptyList(), column, comparator, value, false); } private Criteria(@Nullable Criteria previous, Combinator combinator, List group, @Nullable SqlIdentifier column, @Nullable Comparator comparator, @Nullable Object value) { this(previous, combinator, group, column, comparator, value, false); } private Criteria(@Nullable Criteria previous, Combinator combinator, List group, @Nullable SqlIdentifier column, @Nullable Comparator comparator, @Nullable Object value, boolean ignoreCase) { this.previous = previous; this.combinator = previous != null && previous.isEmpty() ? Combinator.INITIAL : combinator; this.group = group; this.column = column; this.comparator = comparator; this.value = value; this.ignoreCase = ignoreCase; } private Criteria(@Nullable Criteria previous, Combinator combinator, List group) { this.previous = previous; this.combinator = previous != null && previous.isEmpty() ? Combinator.INITIAL : combinator; this.group = group; this.column = null; this.comparator = null; this.value = null; this.ignoreCase = false; } /** * Static factory method to create an empty Criteria. * * @return an empty {@link Criteria}. */ public static Criteria empty() { return EMPTY; } /** * Create a new {@link Criteria} and combine it as group with {@code AND} using the provided {@link List Criterias}. * * @return new {@link Criteria}. */ public static Criteria from(Criteria... criteria) { Assert.notNull(criteria, "Criteria must not be null"); Assert.noNullElements(criteria, "Criteria must not contain null elements"); return from(Arrays.asList(criteria)); } /** * Create a new {@link Criteria} and combine it as group with {@code AND} using the provided {@link List Criterias}. * * @return new {@link Criteria}. */ public static Criteria from(List criteria) { Assert.notNull(criteria, "Criteria must not be null"); Assert.noNullElements(criteria, "Criteria must not contain null elements"); if (criteria.isEmpty()) { return EMPTY; } if (criteria.size() == 1) { return criteria.get(0); } return EMPTY.and(criteria); } /** * Static factory method to create a Criteria using the provided {@code column} name. * * @param column Must not be {@literal null} or empty. * @return a new {@link CriteriaStep} object to complete the first {@link Criteria}. */ public static CriteriaStep where(String column) { Assert.hasText(column, "Column name must not be null or empty"); return new DefaultCriteriaStep(SqlIdentifier.unquoted(column)); } /** * Create a new {@link Criteria} and combine it with {@code AND} using the provided {@code column} name. * * @param column Must not be {@literal null} or empty. * @return a new {@link CriteriaStep} object to complete the next {@link Criteria}. */ public CriteriaStep and(String column) { Assert.hasText(column, "Column name must not be null or empty"); SqlIdentifier identifier = SqlIdentifier.unquoted(column); return new DefaultCriteriaStep(identifier) { @Override protected Criteria createCriteria(Comparator comparator, @Nullable Object value) { return new Criteria(Criteria.this, Combinator.AND, Collections.emptyList(), identifier, comparator, value); } }; } /** * Create a new {@link Criteria} and combine it as group with {@code AND} using the provided {@link Criteria} group. * * @param criteria criteria object. * @return a new {@link Criteria} object. * @since 1.1 */ public Criteria and(CriteriaDefinition criteria) { Assert.notNull(criteria, "Criteria must not be null"); return and(Collections.singletonList(criteria)); } /** * Create a new {@link Criteria} and combine it as group with {@code AND} using the provided {@link Criteria} group. * * @param criteria criteria objects. * @return a new {@link Criteria} object. */ @SuppressWarnings("unchecked") public Criteria and(List criteria) { Assert.notNull(criteria, "Criteria must not be null"); return new Criteria(Criteria.this, Combinator.AND, (List) criteria); } /** * Create a new {@link Criteria} and combine it with {@code OR} using the provided {@code column} name. * * @param column Must not be {@literal null} or empty. * @return a new {@link CriteriaStep} object to complete the next {@link Criteria}. */ public CriteriaStep or(String column) { Assert.hasText(column, "Column name must not be null or empty"); SqlIdentifier identifier = SqlIdentifier.unquoted(column); return new DefaultCriteriaStep(identifier) { @Override protected Criteria createCriteria(Comparator comparator, @Nullable Object value) { return new Criteria(Criteria.this, Combinator.OR, Collections.emptyList(), identifier, comparator, value); } }; } /** * Create a new {@link Criteria} and combine it as group with {@code OR} using the provided {@link Criteria} group. * * @param criteria criteria object. * @return a new {@link Criteria} object. * @since 1.1 */ public Criteria or(CriteriaDefinition criteria) { Assert.notNull(criteria, "Criteria must not be null"); return or(Collections.singletonList(criteria)); } /** * Create a new {@link Criteria} and combine it as group with {@code OR} using the provided {@link Criteria} group. * * @param criteria criteria object. * @return a new {@link Criteria} object. * @since 1.1 */ @SuppressWarnings("unchecked") public Criteria or(List criteria) { Assert.notNull(criteria, "Criteria must not be null"); return new Criteria(Criteria.this, Combinator.OR, (List) criteria); } /** * Creates a new {@link Criteria} with the given "ignore case" flag. * * @param ignoreCase {@literal true} if comparison should be done in case-insensitive way * @return a new {@link Criteria} object */ public Criteria ignoreCase(boolean ignoreCase) { if (this.ignoreCase != ignoreCase) { return new Criteria(previous, combinator, group, column, comparator, value, ignoreCase); } return this; } /** * @return the previous {@link Criteria} object. Can be {@literal null} if there is no previous {@link Criteria}. * @see #hasPrevious() */ @Nullable public Criteria getPrevious() { return previous; } /** * @return {@literal true} if this {@link Criteria} has a previous one. */ public boolean hasPrevious() { return previous != null; } /** * @return {@literal true} if this {@link Criteria} is empty. * @since 1.1 */ @Override public boolean isEmpty() { if (!doIsEmpty()) { return false; } Criteria parent = this.previous; while (parent != null) { if (!parent.doIsEmpty()) { return false; } parent = parent.previous; } return true; } private boolean doIsEmpty() { if (this.comparator == Comparator.INITIAL) { return true; } if (this.column != null) { return false; } for (CriteriaDefinition criteria : group) { if (!criteria.isEmpty()) { return false; } } return true; } /** * @return {@literal true} if this {@link Criteria} is empty. */ public boolean isGroup() { return !this.group.isEmpty(); } /** * @return {@link Combinator} to combine this criteria with a previous one. */ public Combinator getCombinator() { return combinator; } @Override public List getGroup() { return group; } /** * @return the column/property name. */ @Nullable public SqlIdentifier getColumn() { return column; } /** * @return {@link Comparator}. */ @Nullable public Comparator getComparator() { return comparator; } /** * @return the comparison value. Can be {@literal null}. */ @Nullable public Object getValue() { return value; } /** * Checks whether comparison should be done in case-insensitive way. * * @return {@literal true} if comparison should be done in case-insensitive way */ @Override public boolean isIgnoreCase() { return ignoreCase; } @Override public String toString() { if (isEmpty()) { return ""; } StringBuilder builder = new StringBuilder(); unroll(this, builder); return builder.toString(); } private void unroll(CriteriaDefinition criteria, StringBuilder stringBuilder) { CriteriaDefinition current = criteria; // reverse unroll criteria chain Map forwardChain = new HashMap<>(); while (current.hasPrevious()) { forwardChain.put(current.getPrevious(), current); current = current.getPrevious(); } // perform the actual mapping render(current, stringBuilder); while (forwardChain.containsKey(current)) { CriteriaDefinition criterion = forwardChain.get(current); if (criterion.getCombinator() != Combinator.INITIAL) { stringBuilder.append(' ').append(criterion.getCombinator().name()).append(' '); } render(criterion, stringBuilder); current = criterion; } } private void unrollGroup(List criteria, StringBuilder stringBuilder) { stringBuilder.append("("); boolean first = true; for (CriteriaDefinition criterion : criteria) { if (criterion.isEmpty()) { continue; } if (!first) { Combinator combinator = criterion.getCombinator() == Combinator.INITIAL ? Combinator.AND : criterion.getCombinator(); stringBuilder.append(' ').append(combinator.name()).append(' '); } unroll(criterion, stringBuilder); first = false; } stringBuilder.append(")"); } private void render(CriteriaDefinition criteria, StringBuilder stringBuilder) { if (criteria.isEmpty()) { return; } if (criteria.isGroup()) { unrollGroup(criteria.getGroup(), stringBuilder); return; } stringBuilder.append(criteria.getColumn().toSql(IdentifierProcessing.NONE)).append(' ') .append(criteria.getComparator().getComparator()); switch (criteria.getComparator()) { case BETWEEN: case NOT_BETWEEN: Pair pair = (Pair) criteria.getValue(); stringBuilder.append(' ').append(pair.getFirst()).append(" AND ").append(pair.getSecond()); break; case IS_NULL: case IS_NOT_NULL: case IS_TRUE: case IS_FALSE: break; case IN: case NOT_IN: stringBuilder.append(" (").append(renderValue(criteria.getValue())).append(')'); break; default: stringBuilder.append(' ').append(renderValue(criteria.getValue())); } } private static String renderValue(@Nullable Object value) { if (value instanceof Number) { return value.toString(); } if (value instanceof Collection) { StringJoiner joiner = new StringJoiner(", "); ((Collection) value).forEach(o -> joiner.add(renderValue(o))); return joiner.toString(); } if (value != null) { return String.format("'%s'", value); } return "null"; } /** * Interface declaring terminal builder methods to build a {@link Criteria}. */ public interface CriteriaStep { /** * Creates a {@link Criteria} using equality. * * @param value must not be {@literal null}. */ Criteria is(Object value); /** * Creates a {@link Criteria} using equality (is not). * * @param value must not be {@literal null}. */ Criteria not(Object value); /** * Creates a {@link Criteria} using {@code IN}. * * @param values must not be {@literal null}. */ Criteria in(Object... values); /** * Creates a {@link Criteria} using {@code IN}. * * @param values must not be {@literal null}. */ Criteria in(Collection values); /** * Creates a {@link Criteria} using {@code NOT IN}. * * @param values must not be {@literal null}. */ Criteria notIn(Object... values); /** * Creates a {@link Criteria} using {@code NOT IN}. * * @param values must not be {@literal null}. */ Criteria notIn(Collection values); /** * Creates a {@link Criteria} using between ({@literal BETWEEN begin AND end}). * * @param begin must not be {@literal null}. * @param end must not be {@literal null}. * @since 2.2 */ Criteria between(Object begin, Object end); /** * Creates a {@link Criteria} using not between ({@literal NOT BETWEEN begin AND end}). * * @param begin must not be {@literal null}. * @param end must not be {@literal null}. * @since 2.2 */ Criteria notBetween(Object begin, Object end); /** * Creates a {@link Criteria} using less-than ({@literal <}). * * @param value must not be {@literal null}. */ Criteria lessThan(Object value); /** * Creates a {@link Criteria} using less-than or equal to ({@literal <=}). * * @param value must not be {@literal null}. */ Criteria lessThanOrEquals(Object value); /** * Creates a {@link Criteria} using greater-than({@literal >}). * * @param value must not be {@literal null}. */ Criteria greaterThan(Object value); /** * Creates a {@link Criteria} using greater-than or equal to ({@literal >=}). * * @param value must not be {@literal null}. */ Criteria greaterThanOrEquals(Object value); /** * Creates a {@link Criteria} using {@code LIKE}. * * @param value must not be {@literal null}. */ Criteria like(Object value); /** * Creates a {@link Criteria} using {@code NOT LIKE}. * * @param value must not be {@literal null} * @return a new {@link Criteria} object */ Criteria notLike(Object value); /** * Creates a {@link Criteria} using {@code IS NULL}. */ Criteria isNull(); /** * Creates a {@link Criteria} using {@code IS NOT NULL}. */ Criteria isNotNull(); /** * Creates a {@link Criteria} using {@code IS TRUE}. * * @return a new {@link Criteria} object */ Criteria isTrue(); /** * Creates a {@link Criteria} using {@code IS FALSE}. * * @return a new {@link Criteria} object */ Criteria isFalse(); } /** * Default {@link CriteriaStep} implementation. */ static class DefaultCriteriaStep implements CriteriaStep { private final SqlIdentifier property; DefaultCriteriaStep(SqlIdentifier property) { this.property = property; } @Override public Criteria is(Object value) { Assert.notNull(value, "Value must not be null"); return createCriteria(Comparator.EQ, value); } @Override public Criteria not(Object value) { Assert.notNull(value, "Value must not be null"); return createCriteria(Comparator.NEQ, value); } @Override public Criteria in(Object... values) { Assert.notNull(values, "Values must not be null"); Assert.noNullElements(values, "Values must not contain a null value"); if (values.length > 1 && values[1] instanceof Collection) { throw new InvalidDataAccessApiUsageException( "You can only pass in one argument of type " + values[1].getClass().getName()); } return createCriteria(Comparator.IN, Arrays.asList(values)); } @Override public Criteria in(Collection values) { Assert.notNull(values, "Values must not be null"); Assert.noNullElements(values.toArray(), "Values must not contain a null value"); return createCriteria(Comparator.IN, values); } @Override public Criteria notIn(Object... values) { Assert.notNull(values, "Values must not be null"); Assert.noNullElements(values, "Values must not contain a null value"); if (values.length > 1 && values[1] instanceof Collection) { throw new InvalidDataAccessApiUsageException( "You can only pass in one argument of type " + values[1].getClass().getName()); } return createCriteria(Comparator.NOT_IN, Arrays.asList(values)); } @Override public Criteria notIn(Collection values) { Assert.notNull(values, "Values must not be null"); Assert.noNullElements(values.toArray(), "Values must not contain a null value"); return createCriteria(Comparator.NOT_IN, values); } @Override public Criteria between(Object begin, Object end) { Assert.notNull(begin, "Begin value must not be null"); Assert.notNull(end, "End value must not be null"); return createCriteria(Comparator.BETWEEN, Pair.of(begin, end)); } @Override public Criteria notBetween(Object begin, Object end) { Assert.notNull(begin, "Begin value must not be null"); Assert.notNull(end, "End value must not be null"); return createCriteria(Comparator.NOT_BETWEEN, Pair.of(begin, end)); } @Override public Criteria lessThan(Object value) { Assert.notNull(value, "Value must not be null"); return createCriteria(Comparator.LT, value); } @Override public Criteria lessThanOrEquals(Object value) { Assert.notNull(value, "Value must not be null"); return createCriteria(Comparator.LTE, value); } @Override public Criteria greaterThan(Object value) { Assert.notNull(value, "Value must not be null"); return createCriteria(Comparator.GT, value); } @Override public Criteria greaterThanOrEquals(Object value) { Assert.notNull(value, "Value must not be null"); return createCriteria(Comparator.GTE, value); } @Override public Criteria like(Object value) { Assert.notNull(value, "Value must not be null"); return createCriteria(Comparator.LIKE, value); } @Override public Criteria notLike(Object value) { Assert.notNull(value, "Value must not be null"); return createCriteria(Comparator.NOT_LIKE, value); } @Override public Criteria isNull() { return createCriteria(Comparator.IS_NULL, null); } @Override public Criteria isNotNull() { return createCriteria(Comparator.IS_NOT_NULL, null); } @Override public Criteria isTrue() { return createCriteria(Comparator.IS_TRUE, true); } @Override public Criteria isFalse() { return createCriteria(Comparator.IS_FALSE, false); } protected Criteria createCriteria(Comparator comparator, @Nullable Object value) { return new Criteria(this.property, comparator, value); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy