การจัดการค่า DATETIME 0000-00-00 00:00:00 ใน JDBC


85

ฉันได้รับข้อยกเว้น (ดูด้านล่าง) หากฉันพยายามทำ

resultset.getString("add_date");

สำหรับการเชื่อมต่อ JDBC กับฐานข้อมูล MySQL ที่มีค่า DATETIME เป็น 0000-00-00 00:00:00 (ค่ากึ่งว่างสำหรับ DATETIME) แม้ว่าฉันจะพยายามรับค่าเป็นสตริงไม่ใช่ในฐานะ วัตถุ.

ฉันทำได้โดยการทำ

SELECT CAST(add_date AS CHAR) as add_date

ซึ่งได้ผล แต่ดูเหมือนงี่เง่า ... มีวิธีที่ดีกว่านี้ไหม

จุดของฉันเป็นที่ฉันต้องการเพียงแค่สตริง DATETIME ดิบดังนั้นฉันสามารถแยกตัวเองเป็นอยู่

หมายเหตุ:นี่คือที่มาของ 0000: (จากhttp://dev.mysql.com/doc/refman/5.0/en/datetime.html )

ค่า DATETIME, DATE หรือ TIMESTAMP ที่ผิดกฎหมายจะถูกแปลงเป็นค่า "ศูนย์" ของประเภทที่เหมาะสม ('0000-00-00 00:00:00' หรือ '0000-00-00')

ข้อยกเว้นเฉพาะคือข้อยกเว้นนี้:

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)

คำตอบ:


85

ฉันสะดุดกับความพยายามที่จะแก้ไขปัญหาเดียวกันนี้ การติดตั้งที่ฉันใช้งานใช้ JBOSS และ Hibernate ดังนั้นฉันจึงต้องทำวิธีที่แตกต่างออกไป สำหรับกรณีพื้นฐานที่คุณควรจะสามารถที่จะเพิ่มzeroDateTimeBehavior=convertToNullการเชื่อมต่อของคุณ URI ตามนี้หน้าคุณสมบัติการตั้งค่า

ฉันพบคำแนะนำอื่น ๆ ทั่วทั้งแผ่นดินที่อ้างถึงการใส่พารามิเตอร์นั้นในการกำหนดค่าไฮเบอร์เนตของคุณ:

ในhibernate.cfg.xml :

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

ในhibernate.properties :

hibernate.connection.zeroDateTimeBehavior=convertToNull

แต่ฉันต้องใส่ไว้ในไฟล์mysql-ds.xmlสำหรับ JBOSS เป็น:

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

หวังว่านี่จะช่วยใครบางคนได้ :)


การเปลี่ยนแปลงในไฟล์hibernate.cfg.xmlเป็นเคล็ดลับสำหรับฉันขอบคุณเพราะฉันแทบไม่มีความรู้เกี่ยวกับ Java
Gendrith

124

คำตอบอื่นคุณสามารถใช้ JDBC URL นี้โดยตรงในการกำหนดค่าแหล่งข้อมูลของคุณ:

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

แก้ไข:

ที่มา: MySQL Manual

วันที่ที่มีส่วนประกอบเป็นศูนย์ทั้งหมด (0000-00-00 ... ) - ค่าเหล่านี้ไม่สามารถแสดงได้อย่างน่าเชื่อถือใน Java Connector / J 3.0.x แปลงเป็น NULL เสมอเมื่ออ่านจาก ResultSet

ตัวเชื่อมต่อ / J 3.1 แสดงข้อยกเว้นโดยค่าเริ่มต้นเมื่อพบค่าเหล่านี้เนื่องจากเป็นพฤติกรรมที่ถูกต้องที่สุดตามมาตรฐาน JDBC และ SQL พฤติกรรมนี้สามารถแก้ไขได้โดยใช้คุณสมบัติคอนฟิกูเรชัน zeroDateTimeBehavior ค่าที่อนุญาตคือ:

  • ข้อยกเว้น (ค่าเริ่มต้น) ซึ่งพ่น SQLException ด้วย SQLState ของ S1009
  • ConvertToNullซึ่งส่งคืนค่า NULL แทนวันที่
  • รอบซึ่งจะปัดเศษวันที่เป็นค่าที่ใกล้เคียงที่สุดซึ่งก็คือ 0001-01-01

อัปเดต: Alexander รายงานข้อผิดพลาดที่มีผลต่อ mysql-connector-5.1.15 ในคุณสมบัตินั้น ดูchangelogs บนเว็บไซต์อย่างเป็นทางการ


น่าสนใจ. คุณพบข้อมูลนี้ที่ไหน
Jason S

