<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>bocar</title>
    <description></description>
    <link>http://bocar.javaeye.com</link>
    <language>UTF-8</language>
    <copyright>Copyright 2003-2008, JavaEye.com</copyright>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>JavaEye - 做最棒的软件开发交流社区</generator>
      <item>
        <title>使用Spring和Hibernate实现树型结构列表</title>
        <author>bocar</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://bocar.javaeye.com">bocar</a>&nbsp;
          链接：<a href="http://bocar.javaeye.com/blog/189684" style="color:red;">http://bocar.javaeye.com/blog/189684</a>&nbsp;
          发表时间: 2008年05月05日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          最近在自学RoR，看到了rainlife的帖子<a href="http://www.javaeye.com/topic/177501" target="_blank">Rails生成Ext Tree</a>，结合以前的Java项目经验，提出了我的一些想法。<br />以下的文字和rainlife的帖子<a href="http://www.javaeye.com/topic/177501" target="_blank">Rails生成Ext Tree</a>中我的回复大致相同，只是以SSH架构的Java代码重新实现了原帖中的内容。<br /><br />我的疑问主要是在数据库的设计上，对于其中lft和rgt字段的设计感觉不好。当然我刚刚开始学习RoR，其中也许有很完备的解决方案我不知道，仅在此提出我的看法而以，如有异议，欢迎批评指正。<br />维护一个树型结构的类型列表，如下所示：<br />Root<br />&nbsp;&nbsp;&nbsp;&nbsp;|- Child 1<br />&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;|- Child 1.1<br />&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;|- Child 1.2<br />&nbsp;&nbsp;&nbsp;&nbsp;|- Child 2<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|- Child 2.1<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|- Child 2.2<br />原帖中表示树状节点的Category类中有lft和rgt这两个字段看上去是用来定义一个类型边界的。如果需要查找某个特定类型及其子类的话，则先查找该类型的lft和rgt，然后再 (lft > ? and rgt &lt; ?) 获得其子类。<br />这种设计在数据结构完备的情况下能准确的统计出所有的子类。但是我要动态的增加Child子类，或者改变Child的隶属关系的时候，就需要对数据库表中所有数据的lft和rgt做出调整。比如我要在Child 1中增加一个Child 1.3。相应的 Root、Child 1、Child 2、Child 2.1、Child 2.2 的lft和rgt都要做相应的变化。<br />也许acts_as_nested_set可以通过先 delete from category; 后 insert into values(?,?,?,?); 的方式进行全类的维护，但是如果别的类有对Category的引用（即外键）。这样的隶属关系不是会产生混乱了吗？况且如果数据库真的建立了外键的话，也不允许 delete from category; 操作的。<br /><br />所以我的考虑是用一个level字段代替lft和rgt字段，level字段维护着Category实例的层级关系。<br />例如，将root的level定义为1（这个在数据库或程序中可配），那么Child 1的level就为1|${id}（其中'|'为Level分层标记，${id}表示当前数据Id或其它可唯一标识的字段值），假定为1|2，同理Child 1.1的level就是1|2|3。<br />类结构如下所示：使用的是annotation的hibernate<br /><pre name="code" class="java">
@Entity
public class Category implements Serializable {

	/** Level分层标记 */
	public static final String LEVEL_SPLIT = "|";

	@Id
	@GeneratedValue
	private Integer id;
	
	/** 名称 */
	private String name;

	/** level */
	private String level;
	
	/** 删除标记 */
	private Boolean delFlag;

	/** 下级的类别 */
	@OneToMany(fetch = FetchType.LAZY, mappedBy="parent")
	@OrderBy("id")
	private List&lt;Category> children = new ArrayList&lt;Category>();

	/** 上级的类别 */
	@ManyToOne
	@JoinColumn(name = "category_id")
	private Category parent;
	
	// 省略所有 getter/setter 方法...
}
</pre><br />维护后数据库中的数据如下：<br /><img src="http://www.javaeye.com/upload/attachment/22815/f5820e10-439e-3567-8f80-e0b8a8ad3249.jpg" /><br /><br /><br />查找某个特定类型及其子类，只需要获得当前类型的level值，然后查找 (level like 'xxxx%')即可。如下所示：<br />Rails中需要重写Category中类似find的方法。<br /><pre name="code" class="java">
	public List&lt;Category> getCategoryList(Integer startId) {
		Category root = crudDao.get(Category.class, startId);	// crudDao继承HibernateDaoSupport，并封装了HibernateTemplate的操作，下同
		String hql = "from Category where level like ? order by id desc";
		return crudDao.query(hql, root.getLevel + Category.LEVEL_SPLIT + "%");
	}
</pre><br /><br /><br />新增或修改Category的方法。则需要先查找当前父类，再重新维护当前类的level属性值即可。如下所示：<br />Rails中需要重写Category中的add_child方法<br /><pre name="code" class="java">
	public void saveCategory(Category category) {
		Category parent = category.getParent();
		// 判断Category的父类有没有设置
		if (parent == null || parent.getId() == null || parent.getId() == 0) {
			throw new ParentNotFoundException(Category.class); 
		}
		// 重新设置父类关系
		parent = crudDao.get(Category.class, parent.getId());	// crudDao继承HibernateDaoSupport，并封装了HibernateTemplate的操作，下同
		category.setParent(parent);
		
		if (category.getId() == null || category.getId() == 0) {
			crudDao.save(category);
			category.setLevel(parent.getLevel() + Category.LEVEL_SPLIT + category.getId());
			crudDao.update(category);
		} else {
			Category oldCategory = crudDao.get(Category.class, category.getId());
			BeanUtils.copyProperties(category, oldCategory, new String[]{"id", "children"});
			oldCategory.setLevel(parent.getLevel() + Category.LEVEL_SPLIT + oldCategory.getId());
			crudDao.update(oldCategory);
		}
	}
