java - representacion - longitud de arco pdf
¿Cómo truncar silenciosamente cadenas mientras las almacena cuando son más largas que la definición de longitud de columna? (12)
Tengo una aplicación web que usa EclipseLink y MySQL para almacenar datos. Algunos de estos datos son cadenas, es decir, varchar en el DB. En el código de entidades, las cadenas tienen atributos como este:
@Column(name = "MODEL", nullable = true, length = 256)
private String model;
La base de datos no es creada por eclipseLink a partir del código, sino que la longitud coincide con la longitud varchar en el DB. Cuando la longitud de dichos datos de cadena es mayor que el atributo de longitud, se genera una excepción durante la llamada a 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
Luego la transacción se revierte. Si bien entiendo que es el comportamiento predeterminado, este no es el que yo quiero. Me gustaría que los datos se truncaran silenciosamente y que se comprometiera la transacción.
¿Puedo hacer esto sin agregar una llamada a subcadenas a todos y cada uno de los métodos de cadena de datos para las entidades afectadas?
Dado que la excepción parece plantearse en el nivel de la base de datos durante el proceso de confirmación (), un desencadenante antes de insertar en la / s tabla / s de destino podría truncar los nuevos valores antes de que se comprometan, evitando el error.
Dos características muy importantes del diseño de la interfaz de usuario:
- Nunca debe alterar silenciosamente los datos del usuario: el usuario debe tener conocimiento, controlar y consentir dichos cambios.
- Nunca debe permitir que se ingresen datos que no se pueden manejar: use atributos de IU / HTML y validación para restringir los datos a valores legales
La respuesta a tu problema es muy simple. Simplemente limite sus campos de entrada de IU a 256 caracteres, para que coincida con la longitud del campo de la base de datos:
<input type="text" name="widgetDescription" maxlength="256">
Esta es una limitación del sistema: el usuario no debería poder ingresar más datos que este. Si esto es insuficiente, cambie la base de datos.
Hay otra manera de hacerlo, puede ser aún más rápido (al menos funciona en la 5ta versión de MySql):
Primero, verifica tu configuración de sql_mode: hay una descripción detallada de cómo hacerlo . Esta configuración debe tener el valor de "" para Windows y "modos" para Unix.
Eso no me ayudó, así que encontré otra configuración, esta vez en jdbc:
jdbcCompliantTruncation=false.
En mi caso (utilicé persistencia) está definido en persistence.xml:
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/dbname?jdbcCompliantTruncation=false"/>
Estas 2 configuraciones funcionan solo juntas, traté de usarlas por separado y no tuvo ningún efecto.
Aviso : recuerde que si establece sql_mode como se describe arriba, desactiva las comprobaciones importantes de la base de datos, por lo tanto, hágalo con cuidado.
No sé nada sobre EclipseLink, pero en Hibernate es factible: se puede hacer un intérprete org.hibernate.y en el método onFlushDirty modificar el estado actual del objeto usando metadatos de entidad.
Otra opción es declarar una constante y hacer referencia a ella en cualquier lugar que necesite esa longitud, comenzando con la propia anotación @Column.
A continuación, esta constante se puede reenviar a una función truncada o a una función de validación que arroje una excepción preventiva si la cadena pasada es demasiado larga. Esta constante también se puede reutilizar en algunas otras capas, como una IU.
Por ejemplo:
@Entity
public class MyEntity {
public static final int NAME_LENGTH=32;
private Long id;
private String name;
@Id @GeneratedValue
public Long getId() {
return id;
}
protected void setId(Long id) {
this.id = id;
}
@Column(length=NAME_LENGTH)
public String getName() {
return name;
}
public void setName(String name) {
this.name = JpaUtil.truncate(name, NAME_LENGTH);
}
}
public class JpaUtil {
public static String truncate(String value, int length) {
return value != null && value.length() > length ? value.substring(0, length) : value;
}
}
Puede configurar su base de datos para que funcione en un modo no estricto como se explica aquí: recorte automático de la longitud de la cadena enviada a MySQL
Tenga en cuenta que también podría cancelar otras validaciones, así que tenga cuidado con lo que desea
Puede utilizar un evento Descriptor PreInsert / PreUpdate para esto, o posiblemente solo use eventos JPA PreInsert y PreUpdate.
Simplemente verifique el tamaño de los campos y trunquelos en el código del evento.
Si es necesario, puede obtener el tamaño del campo del DatabaseField del mapeo del descriptor, o usar el reflejo de Java para obtenerlo de la anotación.
Probablemente sería aún mejor hacer el truncamiento en los métodos establecidos. Entonces no necesitas preocuparte por los eventos.
También puede truncarlo en la base de datos, verificar su configuración de MySQL o usar un desencadenador.
Si desea hacer esto campo por campo en lugar de globalmente, entonces es posible que pueda hacer una asignación de tipo personalizado que trunca el valor a la longitud determinada antes de insertarlo en la tabla. Luego puede adjuntar el convertidor a la entidad mediante una anotación como:
@Converter(name="myConverter", class="com.example.MyConverter")
y a los campos relevantes a través de:
@Convert("myConverter")
Esto es realmente para soportar tipos de SQL personalizados, pero también podría funcionar para los campos normales de tipo varchar. Here hay un tutorial sobre cómo hacer uno de estos convertidores.
Tal vez AOP ayudará:
Intercepta todo el método establecido en tu JavaBean / POJO, y luego configura el archivo. Compruebe si el campo está anotado con @Column
y el tipo de campo es String
. Luego, trunque el campo si es demasiado largo que la length
.
Tienes diferentes soluciones y soluciones falsas.
Usando trigger o cualquier truco de nivel de base de datos
Esto creará inconsistencia entre los objetos en el ORM y su forma serializada en el DB. Si usa el almacenamiento en caché de segundo nivel: puede ocasionar muchos problemas. En mi opinión, esta no es una solución real para un caso de uso general.
Usando pre-inserción, ganchos de pre-actualización
Modificará silenciosamente los datos del usuario justo antes de persistir. Por lo tanto, su código puede comportarse de manera diferente en función del hecho de que el objeto ya se haya conservado o no. Puede causar problemas también. Además, debe tener cuidado con el orden de invocación de los ganchos: asegúrese de que su "campo-truncador-gancho" sea el primero invocado por el proveedor de persistencia.
Usando aop para interceptar llamadas a setters
Esta solución modificará de manera más o menos silenciosa la entrada de usuario / automática, pero sus objetos no se modificarán después de que los use para hacer alguna lógica comercial. Entonces esto es más aceptable que las 2 soluciones anteriores, pero los incubadores no seguirán el contrato de los instaladores habituales. Además, si usa la inyección de campo: omitirá el aspecto (dependiendo de su configuración, el proveedor de jpa puede usar la inyección de campo. La mayoría de las veces: Spring usa setters. Supongo que algunos otros frameworks pueden usar inyección de campo, por lo tanto, incluso si No lo use explícitamente, tenga en cuenta la implementación subyacente de los marcos que está utilizando).
Usando aop para interceptar la modificación del campo
Similar a la solución anterior, excepto que la inyección de campo también será interceptada por el aspecto. (Tenga en cuenta que nunca escribí un aspecto haciendo este tipo de cosas, pero creo que es factible)
Agregar una capa de controlador para verificar la longitud del campo antes de llamar al colocador
Probablemente la mejor solución en términos de integridad de datos. Pero puede requerir mucha refactorización. Para un caso de uso general, esta es (en mi opinión) la única solución aceptable.
Dependiendo de su caso de uso, puede elegir cualquiera de esas soluciones. Tenga en cuenta los inconvenientes.
Uno puede truncar una cadena de acuerdo con las anotaciones JPA en el setter para el campo correspondiente:
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;
}
La anotación en sí debería verse así:
@Column(name = "x", length=100)
private String x;
(Basado en https://.com/a/1946901/16673 )
Las anotaciones se pueden volver a crear desde la base de datos si la base de datos cambia, como se insinuó en el comentario de https://.com/a/7648243/16673
Ya hubo una respuesta que menciona convertidores , pero quiero agregar más detalles. Mi respuesta también asume Convertidores de JPA, no EclipseLink específico.
Al principio crea esta clase - convertidor de tipo especial cuya responsabilidad será truncar el valor en el momento de persistencia:
import javax.persistence.AttributeConverter;
import javax.persistence.Convert;
@Convert
public class TruncatedStringConverter implements AttributeConverter<String, String> {
private static final int LIMIT = 999;
@Override
public String convertToDatabaseColumn(String attribute) {
if (attribute == null) {
return null;
} else if (attribute.length() > LIMIT) {
return attribute.substring(0, LIMIT);
} else {
return attribute;
}
}
@Override
public String convertToEntityAttribute(String dbData) {
return dbData;
}
}
Entonces puedes usarlo en tus entidades de esta manera:
@Entity(name = "PersonTable")
public class MyEntity {
@Convert(converter = TruncatedStringConverter.class)
private String veryLongValueThatNeedToBeTruncated;
//...
}
Artículo relacionado sobre convertidores JPA: http://www.baeldung.com/jpa-attribute-converters