java - 带有 MariaDB 驱动程序的 MySQL 服务器产生日期排序错误

标签 java mysql jdbc database-connection mariadb

背景和问题

我的公司决定从 MySQL 驱动程序更改为 MariaDB 驱动程序/连接器,并仍然使用 MySQL Server DB 作为我们 Spring 应用程序的数据库服务器。

在此迁移过程中,我发现了与驱动程序/连接器如何处理日期相关的问题,这导致了以下错误:

Illegal mix of collations for operation '<='

运行以下查询时会发生这种情况,请注意查询最后一行的日期比较。

String sql2 = "select coalesce(sum(b.value), 0) " +
            "  from acc_sub_account_booking b " +
            "    join acc_sub_account sa ON sa.id = b.subAccount_id " +
            "    join km_cash_bond cb ON cb.virtualPayInAccountNumber = sa.iban " +
            "    join km_rented_object ro ON ro.id = cb.rentedObject_id " +
            " where b.bookingType in ('PAY_IN', 'PAY_OUT') " +
            "       and ro.id = :rentedObjectId " +
            "       and b.valueDate <= :today";

Object sum1 = entityManager.createNativeQuery(sql).
            setParameter("rentedObjectId", pRentedObject.getId()).
            setParameter("today", new LocalDateTime(d.getYear(), d.getMonthOfYear(), d.getDayOfMonth(), 23, 59)).
            getSingleResult();

版本:

  • Java:7
  • MySQL:5.5
  • 玛丽亚驱动程序:1.4.6


理解

阅读 MySQL documentation 后,它提到:

MySQL Connector/J is flexible in the way it handles conversions between MySQL data types and Java data types.

In general, any MySQL data type can be converted to a java.lang.String, and any numeric type can be converted to any of the Java numeric types, although round-off, overflow, or loss of precision may occur.

也许之前不会发生该错误,因为 MySQL 连接器处理 String 和 Date 之间的转换,而现在由于尝试比较两种不同的数据类型而导致错误。

使用 MariaDB 作为服务器时不会发生此错误,可能是 Maria 中的转换处理是在数据库级别完成的,而不是在连接器中完成的。


测试

我认为是编码/字符集问题,并将所有表和列更改为 utf8_general_ci这并没有解决问题。

我运行了一些测试 - 在通过应用程序运行查询时始终使用 MariaDB 驱动程序 - 并得到了以下结果:

Table Test Description

  • 尽管使用 MariaDB 时测试通过了,但这不是一个选项

  • 如果查询参数转换为日期,则测试 #3 和 4 有效:... and b.valueDate <= DATE(:today)";但这意味着对代码进行许多更改。

  • 测试 #2 是(唯一失败的)我想要遵循的选项,因为它意味着更改量较少。但是,我似乎无法让它发挥作用。

<强>?问题?

  1. 有没有一种方法可以使用 MySQL 和 MariaDB 连接器而不会导致此问题?

  2. 还有比类型转换 DATE(:today) 更好的选择吗?迄今为止的所有参数?

  3. 另一个解决方案?谢谢。


更新:

这些是代码中设置的数据源属性:

dataSource.setJdbcUrl("jdbc:mysql://"+ hostname + ":3306/"
            + databaseName +
            "?useUnicode=true&amp;" +
            "characterEncoding=utf-8");

更新#2:

更多信息:

还执行了以下集合:

ALTER DATABASE km CHARACTER SET utf8 COLLATE utf8_general_ci;
SET collation_connection = 'utf8_general_ci';
SET collation_server = 'utf8_general_ci';

此外,每个INFORMATION_SCHEMA.COLUMNSINFORMATION_SCHEMA.TABLESCONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; .


@elenst解决步骤:

  • 启用general_log;
  • 已确认 NAMES 的值和character_set_results使用 MySQL 连接器时相同(latin1NULL);
  • 切换回 MariaDB 并设置这些值
  • 使用应用程序/查询运行,错误仍然存​​在
  • 我也尝试过 ?sessionVariables=character_set_client=latin1 ,和?sessionVariables=character_set_client=utf8 ,结果是一样的。

@DiegoDupin 无法申请 Timestamp.valueOf()Joda Time LocalDateTime 。您可以将其包装起来:

  • setParameter("today", Timestamp.valueOf(String.valueOf(new LocalDateTime(d.getYear(), d.getMonthOfYear(), d.getDayOfMonth(), 23, 59)))) 但它会导致时间戳格式错误
  • setParameter("today", new LocalDateTime(d.getYear(), d.getMonthOfYear(), d.getDayOfMonth(), 23, 59).toDate())不过有效

