【原創】無緩存數據庫下,部門樹結構處理--轉載請註明出處

1. 數據庫設計

我們在工作中經常會用到樹型結構的數據,比如公司的部門結構,倉庫物品的分類等。一般這些樹的結構,都是任意層級的,而非固定的幾層結構。此時,我們就要用到樹形的數據結構。以下,將會以部門樹爲例進行描述。

數據庫表結構:

部門Id----departmentId

部門Url----url

部門名稱----departmentName

父級部門Id----superDepartmentId

一個公司的部門信息,一定是要先將其持久化到數據庫中的。因爲部門的結構是一種無限層級的樹結構,因此,我們在設計數據庫時,在部門表的部門Id和部門名稱這兩個字段的基礎上,要增加兩個字段,父部門Id和部門的Url鏈接。

父部門Id:該字段用於在進行查詢時使用,如果一個部門A的父部門Id爲部門B的部門Id,我們則會將部門A,看作部門B的多個直接子部門之一,依次類推。

部門Url:如果我們有一個部門A,部門A的父部門爲部門B,部門B的父部門爲部門C,則部門Url則爲  部門CId_部門BId_部門AId。這個字段一般用來查詢一個部門的所有下屬子部門(包括非直接子部門),在樹結構中不做使用。

2. 從數據庫獲取部門樹

a. ModelFrameworkTree

在類FrameworkTree中,有多個屬性,這裏不一一列舉,只對重要的幾個屬性進行描述

String Id----實際上爲部門的Url,但是因爲使用的前端框架爲easyuiTree,所以在這裏id實際上是url

String thisId----Model所代表的部門的實際Id

String text----easyui中,進行顯示的文本,此處填充的爲部門名

List<FrameworkTree> children----該對象存儲的爲這個部門的所有子部門對象。

b. DaoSql語句:

 

<!-- 根據父部門Id查詢部門列表 -->
	<select id="getLowerBySuperId" parameterType="com.mdoa.framework.model.FrameworkTree" 
		resultType="com.mdoa.framework.model.FrameworkTree">
		SELECT
			department_id AS thisId,
			url AS id,
			department_name AS text
		FROM
			framework_department
		WHERE
			super_department_id = #{thisId,jdbcType=CHAR}
		AND alive_flag = '1'
		ORDER BY
			create_time DESC
	</select>

因爲我們所使用的特殊數據庫結構,這裏的Sql語句僅僅根據父級部門Id來查詢該父級部門的所有直系子部門,所以我們要將這些部門以樹的形式全部查出來,就需要使用遞歸的形式來進行查詢。

c. Service層處理

 

/**
	 * 爲單例化的部門結構注入結構
	 * 部門結構從數據庫中進行獲取
	 */
	public List<FrameworkTree> injectFrameworkDepartment(FrameworkTree superDepartment){
		//根據父級部門Id查詢該部門下的下一級子部門
		List<FrameworkTree> departments = departmentDao.getLowerBySuperId(superDepartment);
		//遞歸調用,查詢每一層的子部門的下一層級子部門,並設置進部門中
		for(FrameworkTree department : departments){
			department.setChildren(this.injectFrameworkDepartment(department));
		}
		//返回所有的部門信息
		return departments;
	}

 

我們在Service層中,採用的是遞歸的形式,首先需要傳入一個FrameworkTree對象,該對象爲我們在使用時的父級部門模型。

d. Controller層處理

我們在來看一下controller層啓動遞歸的處理辦法

FrameworkTree superDepartment = new FrameworkTree();
superDepartment.setText("XXXX有限公司");
superDepartment.setId("0000");
superDepartment.setThisId("0000");
superDepartment.setChildren(departmentService.injectFrameworkDepartment(superDepartment));

在Controller層中,我們new了一個根級別的部門,這個部門的部門Id,是所有部門最初級別的父部門,相當與java中的Object。我們利用這個最初節點,來對Service層中的遞歸進行啓動,啓動遞歸後,經過代碼的遞歸,我們可以獲得到一個所有部門的樹結構,這個樹結構,反饋給前端的easyui後,easyui就可以直接進行解析了。

3. 部門樹的單例化

在這個部門樹中,存在着一個重大的問題,那就是這個樹本身在創建的時候,是需要多次對數據庫進行請求的。每當在數據庫中找到一個部門以後,就需要重新調用dao層方法,對這個部門的所有子部門進行查詢。因此,我們需要對這個部門樹進行單例化。

Controller層完整代碼

 

/**
	 * 獲取公司的部門結構信息,如果單例化的部門結構信息是空的,則調用service層中的方法,爲單例化的對象注入結構
	 * @return 部門信息json
	 */
	@RequestMapping("getFramework.do")
	public String getFramework(){
		try{
			Gson gson = new Gson();
			if(FrameWorkConstant.frameworkDepartments != null){
				String json = gson.toJson(FrameWorkConstant.frameworkDepartments);
				return json;
			}else{
				synchronized(this){
					if(FrameWorkConstant.frameworkDepartments != null){
						String json = gson.toJson(FrameWorkConstant.frameworkDepartments);
						return json;
					}
					FrameworkTree superDepartment = new FrameworkTree();
					superDepartment.setText("XXXX有限公司");
					superDepartment.setId("0000");
					superDepartment.setThisId("0000");
					superDepartment.setChildren(departmentService.injectFrameworkDepartment(superDepartment));
					FrameWorkConstant.frameworkDepartments = new LinkedList<FrameworkTree>();
					FrameWorkConstant.frameworkDepartments.add(superDepartment); 
					String json = gson.toJson(FrameWorkConstant.frameworkDepartments);
					return json;
				}
			}
		}catch(Exception e){
			e.printStackTrace();
			return Constant.SERVER_ERROR_CODE;
		}
	}

