java.sql.DataTruncation: исключение усечения данных и отношение оператора LIKE

Я работаю над проектом весенней загрузки. У меня есть веб-служба, которая ищет текст в нескольких полях. Я использую спецификацию Java для генерации запроса.

Результирующий запрос такой

select *
from V_AGENCY agencyview0_ 
where 
agencyview0_.nationcode like '%ABCDEFGHIKLMN% 
or agencyview0_.username like '%ABCDEFGHIKLMN%'

Проблема в том, что я получаю исключение java.sql.DataTruncation: Data truncation, потому что поле nationcode имеет допустимую длину 10 в базе данных. Почему я получаю это исключение? Я не пытаюсь вставить значение в это поле.

Класс org.firebirdsql.jdbc.field.FBWorkaroundStringField выдает эту ошибку.

  public void setString(String value) throws SQLException {
        byte[] data = this.setStringForced(value);
        if (value != null) {
            assert data != null : "Expected non-null data here";

            if (data.length > this.fieldDescriptor.getLength() && !this.isSystemTable(this.fieldDescriptor.getOriginalTableName()) && (value.length() > this.fieldDescriptor.getLength() + 2 || value.charAt(0) != '%' && value.charAt(value.length() - 1) != '%')) {
                throw new DataTruncation(this.fieldDescriptor.getPosition() + 1, true, false, data.length, this.fieldDescriptor.getLength());
            }
        }
    }

ОБНОВЛЕНИЕ: добавлены коды Spring Boot

Контроллер:

    @GetMapping(value = {PATH_SEARCH, PATH_LIST, PATH_VIEW + "/" + PATH_SEARCH, PATH_VIEW + "/" + PATH_LIST}, params = {PARAM_TEXT, PARAM_FIELD})
public List<T> searchInMultipleFields(
        @RequestParam(name = PARAM_START, required = false) String start,
        @RequestParam(name = PARAM_LIMIT, required = false) String limit,
        @RequestParam(name = PARAM_TEXT) String text,
        @RequestParam(name = PARAM_FIELD) List<String> fields
) {

    OoSpecificationsBuilder<T> builder = new MultipleSearchSpecificationBuilder<>();
    for (String field : fields) {
        builder.with(field, ":", text.toUpperCase());
    }
    Specification<T> spec = builder.build();
    return mService.getAll(getValueOf(start), getValueOf(limit, MAX_PAGE_SIZE), spec);
}

Обслуживание:

    @Override
public List<T> getAll(int aStart, int aSize, Specification<T> aSpec) {
    return getRepository().findAll((Specification) aSpec, generatePageRequest(aStart, aSize)).getContent();
}

JpaSpecificationExecutor:

Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);

Технические характеристики:

public class MultipleSearchSpecification<T extends BaseModel> implements Specification<T> {

private SearchCriteria criteria;

public MultipleSearchSpecification(SearchCriteria aCriteria) {
    criteria = aCriteria;
}

@Override
public Predicate toPredicate
        (Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
    if (root.get(criteria.getKey()).getJavaType() == String.class) {
        return builder.like(
                root.get(criteria.getKey()), "%" + criteria.getValue() + "%");
    }
    return null;
}}

Знаете ли вы какой-либо обходной путь для этой проблемы?


person evracle    schedule 12.07.2018    source источник
comment
такая же проблема здесь: forum.hibernate.org/viewtopic.php?f=1&t= 964447   -  person evracle    schedule 12.07.2018
comment
Пожалуйста, включите фактический код, чтобы я мог адаптировать свой ответ к конкретной ситуации, потому что я на 100% уверен, что результирующий запрос не тот, который показан в вашем вопросе (а скорее с использованием like ? и настройки вместо этого значение параметра).   -  person Mark Rotteveel    schedule 12.07.2018
comment
Что вы имеете в виду под фактическим кодом. Я использую класс org.springframework.data.jpa.domain.Specification для создания динамического запроса.   -  person evracle    schedule 13.07.2018
comment
Вам нужно включить минимально воспроизводимый пример, потому что тогда я смогу попробовать его и предложить индивидуальное решение/обходной путь, не имея провести подготовительную работу по созданию необходимых условий для его воспроизводства. В противном случае единственный ответ, который я могу дать: «К сожалению, Firebird ограничивает максимальную длину параметра объявленной длиной поля, с которым он сравнивается, см. также JDBC-477. Вам либо нужно увеличить размер определения столбца, либо найти способ явно указать параметр, чтобы он был шире.   -  person Mark Rotteveel    schedule 13.07.2018
comment
Спасибо за редактирование, я посмотрю, смогу ли я воспроизвести проблему с этим и найти обходной путь. Каково полное имя этого MultipleSearchSpecificationBuilder (или, если оно не является частью стандартной пружины: каков его код)?   -  person Mark Rotteveel    schedule 17.07.2018


