Mongodb是一个高性能,开源,无模式的文档型数据库,在网上的介绍已经有很多,就不多说了。官方网址:http://www.mongodb.org/display/DOCS/Home。在某些对事务处理的要求不严格的项目中,可以考虑用它替代关系型数据库,好处是能够解决甚至是大型关系数据库都难以解决的性能问题。当然,也不是所有项目都适合。
Helma是一个用来开发快速,稳定Web应用程序的开源框架。它使用JavaScript 来作为服务端脚本环境,从而可以除去编译周期。官方网址:http://helma.org/。
那么这两者为什么要结合到一起呢?在学习Mongodb的实践中,我发现虽然现在的java框架有很多种,但是大都和关系型数据库联系紧密,针对nosql数据库的没有(很大可能是我没找到,毕竟java框架太多了)。Mongodb的特点是用二进制的JSON存储数据,在使用方式上有明显的脚本语言的特点,他的控制台管理工具,就是利用javascript语言进行管理。而Helma使用的也是javascript,这就是共同点。
Helma以前我也曾经关注过,印象很深刻,他轻量,快速,稳定,独立性强。有着完美的拿来主义--可以及其简便的使用任何java类扩充功能,这就使他无所不能,这一点对我来说及其看重,我不想因为用上了某种框架,当用户要增加某项功能时,急得抓耳挠腮。当然,也有不好的,他内置一个XML数据库,通过配置文件可以实现ORM的映射。但是XML数据库的功能很弱,不堪大用。当然也可以使用任何一个支持jdbc连接的数据库,但是他的ORM映射又很简陋,远不如ruby on rails的灵活,处理多表关联很麻烦。你还可以直接使用SQL,抛弃ORM。但是经过jdbc封装的面向对象的方式和javascript写在一起总觉得别扭。
现在,和mongodb做搭档情况就不同了,劣势转化成优势,java的强大,脚本语言的灵活可以完美结合,同时还不用担心部署的问题和运行效率,这是我迄今遇到的最好的一个解决方案。下面就以一个小例子进一步说明。这是一个简单的电话号码本的增删查改。
首先是Global下的一个init.js,这里主要定义操作Mongodb的方法。
// mongodb functional function getDb() { var conn = new com.mongodb.Mongo("localhost", 27017); var db = conn.getDB("test"); return db; } function getCollection(table) { return getDb().getCollection(table); } function getDBObject() { return new com.mongodb.BasicDBObject(); } function getDBObjectInit(key, val) { return new com.mongodb.BasicDBObject(key, val); } function getObjectId(id) { return new com.mongodb.ObjectId(id); } function jsonToDBObject(jsonData) { return com.mongodb.util.JSON.parse(jsonData); } function trim(source) { if (source == null) return ''; return source.replace(/^\s+|\s+$/g, "") }
从这些函数,我们不难看出在Helma中使用java类是多么的简单,甚至比在java中使用他们还要简单。
下面是first.hac,增删查改的逻辑都在其中。
var coll = getCollection('name_tel'); // add var add_name = trim(req.data.add_name); var add_tel = trim(req.data.add_tel); if (add_name != '') { var addName = getDBObject(); addName.put('name', add_name); addName.put('telephon', add_tel); coll.insert(addName); } // delete if (req.data.id != null) { coll.remove(getDBObjectInit('_id', getObjectId(req.data.id))); } // update if (req.data.upd_id != null) { var updName = coll.findOne(getDBObjectInit('_id', getObjectId(req.data.upd_id))); updName.put('telephon', trim(req.data.upd_tel)) coll.save(updName); } // select var curSelect = coll.find(); res.data.trRow = ''; while(curSelect.hasNext()){ var cur = curSelect.next(); res.data.trRow += '\ <tr>\ <td>' + cur.get('name') + '</td>\ <td>' + cur.get('telephon') + '</td>\ <td><a href="second?id=' + cur.get('_id') + '">修改</a></td>\ <td><a href="first?id=' + cur.get('_id') + '">删除</a></td>\ </tr>'; } res.data.pushDate = new Date(); res.data.body = this.renderSkinAsString('first'); this.renderSkin('page');
下面是page.skin,可以在其中设置共同的外观,菜单之类的东西。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>my test</title> </head> <body> <div> <img src="/static/helmaheader.png"/> </div> <% response.body %> </body> </html>
下面是first.skin,画面中的主要内容
<script type="text/javascript"> function trim(source) { return source.replace(/^\s+|\s+$/g, "") } function addItem() { var form = document.form1; if (trim(form.add_name.value) == '') { alert('姓名得有'); return; } form.action = 'first'; form.submit(); } </script> <form name='form1' method="post"> <p><% response.pushDate %></p> <table> <tr> <th>姓名</th> <th>电话</th> <th></th> <th></th> </tr> <% response.trRow %> </table> <hr/> <span>姓名:</span> <input type="text" name="add_name"> <span>电话:</span> <input type="text" name="add_tel"> <input type="button" value='添加' onClick="addItem();"> </form>
下面是second.hac要跳转到的更新画面的逻辑
var coll = getCollection('name_tel'); var select_name = coll.findOne(getDBObjectInit('_id', getObjectId(req.data.id))); res.data.pushId = select_name.get('_id'); res.data.pushName = select_name.get('name'); res.data.pushTel = select_name.get('telephon'); res.data.body = this.renderSkinAsString('second'); this.renderSkin('page');
下面是second.skin,更新画面中的内容。
<script type="text/javascript"> function updItem() { document.form1.action = 'first'; document.form1.submit(); } </script> <form name='form1' method="post"> <p> <span>姓名:</span> <span><% response.pushName %></span> </p> <p> <span>电话:</span> <input type="text" name="upd_tel" value='<% response.pushTel %>'> </p> <input type="button" value="修改" onClick="updItem();"> <a href="first">返回</a> <input type="hidden" name="upd_id" value='<% response.pushId %>'> </form>
好了,就这么多了,是不是很简单?在这个框架下开发,几乎和在ror下开发一样的舒服。还有一点是ror所没有的,就是Helma有一个很棒的调试环境,可以单步跟踪,察看变量内容,运行代码,虽然简单,但真的很方便。
前面说了那么多优点,局限性也要说说。
首先是来自Mongodb的,他没有事务,操作也不是原子的,在某些十分重要的场合,例如金融行业,是不能用;Mongodb太过灵活,不像SQL,如果表名,字段名不对是会报错的,在测试中很容易就会发现,Mongodb的理念是不存在,我就给你建一个,所以要格外小心,别写错了。再说说Helma,最大的遗憾我觉得是skin里接收的只能是字符串类型的数据,有些复杂的显示逻辑不得不转移到hac中生成html片断返回,虽然不影响功能,但是毕竟显示层的东西写在两个地方看起来不太舒服。其他的,虽然没有用到ORM,但我觉得不是问题,只要我喜欢这种方式。老学究可能会不高兴,但是我觉得使用框架是要给我们带来方便,而不是束缚我们的枷锁。