背景:
书接上文,当我在接收工单消息时,直接以字符串(String)的形式接收了日期信息,而没有将其转换为日期(Date)格式,而数据表中是日期格式。这样一来,在设计实体类的时候,我也相应地使用了字符串类型来定义这个字段。当时用的mysql测试的没问题,线上环境用的pgsql,直接报错了。
原因:
实体类是String字段,数据表中是Date类型。mysql有隐式类型转换,没有报错,pgsql对类型检查更加严格,直接会报错。
MySQL 在处理数据时,会进行隐式类型转换。将一个字符串插入到日期字段时,MySQL 会尝试将字符串转换为日期。如果转换成功,则插入成功;如果失败,则可能插入 NULL 或者报错。
java
String publishDate = sysDictInfoWithVersionFromDTO.getPublishDate();
// 解析publishDate为时间
if (publishDate == null || publishDate.isEmpty()) {
throw new IllegalArgumentException("生效时间不能为空");
}
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); // 根据实际日期格式调整
Date parsedDate;
try {
parsedDate = dateFormat.parse(publishDate);
// 进一步处理 parsedDate
} catch (ParseException e) {
throw new IllegalArgumentException("Invalid date format: " + publishDate, e);
}
MySQL 对数据类型转换的底层机制
MySQL 在处理数据类型转换时,相对宽松。
1. 隐式类型转换:
MySQL 会在必要时自动进行隐式类型转换,以适应目标列的数据类型。这种转换通常发生在插入数据、更新数据或执行查询时。
2. 日期格式解析:
MySQL 会尝试解析字符串,以确定其是否符合日期格式。如果字符串格式正确,MySQL 会将其转换为 DATE 类型。如果格式不正确,MySQL 会尝试解析并可能插入一个默认值(如 0000-00-00)或抛出错误。
3. 默认值和错误处理:
如果转换失败,MySQL 会根据配置和上下文决定如何处理。例如,如果 sql_mode 设置为 STRICT_TRANS_TABLES 或 STRICT_ALL_TABLES,MySQL 会抛出错误。否则,它可能会插入一个默认值。
4. 示例:
当我传入一个String类型到一个日期类型,mysql会尝试转换,同时传入的字符串是"2024-11-12",它会试着解析这个字符串,看能不能转成 DATE 类型。是的话直接插入2024-11-12 00:00:00,所以当时流程可以走通。
底层机制详解
1. 隐式类型转换
MySQL 的隐式类型转换机制主要依赖于其内部的类型转换函数。这些函数在 SQL 执行过程中被调用,以确保数据类型的一致性。例如:
STR_TO_DATE(str, format):将字符串转换为日期。
DATE(str):将字符串转换为日期。
CAST(expr AS type):将表达式转换为目标类型。
字符串到数字的转换
sql
- MySQL 会尝试将字符串转换为数字。如果字符串以数字开头,MySQL 会提取前面的数字部分进行转换。
- 如果字符串不能转换为数字,MySQL 会将其视为 0。
SELECT 1 + '1'; -- 结果为 2
SELECT 1 + '1a'; -- 结果为 2,因为 '1a' 被转换为 1
SELECT 1 + 'a1'; -- 结果为 1,因为 'a1' 被转换为 0
数字到字符串的转换
arduino
- 当需要将数字与字符串连接时,MySQL 会自动将数字转换为字符串。
SELECT CONCAT('The number is ', 1); -- 结果为 'The number is 1'
日期和时间的转换
sql
- `STR_TO_DATE` 函数将字符串转换为日期。
- `DATE` 函数从日期时间字符串中提取日期部分。
SELECT STR_TO_DATE('2024-11-13', '%Y-%m-%d'); -- 结果为 '2024-11-13'
SELECT DATE('2024-11-13 12:34:56'); -- 结果为 '2024-11-13'
比较操作中的类型转换
ini
- 在比较操作中,MySQL 会尝试将字符串转换为数字进行比较。
SELECT '1' = 1; -- 结果为 1 (true)
SELECT '1a' = 1; -- 结果为 1 (true),因为 '1a' 被转换为 1
SELECT 'a1' = 1; -- 结果为 0 (false),因为 'a1' 被转换为 0
NULL 值的处理
sql
- 任何与 `NULL` 的运算结果都是 `NULL`。
- `IFNULL` 函数可以用来处理 `NULL` 值,提供一个默认值。
SELECT 1 + NULL; -- 结果为 NULL
SELECT IFNULL(1 + NULL, 0); -- 结果为 0
字符集和排序规则的转换
ini
- MySQL 会自动处理字符集和排序规则的转换,以确保字符串比较的正确性。
SELECT 'abc' = _utf8mb4'abc'; -- 结果为 1 (true)
需要类型转换时,使用显式的转换函数(如 CAST
、CONVERT
、STR_TO_DATE
等),以提高代码的可读性和可维护性。
sql
SELECT CAST('1' AS SIGNED) + 1; -- 结果为 2
SELECT CONCAT('The number is ', CAST(1 AS CHAR)); -- 结果为 'The number is 1'
2. 日期格式解析
MySQL 使用内置的日期解析器来处理日期字符串。解析器会尝试识别常见的日期格式,如 YYYY-MM-DD、YYYYMMDD、YY-MM-DD 等。如果字符串格式正确,解析器会返回一个有效的 DATE 值。
3. 默认值和错误处理
MySQL 的行为受 sql_mode 系统变量的影响。sql_mode 定义了 MySQL 的操作模式,包括数据验证和错误处理策略。
常见的 SQL_MODE
模式名称 | 描述 |
---|---|
STRICT_TRANS_TABLES | 对事务表(如 InnoDB)启用严格模式。严格模式:如果插入或更新操作违反了数据类型约束,MySQL 会抛出错误而不是警告,并且不会执行该操作。 |
STRICT_ALL_TABLES | 对所有表(包括 MyISAM 和 InnoDB)启用严格模式。 |
NO_ZERO_DATE | 禁止插入零日期('0000-00-00')。 |
NO_ZERO_IN_DATE | 禁止插入部分零日期(如 '2023-00-00')。 |
ERROR_FOR_DIVISION_BY_ZERO | 当除数为零时,返回错误而不是 NULL。 |
ONLY_FULL_GROUP_BY | 要求 GROUP BY 子句中包含所有选择列,除非这些列是聚合函数的结果。 |
ANSI_QUOTES | 允许使用双引号来表示标识符(如表名和列名),而不是字符串。 |
PIPES_AS_CONCAT | 将 || 作为字符串连接操作符,而不是逻辑或操作符。 |
IGNORE_SPACE | 允许在函数名和左括号之间有空格。 |
TRADITIONAL | 包含多个严格模式和其他限制,使 MySQL 行为更接近于传统 SQL 标准。 |
查看当前的 SQL_MODE
scss
SELECT @@sql_mode;
设置 SQL_MODE
ini
配置文件设置
在 my.cnf 或 my.ini 文件中添加或修改以下内容:
sql_mode='STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,ONLY_FULL_GROUP_BY'
动态设置
SET SESSION sql_mode='STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,ONLY_FULL_GROUP_BY';
全局设置(已经打开的会话不会立即受到新设置的影响)
SET GLOBAL sql_mode='STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,ONLY_FULL_GROUP_BY';
总结
mysql真好用,嘿嘿。其实写完代码,还是要测试一下pg环境的,当时只测试了mysql环境下的情况。还好系统还没正式投入使用。