lidar com valores DATETIME 0000-00-00 00:00:00 em JDBC

85

Eu recebo uma exceção (veja abaixo) se eu tentar fazer

resultset.getString("add_date");

para uma conexão JDBC com um banco de dados MySQL contendo um valor DATETIME de 0000-00-00 00:00:00 (o valor quase nulo para DATETIME), embora eu esteja apenas tentando obter o valor como string, não como um objeto.

Eu contornei isso fazendo

SELECT CAST(add_date AS CHAR) as add_date

que funciona, mas parece bobo ... existe uma maneira melhor de fazer isso?

Meu ponto é que eu só quero a string DATETIME bruta, para que eu possa analisá-la como está .

observação: é aqui que entra o 0000: (de http://dev.mysql.com/doc/refman/5.0/en/datetime.html )

Valores ilegais de DATETIME, DATE ou TIMESTAMP são convertidos para o valor “zero” do tipo apropriado ('0000-00-00 00:00:00' ou '0000-00-00').

A exceção específica é 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
fonte

Respostas:

85

Me deparei com essa tentativa de resolver o mesmo problema. A instalação na qual estou trabalhando usa JBOSS e Hibernate, então tive que fazer isso de uma maneira diferente. Para o caso básico, você deve ser capaz de adicionar zeroDateTimeBehavior=convertToNullao seu URI de conexão de acordo com esta página de propriedades de configuração .

Encontrei outras sugestões em todo o país referentes a colocar esse parâmetro em sua configuração de hibernação:

Em hibernate.cfg.xml :

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

Em hibernate.properties :

hibernate.connection.zeroDateTimeBehavior=convertToNull

Mas eu tive que colocá-lo em meu arquivo mysql-ds.xml para JBOSS como:

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

Espero que isso ajude alguém. :)

Sarumont
fonte
Fazer a alteração no arquivo hibernate.cfg.xml
resolve
124

Resposta alternativa, você pode usar este URL JDBC diretamente na configuração da sua fonte de dados:

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

Editar:

Fonte: Manual MySQL

Datetimes com componentes totalmente nulos (0000-00-00 ...) - Esses valores não podem ser representados de forma confiável em Java. O Connector / J 3.0.x sempre os converteu em NULL ao serem lidos de um ResultSet.

O Connector / J 3.1 lança uma exceção por padrão quando esses valores são encontrados, pois esse é o comportamento mais correto de acordo com os padrões JDBC e SQL. Este comportamento pode ser modificado usando a propriedade de configuração zeroDateTimeBehavior. Os valores permitidos são:

  • exceção (o padrão), que lança uma SQLException com um SQLState de S1009.
  • convertToNull , que retorna NULL em vez da data.
  • round , que arredonda a data para o valor mais próximo mais próximo, que é 0001-01-01.

Atualização: Alexander relatou um bug que afeta o mysql-connector-5.1.15 nesse recurso. Veja CHANGELOGS no site oficial .