</pre><br />使用Rails的ActiveRecord，以上代码可以写的更简练<br /><br /><br />页面显示，我使用的是纯javascript的dtree。代码如下所示：<br /><pre name="code" class="html">
&lt;body class="dtree">
&lt;p>&lt;a href="javascript:categoryTree.openAll();">open all&lt;/a> | &lt;a href="javascript:categoryTree.closeAll();">close all&lt;/a>&lt;/p>
&lt;script type="text/javascript">
	var dtreeImgPath = "${ctx}/script/dtree/img/";
	categoryTree = new dTree('categoryTree');
	&lt;c:forEach var="category" items="${list}">
	&lt;c:choose>
		&lt;c:when test="${category.parent == null or category.parent == null or category.parent.id == 0}">
			categoryTree.add(${category.id},-1,'${category.name }',"javascript:doSelect('${category.id}','${category.name }')");
		&lt;/c:when>
		&lt;c:otherwise>
			categoryTree.add(${category.id},${category.parent.id},'${category.name }',"javascript:doSelect('${category.id}','${category.name }')");
		&lt;/c:otherwise>
	&lt;/c:choose>
	&lt;/c:forEach>
	document.write(categoryTree);
&lt;/script>
&lt;/body>
</pre><br />原帖中是用Ext来实现视图的现实。我只需要在Struts2中使用JSONUtils实现一个Result，使得Action返回一个JSON对象，就可以在JSP中用Ext来显示树型结构的列表<br /><br />显示的效果如下:<br /><img src="http://www.javaeye.com/upload/attachment/22813/6474312e-f740-32bb-ab34-0a3a8a7a7e5c.jpg" />
          <br/>
          <span style="color:red;">
            <a href="http://bocar.javaeye.com/blog/189684#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 05 May 2008 13:36:09 +0800</pubDate>
        <link>http://bocar.javaeye.com/blog/189684</link>
        <guid>http://bocar.javaeye.com/blog/189684</guid>
      </item>
      <item>
        <title>Quartz的 autowire=&quot;byName&quot; 设置</title>
        <author>bocar</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://bocar.javaeye.com">bocar</a>&nbsp;
          链接：<a href="http://bocar.javaeye.com/blog/186316" style="color:red;">http://bocar.javaeye.com/blog/186316</a>&nbsp;
          发表时间: 2008年04月24日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          最近做的一个项目很简单，SSH的组合，所有的配置都用了最简单的配置，比如dataSource、sessionFactory等，需要开发一个定时发送邮件提醒的功能，使用Quartz小菜一碟。<br />但问题来了，测试时报错：org.quartz.impl.jdbcjobstore.LockException: Failure obtaining db row lock: Table 'chem.qrtz_locks' doesn't exist [See nested exception: com.mysql.jdbc.exceptions.MySQLSyntaxErrorException: Table 'chem.qrtz_locks' doesn't exist]<br />在论坛里搜索了一下。<br />有人说：<br /><div class="quote_title">引用</div><div class="quote_div">因为这个时候你的quartz是jobstore用的HDBCJobStore模式，此时会从数据库查询任务。<br />你如果只是测试的话，可以在你的classpath下加一个文件quartz.properties,并且加上一句org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore 。就会采取内存存储的模式了。</div><br />但是我查看了Quartz包中的默认quartz.properties文件，org.quartz.jobStore.class的属性已经是org.quartz.simpl.RAMJobStore，除非你要改成HDBCJobStore的模式才需要重新设置。<br />还有人说：<br /><div class="quote_title">引用</div><div class="quote_div">另外可能是自动装配惹得祸，存在dataSource这个bean就自动用数据库的状态维持了。</div><br />这句话讲到了点子上。我在配置文件中为了注入的方便都使用了default-autowire="byName" ，而Quartz在启动时如果发现了"dataSource"会自动注入变成HDBCJobStore的模式，那么我删掉Quartz配置文件&lt;beans />中的default-autowire="byName"。果然程序顺利运行，Quartz也能正常定时启动。<br />但是这样Quartz配置文件中的注入关系都要明文手动注入了，小的项目到无所谓，大点的项目就麻烦了。何不在"dataSource"的名称上做文章呢？<br />于是将"dataSource"改成"myDataSource"，Quartz配置文件&lt;beans />中依然使用 default-autowire="byName"。OK！程序正常启动，Quartz正确定时启动，而且autowire="byName"也能自动的注入。
          <br/>
          <span style="color:red;">
            <a href="http://bocar.javaeye.com/blog/186316#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 24 Apr 2008 11:47:19 +0800</pubDate>
        <link>http://bocar.javaeye.com/blog/186316</link>
        <guid>http://bocar.javaeye.com/blog/186316</guid>
      </item>
  </channel>
</rss>