我的标题真冗长:P
最近一直在考虑这个问题,如何在现在常用的关系型数据库(如mysql,mssql)中存储含有层次结构(例如xml或者树型结构)的数据,我最终要实现的结果是一个比较好的页面三级联动菜单。比较了一下现有的三级联动,无非有两种解决方案,1、将所有数据写成静态的,这样的缺点在于对更新封闭;2、每改变一次去数据库中获取数据,填充到下一级菜单,这个缺点是对数据库的开销比较大,查询一次数据要对数据库做三次操作,而且数据库设计的比较丑陋。上述两种方法都让我比较不能接受。
我想的是一种比较优雅的解决方案:在数据库中存储的数据不要冗余,便于查询,不要产生递归,对数据库的开销要尽可能的小,尽量做到一次将所有数据都读出来,用户也要有比较好的体验。我最初的解决方案就是用xml来存储联动的数据,但是对于数据库操作来说,最后还是要落实到对文件的I/O操作,从这方面来讲,使用xml和数据库并没有什么本质的不同。另外一种解决方案就是在关系数据库中存类xml的数据了。google了半天,最后发现了一片自己学院一位学长翻译的文章www.nirvanastudio.org/category/database,讲得就是如何在关系型数据库存储这种层次数据的。文中提出了两种解决方案,一是“邻接列表模型”或称为“递归方法”,这种存在着数据冗余,而且递归出于众所周知的原因,效率不高。第二种方法,也就是我现在采用的方法是"前序优先遍历",表结构也很简单,没有冗余
id | name | left | right |
我觉得应该属于一种深度优先。通过每个节点的左右两个属性获得其子女的属性,好处就在于获得一个或几个节点只需要一次查询,而在更新的时候速度会比较慢,因为要更新多个后继节点,不过话说回来,更新相对于获取来说次数要少的多,可以忽略。
下面是我的解决方案:
java 代码
1. import
2. import
3. import
4. import
5. import
6. import
7.
8. import
9. import
10.
11. /**
12. * @author SONG Yihan
13. * @version 1.0
14. * @date: Monday, April 09 2007
15. */
16. public class
17.
18. private
19. private Statement stmt1 = null;
20. private Statement stmt2 = null;
21. private Connection conn = null;
22. private ResultSet rs = null;
23.
24. public
25. try
26. conn = DataSource.getConnection();
27. catch
28. // TODO Auto-generated catch block
29. e.printStackTrace();
30. catch
31. // TODO Auto-generated catch block
32. e.printStackTrace();
33. catch
34. // TODO Auto-generated catch block
35. e.printStackTrace();
36. catch
37. // TODO Auto-generated catch block
38. e.printStackTrace();
39. catch
40. // TODO Auto-generated catch block
41. e.printStackTrace();
42. }
43. }
44.
45. public
46. this.conn = conn;
47. }
48.
49. public boolean
50. try
51. "SELECT * FROM district WHERE districtId='" + parentId + "';";
52. false);
53. stmt1 = conn.createStatement();
54. rs = stmt1.executeQuery(sql);
55. if(rs.next()) {
56. int right = rs.getInt("rgt");
57. "UPDATE district SET lft=lft+2 WHERE lft>="
58. stmt2.addBatch(sql);
59. "UPDATE district SET rgt=rgt+2 WHERE rgt>="
60. stmt2.addBatch(sql);
61. "INSERT INTO district (districtId, districtName, lft, rgt) VALUES ('"+districtId + "', '" + districtName + "', "
62. ", " + (right+1) +");";
63. stmt2.addBatch(sql);
64. int[] flag = stmt2.executeBatch();
65. if(flag[flag.length - 1] == 1) {
66. conn.commit();
67. return true;
68. else
69. conn.rollback();
70. return false;
71. }
72. }
73.
74. catch
75. // TODO Auto-generated catch block
76. e.printStackTrace();
77. finally
78.
79.
80. }
81. return false;
82. }
83.
84. public boolean
85. "SELECT * FROM district WHERE districtId='" + districtId + "';";
86. try
87. false);
88. rs = stmt1.executeQuery(sql);
89. if(rs.next())
90. {
91. int left = rs.getInt("lft");
92. int right = rs.getInt("rgt");
93. int count = (right - left - 1) / 2 + 1;
94. int minus = count * 2;
95. "DELETE FROM district WHERE lft BETWEEN " + left + " AND " + right + ";";
96. stmt2.addBatch(sql);
97. "UPDATE district SET lft=lft-" + minus + " WHERE lft>"+right;
98. stmt2.addBatch(sql);
99. "UPDATE district SET rgt=rgt-" + minus + " WHERE rgt>"+right;
100. stmt2.addBatch(sql);
101. int flag = stmt2.executeBatch()[0];
102. if(flag == 1) {
103. conn.commit();
104. return true;
105. else
106. conn.rollback();
107. return false;
108. }
109.
110. }
111. catch
112. // TODO Auto-generated catch block
113. e.printStackTrace();
114. }
115. return false;
116. }
117.
118. public void
119.
120.
121. }
122.
123. public
124. "SELECT * FROM district WHERE districtId='" + parentId + "';";
125. try
126. stmt1 = conn.createStatement();
127. rs = stmt1.executeQuery(sql);
128. if(rs.next()) {
129. int left = rs.getInt("lft");
130. int right = rs.getInt("rgt");
131. stmt2 = conn.createStatement();
132. "SELECT * FROM district WHERE lft BETWEEN " + left + " AND " + right + " ORDER BY lft ASC";
133. rs = stmt2.executeQuery(sql);
134. new
135. null;
136. while(rs.next()) {
137. new
138. "districtId"));
139. "districtName"));
140. "lft"));
141. "rgt"));
142.
143. tree.add(district);
144. }
145. return
146.
147. }
148. catch
149. // TODO Auto-generated catch block
150. e.printStackTrace();
151. finally
152. try
153. if (rs != null) {
154. rs.close();
155. }
156. stmt1.close();
157. stmt2.close();
158. conn.close();
159. catch
160. // TODO Auto-generated catch block
161. e.printStackTrace();
162. }
163. }
164.
165. return null;
166. }
167.
168. public
169. if(tree == null) {
170. tree = getTree(parentId);
171. }
172. new
173. int index = this.indexof(parentId);
174. if(index != -1) {
175. District d = tree.get(index);
176. final int count = (d.getRight() - d.getLeft() - 1) / 2;
177. for(int i = index + 1; i <= index + count; i++) {
178. d = tree.get(i);
179. children.add(d);
180. 1) / 2;
181. }
182. }
183.
184. return
185. }
186.
187. private int
188. for(int index = 0; index < tree.size(); index++) {
189. if(districtId.equals(tree.get(index).getDistrictId())) {
190. return
191. }
192. }
193. return -1;
194. }
195.
196. public boolean
197. "UPDATE district SET districtName='" + districtName + "' WHERE districtId='" + districtId + "';";
198. try
199. false);
200. stmt1 = conn.createStatement();
201. int
202. if(flag == 1) {
203. conn.commit();
204. return true;
205. }
206. else
207. conn.rollback();
208. return false;
209. }
210.
211. catch
212. // TODO Auto-generated catch block
213. e.printStackTrace();
214. finally
215.
216. }
217. return false;
218. }
219. }
这个最遗憾的地方就是在其中混杂了java代码,这让我及其不爽,但是我没有找到向js传值的好办法:(
js 代码
1. function
2. this.id=id;
3. this.name=name;
4. this.left=left;
5. this.right=right;
6. };
7.
8. var tree = new
9.
10. <%
11. for(District d : tree) {
12. new
13. <%}%>
14.
15. function(id) {
16. for(var
17. if(id == tree[index].id) {
18. return
19. }
20. }
21. return
22. };
23.
24. function
25. document.getElementById('district').length = 0;
26. document.getElementById('area').length = 0;
27. var
28. for(var
29. new
30. }
31. };
32.
33. function
34. document.getElementById('area').length = 0;
35. var
36. for(var
37. new
38. }
39. };
40.
41. function
42. if(tree.length == 0) {
43.
44. }
45. var children = new
46. var
47. if(index != -1) {
48. var
49. var
50. for(var
51. d = tree[i];
52. children.push(d);
53. i += (d.right - d.left - 1) / 2;
54. }
55. }
56. return
57. };
jsp 页面 代码
1. "/fastfoodAct?method=query" styleId="queryFoodForm">
2.
3. searchType :
4. city :
5.
6.
7. district :
8.
9. area :
10.
11. restaurantName :
12.
13. searchType :
14. foodName :
15. <html:submit> <html:cancel> </html:cancel> </html:submit>
16.
17.