เพิ่งอัปเดตคำตอบของฉัน! แต่ URL ของ @sarumont อาจเหมาะสมกว่าในกรณีนี้ (แสดงรายการคุณสมบัติตัวเชื่อมต่อทั้งหมด)
Brian Clozel

1
เวอร์ชัน 5.1.16 ของซอฟต์แวร์ jdbc มี bugfix นี้: - Fix for BUG # 57808 - wasNull ไม่ได้ตั้งค่าสำหรับฟิลด์ DATE ที่มีค่า 0000-00-00 ใน getDate () แม้ว่า zeroDateTimeBehavior จะเป็น convertToNull
Alexander Kjäll

ว้าว! ขอบคุณสำหรับข้อมูล. ฉันเดาว่าคุณมีช่วงเวลาที่ยากลำบากในการหาสิ่งนั้น :-(
Brian Clozel

11

ประเด็นของฉันคือฉันต้องการสตริง DATETIME แบบดิบดังนั้นฉันจึงสามารถแยกวิเคราะห์ได้เองตามที่เป็นอยู่

นั่นทำให้ฉันคิดว่า "วิธีแก้ปัญหา" ของคุณไม่ใช่วิธีแก้ปัญหา แต่ในความเป็นจริงวิธีเดียวที่จะรับค่าจากฐานข้อมูลลงในโค้ดของคุณ:

SELECT CAST(add_date AS CHAR) as add_date

อย่างไรก็ตามหมายเหตุเพิ่มเติมจากเอกสาร MySQL:

ข้อ จำกัด MySQL เกี่ยวกับข้อมูลที่ไม่ถูกต้อง :

ก่อน MySQL 5.0.2 MySQL จะให้อภัยค่าข้อมูลที่ผิดกฎหมายหรือไม่เหมาะสมและบังคับให้เป็นค่าทางกฎหมายสำหรับการป้อนข้อมูล ใน MySQL 5.0.2 ขึ้นไปนั่นยังคงเป็นลักษณะการทำงานเริ่มต้น แต่คุณสามารถเปลี่ยนโหมด SQL ของเซิร์ฟเวอร์เพื่อเลือกการปฏิบัติต่อค่าที่ไม่ดีแบบดั้งเดิมมากขึ้นเพื่อให้เซิร์ฟเวอร์ปฏิเสธและยกเลิกคำสั่งที่เกิดขึ้น

[.. ]

ถ้าคุณพยายามจัดเก็บ NULL ไว้ในคอลัมน์ที่ไม่ใช้ค่า NULL ข้อผิดพลาดจะเกิดขึ้นสำหรับคำสั่ง INSERT แถวเดียว สำหรับคำสั่ง INSERT แบบหลายแถวหรือ INSERT INTO ... คำสั่ง SELECT เซิร์ฟเวอร์ MySQL จะเก็บค่าเริ่มต้นโดยนัยสำหรับชนิดข้อมูลคอลัมน์

MySQL 5.x ประเภทวันที่และเวลา :

MySQL ยังอนุญาตให้คุณจัดเก็บ "0000-00-00" เป็น "วันที่จำลอง" (หากคุณไม่ได้ใช้โหมด NO_ZERO_DATE SQL) ในบางกรณีสะดวกกว่า (และใช้พื้นที่ข้อมูลและดัชนีน้อยกว่า) มากกว่าการใช้ค่า NULL

[.. ]

โดยค่าเริ่มต้นเมื่อ MySQL พบค่าสำหรับประเภทวันที่หรือเวลาที่อยู่นอกช่วงหรือประเภทอื่นที่ผิดกฎหมาย (ตามที่อธิบายไว้ในตอนต้นของส่วนนี้) จะแปลงค่าเป็นค่า "ศูนย์" สำหรับประเภทนั้น


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

ใช้สิ่งนี้เพื่อหลีกเลี่ยงข้อผิดพลาด มันส่งคืนวันที่ในรูปแบบสตริงจากนั้นคุณจะได้รับเป็นสตริง

resultset.getString("dtime");

สิ่งนี้ใช้ไม่ได้จริง แม้ว่าคุณจะเรียก getString mysql ภายในยังคงพยายามแปลงเป็นวันที่ก่อน

ที่ 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]


1
นั่นเหมือนกับโซลูชัน CAST (add_date AS CHAR) ของฉัน แต่ +1 เนื่องจากสิ่งนี้ช่วยให้คุณสามารถจัดรูปแบบได้อย่างชัดเจนในแบบที่คุณต้องการ
Jason S

5

ถ้าหลังจากเพิ่มบรรทัด:

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

hibernate.connection.zeroDateTimeBehavior=convertToNull

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

ยังคงเป็นข้อผิดพลาด:

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

