gestion des valeurs DATETIME 0000-00-00 00:00:00 dans JDBC

85

J'obtiens une exception (voir ci-dessous) si j'essaye de faire

resultset.getString("add_date");

pour une connexion JDBC à une base de données MySQL contenant une valeur DATETIME de 0000-00-00 00:00:00 (la valeur quasi-nulle pour DATETIME), même si j'essaie juste d'obtenir la valeur sous forme de chaîne, pas sous forme de objet.

J'ai contourné ça en faisant

SELECT CAST(add_date AS CHAR) as add_date

qui fonctionne, mais semble idiot ... y a-t-il une meilleure façon de faire cela?

Mon point est que je veux juste la chaîne DATETIME brute, donc je peux l'analyser moi-même tel quel .

note: voici où le 0000 entre en jeu: (à partir de http://dev.mysql.com/doc/refman/5.0/en/datetime.html )

Les valeurs DATETIME, DATE ou TIMESTAMP illégales sont converties en la valeur «zéro» du type approprié («0000-00-00 00:00:00» ou «0000-00-00»).

L'exception spécifique est celle-ci:

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
la source

Réponses:

85

Je suis tombé sur cette tentative de résoudre le même problème. L'installation avec laquelle je travaille utilise JBOSS et Hibernate, j'ai donc dû le faire d'une manière différente. Pour le cas de base, vous devriez pouvoir ajouter zeroDateTimeBehavior=convertToNullà votre URI de connexion selon cette page de propriétés de configuration .

J'ai trouvé d'autres suggestions à travers le pays concernant la mise en place de ce paramètre dans votre configuration d'hibernation:

Dans hibernate.cfg.xml :

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

Dans hibernate.properties :

hibernate.connection.zeroDateTimeBehavior=convertToNull

Mais j'ai dû le mettre dans mon fichier mysql-ds.xml pour JBOSS comme:

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

J'espère que cela aide quelqu'un. :)

Sarumont
la source
Faire le changement sur le fichier hibernate.cfg.xml fait l'affaire pour moi, merci, car je n'ai presque aucune connaissance sur Java
Gendrith
124

Réponse alternative, vous pouvez utiliser cette URL JDBC directement dans la configuration de votre source de données:

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

Éditer:

Source: Manuel MySQL

Datetimes avec des composants entièrement nuls (0000-00-00 ...) - Ces valeurs ne peuvent pas être représentées de manière fiable en Java. Connector / J 3.0.x les convertissait toujours en NULL lors de la lecture à partir d'un ResultSet.

Connector / J 3.1 lève une exception par défaut lorsque ces valeurs sont rencontrées car il s'agit du comportement le plus correct selon les normes JDBC et SQL. Ce comportement peut être modifié à l'aide de la propriété de configuration zeroDateTimeBehavior. Les valeurs autorisées sont:

  • exception (la valeur par défaut), qui lève une exception SQLException avec un SQLState de S1009.
  • convertToNull , qui renvoie NULL au lieu de la date.
  • round , qui arrondit la date à la valeur la plus proche, à savoir 0001-01-01.

Mise à jour: Alexander a signalé un bogue affectant mysql-connector-5.1.15 sur cette fonctionnalité. Voir CHANGELOGS sur le site officiel .

Brian Clozel
la source
intéressant. où avez-vous trouvé cette information?
Jason S
vient de mettre à jour ma réponse! Mais l'URL de @sarumont peut mieux convenir dans ce cas (elle répertorie toutes les propriétés du connecteur).
Brian Clozel
1
la version 5.1.16 du logiciel jdbc contient ce correctif: - Correction du BUG # 57808 - wasNull non défini pour le champ DATE avec la valeur 0000-00-00 dans getDate () bien que zeroDateTimeBehavior soit convertToNull.
Alexander Kjäll
Hou la la! Merci pour l'info. Je suppose que vous avez eu du mal à comprendre cela :-(
Brian Clozel
L'URL .. La vie. Enregistré!
CodingInCircles
11

Mon point est que je veux juste la chaîne DATETIME brute, donc je peux l'analyser moi-même tel quel.

Cela me fait penser que votre "solution de contournement" n'est pas une solution de contournement, mais en fait le seul moyen d'obtenir la valeur de la base de données dans votre code:

SELECT CAST(add_date AS CHAR) as add_date

Au fait, quelques notes supplémentaires de la documentation MySQL:

Contraintes MySQL sur les données non valides :

Avant MySQL 5.0.2, MySQL pardonne les valeurs de données illégales ou inappropriées et les contraint à des valeurs légales pour la saisie de données. Dans MySQL 5.0.2 et versions ultérieures, cela reste le comportement par défaut, mais vous pouvez changer le mode SQL du serveur pour sélectionner un traitement plus traditionnel des mauvaises valeurs de sorte que le serveur les rejette et abandonne l'instruction dans laquelle elles se produisent.

