manejo de valores DATETIME 0000-00-00 00:00:00 en JDBC

85

Recibo una excepción (ver más abajo) si trato de hacer

resultset.getString("add_date");

para una conexión JDBC a una base de datos MySQL que contiene un valor DATETIME de 0000-00-00 00:00:00 (el valor cuasi nulo para DATETIME), aunque solo estoy tratando de obtener el valor como una cadena, no como un objeto.

Me las arreglé haciendo

SELECT CAST(add_date AS CHAR) as add_date

que funciona, pero parece una tontería ... ¿hay una mejor manera de hacer esto?

Mi punto es que solo quiero la cadena DATETIME sin procesar, para poder analizarla yo mismo como está .

nota: aquí es donde entra el 0000: (de http://dev.mysql.com/doc/refman/5.0/en/datetime.html )

Los valores ilegales DATETIME, DATE o TIMESTAMP se convierten al valor "cero" del tipo apropiado ('0000-00-00 00:00:00' o '0000-00-00').

La excepción específica es esta:

SQLException: Cannot convert value '0000-00-00 00:00:00' from column 5 to TIMESTAMP.
SQLState: S1009
VendorError: 0
java.sql.SQLException: Cannot convert value '0000-00-00 00:00:00' from column 5 to TIMESTAMP.
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1055)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:926)
    at com.mysql.jdbc.ResultSetImpl.getTimestampFromString(ResultSetImpl.java:6343)
    at com.mysql.jdbc.ResultSetImpl.getStringInternal(ResultSetImpl.java:5670)
    at com.mysql.jdbc.ResultSetImpl.getString(ResultSetImpl.java:5491)
    at com.mysql.jdbc.ResultSetImpl.getString(ResultSetImpl.java:5531)
Jason S
fuente

Respuestas:

85

Me encontré con esto intentando resolver el mismo problema. La instalación con la que estoy trabajando usa JBOSS e Hibernate, así que tuve que hacer esto de una manera diferente. Para el caso básico, debería poder agregar zeroDateTimeBehavior=convertToNulla su URI de conexión según esta página de propiedades de configuración .

Encontré otras sugerencias en todo el país que se refieren a poner ese parámetro en su configuración de hibernación:

En hibernate.cfg.xml :

<property name="hibernate.connection.zeroDateTimeBehavior">convertToNull</property>

En hibernate.properties :

hibernate.connection.zeroDateTimeBehavior=convertToNull

Pero tuve que ponerlo en mi archivo mysql-ds.xml para JBOSS como:

<connection-property name="zeroDateTimeBehavior">convertToNull</connection-property>

Espero que esto ayude a alguien. :)

sarumont
fuente
Hacer el cambio en el archivo hibernate.cfg.xml me
ayudó
124

Respuesta alternativa, puede usar esta URL de JDBC directamente en la configuración de su fuente de datos:

jdbc:mysql://yourserver:3306/yourdatabase?zeroDateTimeBehavior=convertToNull

Editar:

Fuente: Manual MySQL

Fecha y hora con componentes todos cero (0000-00-00 ...): estos valores no se pueden representar de manera confiable en Java. Connector / J 3.0.x siempre los convertía a NULL cuando se leían desde un ResultSet.

Connector / J 3.1 lanza una excepción de forma predeterminada cuando se encuentran estos valores, ya que este es el comportamiento más correcto de acuerdo con los estándares JDBC y SQL. Este comportamiento se puede modificar mediante la propiedad de configuración zeroDateTimeBehavior. Los valores permitidos son:

  • excepción (la predeterminada), que arroja una SQLException con un SQLState de S1009.
  • convertToNull , que devuelve NULL en lugar de la fecha.
  • round , que redondea la fecha al valor más cercano más cercano que es 0001-01-01.

Actualización: Alexander informó un error que afectaba a mysql-connector-5.1.15 en esa función. Consulte CHANGELOGS en el sitio web oficial .

