開門見山,先提出問題和解決方案
問題:Incorrect string value: '\xF0\x9F\x99\x82' for column 'device_name' at row 1; nested exception is java.sql.SQLException: Incorrect string value: '\xF0\x9F\x99\x82' for column 'device_name' at row 1
這個是java開發(fā)人員通過向數(shù)據(jù)庫插入中文或者表情符號時一個經(jīng)典的錯誤。網(wǎng)上提供的原因通常是將數(shù)據(jù)庫的編碼為gbk或其他非utf-8編碼,解決方案為將數(shù)據(jù)庫編碼改為utf-8
set character_set_client = utf8;
set character_set_server = utf8;
set character_set_connection = utf8;
set character_set_database = utf8;
set character_set_results = utf8;
set collation_connection = utf8_general_ci;
set collation_database = utf8_general_ci;
set collation_server = utf8_general_ci;
但除此之外,還有兩個原因會導致這個錯誤的發(fā)生:
原因1:
tomcat編碼和數(shù)據(jù)庫編碼不一致,windows下tomcat默認編碼為gbk,若數(shù)據(jù)庫編碼為utf-8,會導致中文無法插入
解決方案:
Tomcat啟動參數(shù)中,VM Options 增加-Dfile.encoding=UTF-8。
原因2:
數(shù)據(jù)庫字段編碼utf8_general_ci,這種編碼僅支持3字節(jié)utf-9字符,插入的內(nèi)容中含特殊符號及表情等四字節(jié)utf-8字符
解決方案:
show full columns from {table} ? ? 查看字段編碼,將需要插入表情的字符串編碼改為utf8mb4_unicode_ci
語句范例
ALTER TABLE tb_case MODIFY COLUMN content VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE tb_case MODIFY COLUMN LAST_CHAT_CONTENT VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE tb_chat MODIFY COLUMN content VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE ofoffline MODIFY COLUMN stanza TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
借著這個機會,復習了編碼的相關知識,在這里分享一下心得。
首先介紹一下遇到這個問題的場景和解決過程,某日在排查錯誤日志時發(fā)現(xiàn),一張表的device_name字段有出現(xiàn)上述Incorrect string value的錯誤,將代碼部署在本地進行調(diào)試時,中文和表情的錯誤率卻高達100%,同樣的中文,在本地報錯的,在測試環(huán)境和生產(chǎn)環(huán)境正常。
于是排查本地環(huán)境和測試環(huán)境的區(qū)別,首先想到編碼問題,檢查確認了IDE編碼為utf-8,數(shù)據(jù)庫的編碼為utf-8
???這是什么情況,于是嘗試將\xC9\xE8\xB1\xB8?轉(zhuǎn)換成中文是否正確,結(jié)果發(fā)現(xiàn)無法使用utf-8編碼。難道這是gbk編碼,嘗試了一下發(fā)現(xiàn)果然使用gbk編碼可以轉(zhuǎn)換為中文,但我IDEA的編碼已經(jīng)全部改成utf-8了啊0 0?網(wǎng)上一搜,發(fā)現(xiàn)是tomcat編碼的問題。那生產(chǎn)環(huán)境的編碼錯誤又是什么原因呢,\xF0\x9F\x99\x82通過utf-8解碼得到一個emoji表情,查看device_name字段編碼為utf8_general_ci,不支持4字節(jié)utf8,將其改為utf8mb4_unicode_ci后問題解決。
然后對問題中的相關概念做了個總結(jié)
1.字節(jié)和字符
眾所周知,計算機世界是個二進制的世界,1字節(jié)(byte)= 8比特bit。我們看到的中文和表情是字符通過規(guī)定的編碼方式轉(zhuǎn)換為幾個字節(jié)后存儲的。
2.?\xC9\xE8\xB1\xB8 的含義
對于這樣的字符串,其實是用兩個十六進制數(shù)字來表示8bit即一個字節(jié),是屬于編碼后的字節(jié)串(字節(jié)數(shù)組),從這個串中只能得出這是4個字節(jié),無法得知其編碼方式和代表的中文或表情。
3.編碼和解碼
中文和表情的傳輸是一個編碼和解碼的過程。假設設定編碼為utf-8,在傳輸“設備”這個中文詞時,實際上傳輸?shù)臅r這個詞編碼后的四個字節(jié)
\xC9\xE8 設 ?\xB1\xB8備(16進制表示,\xC9轉(zhuǎn)換為2進制是11001001,一個字節(jié))。若發(fā)送端和接收端編碼不同,若接收端(如數(shù)據(jù)庫)嘗試以gbk對?\xC9\xE8 進行解碼,則無法獲得正確的字符報錯。
4.IDE編碼、數(shù)據(jù)庫編碼和Tomcat編碼
IDE編碼和Tomcat編碼不等同,需要保持一致,避免異常。IDE編碼決定java源碼的編碼,與數(shù)據(jù)的編碼不一致會導致插入數(shù)據(jù)時編碼異常
5.utf8和utf8mb4
utf8 是 Mysql 中的一種字符集,只支持最長三個字節(jié)的 UTF-8字符,utf8mb4支持4個字節(jié),對于部分生僻字和表情,應該使用utf8mb4