Используя критерии спящего режима, есть ли способ избежать специальных символов?

Для этого вопроса мы хотим избежать необходимости писать специальный запрос, поскольку запрос должен быть разным для нескольких баз данных. Используя только критерии спящего режима, мы хотим избежать специальных символов.

Эта ситуация является причиной необходимости избегать специальных символов:

Предположим, что в базе данных есть таблица 'foo'. Таблица 'foo' содержит только 1 поле, называемое 'name'. Поле "имя" может содержать символы, которые могут считаться специальными в базе данных. Два примера такого имени: "имя_1" и "имя% 1". И "_", и "%" являются специальными символами, по крайней мере, в Oracle. Если пользователь хочет выполнить поиск одного из этих примеров после их ввода в базу данных, могут возникнуть проблемы.

criterion = Restrictions.ilike("name", searchValue, MatchMode.ANYWHERE);
return findByCriteria(null, criterion);

В этом коде "searchValue" - это значение, которое пользователь дал приложению для его поиска. Если пользователь хочет найти "%" , пользователь будет возвращен с каждой записью "foo" в базе данных. Это потому, что символ "%" представляет собой подстановочный знак "любое количество символов" для сопоставления строк, а код SQL, который производит спящий режим, будет выглядеть так:

select * from foo where name like '%' 

Есть ли способ сказать спящий режим для выхода из определенных символов или создать обходное решение, не относящееся к конкретному типу базы данных?

Ответы

Ответ 1

Конструкторы LikeExpression полностью защищены, поэтому это не жизнеспособный вариант. Кроме того, он имеет свои проблемы.

Мы с коллегой создали патч, который работает очень хорошо. Суть патча заключается в том, что для конструктора LikeExpression, который использует MatchMode, мы избегаем специальных символов. Для конструктора, который потребляет символ (escape-символ), мы предполагаем, что пользователь самостоятельно выдает специальные символы.

Мы также параметризировали escape-символ, чтобы гарантировать, что он не может повредить SQL-запрос, если они используют что-то вроде символа кавычки.

package org.hibernate.criterion;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.TypedValue;

public class LikeExpression implements Criterion {
    private final String propertyName;
    private final String value;
    private final Character escapeChar;

    protected LikeExpression(
            String propertyName,
            Object value) {
        this(propertyName, value.toString(), (Character) null);
    }

    protected LikeExpression(
            String propertyName,
            String value,
            MatchMode matchMode) {
        this( propertyName, matchMode.toMatchString( value
                .toString()
                .replaceAll("!", "!!")
                .replaceAll("%", "!%")
                .replaceAll("_", "!_")), '!' );
    }

    protected LikeExpression(
            String propertyName,
            String value,
            Character escapeChar) {
        this.propertyName = propertyName;
        this.value = value;
        this.escapeChar = escapeChar;
    }

    public String toSqlString(
            Criteria criteria,
            CriteriaQuery criteriaQuery) throws HibernateException {
        Dialect dialect = criteriaQuery.getFactory().getDialect();
        String[] columns = criteriaQuery.getColumnsUsingProjection( criteria, propertyName );
        if ( columns.length != 1 ) {
            throw new HibernateException( "Like may only be used with single-column properties" );
        }
        String lhs = lhs(dialect, columns[0]);
        return lhs + " like ?" + ( escapeChar == null ? "" : " escape ?" );

    }

    public TypedValue[] getTypedValues(
            Criteria criteria,
            CriteriaQuery criteriaQuery) throws HibernateException {
        return new TypedValue[] {
                criteriaQuery.getTypedValue( criteria, propertyName, typedValue(value) ),
                criteriaQuery.getTypedValue( criteria, propertyName, escapeChar.toString() )
        };
    }

    protected String lhs(Dialect dialect, String column) {
        return column;
    }

    protected String typedValue(String value) {
        return value;
    }

}

Если вам интересно, для чего нужны методы lhs и typedValue, новое IlikeExpression должно ответить на эти вопросы.

package org.hibernate.criterion;

import org.hibernate.dialect.Dialect;

public class IlikeExpression extends LikeExpression {

    protected IlikeExpression(
            String propertyName,
            Object value) {
        super(propertyName, value);
    }

    protected IlikeExpression(
            String propertyName,
            String value,
            MatchMode matchMode) {
        super(propertyName, value, matchMode);

    }

    protected IlikeExpression(
            String propertyName,
            String value,
            Character escapeChar) {
        super(propertyName, value, escapeChar);
    }

    @Override
    protected String lhs(Dialect dialect, String column) {
        return dialect.getLowercaseFunction() + '(' + column + ')';
    }

    @Override
    protected String typedValue(String value) {
        return super.typedValue(value).toLowerCase();
    }

}

После этого осталось только ограничить использование этих новых классов:

public static Criterion like(String propertyName, Object value) {
    return new LikeExpression(propertyName, value);
}

public static Criterion like(String propertyName, String value, MatchMode matchMode) {
    return new LikeExpression(propertyName, value, matchMode);
}

public static Criterion like(String propertyName, String value, Character escapeChar) {
    return new LikeExpression(propertyName, value, escapeChar);
}

public static Criterion ilike(String propertyName, Object value) {
    return new IlikeExpression(propertyName, value);
}

public static Criterion ilike(String propertyName, String value, MatchMode matchMode) {
    return new IlikeExpression(propertyName, value, matchMode);
}

public static Criterion ilike(String propertyName, String value, Character escapeChar) {
    return new IlikeExpression(propertyName, value, escapeChar);
}

Править: О да. Это работает для Oracle. Однако мы не уверены в других базах данных.

Ответ 2

Это не очень чистый способ сделать это, но sqlRestrinction должно быть проще:

criterions.add(Restrictions.sqlRestriction(columnName+ " ilike '!%' escape '!'"));

Вы даже можете начать поиск с использованием того же принципа:

criterions.add(Restrictions.sqlRestriction(columnName+ " ilike '!%%' escape '!'"));

Ответ 3

если вы используете LikeExpression напрямую, он позволяет указать escape-символ. Я предполагаю, что это должно быть все, что вам нужно.

Ответ 4

Если вы используете Hibernate 3.2+, вы можете подклассифицировать LikeExpression, а затем создать методы factory like/ilike:

import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.LikeExpression;
import org.hibernate.criterion.MatchMode;

public class EscapedLikeRestrictions {
    private EscapedLikeRestrictions() {}

    public static Criterion likeEscaped(String propertyName, String value, MatchMode matchMode) {
        return likeEscaped(propertyName, value, matchMode, false);
    }

    public static Criterion ilikeEscaped(String propertyName, String value, MatchMode matchMode) {
        return likeEscaped(propertyName, value, matchMode, true);
    }

    private static Criterion likeEscaped(String propertyName, String value, MatchMode matchMode, boolean ignoreCase) {
        return new LikeExpression(propertyName, escape(value), matchMode, '!', ignoreCase) {/*a trick to call protected constructor*/};
    }

    private static String escape(String value) {
        return value
                .replace("!", "!!")
                .replace("%", "!%")
                .replace("_", "!_");
    }
}

Ответ 5

Если вы используете sqlRectrictions, правильный способ сделать это заключается в следующем:

criterions.add(Restrictions.sqlRestriction(columnName+" LIKE '!%' ESCAPE '!'"));

Это как SQL-запрос, не работает ilike => использовать LIKE с использованием Oracle 12i.