1. 程式人生 > >【原創】無快取資料庫下,部門樹結構處理--轉載請註明出處

【原創】無快取資料庫下,部門樹結構處理--轉載請註明出處

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;
	}