在MySQL中管理分層數據的Java實現
概述:
最近看到一個有意思的在MySQL中管理分層數據的文章,爲每個節點添加了lft
和rgt
兩個屬性。這樣查找該節點的子節點、查找該節點所有父節點,就不用去遞歸查詢,只需要用between、and語句就可以實現。下面以創建一個欄目樹爲例,以下是我的理解。
一般來講,我們創建欄目樹的時候,大多隻需要一個外鍵parentid來區分該節點屬於哪個父節點。數據庫的設計如下圖:
1.查找該節點的所有子節點,則需要採用sql的遞歸語句:
select * from tableName connect by prior id=sj_parent_id start with id=1
2.查找該節點的父節點, sql遞歸語句:
select * from tableName connect by prior sj_parent_id =id start with id=1
oracle的寫法,mysql不支持,如果mysql想查找樹形,需要利用存儲過程
如果數據量過大或者層次太多,那麼這樣操作是會影響性能。
“任何樹形結構都可以用二叉樹來表示”。其實我們創建的欄目樹就是一個簡型的二叉樹。根據數據結構裏面二叉樹的遍歷,再稍微修改下,將數據庫設計如下圖所示:
這樣我們查找該節點的所有子節點,則只需要查找id在lft
和rgt
之間的所有節點即可。
1.查找該節點的所有子節點的sql語句爲:
select * from tb_subject s,tb_subject t where s.lft between t.lft and t.rgt and t.id=1
2.查找該節點的所有父節點的sql語句爲:
select s.* from tb_subject s,tb_subject t where s.lft<t.lft and (s.rgt-s.lft)>1 and s.rgt>t.rgt and t.id=1
下面來詳細講解下,怎麼用java來實現這種算法。
java實現:
1: 新增節點
新增節點比較簡單,基本步驟爲
A. 查找當前插入節點的父節點的lft
值
B. 將樹形中所有lft
和rgt
節點大於父節點lft
值的節點都+2
C. 將父節點lft值+1,lft值+2分別作爲當前節點的lft
和rgt
因爲項目中採用的是struts2+hibernate3.2+spring2.5的框架,代碼如下:
public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
if (entity instanceof HibernateTree) {
HibernateTree tree = (HibernateTree) entity;
Long parentId = tree.getParentId();
String beanName = tree.getClass().getName();
Session session = getSession();
FlushMode model = session.getFlushMode();
session.setFlushMode(FlushMode.MANUAL);
Integer myPosition = new Integer(0);
//查找父節點的左值
if (parentId != null) {
String hql = "select b.lft from " + beanName
+ " b where b.id=:pid";
myPosition = (Integer) session.createQuery(hql).setLong("pid",
parentId).uniqueResult();
}
//將樹形結構中所有大於父節點左值的右節點+2
String hql1 = "update " + beanName
+ " b set b.rgt = b.rgt + 2 WHERE b.rgt > :myPosition";
//將樹形結構中所有大於父節點左值的左節點+2
String hql2 = "update " + beanName
+ " b set b.lft = b.lft + 2 WHERE b.lft > :myPosition";
if (!StringUtils.isBlank(tree.getTreeCondition())) {
hql1 += " and (" + tree.getTreeCondition() + ")";
hql2 += " and (" + tree.getTreeCondition() + ")";
}
session.createQuery(hql1).setInteger("myPosition", myPosition)
.executeUpdate();
session.createQuery(hql2).setInteger("myPosition", myPosition)
.executeUpdate();
session.setFlushMode(model);
//定位自己的左值(父節點左值+1)和右值(父節點左值+2)
for (int i = 0; i < propertyNames.length; i++) {
if (propertyNames[i].equals(HibernateTree.LFT)) {
state[i] = myPosition + 1;
}
if (propertyNames[i].equals(HibernateTree.RGT)) {
state[i] = myPosition + 2;
}
}
return true;
}
return false;
}
2: 修改節點
修改的時候比較麻煩(在修改lft
和rgt
之前,當前節點的父節點id
已經改變),具體步驟爲:
A. 查出當前節點的左右節點(nodelft、nodergt),並span = nodergt-nodelft+1
,獲取父節點的左節點parentlft
B. 將所有大於parentlft的lft(左節點)、rgt(右節點)的值+span
C. 查找當前節點的左右節點(nodelft、nodergt),並offset = parentlft-nodelft+1
D. 將所有lft(左節點) between nodelft and nodergt的值+offset
E. 將所有大於nodergt的lft(左節點)、rgt(右節點)的值-span
Java代碼如下:
public void updateParent(HibernateTree tree, HibernateTree preParent,
HibernateTree curParent) {
if (preParent != null && preParent != null
&& !preParent.equals(curParent)) {
String beanName = tree.getClass().getName();
// 獲得節點位置
String hql = "select b.lft,b.rgt from " + beanName
+ " b where b.id=:id";
Object[] position = (Object[]) super.createQuery(hql).setLong(
"id", tree.getId()).uniqueResult();
System.out.println(hql+"| id = "+tree.getId());
int nodeLft = ((Number) position[0]).intValue();
int nodeRgt = ((Number) position[1]).intValue();
int span = nodeRgt - nodeLft + 1;
// 獲得當前父節點左位置
hql = "select b.lft from " + beanName + " b where b.id=:id";
int parentLft = ((Number) super.createQuery(hql).setLong("id",
curParent.getId()).uniqueResult()).intValue();
System.out.println(hql+"| id = "+curParent.getId());
// 先空出位置
String hql1 = "update " + beanName + " b set b.rgt = b.rgt + "
+ span + " WHERE b.rgt > :parentLft";
String hql2 = "update " + beanName + " b set b.lft = b.lft + "
+ span + " WHERE b.lft > :parentLft";
if (!StringUtils.isBlank(tree.getTreeCondition())) {
hql1 += " and (" + tree.getTreeCondition() + ")";
hql2 += " and (" + tree.getTreeCondition() + ")";
}
super.createQuery(hql1).setInteger("parentLft", parentLft)
.executeUpdate();
super.createQuery(hql2).setInteger("parentLft", parentLft)
.executeUpdate();
System.out.println(hql1+"| parentLft = "+parentLft);
System.out.println(hql2+"| parentLft = "+parentLft);
// 再調整自己
hql = "select b.lft,b.rgt from " + beanName + " b where b.id=:id";
position = (Object[]) super.createQuery(hql).setLong("id",
tree.getId()).uniqueResult();
System.out.println(hql+"| id = "+tree.getId());
nodeLft = ((Number) position[0]).intValue();
nodeRgt = ((Number) position[1]).intValue();
int offset = parentLft - nodeLft + 1;
hql = "update "
+ beanName
+ " b set b.lft=b.lft+:offset, b.rgt=b.rgt+:offset WHERE b.lft between :nodeLft and :nodeRgt";
if (!StringUtils.isBlank(tree.getTreeCondition())) {
hql += " and (" + tree.getTreeCondition() + ")";
}
super.createQuery(hql).setParameter("offset", offset)
.setParameter("nodeLft", nodeLft).setParameter("nodeRgt",
nodeRgt).executeUpdate();
System.out.println(hql+"| offset = "+offset+" | nodelft = "+nodeLft+" | nodergt = "+ nodeRgt);
// 最後刪除(清空位置)
hql1 = "update " + beanName + " b set b.rgt = b.rgt - " + span
+ " WHERE b.rgt > :nodeRgt";
hql2 = "update " + beanName + " b set b.lft = b.lft - " + span
+ " WHERE b.lft > :nodeRgt";
if (tree.getTreeCondition() != null) {
hql1 += " and (" + tree.getTreeCondition() + ")";
hql2 += " and (" + tree.getTreeCondition() + ")";
}
super.createQuery(hql1).setParameter("nodeRgt", nodeRgt)
.executeUpdate();
super.createQuery(hql2).setParameter("nodeRgt", nodeRgt)
.executeUpdate();
System.out.println(hql1+"| nodeRgt = "+nodeRgt);
System.out.println(hql2+"| nodeRgt = "+nodeRgt);
}
}
3: 刪除節點
刪除節點也比較簡單,具體步驟爲:
A. 查找要刪除節點的lft
值
B. 將所有lft
和rgt
大於刪除節點lft
值的都-2
Java代碼如下:
public void onDelete(Object entity, Serializable id, Object[] state,
String[] propertyNames, Type[] types) {
if (entity instanceof HibernateTree) {
HibernateTree tree = (HibernateTree) entity;
String beanName = tree.getClass().getName();
Session session = getSession();
FlushMode model = session.getFlushMode();
session.setFlushMode(FlushMode.MANUAL);
//查找要刪除的節點的左值
String hql = "select b.lft from " + beanName + " b where b.id=:id";
Integer myPosition = (Integer) session.createQuery(hql).setLong(
"id", tree.getId()).uniqueResult();
//將所有大於刪除節點左值的rgt都-2
String hql1 = "update " + beanName
+ " b set b.rgt = b.rgt - 2 WHERE b.rgt > :myPosition";
//將所有大於刪除節點左值的lft都-2
String hql2 = "update " + beanName
+ " b set b.lft = b.lft - 2 WHERE b.lft > :myPosition";
if (tree.getTreeCondition() != null) {
hql1 += " and (" + tree.getTreeCondition() + ")";
hql2 += " and (" + tree.getTreeCondition() + ")";
}
session.createQuery(hql1).setInteger("myPosition", myPosition)
.executeUpdate();
session.createQuery(hql2).setInteger("myPosition", myPosition)
.executeUpdate();
session.setFlushMode(model);
}
}