ค้นหาเส้น:

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

แทนที่ด้วยบรรทัดต่อไปนี้ตามลำดับ:

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

5

ฉันต่อสู้กับปัญหานี้และใช้โซลูชัน 'convertToNull' ที่กล่าวถึงข้างต้น มันทำงานในอินสแตนซ์ MySql ในพื้นที่ของฉัน แต่เมื่อฉันปรับใช้แอป Play / Scala กับ Heroku มันจะไม่ทำงานอีกต่อไป นอกจากนี้ Heroku ยังเชื่อมต่อ args หลาย ๆ ตัวเข้ากับ DB URL ที่พวกเขาให้กับผู้ใช้และโซลูชันนี้เนื่องจาก Heroku ใช้การเรียงต่อกันของ "?" ก่อนชุดอาร์เรย์ของตัวเองจะไม่ทำงาน อย่างไรก็ตามฉันพบวิธีแก้ปัญหาอื่นซึ่งดูเหมือนว่าจะใช้ได้ดีพอ ๆ กัน

SET sql_mode = 'NO_ZERO_DATE';

ฉันใส่สิ่งนี้ในคำอธิบายตารางของฉันและมันแก้ปัญหาของ '0000-00-00 00:00:00' ไม่สามารถแสดงเป็น java.sql.Timestamp


3

ฉันขอแนะนำให้คุณใช้ null เพื่อแทนค่า null

อะไรคือข้อยกเว้นที่คุณได้รับ?

BTW:

ไม่มีปีที่เรียกว่า 0 หรือ 0000 (แม้ว่าบางวันจะอนุญาตให้ปีนี้)

และไม่มี 0 เดือนของปีหรือ 0 วันของเดือน (ซึ่งอาจเป็นสาเหตุของปัญหาของคุณ)


ใช่ แต่ยังไม่มีปีที่เรียกว่า 0 ย้อนหลังด้วยซ้ำ ปีก่อน 1 AD / CE คือ 1 BC / BCE
Peter Lawrey

2
นั่นเป็นเหตุผลทั้งหมดที่ MySQL ใช้ 0000 เป็นวันที่ที่ไม่ถูกต้องเนื่องจากไม่ขัดแย้งกับวันที่ที่มีอยู่ นอกจากนี้บางครั้งก็มีการใช้วันที่เช่น 1999-00-00 (ไม่ใช่ของฉัน!) เพื่อแสดงถึงปี 1999 แทนที่จะเป็นวันที่เฉพาะเจาะจง
Jason S

3

ฉันแก้ไขปัญหาที่พิจารณาว่า '00 -00 -.... 'ไม่ใช่วันที่ที่ถูกต้องดังนั้นฉันจึงเปลี่ยนคำจำกัดความคอลัมน์ SQL ของฉันโดยเพิ่มค่าประสบการณ์ "NULL" เพื่ออนุญาตค่า null:

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

จากนั้นฉันจะสามารถแทรกค่า NULL ได้นั่นหมายความว่า "ฉันยังไม่ได้ลงทะเบียนการประทับเวลานั้น" ...

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

ตารางมีลักษณะดังนี้:

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)

สุดท้าย: JPA ในการดำเนินการ:

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

ในที่สุดก็ทำงานทั้งหมด ฉันหวังว่าจะช่วยคุณ


ฉันไม่คิดว่านี่จะช่วยแก้ปัญหาในการแปลง MySQL DATETIME ของศูนย์ทั้งหมดเป็นวันที่ที่ถูกต้อง เพียงแค่แสดงวิธีการป้อนค่า null ในช่อง DATETIME ซึ่งไม่ได้ช่วยอะไรมากนัก
Mark Chorley

2

หากต้องการเพิ่มคำตอบอื่น ๆ : หากคุณไม่ต้องการ0000-00-00สตริงคุณสามารถใช้noDatetimeStringSync=true(โดยมีข้อแม้ในการเสียสละการแปลงเขตเวลา)

ข้อผิดพลาด MySQL อย่างเป็นทางการ: https://bugs.mysql.com/bug.php?id=47108

นอกจากนี้สำหรับประวัติ JDBC ใช้ในการแสดงNULLสำหรับ0000-00-00วัน แต่ตอนนี้กลับมีการยกเว้นโดยค่าเริ่มต้น ที่มา


2

คุณสามารถต่อท้าย jdbc url ด้วย

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

ด้วยความช่วยเหลือของสิ่งนี้ sql แปลง '0000-00-00 00:00:00' เป็นค่า null

เช่น:

jdbc:mysql:<host-name>/<db-name>?zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=UTF-8&characterSetResults=UTF-8
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.