Подтвердить что ты не робот

Как тихо усекать строки при их хранении, когда они длиннее определения длины столбца?

У меня есть веб-приложение, использующее EclipseLink и MySQL для хранения данных. Некоторые из этих данных представляют собой строки, т.е. varchars в БД. В коде сущностей строки имеют такие атрибуты:

@Column(name = "MODEL", nullable = true, length = 256)
private String model;

База данных не создается eclipseLink из кода, но длина соответствует длине varchar в БД. Когда длина таких строковых данных больше атрибута длины, исключение возникает во время вызова javax.persistence.EntityTransaction.commit():

javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.1.0.v20100614-r7608): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'MODEL' at row 1

Затем транзакция отменяется. Хотя я понимаю, что это поведение по умолчанию, это не тот, который я хочу. Я хотел бы, чтобы данные были молча усечены, и транзакция должна быть выполнена.

Могу ли я это сделать, не добавляя вызов подстроки к каждому установленному методу строковых данных для соответствующих объектов?

4b9b3361

Ответ 1

Можно усечь строку в соответствии с аннотациями JPA в установщике для соответствующего поля:

public void setX(String x) {
    try {
        int size = getClass().getDeclaredField("x").getAnnotation(Column.class).length();
        int inLength = x.length();
        if (inLength>size)
        {
            x = x.substring(0, size);
        }
    } catch (NoSuchFieldException ex) {
    } catch (SecurityException ex) {
    }
    this.x = x;
}

Сама аннотация должна выглядеть так:

@Column(name = "x", length=100)
private String x;

(На основе fooobar.com/questions/207297/...)

Аннотации могут быть воссозданы из базы данных, если база данных изменяется, как указано в комментарии к fooobar.com/questions/207291/...

Ответ 2

У вас есть разные решения и ложные решения.

Использование триггера или любого трюка уровня базы данных

Это создаст несогласованность между объектами в ORM и их сериализованной формой в БД. Если вы используете кэширование второго уровня: это может привести к множеству проблем. По-моему, это не настоящее решение для общего использования.

Использование предварительных вставок, предварительных обновлений

Вы будете молча изменять пользовательские данные непосредственно перед его сохранением. Таким образом, ваш код может вести себя по-разному в зависимости от того, что объект уже сохраняется или нет. Это может вызвать проблемы. Кроме того, вы должны быть осторожны с порядком обращения с крючками: убедитесь, что ваш "крючок с полевым уклоном" - это самый первый, который вызывается поставщиком сохранения.

Использование aop для перехвата вызовов в сеттеры

Это решение будет более или менее молчаливо изменять пользовательский/автоматический ввод, но ваши объекты не будут изменены после использования их для выполнения какой-либо бизнес-логики. Так что это более приемлемо, чем два предыдущих решения, но сеттеры не будут следовать контракту обычных сеттеров. Кроме того, если вы используете полевую инъекцию: она обходит этот аспект (в зависимости от вашей конфигурации поставщик jpa может использовать инъекцию поля. В большинстве случаев: Spring используют сеттеры. Я предполагаю, что некоторые другие фреймворки могут использовать инъекцию поля, поэтому даже если вы не используете его явно, чтобы знать о базовой реализации используемых вами фреймворков).

Использование aop для перехвата модификации поля

Подобно предыдущему решению, за исключением того, что полевая инъекция также будет перехвачена аспект. (обратите внимание, что я никогда не писал аспект, делающий такие вещи, но я думаю, что это возможно)

Добавление уровня контроллера для проверки длины поля перед вызовом setter

Вероятно, лучшее решение с точки зрения целостности данных. Но это может потребовать много рефакторинга. Для общего прецедента это (на мой взгляд) единственное приемлемое решение.

В зависимости от вашего варианта использования вы можете выбрать любое из этих решений. Помните о недостатках.

Ответ 3

Для этого вы можете использовать событие PreInsert/PreUpdate для дескриптора или, возможно, просто использовать события JPA PreInsert и PreUpdate.

Просто проверьте размер полей и укоротите их в коде события.

При необходимости вы можете получить размер поля из описания дескриптора DatabaseField или использовать отражение Java, чтобы получить его из аннотации.

Возможно, было бы еще лучше просто выполнить усечение в ваших методах набора. Тогда вам не нужно беспокоиться о событиях.

Вы также можете усечь его в базе данных, проверить свои настройки MySQL или использовать триггер.

Ответ 4

Есть и другой способ сделать это, может быть еще быстрее (по крайней мере, он работает на 5-й версии MySql):

Сначала проверьте настройки sql_mode: есть подробное описание, как это сделать. Этот параметр должен иметь значение "для окон и" режимов" для Unix.

Это не помогло мне, поэтому я нашел другую настройку, на этот раз в jdbc:

jdbcCompliantTruncation=false. 

В моем случае (я использовал постоянство) он определен в persistence.xml:

<property name="javax.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/dbname?jdbcCompliantTruncation=false"/>

Эти 2 настройки работают только вместе, я пытался использовать их отдельно и не имел никакого эффекта.

Примечание: помните, что, установив sql_mode, как описано выше, вы отключите важные проверки базы данных, поэтому, пожалуйста, сделайте это внимательно.

Ответ 5

Если вы хотите сделать это в поле за полем, а не во всем мире, то вы можете сделать настраиваемое сопоставление типов, которое усекает значение до заданной длины, прежде чем вставлять его в таблицу. Затем вы можете присоединить конвертер к объекту с помощью аннотации, например:

@Converter(name="myConverter", class="com.example.MyConverter")

и в соответствующие поля:

@Convert("myConverter")

Это действительно предназначено для поддержки пользовательских типов SQL, но оно может работать и для нормальных полей типа varchar. Здесь - это учебник по созданию одного из этих преобразователей.

Ответ 6

Две очень важные особенности дизайна пользовательского интерфейса:

  • Вы никогда не должны молча изменять пользовательские данные - пользователь должен знать, контролировать и соглашаться на такие изменения.
  • Вы никогда не должны позволять вводить данные, которые не могут быть обработаны - используйте атрибуты UI/HTML и проверку, чтобы ограничить данные допустимыми значениями

Ответ на вашу проблему очень прост. Просто ограничьте поля ввода пользовательского интерфейса на 256 символов, чтобы соответствовать длине поля базы данных:

<input type="text" name="widgetDescription" maxlength="256">

Это системное ограничение - пользователь не должен иметь возможность вводить больше данных, чем это. Если этого недостаточно, измените базу данных.

Ответ 7

Я ничего не знаю об EclipseLink, но в Hibernate это выполнимо - вы можете сделать org.hibernate.Interceptor и inFlushDirty метод модифицировать объект currentState с использованием метаданных объекта.

Ответ 9

Так как Exception, как представляется, поднимается на уровне базы данных во время процесса commit(), триггер before-insert в целевой таблице /s может обрезать новые значения до их фиксации, минуя ошибку.

Ответ 10

Возможно, АОП поможет:

Перехватите все заданные методы в вашем JavaBean/POJO, а затем получите задание. Проверьте, является ли поле аннотацией с помощью @Column, а тип поля - String. Затем усечь поле, если оно слишком длинное, чем length.