mysql的文本(varchar,
text),对emoji表情符号不是很好的支持,在5.5之前的版本,varchar和text都是不支持存储emoji表情符号的(即使是utf8)的编码模式。原因在于mysql的utf8是规定了每一个utf8字符按照3个字节来存储,而一个emoji(最初来自苹果系统,现在流行于各种移动操作系统)却需要4个字节来存储。这就导致了如果强制将emoji存储到varchar,text等字段上的时候,mysql会抛出异常,认为emoji是个“不正确”的文本。
ERROR 1366 (HY000): Incorrect string value:
‘\xF0\x9F\x91\xBD\xF0\x9F…’ for column ‘name’ at row 31
所幸,mysql在5.5之后的版本,针对四个字节的utf8字符推出了一种新的兼容的编码,叫
utf8mb4。utf8mb4比utf8支持的字符集更广,可以支持utf8以及四个字节的字符集,关于utf8mb4和utf8的区别可以这篇官方文档
简而言之就是:“utf8mb4
is a superset of
utf8” ,utf8mb4是utf8的超集,utf8是utf8mb4的子集。utf8mb4理论上是兼容utf8.
所以如果你的项目需要支持存储emoji表情,同时mysql的版本是5.5以上的版本,那么就可以把字段的charset改为
utf8mb4就可以完美支持emoji了。
那如果当前mysql版本不支持utf8mb4编码怎么办?
解决方法:
1. 升级mysql版本到5.5.3以上的 :)
2. 把需要支持emoji表情存储的字段改成 blob的。(这是针对mysql升级有限制的情况)
blob类型一般是用来存储二进制文件的,当时用来存储文本其实也是可以的,只不过存进去之前,把文本变成byte数据就可以了。已java为例,使用String.getBytes(charset)方法,可以把字符串转化成二进制,然后存储到数据库中。如果你有很多字段都要这么搞的话,估计都得疯了。怎么办?用orm框架~
已ibatis为例,如果你的对象字段是String文本,存储的字段确实blob,其实是没有关系的,不需要写特殊的代码,直接支持写入。但是读出来的时候就需要做转换,否则出来的是乱码。所以这里需要借助ibatis的typehandler和resultMap来解决这个问题。ORM框架的好处就是你不用一直重复劳动,可以在各种地方留着钩子(hook),随时让你在需要的时候可以插点东西到关键的地方上去。好了,废话不多说,看看这个typeHandler怎么实现:(这里是ibatis2.3.*的版本,如果是myBatis,可能报名和接口参数不太一样,但实现方式是一样的)
public class
BlobStringTypeHandler extends BaseTypeHandler { //charset private
static final String DEFAULT_CHARSET = "utf-8"; @Override public
void setParameter(PreparedStatement ps, int i, Object parameter,
String jdbcType) throws SQLException { ByteArrayInputStream bis;
String param = (String) parameter; try { //###把String转化成byte流 bis =
new ByteArrayInputStream(param.getBytes(DEFAULT_CHARSET)); } catch
(UnsupportedEncodingException e) { throw new RuntimeException("Blob
Encoding Error!"); } ps.setBinaryStream(i, bis, param.length()); }
@Override public Object getResult(ResultSet rs, String columnName)
throws SQLException { Blob blob = rs.getBlob(columnName); byte[]
returnValue = null; if (null != blob) { returnValue =
blob.getBytes(1, (int) blob.length()); } try { //###把byte转化成string
return new String(returnValue, DEFAULT_CHARSET); } catch
(UnsupportedEncodingException e) { throw new RuntimeException("Blob
Encoding Error!"); } } @Override public Object getResult(ResultSet
rs, int columnIndex) throws SQLException { Blob blob =
rs.getBlob(columnIndex); byte[] returnValue = null; if (null !=
blob) { returnValue = blob.getBytes(1, (int) blob.length()); } try
{ //###把byte转化成string return new String(returnValue,
DEFAULT_CHARSET); } catch (UnsupportedEncodingException e) { throw
new RuntimeException("Blob Encoding Error!"); } } @Override public
Object getResult(CallableStatement cs, int columnIndex) throws
SQLException { Blob blob = cs.getBlob(columnIndex); byte[]
returnValue = null; if (null != blob) { returnValue =
blob.getBytes(1, (int) blob.length()); } try { return new
String(returnValue, DEFAULT_CHARSET); } catch
(UnsupportedEncodingException e) { throw new RuntimeException("Blob
Encoding Error!"); } } @Override public Object valueOf(String s) {
try{ return s.getBytes(DEFAULT_CHARSET); }catch (Exception e){
return null; } } }
重点看几个getResult方法,就是从ResultSet中拿到blob字段数据(byte[]),然后把byte数组转化成string就OK了。
怎么使用?
在sqlMap文件定义resultmap, 对需要转换的字段指定这个typeHandler就可以了:
<resultMap
id=“EntityMap" class=“your.pack.Entity">
<result
property="name"
column="name"
jdbcType="BLOB"
javaType="java.lang.String"
typeHandler=“your.BlobStringTypeHandler">result>
resultMap>
注意select的statement语句返回使用resultMap指定这个resultMap
<select
id=“EntityDAO.getByXXX" parameterClass="java.util.Map"
resultMap="EntityMap">
select
name
from
your_table
where
...
limit
1
select>
OK,
mysql支持emoji了:)