Brian Clozel
fonte
interessante. onde você encontrou essa informação?
Jason S
acabei de atualizar minha resposta! Mas a URL de @sarumont pode se ajustar melhor neste caso (ela lista todas as propriedades do conector).
Brian Clozel,
1
a versão 5.1.16 do software jdbc contém esta correção de bug: - Correção para o BUG # 57808 - wasNull não definido para o campo DATE com o valor 0000-00-00 em getDate () embora zeroDateTimeBehavior seja convertToNull.
Alexander Kjäll
Uau! Obrigado pela informação. Acho que foi difícil para você descobrir :-(
Brian Clozel
11

Meu ponto é que eu só quero a string DATETIME bruta, para que eu possa analisá-la como está.

Isso me faz pensar que sua "solução alternativa" não é uma solução alternativa, mas na verdade a única maneira de obter o valor do banco de dados em seu código:

SELECT CAST(add_date AS CHAR) as add_date

A propósito, mais algumas notas da documentação do MySQL:

Restrições do MySQL em dados inválidos :

Antes do MySQL 5.0.2, o MySQL perdoava os valores de dados ilegais ou impróprios e os coagia a valores legais para entrada de dados. No MySQL 5.0.2 e superior, esse permanece o comportamento padrão, mas você pode alterar o modo SQL do servidor para selecionar um tratamento mais tradicional de valores inválidos, de forma que o servidor os rejeite e aborte a instrução em que ocorrem.

[..]

Se você tentar armazenar NULL em uma coluna que não aceita valores NULL, ocorrerá um erro para instruções INSERT de linha única. Para instruções INSERT de várias linhas ou para instruções INSERT INTO ... SELECT, o MySQL Server armazena o valor padrão implícito para o tipo de dados da coluna.

Tipos de data e hora do MySQL 5.x :

O MySQL também permite que você armazene '0000-00-00' como uma “data fictícia” (se você não estiver usando o modo NO_ZERO_DATE SQL). Em alguns casos, isso é mais conveniente (e usa menos dados e espaço de índice) do que usar valores NULL.

[..]

Por padrão, quando o MySQL encontra um valor para um tipo de data ou hora que está fora do intervalo ou de outra forma ilegal para o tipo (conforme descrito no início desta seção), ele converte o valor para o valor “zero” para aquele tipo.

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

Use isso para evitar o erro. Ele retorna a data em formato de string e então você pode obtê-la como uma string.

resultset.getString("dtime");

Isso realmente NÃO funciona. Mesmo que você chame getString. Internamente, o mysql ainda tenta convertê-lo para data primeiro.

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

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

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

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

Comunidade
fonte
1
Isso é quase o mesmo que minha solução CAST (add_date AS CHAR), mas +1, uma vez que permite formatar explicitamente da maneira que desejar.
Jason S
5

Se, após adicionar linhas:

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

hibernate.connection.zeroDateTimeBehavior=convertToNull

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

continua a ser um erro:

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 linhas:

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

substitua pelas seguintes linhas, respectivamente:

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

Eu lutei com esse problema e implementei as soluções 'convertToNull' discutidas acima. Funcionou na minha instância local do MySql. Mas quando implantei meu aplicativo Play / Scala no Heroku, ele não funcionava mais. O Heroku também concatena vários argumentos à URL do banco de dados que eles fornecem aos usuários, e esta solução, por causa da concatenação de uso do Heroku de "?" antes de seu próprio conjunto de args, não funcionará. No entanto, encontrei uma solução diferente que parece funcionar igualmente bem.

SET sql_mode = 'NO_ZERO_DATE';

Coloquei isso nas descrições de minha tabela e resolveu o problema de '0000-00-00 00:00:00' não pode ser representado como java.sql.Timestamp

Aqume
fonte
3

Eu sugiro que você use nulo para representar um valor nulo.

Qual é a exceção que você recebe?

BTW:

Não há um ano chamado 0 ou 0000. (embora algumas datas permitam este ano)

E não há 0 mês do ano ou 0 dia do mês. (Que pode ser a causa do seu problema)

Peter Lawrey
fonte
sim, mas ainda não há um ano chamado 0, mesmo retrospectivamente. O ano anterior a 1 DC / CE foi 1 AC / AEC
Peter Lawrey
2
Esta é a razão pela qual o MySQL usa 0000 como uma data inválida, porque não conflita com as datas existentes. Além disso, datas como 1.999-00 podem ser usadas (não por mim!) Para representar o ano de 1999, em vez de um dia específico.
Jason S
3

Resolvi o problema considerando que '00 -00 -.... 'não é uma data válida, então, mudei minha definição de coluna SQL adicionando a expressão "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),...

Então, eu tenho que ser capaz de inserir valores NULL, que significa "Eu não registrei aquele timestamp ainda" ...

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

A 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 em ação:

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

Enfim todo o seu trabalho. Espero que ajude você.

EdU
fonte
Não acho que isso resolva o problema de converter um MySQL DATETIME de todos os zeros em uma data válida. Ele apenas mostra como inserir null em um campo DATETIME, o que não vai ajudar muito.
Mark Chorley
2

Para adicionar às outras respostas: Se você não quiser a 0000-00-00string, você pode usar noDatetimeStringSync=true(com a ressalva de sacrificar a conversão de fuso horário).

O bug oficial do MySQL: https://bugs.mysql.com/bug.php?id=47108 .

Além disso, para histórico, o JDBC costumava retornar NULLpara 0000-00-00datas, mas agora retorna uma exceção por padrão. Fonte

Damio
fonte
2

você pode anexar o url jdbc com

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

Com a ajuda disso, sql convert '0000-00-00 00:00:00' como valor nulo.

por exemplo:

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