这篇文章源自刚开发的一个小项目。项目中并未使用hibernate ,但我还是要把它放在hibernate 栏下。理由很简单,技术是死的,而人是活的,能熟练的使用一项技术不算什么,但能恰当的选择相应的技术,甚至自己想出办法来优雅的解决实际问题,就需要一定的积累了。而这种积累就来源自项目实践和对各种技术其实质的理解。我记得在某个论坛上某人(名字忘了)说过一句话:如果学习hibernate 只是学会了怎么mapping ,怎么写DAO ,就只是学了皮毛,学hibernate 就要从中了解到很多持久层的最佳实践。我深以为然!
项目很简单,页面也不多,但页面字段较多(100 多个),相互间还有一定关联。而且存入数据库类型多样,有varchar, integer 和date 几种。我是很希望用hibernate 来实现的,但考虑到项目时间较紧,而我对hibernate 的了解还是停留在理论和学习阶段(有点落后啊!),采用不熟悉的技术项目风险较大,所以还是使用普通JDBC 作为持久层方案。面对这么多字段,一个个去拼SQL 语句,代码臃肿,而且容易出错,也难以维护。我得对这几种类型的字段,在插入、更新和读取时分别处理,写一个private 方法,传入类型和字段名,读取相应的ResultSet ,是不错的方法,至少是比较优雅的实现。
什么是优雅?动态设置,避免hardcode ,就是优雅;层次清晰,层次间耦合最低,就是优雅;只写一处,处处引用,就是优雅;代码精炼,避免过度设计,就是优雅;接口明确,调用简单,就是优雅;调试容易,便于测试,就是优雅。。。。。。而优雅的设计和实现,在可扩展性、可维护性、开发效率、开发成本等方面都是最好的。
Hibernate 就是优雅的设计,它通过配置文件,建立实体与数据库的映射,动态的生成SQL 语句,避免了对属性字段的hardcode ,这就是它最本质的思想。我不使用hibernate ,但一样可以借鉴它的思想,我不需要对象容器、分页查询等等高级功能,因此可以简单的实现类似ORM 的功能。
首先,定义一个配置类,将数据库字段和类型定义下来。按照常规的做法,从页面字段到对象属性到数据库都应该建立映射,这样需要生成相应的映射类。为简单起见,我不使用POJO ,而是使用Map 作为数据的载体,key 就是数据库字段名,在页面端我也用数据库字段名作为域的名称。这样我直接通过名称建立映射,牺牲了扩展性和灵活性,但简化了操作,也无需映射类,只要一个映射Map ,key 是数据库字段名,value 是我自己定义的字段类型。以后动态生成SQL 和Map 传输对象,以及从页面request 动态生成Map 都是基于这个配置。
接下来,就很简单了。先处理DAO 的动态生成SQL 代码,下面是生成insertSQL 的方法,
private
static
String generateInsertScoreSQL(Map m, String studentId)
{ StringBuffer sb = new StringBuffer(); StringBuffer sb2 = new StringBuffer(); sb.append( " insert into STUDENT_SCORE (id, " ); sb2.append( " values(' " ); sb2.append(studentId); sb2.append( " ', " ); for (Iterator iter = m.keySet().iterator(); iter.hasNext();) { String name = (String) iter.next(); String value = (String) m.get(name); sb.append(name); sb.append( " , " ); String type = (String) ScoreColumnMapping.scoreItemMap().get(name); if (ScoreColumnMapping.INT.equalsIgnoreCase(type)) { if (value == null || value.length() == 0 ) { sb2.append( " null, " ); } else { sb2.append(Integer.parseInt(value)); sb2.append( " , " ); } } else if (ScoreColumnMapping.STRING.equalsIgnoreCase(type)) { sb2.append( " ' " ); sb2.append(value); sb2.append( " ', " ); } } sb.replace(sb.length() - 1 , sb.length(), " ) " ); sb2.replace(sb2.length() - 1 , sb2.length(), " ) " ); log.info(sb.toString() + sb2.toString()); return sb.toString() + sb2.toString(); }
生成updateSQL 的代码:
private
static
String generateUpdateScoreSQL(Map m, String studentId)
{ StringBuffer sb = new StringBuffer(); sb.append( " update STUDENT_SCORE set " ); for (Iterator iter = m.keySet().iterator(); iter.hasNext();) { String name = (String) iter.next(); String value = (String) m.get(name); sb.append(name); sb.append( " = " ); String type = (String) ScoreColumnMapping.scoreItemMap().get(name); if (ScoreColumnMapping.INT.equalsIgnoreCase(type)) { if (value == null || value.length() == 0 ) { sb.append( " null, " ); } else { sb.append(Integer.parseInt(value)); sb.append( " , " ); } } else if (ScoreColumnMapping.STRING.equalsIgnoreCase(type)) { sb.append( " ' " ); sb.append(value); sb.append( " ', " ); } } sb.replace(sb.length() - 1 , sb.length(), "" ); sb.append( " where id=' " ); sb.append(studentId); sb.append( " ' " ); log.info(sb.toString()); return sb.toString(); }
从ResultSet 中生成Map 对象的代码:
private
static
void
getScoreFromRs(ResultSet rs, Map m)
throws
SQLException
{ String name; String type; for (Iterator iter = ScoreColumnMapping.scoreItemMap().keySet() .iterator(); iter.hasNext();) { name = (String) iter.next(); type = (String) ScoreColumnMapping.scoreItemMap().get(name); if (ScoreColumnMapping.INT.equalsIgnoreCase(type)) { Object value = rs.getObject(name); m.put(name, String.valueOf(value == null ? "" : value)); } else if (ScoreColumnMapping.STRING.equalsIgnoreCase(type)) { String value = rs.getString(name); m.put(name, value); } } }
因为只有一个DAO 采用这种方式,所以我用private 方法,这可以通过重构,将其抽取到Util 类中,供所有DAO 使用。页面端也很简单,我做个Façade 类,它获取request ,并将其处理成一个Map ,然后交给数据层处理(因为比较简单,省去了业务层),代码如下:
<o:p>
Map m
=
new
HashMap();
for
(Iterator iter
=
ScoreColumnMapping.scoreItemMap().keySet() .iterator(); iter.hasNext();)
{ String name = (String) iter.next(); String value = request.getParameter(name); …… m.put(name, value); ……
</o:p>
所有方法中未出现一个字段名称,全部是从配置类中动态生成。这样带来了很多好处:
u 扩展容易,如果需增加字段,无需更改核心代码,只要修改配置文件和数据库表定义,然后页面上加加域