时间 : 2023年3月11日14:08:47
bug描述
发布文章出错。
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@55105a0c]
org.springframework.jdbc.UncategorizedSQLException:
### Error updating database. Cause: java.sql.SQLException: Incorrect string value: '\xF0\x9F\x86\x94 \xE8...' for column 'content_html' at row 1
### The error may exist in com/mszlu/blog/dao/mapper/ArticleBodyMapper.java (best guess)
### The error may involve com.mszlu.blog.dao.mapper.ArticleBodyMapper.insert-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO ms_article_body ( id, content, content_html, article_id ) VALUES ( ?, ?, ?, ? )
### Cause: java.sql.SQLException: Incorrect string value: '\xF0\x9F\x86\x94 \xE8...' for column 'content_html' at row 1
; uncategorized SQLException; SQL state [HY000]; error code [1366]; Incorrect string value: '\xF0\x9F\x86\x94 \xE8...' for column 'content_html' at row 1; nested exception is java.sql.SQLException: Incorrect string value: '\xF0\x9F\x86\x94 \xE8...' for column 'content_html' at row 1
可以读出,在插入ms_article_body表的时候插入了错误的String value 在content_html 字段。
经过我排查发现,
我发布的文章中有一行语句 PUT /users/:id 中的“:id” 直接转换成了一个emoji
如下图所示,
就是这个emoji导致报错。
PUT /users/🆔
我的数据库使用的字符集是utf-8,不支持emoji
为什么有这么多的character_set
server system database client都是什么呢
- character_set_connection:从客户端接收到数据,然后传输的字符集
- character_set_database:默认数据库的字符集,无论默认数据库如何改变,都是这个字符集;如果没有默认数据库,那就使用 character_set_server指定的字符集,这个变量建议由系统自己管理,不要人为定义。
- character_set_filesystem:把操作系统上的文件名转化成此字符集,即把 character_set_client转换character_set_filesystem, 默认binary是不做任何转换的
- character_set_results:结果集的字符集
- character_set_server:数据库服务器的默认字符集
- character_set_system:存储系统元数据的字符集,总是 utf8,不需要设置
相关知识
因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理。
-
ASCII编码,占用0 - 127用来表示大小写英文字母、数字和一些符号,这个编码表被称为Ascll表。
-
Unicode通常用两个字节表示一个字符,原有的英文编码从单字节变成双字节,只需要把高字节全部填为0就可以,将所有语言统一到一套编码里。
-
utf8是针对Unicode的一种可变长度字符编码,由于对可以用Ascll表示的字符,使用Unicode并不高效,因为Unicode比Ascll占用大一倍的空间,而对ASCII来说高字节的0对他毫无用处。就出现了一些中间格式的字符集,他们被称为通用转换格式,即UTF(Unicode Transformation Format)。
-
utf8mb4(mb4 = most bytes 4)utf8mb4是utf8的超集,门用来兼容四字节的unicode。
低版本的MySQL支持的utf8编码,最大字符长度为 3 字节,如果遇到 4 字节的字符就会出现错误了。
三个字节的 UTF-8 最大能编码的 Unicode 字符是 0xFFFF,也就是 Unicode 中的基本多文平面(BMP)。
看unicode编码区从1 ~ 126就属于传统utf8区,当然utf8mb4也兼容这个区,126行以下就是utf8mb4扩充区,什么时候你需要存储那些字符,你才用utf8mb4,否则只是浪费空间。
utf8mb4比utf8多了emoji编码支持
总结与解决办法
简单总结就是 utf8mb4 >utf8>ASCII ,高级的字符集可以兼容低级的。 utf8mb4 支持emoji,而我的数据库是utf8,我无意间使用了emoji,数据库不支持,无法解析导致报错。
utf8字符更省空间,但是utf8mb4支持emoji。
要么发文章的时候不使用emoji 或者将数据库编码改为utf8mb4,支持emoji。
权衡了一下我决定将数据库编码改为utf8mb4,一个好的博客应该支持emoji才对。
数据库编码改为utf8mb4步骤
1.检查版本
utf8mb4的最低mysql版本支持版本为5.5.3+ ,mysql驱动5.1.34可用,最低不能低于5.1.13
2.修改配置文件
修改mysql配置文件my.cnf(windows为my.ini) my.cnf一般在/etc/mysql/my.cnf
位置。找到后请在以下三部分里添加如下内容:
[client]
default-character-set = utf8mb4
[mysql]
default-character-set = utf8mb4
[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
init_connect=’SET NAMES utf8mb4’
3.重启数据库检查变量
SHOW VARIABLES WHERE Variable_name LIKE 'character_set_%' OR Variable_name LIKE 'collation%';
4.将已经建立的数据库和表设置为utf8mb4
手动改
更改数据库编码
ALTER DATABASE blog CHARACTER SET `utf8mb4` COLLATE `utf8mb4_general_ci`;
更改表编码
ALTER TABLE `TABLE_NAME` CONVERT TO CHARACTER SET `utf8mb4` COLLATE `utf8mb4_general_ci`;
可以写代码批量转换,涉及到一个包dbutils,写进maven里
写程序
//获取所有表
Connection conn = null;
try {
conn = DbUtilsTool.openConn("MySQL", "127.0.0.1", "3306", "caitu99", "root", "root");
String sql = "show tables";
QueryRunner qr = new QueryRunner();
List<String> tblNameList= (List<String>) qr.query(conn, sql, new ColumnListHandler(1));
sql = "ALTER DATABASE caitu99 CHARACTER SET `utf8mb4` COLLATE `utf8mb4_general_ci`";
qr.update(conn,sql);
for (String str:tblNameList)
{
sql = "ALTER TABLE "+str+" CONVERT TO CHARACTER SET `utf8mb4` COLLATE `utf8mb4_general_ci`";
qr.update(conn,sql);
}
}
catch (Exception e)
{
e.printStackTrace();
throw new RuntimeException(e);
}
finally {
if(conn!=null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
DbUtilsTool类:
package com.mysql.chartest;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import org.apache.commons.dbutils.BasicRowProcessor;
import org.apache.commons.dbutils.BeanProcessor;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ArrayHandler;
import org.apache.commons.dbutils.handlers.ArrayListHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.dbutils.handlers.KeyedHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
public class DbUtilsTool {
private static final QueryRunner runner = new QueryRunner();
/* 打开数据库连接(type: MySQL,Oracle,SQLServer) */
public static Connection openConn(String type, //数据库类型
String host, //主机ip
String port, //主机端口
String name, //数据库名
String username, //用户名
String password)//密码
{
Connection conn = null;
try {
String driver;
String url;
if (type.equalsIgnoreCase("MySQL")) {
driver = "com.mysql.jdbc.Driver";
url = "jdbc:mysql://" + host + ":" + port + "/" + name;
} else if (type.equalsIgnoreCase("Oracle")) {
driver = "oracle.jdbc.driver.OracleDriver";
url = "jdbc:oracle:thin:@" + host + ":" + port + ":" + name;
} else if (type.equalsIgnoreCase("SQLServer")) {
driver = "com.microsoft.jdbc.sqlserver.SQLServerDriver";
url = "jdbc:sqlserver://" + host + ":" + port + ";databaseName=" + name;
} else {
throw new RuntimeException();
}
Class.forName(driver);
conn = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
/* 关闭数据库连接 */
public static void closeConn(Connection conn) {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/* 查询(返回Array结果) */
public static Object[] queryArray(Connection conn, String sql, Object... params) {
Object[] result = null;
try {
result = runner.query(conn, sql, new ArrayHandler(), params);
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
/* 查询(返回ArrayList结果) */
public static List<Object[]> queryArrayList(Connection conn, String sql, Object... params) {
List<Object[]> result = null;
try {
result = runner.query(conn, sql, new ArrayListHandler(), params);
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
/* 查询(返回Map结果) */
public static Map<String, Object> queryMap(Connection conn, String sql, Object... params) {
Map<String, Object> result = null;
try {
result = runner.query(conn, sql, new MapHandler(), params);
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
/* 查询(返回MapList结果) */
public static List<Map<String, Object>> queryMapList(Connection conn, String sql, Object... params) {
List<Map<String, Object>> result = null;
try {
result = runner.query(conn, sql, new MapListHandler(), params);
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
/* 查询(返回Bean结果) */
public static <T> T queryBean(Class<T> cls, Map<String, String> map, Connection conn, String sql, Object... params) {
T result = null;
try {
if (map != null) {
result = runner.query(conn, sql, new BeanHandler<T>(cls, new BasicRowProcessor(new BeanProcessor(map))), params);
} else {
result = runner.query(conn, sql, new BeanHandler<T>(cls), params);
}
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
/* 查询(返回BeanList结果) */
public static <T> List<T> queryBeanList(Class<T> cls, Map<String, String> map, Connection conn, String sql, Object... params) {
List<T> result = null;
try {
if (map != null) {
result = runner.query(conn, sql, new BeanListHandler<T>(cls, new BasicRowProcessor(new BeanProcessor(map))), params);
} else {
result = runner.query(conn, sql, new BeanListHandler<T>(cls), params);
}
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
/* 查询指定列名的值(单条数据) */
public static <T> T queryColumn(String column, Connection conn, String sql, Object... params) {
T result = null;
try {
result = runner.query(conn, sql, new ScalarHandler<T>(column), params);
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
/* 查询指定列名的值(多条数据) */
public static <T> List<T> queryColumnList(String column, Connection conn, String sql, Object... params) {
List<T> result = null;
try {
result = runner.query(conn, sql, new ColumnListHandler<T>(column), params);
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
/* 查询指定列名对应的记录映射 */
public static <T> Map<T, Map<String, Object>> queryKeyMap(String column, Connection conn, String sql, Object... params) {
Map<T, Map<String, Object>> result = null;
try {
result = runner.query(conn, sql, new KeyedHandler<T>(column), params);
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
/* 更新(包括UPDATE、INSERT、DELETE,返回受影响的行数) */
public static int update(Connection conn, String sql, Object... params) {
int result = 0;
try {
result = runner.update(conn, sql, params);
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
}
版权声明:本文为CSDN博主「woslx」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/woslx/artic…