问题仍然存在,系统的其他部分由于驱动程序的更改,仍然可以共享此故障。


@RickJames:SHOW CREATE TABLE acc_sub_account_booking 。比较是在 valueDate 上完成的字段,类型 date ,尽管 datetime 也会发生同样的情况另一个(类似)查询中存在的字段。

| acc_sub_account_booking | CREATE TABLE `acc_sub_account_booking` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `creationDate` datetime DEFAULT NULL,
  `lastModifiedDate` datetime DEFAULT NULL,
  `bookingText` varchar(255) DEFAULT NULL,
  `bookingType` varchar(255) DEFAULT NULL,
  `uuid` varchar(255) DEFAULT NULL,
  `value` decimal(19,2) DEFAULT NULL,
  `valueDate` date DEFAULT NULL,
  `zkaGVC` int(4) NOT NULL,
  `subAccount_id` bigint(20) DEFAULT NULL,
  `bankStatementDate` date DEFAULT NULL,
  `counterpartHolder` varchar(255) DEFAULT NULL,
  `counterpartIban` varchar(255) DEFAULT NULL,
  `customerReference` varchar(255) DEFAULT NULL,
  `endToEndReference` varchar(255) DEFAULT NULL,
  `returnReason` varchar(255) DEFAULT NULL,
  `customerSpecificInformations` text,
  `counterpartBic` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uuid` (`uuid`),
  KEY `FK_SAB_SA` (`subAccount_id`),
  CONSTRAINT `FK_SAB_SA` FOREIGN KEY (`subAccount_id`) REFERENCES `acc_sub_account` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3407 DEFAULT CHARSET=utf8 |

最终更新:

此处列出的问题可以通过转换为 Java Date 来解决对象,而不是直接使用 JodaTime :

setParameter("today", new LocalDateTime(d.getYear(), d.getMonthOfYear(), d.getDayOfMonth(), 23, 59).toDate())

尽管如此,还是发现了其他问题,最终我的公司决定恢复使用 MariaDB 驱动程序的决定。

非常感谢所有愿意花时间提供帮助的人。

最佳答案

Query.setParameter依赖于PrepareStatement.setObject(...)

MariaDB jdbc 驱动程序无法处理 setObject 中的 LocalDateTime 对象
我只是创建this issue用于处理该问题。

解决方法是将 LocalDateTime 转换为时间戳:

Object sum1 = entityManager.createNativeQuery(sql).
            setParameter("rentedObjectId", pRentedObject.getId()).
            setParameter("today", Timestamp.valueOf(new LocalDateTime(d.getYear(), d.getMonthOfYear(), d.getDayOfMonth(), 23, 59))).
            getSingleResult());

编辑:

如果 LocalDateTime 对应于 java.time.LocalDateTime,则使用 Timestamp.valueOf((LocalDateTime)x) 是一种解决方法。

如果 LocalDateTime 对应 org.joda.time.LocalDateTime,那么 toDate() 是一个解决方案。

在这种特殊情况下,MySQL 驱动程序的工作方式与 MariaDB 相同,如果对象类未知,对象将被序列化并发送到服务器。由于您想象中的 org.joda.time.LocalDateTime 并未在 JDBC 中定义,因此您一定已经对此感到惊讶。发送到服务器的数据不是临时值。

关于java - 带有 MariaDB 驱动程序的 MySQL 服务器产生日期排序错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41567258/

相关文章:

php - 使用 PDO 时为 "Notice: Undefined variable: db"

sql - 无法使用 mysqldump 连接到 mysql 远程服务器

php - 用户上次登录/浏览网站的时间

java - 连接数据库时出错 : Cannot create JDBC driver of class '' for connect URL 'null'

java - JDBC 连接在 IDE (eclipse) 中有效,但在 .jar 文件中无效

java - 使用JDBC在MySQL中插入多条数据(List)

java - 这个错误是什么?为什么当我不使用像代码这样的库而不是像 aar 这样的库时不会发生这种情况?

java - 需要persistence.xml属性标签来设置sybase数据库驱动程序版本

java - 如何向 JFrame 添加下拉菜单?

java - netty tcp bytebuffer 服务器和客户端