之前在开发过程当中会涉及到一些层级结构访问的问题。这种层级结构往往是无限的,而且十分常见。如企业内部的员工分组。
常见的层级结构如下:
全国总行
-- 北京分行
-- 上海分行
-- 福建分行
-- 福州分行
-- 厦门分行
-- 思明区分行
-- 财政部
-- 开发部
-- 行政部
-- 张三
-- 李四
-- 广东分行
向上图的访问结构是非常常见的,而传统的算法会采用一下两种方式进行访问
毗邻目录算法:
即通过标识上一级目录来索引上一级,
传统的数据库结构会用如下的设计方式
id parentId name isLeaf
通过parentId既可以找到上级的目录。
而通过这种方式有一个最大的问题就是,当要遍历某个叶子节点的层级关系时,需要不断递归到根节点为止,这种算法只适合更新比较频繁,层级查询比较少的场景,所以就有了一下的算法
左右值无限分支算法:
通过增加左右值来标志某个层级
数据库会如下所示:
id name lft rgt 其中lft代表左值,rgt代表右值,每个节点的父节点的左右值将会包含子节点的左右值
如下:
1 全国总行 24
|
________________________________________________________________
| | | |
2 北京分行 3 4 上海分行 5 6 福建分行 21 22 广东分行 23
|
__________________________________
| |
7 福州分行 8 9 厦门分行 20
|
———————————————————————
| | |
10 财政部 11 12 开发部 13 14 行政部 19
|
—————————
| |
15 张三 16 17 李四 18
使用左右值方式可以很好的表示层级方式,如要获取李四的整个层级只要找到所有 左值小于17且右值大于18的节点即可
但是左右值算法的缺点在于更新的复杂,即每当子节点更新(添加,删除,移动)都会影响到很多的节点,所以这种算法使用于更新比较少,查询比较频繁的场景
所以综合实际的场景,需要把以上两种算法结合起来,对于部门等层级关系,采用左右值算法保存,因为这部分关系相对来讲更新到频率比较低,利用左右值算法可以很快的查询到整个层级关系,而对于具体的人员,采用毗邻目录算法来保存上级部分的关系。因为人员的更新比较频繁,更新人员只要更新parentId即可。
下面附上一些更新部门等级关系的语句
新加
Session session = this.getSession();
try {
session.beginTransaction().begin();
//实时从数据库中获取记录,保持与数据库中一致
UserGroup parentGroup = this.findById(transientInstance.getUserGroup().getId());
if(parentGroup==null) return;
transientInstance.setUserGroup(parentGroup);
//更新所有父节点的右值
String updateSql = "update UserGroup as Model set Model.right = Model.right + 2 where Model.right >= :right";
Query query1 = session.createQuery(updateSql);
query1.setParameter("right", parentGroup.getRight());
query1.executeUpdate();
//更新所有父节点的左值
updateSql = "update UserGroup as Model set Model.left = Model.left + 2 where Model.left >= :right";
Query query2 = session.createQuery(updateSql);
query2.setParameter("right", parentGroup.getRight());
query2.executeUpdate();
//设置新添加节点的的左值和右值
transientInstance.setLeft(parentGroup.getRight());
transientInstance.setRight(parentGroup.getRight() + 1);
transientInstance.setTempFlag(0);
session.save(transientInstance);
session.beginTransaction().commit();
session.close();
log.debug("save successful");
} catch (RuntimeException re) {
session.beginTransaction().rollback();
session.close();
log.error("save failed", re);
throw re;
}
删除
Session session = this.getSession();
try
{
session.beginTransaction().begin();
//实时从数据库中获取记录,保持与数据库中一致
UserGroup newGroup = this.findById(persistentInstance.getId());
if(newGroup==null) return;
Integer left = newGroup.getLeft();
Integer right = newGroup.getRight();
session.delete(persistentInstance);
//更新所有父节点的右值
String updateSql = "update UserGroup as Model set Model.right = Model.right - (:right - :left + 1) where Model.right > :right";
Query query1 = session.createQuery(updateSql);
query1.setParameter("right", right);
query1.setParameter("left", left);
query1.executeUpdate();
//更新所有父节点的左值
updateSql = "update UserGroup as Model set Model.left = Model.left - (:right - :left + 1) where Model.left >= :left";
Query query2 = session.createQuery(updateSql);
query2.setParameter("right", right);
query2.setParameter("left", left);
query2.executeUpdate();
session.beginTransaction().commit();
session.close();
log.debug("delete successful");
} catch (RuntimeException re) {
session.beginTransaction().rollback();
session.close();
log.error("delete failed", re);
throw re;
}
移动
try
{
session.beginTransaction().begin();
Integer left = instance.getLeft();
Integer right = instance.getRight();
//为移动节点及所有子节点的设置一个标记
String updateSql = "update UserGroup as Model set Model.tempFlag = 1 where Model.left >= :left and Model.right <= :right";
Query query1 = session.createQuery(updateSql);
query1.setParameter("right", right);
query1.setParameter("left", left);
query1.executeUpdate();
//【按删除该节点的方式更新左右值,除删除节点和子节点除外】
//更新所有父节点的右值
updateSql = "updateUserGroup as Model set Model.right = Model.right - (:right - :left + 1) where Model.right > :right and Model.tempFlag != 1";
Query query2 = session.createQuery(updateSql);
query2.setParameter("right", right);
query2.setParameter("left", left);
query2.executeUpdate();
//更新所有父节点的左值
updateSql = "update UserGroup as Model set Model.left = Model.left - (:right - :left + 1) where Model.left >= :left and Model.tempFlag != 1";
Query query3 = session.createQuery(updateSql);
query3.setParameter("right", right);
query3.setParameter("left", left);
query3.executeUpdate();
//重新获取目标父亲节点的左右值
String querySql = "from UserGroup as Model where Model.id = :groupId";
Query query4 = session.createQuery(querySql);
query4.setParameter("groupId", instance.getUserGroup().getId());
List listTemp = query4.list();
if(listTemp.size() == 0 || listTemp.size() > 1)
{
throw new RuntimeException("target group with id :" + instance.getUserGroup().getId() + " is not exist in database.");
}
UserGroup targetGroup = (UserGroup) listTemp.get(0);
Integer targetGroupRight = targetGroup.getRight();
Integer targetGroupLeft = targetGroup.getLeft();
//【按增加节点的方式更新左右值,除删除节点和子节点除外】
//更新所有父节点的右值
updateSql = "update UserGroup as Model set Model.right = Model.right + (:right - :left + 1) where Model.right >= :targetRight and Model.tempFlag != 1";
Query query5 = session.createQuery(updateSql);
query5.setParameter("left", left);
query5.setParameter("right", right);
query5.setParameter("targetRight", targetGroupRight);
query5.executeUpdate();
//更新所有父节点的左值
updateSql = "update UserGroup as Model set Model.left = Model.left + (:right - :left + 1) where Model.left >= :targetRight and Model.tempFlag != 1";
Query query6 = session.createQuery(updateSql);
query6.setParameter("left", left);
query6.setParameter("right", right);
query6.setParameter("targetRight", targetGroupRight);
query6.executeUpdate();
//【为移动节点及所有子节点设置左右值及清除临时标记】
updateSql = "update UserGroup as Model set Model.left = Model.left + :offset, Model.right = Model.right + :offset, Model.tempFlag = 0 where Model.left >= :left and Model.right <= :right and Model.tempFlag = 1";
Query query7 = session.createQuery(updateSql);
query7.setParameter("right", right);
query7.setParameter("left", left);
Integer offset = targetGroupRight - left;
query7.setParameter("offset", offset);
query7.executeUpdate();
instance.setLeft(instance.getLeft() + offset);
instance.setRight(instance.getRight() + offset);
session.saveOrUpdate(instance);
session.beginTransaction().commit();
session.close();
}
catch(RuntimeException re)
{
session.beginTransaction().rollback();
session.close();
throw re;
}
由于是第一次写博客,写的不好,清见谅。