这几天遇到Mysql数据落库报编码错误:
代码语言:javascript复制Caused by: java.sql.BatchUpdateException: Incorrect string value: 'xF0x9Fx98x8A',...' for column 'statement_text' at row 1
Caused by: java.sql.BatchUpdateException: Incorrect string value: 'xF0xA0x81x81%'...' for column 'statement_sample' at row 1
网上提供了大部分的解决方法是修改数据库配置,但是数据库如果使用连接池,无法保证其他连接时不指定utf-8,所以避免不了其他连接污染连接池。这里给出另一种解决方法,过滤掉特殊字符。
1 UTF-8编码分段
UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部分修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或发送文字优先采用的编码。
1.0 符号查询方法
http://www.fileformat.info/info/unicode/char/xxxxx/index.htm
替换xxxx为需要查询的字符16进制编码
例如emoji的SMILING FACE的编码为1f60a:查询地址 字符a的编码为61:查询地址
1.1 Ascii
128个US-ASCII字符只需一个字节编码(Unicode范围由U 0000至U 007F)
例如
十六进制(JAVA) | 图形 |
---|---|
“u0060” | ` |
“u0061” | a |
“u0062” | b |
“u0063” | c |
“u0064” | d |
“u0065” | e |
1.2 拉丁文等
带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要两个字节编码(Unicode范围由U 0080至U 07FF)。
十六进制(JAVA) | 图形 | 查询连接 |
---|---|---|
“u0550” | Ր | link |
“u0450” | ѐ | link |
1.3 中文等
其他基本多文种平面(BMP)中的字符(这包含了大部分常用字,如大部分的汉字)使用三个字节编码(Unicode范围由U 0800至U FFFF)。
十六进制(JAVA) | 图形 | 查询连接 |
---|---|---|
“u9AD8” | 高 | link |
“u738B” | 王 | link |
1.4 其他
其他极少使用的Unicode 辅助平面的字符使用四至六字节编码(Unicode范围由U 10000至U 1FFFFF使用四字节,Unicode范围由U 200000至U 3FFFFFF使用五字节,Unicode范围由U 4000000至U 7FFFFFFF使用六字节)。
十六进制(JAVA) | 图形 | 查询连接 |
---|---|---|
“uD83DuDE0A” | ? | link |
“uD83DuDE0F” | ? | link |
2 UTF-8编码字节含义
- 对于UTF-8编码中的任意字节B,如果B的第一位为0,则B独立的表示一个字符(ASCII码);
- 如果B的第一位为1,第二位为0,则B为一个多字节字符中的一个字节(非ASCII字符);
- 如果B的前两位为1,第三位为0,则B为两个字节表示的字符中的第一个字节;
- 如果B的前三位为1,第四位为0,则B为三个字节表示的字符中的第一个字节;
- 如果B的前四位为1,第五位为0,则B为四个字节表示的字符中的第一个字节;
因此,对UTF-8编码中的任意字节,根据第一位,可判断是否为ASCII字符;根据前二位,可判断该字节是否为一个字符编码的第一个字节;根据前四位(如果前两位均为1),可确定该字节为字符编码的第一个字节,并且可判断对应的字符由几个字节表示;根据前五位(如果前四位为1),可判断编码是否有错误或数据传输过程中是否有错误。
码点的位数 | 码点起值 | 码点终值 | 字节序列 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 |
---|---|---|---|---|---|---|---|---|---|
7 | U 0000 | U 007F | 1 | 0xxxxxxx | |||||
11 | U 0080 | U 07FF | 2 | 110xxxxx | 10xxxxxx | ||||
16 | U 0800 | U FFFF | 3 | 1110xxxx | 10xxxxxx | 10xxxxxx | |||
21 | U 10000 | U 1FFFFF | 4 | 11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx | ||
26 | U 200000 | U 3FFFFFF | 5 | 111110xx | 10xxxxxx | 10xxxxxx | 10xxxxxx | 10xxxxxx | |
31 | U 4000000 | U 7FFFFFFF | 6 | 1111110x | 10xxxxxx | 10xxxxxx | 10xxxxxx | 10xxxxxx | 10xxxxxx |
- 在ASCII码的范围,用一个字节表示,超出ASCII码的范围就用字节表示,这就形成了我们上面看到的UTF-8的表示方法,好处是当UNICODE文件中只有ASCII码时,存储的文件都为一个字节,所以就是普通的ASCII文件无异,读取的时候也是如此,所以能与以前的ASCII文件兼容。
- 大于ASCII码的,就会由上面的第一字节的前几位表示该unicode字符的长度,比如110xxxxx前三位的二进制表示告诉我们这是个2BYTE的UNICODE字符;1110xxxx是个三位的UNICODE字符,依此类推;xxx的位置由字符编码数的二进制表示的位填入。越靠右的x具有越少的特殊意义。只用最短的那个足够表达一个字符编码数的多字节串。注意在多字节串中,第一个字节的开头"1"的数目就是整个串中字节的数目。
3 Java过滤4字长UTF-8编码字符(保留3字长字符)
- 如上述1.1,1.2,1.3中提到,三字长编码保存了大部分常规字符,使用白名单保留这部份字符可以满足一般业务需求,过滤掉特殊字符串(解决MYSQL特殊字符无法插入的问题)。
- 4字长的UTF-8字符就是Unicode SMP(辅助平面)中的字符, 也就是Unicode编码大于U FFFF的字符, 所以我们只需要获取字符串中各个字符的code point,当code point 大于FFFF时(或者直接使用Character.isSupplementaryCodePoint来判断),过滤掉即可。
示例代码如下:
代码语言:javascript复制 @Test
public void filterUtf8mb4Test() {
String s = "a中uD83DuDD11a中";
log.info(filterUtf8mb4(s));
}
public static String filterUtf8mb4(String str) {
final int LAST_BMP = 0xFFFF;
StringBuilder sb = new StringBuilder(str.length());
for (int i = 0; i < str.length(); i ) {
int codePoint = str.codePointAt(i);
if (codePoint < LAST_BMP) {
sb.appendCodePoint(codePoint);
} else {
i ;
}
}
return sb.toString();
}
输出结果为:
代码语言:javascript复制a中a中
4 一些笔记
不想重启方案
执行之前在当前会话执行下面二选一
代码语言:javascript复制set character_set_client = utf8mb4;
SET NAMES utf8mb4;
服务端修改方案记录
(要改全,改完了重启)
代码语言:javascript复制[client]
default-character-set = utf8mb4
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
[mysql]
default-character-set = utf8mb4
参考官方文档 mysql域含义 参数 字符集
SQL修改字符集
代码语言:javascript复制ALTER DATABASE database_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE table_name CHANGE column_name column_name VARCHAR(length) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
5 引用
字符集资料出处 https://zh.wikipedia.org/wiki/UTF-8 http://www.fileformat.info https://www.cnblogs.com/chrischennx/p/6623610.html 修改数据库的方法请参考 https://blog.csdn.net/hzw19920329/article/details/55670782