單例化部門樹結構的時候,只需要進行判斷是否已經創建了這個靜態對象即可,如果已經創建了對象,則直接返回該對象,如果未創建,則創建一個對象。

我們在這裏需要注意的一個問題,在啓動服務器後,在第一次獲取部門信息的時候,如果出現並發現象,可能依然會出新兩次創建對象賦值的情況,因此,我們要使用synchronized將部分代碼進行鎖定。鎖定的代碼中,需要重新判斷是否爲空。

4. 部門的增刪改查處理

在進行對部門的增刪改查時,因爲我們所獲取的部門結構是位於內存中的單例部門,而不是數據庫中的部門,所以我們在對數據庫進行增刪改查操作時,也需要對單例的部門樹進行處理。比如修改部門名稱時,要先修改部門的數據庫中的名稱,再修改單例部門樹中的數據。

在對單例部門樹中的數據進行尋址的代碼如下:

 

	/**
	 * 通過Url獲取樹節點
	 * @param url
	 * @return
	 */
	public FrameworkTree getTargetByUrl(String url, List<FrameworkTree> targets){
		FrameworkTree child = null;
		for(FrameworkTree target:targets){
			if (StringUtil.isInclude(url, target.getId())) {
				
				if(url.equals(target.getId())){
					return target;
				}else{
					child =  getTargetByUrl(url, target.getChildren());
				}
				break ;
			}
		}
		return child;
	}

在我們擁有了通過節點Url來尋找節點的方法以後,對部門的修改與刪除就變得簡單化了。比如,我們需要將這個節點刪除,則可以設置爲null,需要修改部門名稱,則修改節點中的text,即可實現數據庫中的信息與單例樹的信息一致。

5. 節點拖動

當我們需要進行節點拖動時(如現有部門A,父部門爲B,將A修改至部門C下),依然要對數據庫進行處理,然後再對單例的樹進行處理。要注意的是,在拖動後,該節點的所有子節點的Url也需要進行修改。

Service層拖動代碼

/**
 * 更改部門的父部門,在前端通過拖動的方式修改
 */
public void moveDepartment(String startNodeUrl, String endNodeUrl,HttpServletRequest reuqest){
String newUrl = endNodeUrl + "_" +StringUtil.getIdFromUrl(startNodeUrl);
String superDeptId = StringUtil.getIdFromUrl(endNodeUrl);
HashMap<String ,String> params = new HashMap<String, String>();
params.put("newUrl",newUrl);
params.put("superDeptId",superDeptId);
params.put("startNodeUrl",startNodeUrl);
UserInfo userInfo = getUser(reuqest);
params.put("updateUserId", userInfo.getUserId());
params.put("updateUserName", userInfo.getUserName());
if(!departmentDao.updateDepartmentUrl(params)){
throw new RuntimeException("更改部門Url失敗");
}
params.put("startNodeUrl", "'" + startNodeUrl + "%'");
departmentDao.updateChildDepartmentUrl(params);
//獲取所移動的節點的原始父節點
FrameworkTree oldParent = this.getTargetByUrl(
startNodeUrl.substring(0, startNodeUrl.lastIndexOf("_")), FrameWorkConstant.frameworkDepartments);
//尋找原始父節點下的所移動的節點
for(int i = 0; i < oldParent.getChildren().size() ; i++){
FrameworkTree target = oldParent.getChildren().get(i);
if(target.getId().equals(startNodeUrl)){
//尋找新的位置的父節點
FrameworkTree newParent = this.getTargetByUrl(endNodeUrl, FrameWorkConstant.frameworkDepartments);
//添加到新的父節點下
newParent.getChildren().add(target);
//替換Url爲新的Url
target.setId(newUrl);
//獲取所有移動的節點的子節點
List<FrameworkTree> childs = this.getAllChildren(target);
//獲取新的Url
for(FrameworkTree child : childs){
//替換所有的舊父節點Url爲新的父節點url
child.getId().replace(startNodeUrl, newUrl);
}
//從舊的父節點下移除目標子節點
oldParent.getChildren().remove(target);
break ;
}
}
}

數據庫處理sql如下:

	<!-- 根據部門的Url來修改部門的父級Url和父級部門Id -->
	<update id="updateDepartmentUrl" parameterType="java.util.HashMap">
		UPDATE framework_department
		SET
			super_department_id = #{superDeptId},
			url = #{newUrl},
			update_time = NOW(),
			update_user_id = #{updateUserId},
			update_user_name = #{updateUserName}
		WHERE
			url = #{startNodeUrl}
	</update>
	<!-- 修改數據庫中被拖動的部門節點的所有子節點的Url -->
	<update id="updateChildDepartmentUrl" parameterType="java.util.HashMap">
		UPDATE framework_department
	 	SET
	 		url = REPLACE(url,#{startNodeUrl},#{newUrl}),
	 		update_time = NOW(),
	 		update_user_id = #{updateUserId},
	 		update_user_name = #{updateUserName}
	 	WHERE
	 		url LIKE (${startNodeUrl})
	</update>

獲取一個節點的所有子節點方法:

 

	/**
	 * 通過節點的目標,獲取該節點的所有子節點
	 */
	public List<FrameworkTree> getAllChildren(FrameworkTree target){
		List<FrameworkTree> childs = target.getChildren();
		for(FrameworkTree child: childs){
			childs.addAll(getAllChildren(child));
		}
		return childs;
	}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章