[..]

Si vous essayez de stocker NULL dans une colonne qui ne prend pas de valeurs NULL, une erreur se produit pour les instructions INSERT à une seule ligne. Pour les instructions INSERT à plusieurs lignes ou pour les instructions INSERT INTO ... SELECT, MySQL Server stocke la valeur implicite par défaut pour le type de données de colonne.

Types de date et d'heure MySQL 5.x :

MySQL vous permet également de stocker «0000-00-00» comme «date fictive» (si vous n'utilisez pas le mode SQL NO_ZERO_DATE). C'est dans certains cas plus pratique (et utilise moins de données et d'espace d'index) que d'utiliser des valeurs NULL.

[..]

Par défaut, lorsque MySQL rencontre une valeur pour un type de date ou d'heure qui est hors de portée ou autrement illégale pour le type (comme décrit au début de cette section), il convertit la valeur en valeur «zéro» pour ce type.

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

Utilisez ceci pour éviter l'erreur. Il renvoie la date au format chaîne et vous pouvez ensuite l'obtenir sous forme de chaîne.

resultset.getString("dtime");

Cela ne fonctionne PAS réellement. Même si vous appelez getString. En interne, mysql essaie toujours de le convertir en date en premier.

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

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

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

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

Communauté
la source
1
C'est un peu la même chose que ma solution CAST (add_date AS CHAR), mais +1 puisque cela vous permet de la formater explicitement comme vous le souhaitez.
Jason S
5

Si, après avoir ajouté des lignes:

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

hibernate.connection.zeroDateTimeBehavior=convertToNull

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

continue d'être une erreur:

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').

rechercher des lignes:

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

remplacer par les lignes suivantes, respectivement:

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

J'ai lutté avec ce problème et mis en œuvre les solutions «convertToNull» décrites ci-dessus. Cela a fonctionné dans mon instance MySql locale. Mais lorsque j'ai déployé mon application Play / Scala sur Heroku, cela ne fonctionnait plus. Heroku concatène également plusieurs arguments à l'URL de base de données qu'ils fournissent aux utilisateurs, et cette solution, en raison de la concaténation d'utilisation de Heroku de "?" avant leur propre ensemble d'arguments, ne fonctionnera pas. Cependant, j'ai trouvé une solution différente qui semble fonctionner aussi bien.

SET sql_mode = 'NO_ZERO_DATE';

J'ai mis cela dans mes descriptions de table et cela a résolu le problème de '0000-00-00 00:00:00' ne peut pas être représenté comme java.sql.

Aqume
la source
3

Je vous suggère d'utiliser null pour représenter une valeur nulle.

Quelle est l'exception que vous obtenez?

BTW:

Il n'y a pas d'année appelée 0 ou 0000 (bien que certaines dates le permettent cette année)

Et il n'y a ni 0 mois de l'année ni 0 jour du mois. (Ce qui peut être la cause de votre problème)

Peter Lawrey
la source
oui, mais il n'y a toujours pas d'année appelée 0, même rétrospectivement. L'année avant 1 AD / CE était 1 BC / BCE
Peter Lawrey
2
C'est la raison pour laquelle MySQL utilise 0000 comme date non valide, car elle n'est pas en conflit avec les dates existantes. En outre, des dates comme 1999-00-00 sont parfois utilisées (pas par moi!) Pour représenter l'année 1999 plutôt qu'un jour spécifique.
Jason S
3

J'ai résolu le problème en considérant que '00 -00 -.... 'n'est pas une date valide, puis j'ai changé ma définition de colonne SQL en ajoutant une expression "NULL" pour autoriser les valeurs nulles:

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),...

Ensuite, je dois pouvoir insérer des valeurs NULL, ce qui signifie "Je n'ai pas encore enregistré cet horodatage" ...

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 table ressemble à ça:

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)

Enfin: JPA en action:

@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();
    }

Enfin tout son travail. J'espère que cela vous aidera.

EdU
la source
Je ne pense pas que cela résout le problème de la conversion d'un DATETIME MySQL de tous les zéros en une date valide. Il montre simplement comment entrer null dans un champ DATETIME, ce qui n'aidera pas vraiment beaucoup.
Mark Chorley
2

Pour ajouter aux autres réponses: Si vous voulez la 0000-00-00chaîne, vous pouvez utiliser noDatetimeStringSync=true(avec la mise en garde de sacrifier la conversion de fuseau horaire).

Le bogue officiel de MySQL: https://bugs.mysql.com/bug.php?id=47108 .

De plus, pour l'historique, JDBC avait l'habitude de retourner NULLpour les 0000-00-00dates mais renvoie maintenant une exception par défaut. La source

Damio
la source
2

vous pouvez ajouter l'url jdbc avec

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

Avec l'aide de cela, sql convertit '0000-00-00 00:00:00' en valeur nulle.

par exemple:

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