MySQL插入emoji表情失敗問題的解決方法
前言
之前一直認為UTF-8是萬能的字符集問題解決方案,直到最近遇到這個問題。最近在做新浪微博的爬蟲, 在存庫的時候發現只要保持emoji表情,就回拋出以下異常:
Incorrect string value: ‘\xF0\x90\x8D\x83\xF0\x90...‘
眾所周知UTF-8是3個字節, 其中已經包括我們日常能見過的絕大多數字體. 但3個字節遠遠不夠容納所有的文字, 所以便有了utf8mb4, utf8mb4是utf8的超集, 占4個字節, 向下兼容utf8. 我們日常用的emoji表情就是4個字節了.
所以在此我們像utf8的數據表插入數據就會報出Incorrect string value
Google一下很容易就找到了解決方案, 具體解決辦法是如下:
一、修改數據表的字符集為utf8mb4
這點很簡單, 修改語句網上找一大堆, 不過建議重新建表, 使用 mysqldump -uusername -ppassword database_name table_name > table.sql
備份相應數據表, 並修改其中的建表語句的字符集為 utf8mb4 即可, 然後 mysql -uusername -ppassword database_name < table.sql
重新導入sql即可完成修改字符集操作.
二、MySQL數據庫版本要5.5.3及以上
網絡上所有的文章都說明要MySQL 5.5.3以上的版本才支持utf8mb4, 不過我使用的數據庫版本為5.5.18, 最終仍能解決問題, 所以同學們不要急著找運維哥哥升級數據庫先, 先試試能不能自己解決問題.
三、修改數據庫配置文件/etc/my.cnf並重啟mysql服務
主要是修改數據庫的默認字符集, 以及連接, 查詢的字符集, [Mysql支持emoji 表情符號 升級編碼為UTF8MB4][1] 這篇文章有詳細的設置方法, [深入Mysql字符集設置][2] 這篇文章有其中設置的各個字符集的作用, 大家可以科普下.
四、升級MySQL Connector到5.1.21及以上
以上所有的操作, 最關鍵的是步驟3, 修改數據庫的配置文件, 其中大概修改了
[client] # 客戶端來源數據的默認字符集 default-character-set = utf8mb4 [mysqld] # 服務端默認字符集 character-set-server=utf8mb4 # 連接層默認字符集 collation-server=utf8mb4_unicode_ci [mysql] # 數據庫默認字符集 default-character-set = utf8mb4
這些配置指定了數據從客戶端到服務端所經過的一條條管道使用的字符集, 其中每一個管道出現問題都可能會導致插入失敗或者亂碼.
但很多時候, 線上的數據庫是不能隨便修改數據庫文件的, 所以我們的運維同學很果斷的回絕了我修改數據庫配置文件的請求(T_T)
所以就只能用代碼解決了, 一開始是準備從JDBC連接時候就指定使用的字符集處下手.
jdbc:mysql://localhost:3306/ding?characterEncoding=UTF-8
主要把UTF-8修改為utf8mb4對於的Java Style Charset字符串應該就能解決問題吧?
不過很遺憾的是, Java JDBC並不存在utf8mb4對於的字符集. 使用UTF-8的時候可以兼容urf8mb4並自動轉換字符集.
For example, to use 4-byte UTF-8 character sets with Connector/J, configure the MySQL server with character_set_server=utf8mb4, and leave characterEncoding out of the Connector/J connection string. Connector/J will then autodetect the UTF-8 setting. – [MySQL:Using Character Sets and Unicode][3]
後來科普了一下, 在每一次查詢請求的時候, 可以顯式的指定使用的字符集, 使用 set names utf8mb4
可以指定本次鏈接的字符集為utf8mb4, 但這個設置在每次連接被釋放後都會失效.
目前的解決辦法是, 在需要插入utf8mb4的時候, 顯示地調用執行set names utf8mb4
, 如:
jdbcTemplate.execute("set names utf8mb4"); jdbcTempalte.execute("...");
需要註意的是, 我們在使用一下ORM框架的時候, 因為性能優化原因, 框架會延遲提交, 除非事務結束或者用戶主動調用強制提交, 負責執行的set names utf8mb4
仍然不會生效.
在這裏我使用的是myBatis, 以MessageDao為例
// MessageDao public interface MessageDao { @Update("set names utf8mb4") public void setCharsetToUtf8mb4(); @Insert("insert into tb_message ......") public void insert(Message msg); } // test code SqlSession sqlSession = sqlSessioFactory.openSession(); messageDao = sqlSession.getMapper(MessageDao.class); messageDao.setCharsetToUtf8mb4(); // 強制提交 sqlSession.commit(); messageDao.insert(message);
至此, 問題便解決了..
哎, 如果世事能那麽順利就好了, 在項目中, mybatis是實例是交由Spring去管理的, 也就是說我拿不到sqlSession, 也就是強制提交不了. 並且因為Spring事務框架的限制, 他並不允許用戶顯式調用強制提交. 目前還在糾結這個問題.
有兩個解決思路:
- 使用AOP, 在可能插入4字節UTF8字符的時候, 前置方法執行
set names utf8mb4
, 但該方案還不能確定AOP的方法會被Spring進行事務管理麽, 並且在前置方法中,拿到的鏈接是否和接下來拿到的連接對象是同一個session. - 研究Spring JDBC的創建方法, 寫一個hook在每次創建新的數據庫連接的時候, 都執行一次
set names utf8mb4
, 這樣就保證每一次拿到的鏈接都是設置過字符集的.
總結
以上就是這篇文章的全部內容了,待有時間再實驗一下以上兩種方案。希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
MySQL插入emoji表情失敗問題的解決方法