背景和问题
我的公司决定从 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 驱动程序 - 并得到了以下结果:
尽管使用 MariaDB 时测试通过了,但这不是一个选项。
如果查询参数转换为日期,则测试 #3 和 4 有效:
... and b.valueDate <= DATE(:today)";
但这意味着对代码进行许多更改。测试 #2 是(唯一失败的)我想要遵循的选项,因为它意味着更改量较少。但是,我似乎无法让它发挥作用。
<强>?问题?
有没有一种方法可以使用 MySQL 和 MariaDB 连接器而不会导致此问题?
还有比类型转换
DATE(:today)
更好的选择吗?迄今为止的所有参数?另一个解决方案?谢谢。
更新:
这些是代码中设置的数据源属性:
dataSource.setJdbcUrl("jdbc:mysql://"+ hostname + ":3306/"
+ databaseName +
"?useUnicode=true&" +
"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.COLUMNS
和INFORMATION_SCHEMA.TABLES
是CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
.
@elenst解决步骤:
- 启用general_log;
- 已确认
NAMES
的值和character_set_results
使用 MySQL 连接器时相同(latin1 和 NULL); - 切换回 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/