Как тихо усекать строки при их хранении, когда они длиннее определения длины столбца?
У меня есть веб-приложение, использующее 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
Затем транзакция отменяется.
Хотя я понимаю, что это поведение по умолчанию, это не тот, который я хочу.
Я хотел бы, чтобы данные были молча усечены, и транзакция должна быть выполнена.
Могу ли я это сделать, не добавляя вызов подстроки к каждому установленному методу строковых данных для соответствующих объектов?
Ответы
Ответ 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 с использованием метаданных объекта.
Ответ 8
Вы можете настроить свою базу данных на работу в нестандартном режиме, как описано здесь:
Автоматическая обрезка длины строки, представленной в MySQL
Обратите внимание, что это может также отменить другие проверки, поэтому будьте осторожны в том, что вы хотите для
Ответ 9
Так как Exception, как представляется, поднимается на уровне базы данных во время процесса commit(), триггер before-insert в целевой таблице /s может обрезать новые значения до их фиксации, минуя ошибку.
Ответ 10
Возможно, АОП поможет:
Перехватите все заданные методы в вашем JavaBean/POJO, а затем получите задание. Проверьте, является ли поле аннотацией с помощью @Column
, а тип поля - String
. Затем усечь поле, если оно слишком длинное, чем length
.