数据库字符国际化是大家提问最多的问题,例如mysql数据库大家可能在JDBC-URL添加useUnicode=true&CharacterEncoding=GBK作为中文支持的基本条件。但这有时破坏了数据的完整性,假如某些人粗心大意,就会导致数据编码错误,产生乱码。因此,我们需要一些手段在程序内部进行编码处理。人们一般通过在应用上使用 String(bytes:byte[], enc:String)/String.getBytes(enc:String)进行字符串编解码,这样做虽然易懂,但是假如碰到大字段表格,手动编码时费时费力。
我的方法:通过研究JDK类库,可以感觉到多层处理机制在数据处理上的优越性。我们完全有可能在数据库上建立一个中间层用于字符的国际化处理,我就是这么做的。仔细研究一下JDBC操作数据库出现字符编码问题的根源,很轻易发现多数情况是ResultSet的几个String方法在作怪,因此我们就完全可以编写一个ResultSet中间层进行国际化处理,源码如下:
public class I18nResultSet implements ResultSet{
private String encoding;
private ResultSet rs;
public I18nResultSet(ResultSet rs, String encoding) throws Java.io.UnsupportedEncodingException{
//检查该编码名称是否被系统支持。
"".getBytes(encoding);
this.rs = rs;
this.encoding = encoding;
}
… …
//以下几个方法是进行String字符串的重编码.
public String getString(int index) throws SQLException{
String data = null;
try{
data = new String(rs.getBytes(index), encoding);
}catch(java.io.UnsupportedEncodingException uee){}
}
public String getString(Stirng field) throws SQLException{
String data = null;
try{
data = new String(rs.getBytes(field), encoding);
}catch(java.io.UnsupportedEncodingException uee){}
}
public void updateString(int index, String value) throws SQLException{
try{
rs.updateBytes(index, value.getBytes(encoding));
}catch(java.io.UnsupportedEncodingException uee){}
}
public void updateString(String field, String value) throws SQLException{
try{
rs.updateBytes(field, value.getBytes(encoding));
}catch(java.io.UnsupportedEncodingException uee){}
}
… …
}
可以看出, 所有的String操作都使用特定编码的字节数组进行存取,这样通过定义encoding的值实现数据库存取数据编码的一致性,且encoding完全可以通过在配置信息中动态定义。
同时,上面的程序又可以解决一些固有的字符串处理问题,例如控制符如\r\n导入到数据库中很有可能被解析为\\r\\n使其不能换行,通过字节数组操作,就可以解决这个问题。这样像文章固有格式就可以完整地保留下来而不需要进行额外转换操作。
结论,通过多层处理机制使用中间层对数据库数据进行层层处理可使处理环节之间形成松耦合关系,从而可以进行有效的控制。
下面给一个使用动态代理进行字符控制的代码(原创):
package com.yipsilon.crocodile.database;
import java.sql.ResultSet;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.io.UnsupportedEncodingException;
/**
* 作者 yipsilon
* 如要转载, 请通知作者
*/
public class I18nResultSetHandler implements InvocationHandler{
private ResultSet rs;
private String encoding;
public I18nResultSetHandler(ResultSet rs, String encoding) throws UnsupportedEncodingException{
this.rs = rs;
"".getBytes(encoding);
this.encoding = encoding;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable{
String methodName = method.getName();
if(methodName.equals("getString")){
Object obj = args[0];
if(obj instanceof Integer){
return decodeString(rs.getBytes(((Integer)obj).intValue()), encoding);
}else{
return decodeString(rs.getBytes((String)obj), encoding);
}
}else if(methodName.equals("updateString")){
Object obj = args[0];
if(obj instanceof Integer){
rs.updateBytes(((Integer)obj).intValue(), encodeString((String)args[1], encoding));
}else{
rs.updateBytes((String)obj, encodeString((String)args[1], encoding));
}
return null;
}
return method.invoke(rs, args);
}
private String decodeString(byte[] bytes, String enc){
try{
return new String(bytes, enc);
} catch(UnsupportedEncodingException uee){
return new String(bytes);
}
}
private byte[] encodeString(String str, String enc){
try{
return str.getBytes(enc);
} catch(UnsupportedEncodingException uee){
return str.getBytes();
}
}
}
使用时调用:
ResultSet rs = ... ; //原始的ResultSet结果集
String encoding = "GBK"; //字符编码
(ResultSet)Proxy.newProxyInstance(rs.getClass().getClassLoader(),
rs.getClass().getInterfaces(),
new I18nResultSetHandler(rs, encoding));