Brian Clozel
fuente
interesante. donde encontraste esta informacion?
Jason S
acabo de actualizar mi respuesta! Pero la URL de @sarumont puede encajar mejor en este caso (enumera todas las propiedades del conector).
Brian Clozel
1
La versión 5.1.16 del software jdbc contiene esta corrección de errores: - Corrección del ERROR # 57808 - wasNull no configurado para el campo DATE con el valor 0000-00-00 en getDate () aunque zeroDateTimeBehavior es convertToNull.
Alexander Kjäll
¡Guauu! Gracias por la info. Supongo que te costó darte cuenta de eso :-(
Brian Clozel
La URL .. Vida. ¡Salvado!
CodingInCircles
11

Mi punto es que solo quiero la cadena DATETIME sin procesar, para poder analizarla yo mismo como está.

Eso me hace pensar que su "solución alternativa" no es una solución alternativa, sino que, de hecho, es la única forma de obtener el valor de la base de datos en su código:

SELECT CAST(add_date AS CHAR) as add_date

Por cierto, algunas notas más de la documentación de MySQL:

Restricciones de MySQL sobre datos no válidos :

Antes de MySQL 5.0.2, MySQL perdona valores de datos ilegales o incorrectos y los obliga a valores legales para la entrada de datos. En MySQL 5.0.2 y versiones posteriores, ese sigue siendo el comportamiento predeterminado, pero puede cambiar el modo SQL del servidor para seleccionar un tratamiento más tradicional de valores incorrectos, de modo que el servidor los rechace y anule la declaración en la que ocurren.

[..]

Si intenta almacenar NULL en una columna que no toma valores NULL, se produce un error para las declaraciones INSERT de una sola fila. Para declaraciones INSERT de varias filas o INSERT INTO ... SELECT, MySQL Server almacena el valor predeterminado implícito para el tipo de datos de la columna.

Tipos de fecha y hora de MySQL 5.x :

MySQL también le permite almacenar '0000-00-00' como una "fecha ficticia" (si no está usando el modo SQL NO_ZERO_DATE). En algunos casos, esto es más conveniente (y usa menos datos y espacio de índice) que usar valores NULL.

[..]

De forma predeterminada, cuando MySQL encuentra un valor para un tipo de fecha u hora que está fuera de rango o es ilegal para el tipo (como se describe al principio de esta sección), convierte el valor al valor "cero" para ese tipo.

Arjan
fuente
6
DATE_FORMAT(column name, '%Y-%m-%d %T') as dtime

Use esto para evitar el error. Devuelve la fecha en formato de cadena y luego puede obtenerla como una cadena.

resultset.getString("dtime");

En realidad, esto NO funciona. Aunque llame a getString. Internamente, mysql todavía intenta convertirlo a la fecha primero.

en com.mysql.jdbc.ResultSetImpl.getDateFromString (ResultSetImpl.java:2270)

~ [mysql-connector-java-5.1.15.jar: na] en com.mysql.jdbc.ResultSetImpl.getStringInternal (ResultSetImpl.java:5743)

~ [mysql-connector-java-5.1.15.jar: na] en com.mysql.jdbc.ResultSetImpl.getString (ResultSetImpl.java:5576)

~ [conector-mysql-java-5.1.15.jar: na]

Comunidad
fuente
1
Eso es un poco lo mismo que mi solución CAST (add_date AS CHAR), pero +1 ya que esto le permite formatearlo explícitamente de la manera que desee.
Jason S
5

Si, después de agregar líneas:

<property
name="hibernate.connection.zeroDateTimeBehavior">convertToNull</property>

hibernate.connection.zeroDateTimeBehavior=convertToNull

<connection-property
name="zeroDateTimeBehavior">convertToNull</connection-property>

sigue siendo un error:

Illegal DATETIME, DATE, or TIMESTAMP values are converted to the “zero” value of the appropriate type ('0000-00-00 00:00:00' or '0000-00-00').

encontrar líneas:

1) resultSet.getTime("time"); // time = 00:00:00
2) resultSet.getTimestamp("timestamp"); // timestamp = 00000000000000
3) resultSet.getDate("date"); // date = 0000-00-00 00:00:00

reemplazar con las siguientes líneas, respectivamente:

1) Time.valueOf(resultSet.getString("time"));
2) Timestamp.valueOf(resultSet.getString("timestamp"));
3) Date.valueOf(resultSet.getString("date"));
Vadim
fuente
5

Luché con este problema e implementé las soluciones 'convertToNull' discutidas anteriormente. Funcionó en mi instancia local de MySql. Pero cuando implementé mi aplicación Play / Scala en Heroku, ya no funcionaba. Heroku también concatena varios argumentos a la URL de la base de datos que proporcionan a los usuarios, y esta solución, debido a la concatenación de uso de Heroku de "?" antes de su propio conjunto de argumentos, no funcionará. Sin embargo, encontré una solución diferente que parece funcionar igualmente bien.

SET sql_mode = 'NO_ZERO_DATE';

Puse esto en las descripciones de mi tabla y resolvió el problema de '0000-00-00 00:00:00' no se puede representar como java.sql.Timestamp

Aqume
fuente
3

Le sugiero que use nulo para representar un valor nulo.

¿Cuál es la excepción que obtienes?

Por cierto:

No hay un año llamado 0 o 0000 (aunque algunas fechas permiten este año)

Y no hay 0 mes del año ni 0 día del mes. (Que puede ser la causa de su problema)

Peter Lawrey
fuente
sí, pero todavía no hay un año llamado 0, ni siquiera retrospectivamente. El año anterior al 1 d. C. / d. C. fue el 1 a. C. / a. C.
Peter Lawrey
2
Esa es la razón por la que MySQL usa 0000 como una fecha no válida, porque no entra en conflicto con las fechas existentes. Además, a veces se usan fechas como 1999-00-00 (¡no yo!) Para representar el año 1999 en lugar de un día específico.
Jason S
3

