在MySQL中管理分層數據的Java實現

在MySQL中管理分層數據的Java實現

概述:

最近看到一個有意思的在MySQL中管理分層數據的文章,爲每個節點添加了lftrgt兩個屬性。這樣查找該節點的子節點、查找該節點所有父節點,就不用去遞歸查詢,只需要用betweenand語句就可以實現。下面以創建一個欄目樹爲例,以下是我的理解。

一般來講,我們創建欄目樹的時候,大多隻需要一個外鍵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在lftrgt之間的所有節點即可。

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. 將樹形中所有lftrgt節點大於父節點lft值的節點都+2

C. 將父節點lft值+1,lft值+2分別作爲當前節點的lftrgt

因爲項目中採用的是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: 修改節點

修改的時候比較麻煩(在修改lftrgt之前,當前節點的父節點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. 將所有lftrgt大於刪除節點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);
		}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章