首页 > 代码库 > 动态代理在WEB与JDBC开发中的应用(JDBC篇)
动态代理在WEB与JDBC开发中的应用(JDBC篇)
背景描述
如果之前看过《动态代理在WEB与JDBC开发中的应用(WEB篇)》,这篇的内容可以全当是另一种应用的进阶举例,而在实现上确实没有太多进步的地方。我们先看一下项目所面临问题以及期望解决方案。在作者所接触的这个项目中,直接使用原始JDBC技术,java.sql.PreparedStatement和java.sql.ResultSet几乎占领了数据访问层,没有半点OR Mapping的迹象,看起来是不是很悲催?命啊~在项目开发至一半的时候,突然发现要对日文字符进行支持,而在之前一直使用英文进行测试。好了,问题来了,一个编码为8859_1的数据库怎么来接收Java中的UTF-8呢?并且很多CRUD功能已经完成,每个实现中涉及的字段数目相当之大,难道针对每个字段逐一调整不成?
问题分析
针对上面描述的情况,我们先做一个简单的分析。目前所面临的问题可以归为两种类型,第一类就是乱码的问题,这个可以通过重新编码得到解决;第二类则是面临大量的代码实现,如果对所有内容进行调整的话,很多功能就得重新测试,成本较高,但确实可行。
针对乱码问题,从实现的角度考虑,可以分别针对输入数据和输出数据进行编码转换。是在使用PreparedStatement的时候,对所有调用setString的地方进行编码转换;而对于数据读取的情况,可以对ResultSet.getString所返回的结果字符串进行编码转换。下面以UTF-8转8859_1为例子,实现编码转换(逆向转换只需交换编码名称的位置即可):
new String(new String("abc").getByte("UTF-8"), "8859_1")如果以这种方式对每处PreparedStatement.setString和ResultSet.getString的调用都进行编码转换操作的话,工作量不仅繁重,而且不利于代码的维护与移植。从API上看,其实我们可以针对setString和getString进行“HOOK”,使用代理模式比较适合,动态代理最为适宜。
解决方案
编码问题解决了,利用动态代理统一地对setString和getString接口进行自制,让所有的调用都在我们的控制之下还何愁不能统一江山!
public static ResultSet createResultSetJPWrapper(ResultSet rs) { return (ResultSet)(Proxy.newProxyInstance(ResultSet.class.getClassLoader(), new Class[] {ResultSet.class}, new ResultSetJPWrapper(rs))); } private static class ResultSetJPWrapper implements InvocationHandler { private ResultSet wrappedResultSet; public ResultSetJPWrapper(ResultSet rs) { wrappedResultSet = rs; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = method.invoke(wrappedResultSet, args); if ("getString".equals(method.getName()) && null != result) { result = CommonUtils.convertCharset((String)result, ISAConstants.CHARSET_8859_1, ISAConstants.CHARSET_UTF_8); } return result; } } public static PreparedStatement createPreparedStatementJPWrapper(PreparedStatement pstmt) { return (PreparedStatement)(Proxy.newProxyInstance(PreparedStatement.class.getClassLoader(), new Class[] {PreparedStatement.class}, new PreparedStatementJPWrapper(pstmt))); } private static class PreparedStatementJPWrapper implements InvocationHandler { private PreparedStatement wrappedStatement; public PreparedStatementJPWrapper(PreparedStatement pstmt) { wrappedStatement = pstmt; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("setString".equals(method.getName()) && args.length == 2 && null != args[1] && String.class.equals(args[1].getClass())) { args[1] = CommonUtils.convertCharset((String)args[1], ISAConstants.CHARSET_UTF_8, ISAConstants.CHARSET_8859_1); } return method.invoke(wrappedStatement, args); } }接下来就是在需要使用PreparedStatement和ResultSet的地方进行重构。
// 重构前 PreparedStatement stmt = conn.prepareStatement(sql); // 重构后 PreparedStatement stmt = DBUtils.createPreparedStatementJPWrapper(conn.prepareStatement(sql)); // 重构前 ResultSet rs = stmt.executeQuery(); // 重构后 ResultSet rs = DBUtils.createResultSetJPWrapper(stmt.executeQuery());
重构后不论是stmt还是rs,setString和getString都会被我们定义的动态代理所劫持,在数据输入前或数据输出后重新编码,而这一切对于开发人员来说一切都是透明的,并且不会对现有逻辑造成污染。
后记
这里谈论的只是动态代理应用案例,并不意味着必须使用动态代理是该案例中解决问题的唯一方案。其实动态代理在这里不一定是最佳的实现方案,如果可以的话最好对数据库进行重新设置修改默认编码,或者是在应用层统一编码,但这些往往都不适用于一个经过十多年、几十人、且风格各异且“烂”的发臭的项目了。其实实际的数据库VARCHAR类型中存放的字符串编码五花八门,EUC-JP/SHIFTJIS/UTF-8/8859_1,并且这些编码可能同时存在于一张表,要了命了!
动态代理在WEB与JDBC开发中的应用(JDBC篇)