分类,分组在Web应用中到处可见,理想的原型设计,总是会遇到各种各样的新的更新需求,而来自客户的需求变更(客户简单的一句加一个下级分类),可能导致的是项目重大重构。为什么我们不从一开始就把分类做成树形,可无限扩展呢?
最简单的树形分类扩展其实很好实现,一个记录中加一个parent字段指向它的父结点就行,但这样做有很多问题实现起来很复杂,有的甚至需要用到递归来实现,对编程和数据库压力都很大,如:
1. 一个节点下有多少个子孙结点(注意是子孙)?
2. 从根到本节点的路径如何?
很幸运的是,如何你不是野路子出身,则一定学过数据结构。就应该记得在树的那一章中一个概念称做【穿线树】,它特别适应我们WEB中分类、分组这样可能出现无限层级,读取多而插入少的应用场景。结合穿线树的概念,我们改造一下,在数据训中实现无限级树形分类。
1. 树的结构
图中节点: 字母为节点内容,第一个数字为节点ID,第二个数字为节点父ID,通过父ID的指向,我们可以构造出无限分级的树。
2. 树的穿线
对上面已生成的树,我们给各个节点加上LFT和RGT属性,其取值规则为:
1. LFT为自己的前一个兄弟的RGT+1,如果自己是长子,则为父节点LFT+1
2. RGT为自己的最小的儿子的RGT+1,如果没有子结点,则为自己的LFT+1
穿完线后的树样子如下(节点后面新增的数字分别为LFT和RGT,原来的ID,父ID用灰色以免干扰),注意,各节点的LFT,RGT与节点的ID,父ID没有任何关系!
3. 树的访问:将上述树的各个节点保存到数据库中,之后可以:
1) 选出所有叶节点(即没有子结点)
select * from tree where RGT=LFT+1
2) 获取本节点的所有子节点
select * from tree where pid=me.id
3) 获取本节点所有子孙节点
select * from tree where LET>me.LFT and RGT 4) 获取本节点的直系祖先(到根的路径) select * from tree where LFT 5)获取我的所有兄弟们(从大到小) select * from tree where pid=m.pid order by LFT 6)获取我的哥哥(我前一个节点),弟弟同理 select * from tree where pid=me.pid and GRT=me.LFT-1 4. 树的增加、删除与移动, 穿线树利于查询,无限级都可以用简单的SQL语句来实现,但为此付出的代价是新增、删除和移动节点相当麻烦。直接上数据库结构与JFinal代码如下: 1)数据库结构 欢迎同行批评指正
2) 新增、删除、移动
CREATE TABLE category(
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(200) NOT NULL,
father INT DEFAULT '1',
lft INT,
rgt INT,
moving INT DEFAULT '0' NOT NULL,
PRIMARY KEY (id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
上述代码是与jsTree进行树的展示和编辑
public void jsTreeCreate() {
int rgt = Db.queryInt("select rgt from category where id=?", getParaToInt("id"));
Db.update("update category set rgt=rgt+2 where rgt>=?", rgt);
Db.update("update category set lft=lft+2 where lft>=?", rgt);
new Category().set("father", getParaToInt("id")).set("name", getPara("text")).set("lft", rgt).set("rgt", rgt + 1).save();
final int id = Db.queryInt("select max(id) from category");
CacheKit.removeAll("Memory1000");
renderJson(new HashMap() {{
put("id", id);
}});
}
public void jsTreeDelete() {
Record record = Db.findFirst("select lft,rgt from category where id=?", getParaToInt("id"));
int lft = record.getInt("lft");
int rgt = record.getInt("rgt");
Db.update("delete from category where lft>=? and rgt<=?", lft, rgt);
Db.update("update category set lft=lft-? where lft>?", rgt - lft + 1, rgt);
Db.update("update category set rgt=rgt-? where rgt>?", rgt - lft + 1, rgt);
CacheKit.removeAll("Memory1000");
renderText("ok");
}
public void jsTreeMove() {
//参数:jsTreeMove?id=76&parent=72&position=0
// 1.标记
Record record = Db.findFirst("select lft,rgt from category where id=?", getParaToInt("id"));
int srcLeft = record.getInt("lft");
int srcRight = record.getInt("rgt");
Db.update("update category set moving=1 where lft>=? and rgt<=?", srcLeft, srcRight);
// 2.逻辑删除
Db.update("update category set lft=lft-? where lft>?", srcRight - srcLeft + 1, srcRight);
Db.update("update category set rgt=rgt-? where rgt>?", srcRight - srcLeft + 1, srcRight);
// 3.找到插入点
Category father = Category.dao.findById(getParaToInt("parent"));
int dstLeft = father.getInt("lft") + 1;
List