Resolví el problema considerando que '00 -00 -.... 'no es una fecha válida, luego, cambié la definición de mi columna SQL agregando la expresión "NULL" para permitir valores nulos:

SELECT "-- Tabla item_pedido";
CREATE TABLE item_pedido (
    id INTEGER AUTO_INCREMENT PRIMARY KEY,
    id_pedido INTEGER,
    id_item_carta INTEGER,
    observacion VARCHAR(64),
    fecha_estimada TIMESTAMP,
    fecha_entrega TIMESTAMP NULL, // HERE IS!!.. NULL = DELIVERY DATE NOT SET YET
    CONSTRAINT fk_item_pedido_id_pedido FOREIGN KEY (id_pedido)
        REFERENCES pedido(id),...

Entonces, tengo que poder insertar valores NULL, eso significa "No registré esa marca de tiempo todavía" ...

SELECT "++ INSERT item_pedido";
INSERT INTO item_pedido VALUES
(01, 01, 01, 'Ninguna', ADDDATE(@HOY, INTERVAL 5 MINUTE), NULL),
(02, 01, 02, 'Ninguna', ADDDATE(@HOY, INTERVAL 3 MINUTE), NULL),...

La mesa parece que:

mysql> select * from item_pedido;
+----+-----------+---------------+-------------+---------------------+---------------------+
| id | id_pedido | id_item_carta | observacion | fecha_estimada      | fecha_entrega       |
+----+-----------+---------------+-------------+---------------------+---------------------+
|  1 |         1 |             1 | Ninguna     | 2013-05-19 15:09:48 | NULL                |
|  2 |         1 |             2 | Ninguna     | 2013-05-19 15:07:48 | NULL                |
|  3 |         1 |             3 | Ninguna     | 2013-05-19 15:24:48 | NULL                |
|  4 |         1 |             6 | Ninguna     | 2013-05-19 15:06:48 | NULL                |
|  5 |         2 |             4 | Suave       | 2013-05-19 15:07:48 | 2013-05-19 15:09:48 |
|  6 |         2 |             5 | Seco        | 2013-05-19 15:07:48 | 2013-05-19 15:12:48 |
|  7 |         3 |             5 | Con Mayo    | 2013-05-19 14:54:48 | NULL                |
|  8 |         3 |             6 | Bilz        | 2013-05-19 14:57:48 | NULL                |
+----+-----------+---------------+-------------+---------------------+---------------------+
8 rows in set (0.00 sec)

Finalmente: JPA en acción:

@Stateless
@LocalBean
public class PedidosServices {
    @PersistenceContext(unitName="vagonpubPU")
    private EntityManager em;

    private Logger log = Logger.getLogger(PedidosServices.class.getName());

    @SuppressWarnings("unchecked")
    public List<ItemPedido> obtenerPedidosRetrasados() {
        log.info("Obteniendo listado de pedidos retrasados");
        Query qry = em.createQuery("SELECT ip FROM ItemPedido ip, Pedido p WHERE" +
                " ip.fechaEntrega=NULL" +
                " AND ip.idPedido=p.id" +
                " AND ip.fechaEstimada < :arg3" +
                " AND (p.idTipoEstado=:arg0 OR p.idTipoEstado=:arg1 OR p.idTipoEstado=:arg2)");
        qry.setParameter("arg0", Tipo.ESTADO_BOUCHER_ESPERA_PAGO);
        qry.setParameter("arg1", Tipo.ESTADO_BOUCHER_EN_SERVICIO);
        qry.setParameter("arg2", Tipo.ESTADO_BOUCHER_RECIBIDO);
        qry.setParameter("arg3", new Date());

        return qry.getResultList();
    }

Por fin todo su trabajo. Espero que te ayude.

EdU
fuente
No creo que esto resuelva el problema de convertir un MySQL DATETIME de todos los ceros en una fecha válida. Simplemente muestra cómo ingresar un valor nulo en un campo DATETIME, lo que realmente no ayudará mucho.
Mark Chorley
2

Para agregar a las otras respuestas: si desea la 0000-00-00cadena, puede usar noDatetimeStringSync=true(con la advertencia de sacrificar la conversión de zona horaria).

El error oficial de MySQL: https://bugs.mysql.com/bug.php?id=47108 .

Además, para el historial, JDBC solía regresar NULLpara las 0000-00-00fechas, pero ahora devuelve una excepción de forma predeterminada. Fuente

Damio
fuente
2

puede agregar la url jdbc con

?zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=UTF-8&characterSetResults=UTF-8

Con la ayuda de esto, sql convierte '0000-00-00 00:00:00' como valor nulo.

p.ej:

jdbc:mysql:<host-name>/<db-name>?zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=UTF-8&characterSetResults=UTF-8
Mukul Aggarwal
fuente