Используя критерии гибернации, есть ли способ избежать специальных символов? - PullRequest
16 голосов
/ 23 марта 2009

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

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

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

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

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

select * from foo where name like '%' 

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

Ответы [ 5 ]

11 голосов
/ 11 июля 2009

Все конструкторы 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. Мы не уверены насчет других баз данных.

4 голосов
/ 01 августа 2011

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

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

Вы даже можете начать поиск по тому же принципу:

criterions.add(Restrictions.sqlRestriction(columnName+ " ilike '!%%' escape '!'"));
2 голосов
/ 23 марта 2009

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

0 голосов
/ 16 января 2019

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

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

Похоже на SQL-запрос, не работает, или = = использовать LIKE с использованием Oracle 12i.

0 голосов
/ 23 февраля 2016

Если вы используете Hibernate 3.2+, вы можете создать подкласс LikeExpression, а затем создать фабричные 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("_", "!_");
    }
}
...