Ответы (2)


Код, который вы используете, не генерирует этот литеральный запрос, а вместо этого использует параметры (т.е. agencyview0_.nationcod like ?) и устанавливает значение параметра равным "%ABCDEFGHIKLMN%".

К сожалению, Firebird ограничивает максимальную длину параметра заявленной длиной поля, с которым он сравнивается, см. также JDBC-477, и Jaybird не может автоматически переопределить это. Вам нужно либо увеличить размер определения столбца, либо найти способ явного приведения параметра, чтобы он был шире, то есть убедиться, что код генерирует что-то вроде agencyview0_.nationcod like cast(? as varchar(100)).

person Mark Rotteveel    schedule 14.07.2018

Добавьте регистрацию службы в путь src/main/resources/META-INF/services/org.hibernate.boot.spi.SessionFactoryBuilderFactory с данными: systems.config.orm.CustomDataTypesRegistration.

public class CustomDataTypesRegistration implements SessionFactoryBuilderFactory {

private static final org.slf4j.Logger logger = LoggerFactory.getLogger(CustomDataTypesRegistration.class);

@Override
public SessionFactoryBuilder getSessionFactoryBuilder(final MetadataImplementor metadata, final SessionFactoryBuilderImplementor defaultBuilder) {
    logger.info("Registering custom Hibernate data types");
    //Deprecated. (since 5.3) No replacement, access to and handling of Types will be much different in 6.0
    metadata.getTypeResolver().registerTypeOverride(new OoStringType());
    return defaultBuilder;
}}

Пользовательский тип строки:

public class OoStringType extends StringType {

public OoStringType() {
    setSqlTypeDescriptor(OoVarcharTypeDescriptor.INSTANCE);
}}

Пользовательский дескриптор VarcharTypeDescriptor:

public class OoVarcharTypeDescriptor extends VarcharTypeDescriptor {
protected static final Logger log = LoggerFactory.getLogger(OoVarcharTypeDescriptor.class.getName());
public static final OoVarcharTypeDescriptor INSTANCE = new OoVarcharTypeDescriptor();
public static final String TRUNCATED_PARAMETER = "truncated parameter [%s] as [%s] - [%s]";
public static final char PERCENTAGE_CHAR = '%';

public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
    return new BasicBinder<X>(javaTypeDescriptor, this) {
        protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
            FBParameterMetaData parameterMetaData = (FBParameterMetaData) st.getParameterMetaData();
            int precision = parameterMetaData.getPrecision(index);
            String unwrappedValue = javaTypeDescriptor.unwrap(value, String.class, options);
            if (unwrappedValue.charAt(0) == PERCENTAGE_CHAR && unwrappedValue.charAt(unwrappedValue.length() - 1) == PERCENTAGE_CHAR) {
                String coreValue = unwrappedValue.substring(1, unwrappedValue.length() - 1);
                if (coreValue.length() > precision) {
                    unwrappedValue = PERCENTAGE_CHAR + coreValue.substring(0, precision - 2) + PERCENTAGE_CHAR;
                    log.info(String.format(TRUNCATED_PARAMETER, index, JdbcTypeNameMapper.getTypeName(this.getSqlDescriptor().getSqlType()), unwrappedValue));
                }
            } else if (unwrappedValue.length() > precision) {
                unwrappedValue = unwrappedValue.substring(0, precision);
                log.info(String.format(TRUNCATED_PARAMETER, index, JdbcTypeNameMapper.getTypeName(this.getSqlDescriptor().getSqlType()), unwrappedValue));
            }

            st.setString(index, unwrappedValue);
        }

        protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException {
            st.setString(name, (String) javaTypeDescriptor.unwrap(value, String.class, options));
        }
    };
}}

Имейте в виду, что это решение может привести к нежелательным результатам. Например: вы ищете «ABCD», но у вас есть поле длиной 3. Он вернет результат, в котором это поле содержит «ABC».

person evracle    schedule 19.07.2018
comment
Отлично, что вы нашли обходной путь. Я все еще ищу, есть ли способ заставить генерировать приведение, но пока не похоже, что в JPA есть что-то подобное. Если вы используете Firebird 3, вы можете попытаться определить хранимую функцию, которая принимает varchar с определенной максимальной длиной и просто возвращает это значение; затем в вашем построителе критериев вы можете обернуть значение вызовом этой функции. Это немного кладж, но это может сработать. Я посмотрю, смогу ли я найти время для более полного описания. - person Mark Rotteveel; 19.07.2018
comment
Предложение для вашего текущего решения: если вы знаете, что ваш подобный шаблон длиннее, чем определенное поле, вы также можете рассмотреть возможность привязки null вместо этого, чтобы избежать проблемы, когда ABC рассматривается как ABCD в трехсимвольном поле. - person Mark Rotteveel; 19.07.2018