<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>JavaEye论坛精彩帖子</title>
    <description>JavaEye论坛精彩帖子 - Java编程，Ruby编程，微软.net，AJAX，敏捷软件开发，综合软件技术</description>
    <link>http://www.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>忽悠，继续忽悠，组团忽悠...</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://readonly.javaeye.com">Readonly</a>&nbsp;
                    链接：<a href="http://www.javaeye.com/topic/186309" style="color:red;">http://www.javaeye.com/topic/186309</a>&nbsp;
          发表时间: 2008年04月24日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/>
          在偶开始喷SOA之前，让偶们先来看看一段大家都熟悉的历史：<br />1. 1999~2001之间EJB被各大厂商热炒 (IBM, Bea, Oracle, etc...)<br />2. 广告铺天盖地, Transaction, Security, Spec, Architecture, Remote procedure calls, Code reuse, Assembly等等buzz word犹如苍蝇成天在你耳边绕<br />3. 大型项目设计，企业系统构建，言必称采用先进的EJB技术云云<br />4. 各个银行，证券，保险，大型企业项目都被挂上成功的EJB技术光环<br /><br />但是后续的发展事实呢：<br />1. EJB规范1.0及其难用, 厂商们开始推出EJB2，继续不遗余力地忽悠<br />2. 大规模的批评和抱怨：性能低下，编程效率低，平台成本高<br />3. Hibernate出现，Spring出现, J2EE development without EJB出现<br />4. Hibernate/Spring成为Java开发的事实标准<br /><br />当然厂商们不甘心了，继续忽悠EJB3么？说白了它就是Hibernate/Spring的翻版，不好忽悠了怎么办，新瓶装旧酒，不忽悠某些具体技术，开始忽悠整体架构了：<br />1. 2007~2008之间SOA被各大厂商热炒 (IBM, Oracle/Bea, 普元, etc...)<br />2. 广告铺天盖地, Transaction, Security, Architecture, Integration, Workflow, Service reuse, Service assembly等等buzz word犹如苍蝇成天在你耳边继续绕<br />3. 大型项目设计，企业系统构建，言必称采用先进的SOA架构云云<br />4. 各个银行，证券，保险，大型企业项目都被挂上成功的SOA架构光环<br /><br />然偶来预言一下后续的发展事实吧：<br />1. SOA规范1.0及其难用, 厂商们开始推出SOA2.0，继续不遗余力地忽悠 <br />2. 大规模的批评和抱怨：性能低下，编程效率低，平台成本高<br />3. J2EE development without SOA出现<br />4. ...
          <br/>
          <span style="color:red;">
            <a href="http://www.javaeye.com/topic/186309#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/70' target='_blank'><span style="color:red;font-weight:bold;">第二届网络工程师侠客行大会5月24日杭州举行</span></a></li><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>Thu, 24 Apr 2008 11:37:25 +0800</pubDate>
        <link>http://www.javaeye.com/topic/186309</link>
        <guid>http://www.javaeye.com/topic/186309</guid>
      </item>
          <item>
        <title>贫血的Domain Model</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://taowen.javaeye.com">taowen</a>&nbsp;
                    链接：<a href="http://www.javaeye.com/topic/191261" style="color:red;">http://www.javaeye.com/topic/191261</a>&nbsp;
          发表时间: 2008年05月09日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/>
          好老的话题啦。拿出来炒炒冷饭。各位见谅。<br />——————————————————————<br />Domain Model贫血是说属于Domain Model的逻辑没有放在Domain Model中。那是哪些逻辑没有放到Domain Model中，从而导致贫血一说呢？原因有很多，但是我认为最主要是Service中的那些逻辑。而这些逻辑又有一个共同的特点就是依赖于DAO，或者说需要查询数据库。Robbin的帖子：http://www.javaeye.com/topic/57075，举了一个很好的例子。我取其中的一个部分在这里做演示用。<br /><br /><pre name="code" class="java">
public class Employee {
    private Set&lt;Task> tasks = new HashSet&lt;Task>();
}
</pre><br /><br /><pre name="code" class="java">
public class Task {
    private String name;
    private Employee owner;
    private Date startTime;
    private Date endTime;
}
</pre><br /><br />这是一个很简单的一对多的关系。现在要查找指定员工的处理中的任务。如果忽略数据库的存在，我想大部分的同志都会这么实现：<br /><br /><pre name="code" class="java">
public class Employee {
    private Set&lt;Task> tasks = new HashSet&lt;Task>();
    public Set&lt;Task> getProcessingTask() {
       ...
    }
}
</pre><br /><br />这也符合OO数据隐藏的基本原则。但是如果有数据库存在，怎么写就不那么容易决定了。如果没有Hibernate这样的ORM。那肯定是：<br /><br /><pre name="code" class="java">
public class TaskDAO {
   public Set&lt;Task> getProcessingTasks(Employee employee) {
      ...//sql
   }
}
</pre><br /><br />那我觉得，这就导致了Domain Model的失血。因为没有数据库的时候，这这个方法本来应该在Employee上的，而不是在DAO上的。<br />如果有Hibernate呢？是不是我就可以把这段代码写到Employee里面去呢？<br /><br /><pre name="code" class="java">
@Entity
public class Employee {
    @OneToMany
    private Set&lt;Task> tasks = new HashSet&lt;Task>();
    public Set&lt;Task> getProcessingTask() {
       ...
    }
}
</pre><br /><br />还是有问题。因为访问tasks的时候，Hibernate会去加载数据。getProcessingTask会便利所有的task。如果task的数量很多，这降极大的影响性能。所以为了能够享受到关系数据库查询速度的好处，我们要还要利用SQL。于是DAO又再次地找到了自己的位置。那么怎么解决这个问题呢？在http://www.javaeye.com/topic/57075的回帖中nihongye同学提出了一个解决方案。本质来说就是不让hibernate来映射tasks，改由查询来获得。加上Spring支持的@Configurable标记，我们可以把代码写成这样<br /><br /><pre name="code" class="java">
@Entity
@Configurable
public class Employee {
    private TaskDao dao;
    public Set&lt;Task> getProcessingTask() {
        return dao.getProcessingTask(this);
    }
    public void setTaskDao(TaskDao dao) {
        this.dao = dao;
    }
}
</pre><br /><br />我们当然还可以把TaskDao替换成变的形式。比如http://www.javaeye.com/topic/65406里firebody提到的那样。但是本质上来说，都是让Employee能够直接去使用Hibernate做查询。但是坏处是给Domain纯净分子的口实。虽然，我认为和ActiveRecord类似，entity绑定在数据库上没啥不好。另外一个缺点就是，要么仍然有一个Dao来封装查询逻辑的实现，要么Employee的实现中出现太多的hibernate api，而且写法复杂。这也就是Robbin一再强调，ActiveRecord那样的api在Java世界中不是不可以，而是实现复杂难度高的原因。注入可以解决问题，但是对Hibernate的依赖强而且写法丑陋。<br />那么有没有更优美的方案呢？有：<br /><br /><pre name="code" class="java">
public class Employee {
    private RichSet&lt;Task> tasks = new DefaultRichSet&lt;Task>();
    public RichSet&lt;Task> getProcessingTasks() {
        return tasks.find("startTime").le(new Date()).find("endTime").isNull();
    }
...
}
</pre><br /><br /><br />RichSet是我自己编造的一个名字。它是一个”rich“的set。其实就是附加了一些find，sort，sum之类的操作。<br /><br /><pre name="code" class="java">
public interface RichSet&lt;T> extends Set&lt;T> {
    Finder&lt;RichSet&lt;T>> find(String expression);
    int sum(String expression);
}
</pre><br /><br />DefaultRichSet是这些附加操作的内存版本的实现。这个能解决问题么？还是不能，这时候getProcessingTasks的时候，richSet还是去遍历内部的_tasks，然后把结果过滤出来。而且，hibernate还拒绝接受这样set。为了让hibernate能够接受RichSet，我们需要这么写配置文件。<br /><br /><pre name="code" class="xml">
&lt;hibernate-mapping default-access="field" package="net.sf.ferrum.example.domain">
    &lt;class name="Employee">
        &lt;tuplizer entity-mode="pojo" class="net.sf.ferrum.RichEntityTuplizer"/>
        &lt;id name="id">
            &lt;generator class="native"/>
        &lt;/id>
        &lt;property name="name"/>
        &lt;property name="salary"/>
        &lt;many-to-one name="department"/>
        &lt;set name="tasks" cascade="all" inverse="true" lazy="true">
            &lt;key/>
            &lt;one-to-many class="Task" />
        &lt;/set>
    &lt;/class>
&lt;/hibernate-mapping>
</pre><br /><br />通过指定RichEntityTuplizer，我们可以控制Hibernate的动态增强过程。<br /><br /><pre name="code" class="java">
public class RichEntityTuplizer extends PojoEntityTuplizer {
    public RichEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) {
        super(entityMetamodel, mappedEntity);
    }

    protected Setter buildPropertySetter(final Property mappedProperty, PersistentClass mappedEntity) {
        final Setter setter = super.buildPropertySetter(mappedProperty, mappedEntity);
        if (!(mappedProperty.getValue() instanceof org.hibernate.mapping.Set)) {
            return setter;
        }
        return new Setter() {
            public void set(Object target, Object value, SessionFactoryImplementor factory) throws HibernateException {
                Object wrappedValue = value;
                if (value instanceof Set) {
                    HibernateRepository repository = new HibernateRepository();
                    repository.setSessionFactory(factory);
                    wrappedValue = new HibernateRichSet((Set) value, repository, getCriteria(mappedProperty, target));
                }
                setter.set(target, wrappedValue, factory);
            }

            public String getMethodName() {
                return setter.getMethodName();
            }

            public Method getMethod() {
                return setter.getMethod();
            }
        };
    }
}
</pre><br /><br />这样，tasks就不再是DefaultRichSet了。Hibernate会尝试去增强为PersisentSet，但是被RichEntityTuplizer改写为增强HibernateRichSet了。这样就形成了HibernateRichSet -> PersisentSet -> DefaultRichSet -> HashSet 的包含关系。<br /><br />当用户尝试在tasks上做find的时候，就不再是DefaultRichSet来做collection遍历了，而是HibernateRichSet去拼装一个DetachedCriteria。最后当用户在查询的结果上取size()或者取具体元素的时候，这个criteria被拿去求值。<br /><br />通过使用RichSet，domain model具有了对自身进行查询的能力。更重要的是，这种能力的获得，不是通过把Hibernate session注入到domain model中。domain仍然是纯净的，没有依赖于数据库的东西。而且domain是可以脱离容器使用的。new Employee出来就可以直接使用，测试。区别只是经过repository增强的entity会使用sql，而transient的entity所有的查询都是通过遍历实现的。<br /><br />没有了DAO之后，Domain Model是不是能够摆脱贫血的困扰呢？这个还需要观察。不过我认为至少是向前迈了一步了。
          <br/>
          <span style="color:red;">
            <a href="http://www.javaeye.com/topic/191261#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/70' target='_blank'><span style="color:red;font-weight:bold;">第二届网络工程师侠客行大会5月24日杭州举行</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>Fri, 09 May 2008 00:18:47 +0800</pubDate>
        <link>http://www.javaeye.com/topic/191261</link>
        <guid>http://www.javaeye.com/topic/191261</guid>
      </item>
          <item>
        <title>从JSF和Ext看WebUI开发－－给对JavaScript 有恐惧感的朋友</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://leebai.javaeye.com">leebai</a>&nbsp;
                    链接：<a href="http://www.javaeye.com/topic/185839" style="color:red;">http://www.javaeye.com/topic/185839</a>&nbsp;
          发表时间: 2008年04月23日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/>
          <p>话题由来：<a href="../../../../post/523520?page=1" target="_blank">http://www.javaeye.com/post/523520?page=1</a> </p>
<p>把其中我的观点整理出来： 
<table style="background-color: #ffffff;" border="2">
<tbody>
<tr>
<td>
<p><span style="color: #ff0000;">100％支持fins！！！</span> B端和S端彻底分开，分别有自己的框架，&ldquo;UI层与系统其他层面的东西的唯一联系应该是"数据" ，UI层应该是在后台系统不变的情况下可切换的&rdquo;，这种架构策略完全可行，而且实际代码上也比JSF/asp.net等&ldquo;server page&rdquo;变种优雅，本人已经在N个项目中实践过： <br /><br /><a href="http://www.xjawa.org/xjawa/kontent/10020.html" target="_blank"><span style="color: #006699;">http://www.xjawa.org/xjawa/kontent/10020.html</span></a> <br /><br />使用这个7wxAop框架（<span style="color: #ff0000;">浏览器端7wx + 服务器端Aop</span>），有个朋友喜欢用Ext做前端，他就把7wx替换成Ext， 照样跑得很好：</p>
<p><a href="http://www.deepsoft.com.cn/ext-aop/demo.html"><span style="color: #006699;">http://www.deepsoft.com.cn/ext-aop/demo.html</span></a><br /><br /><br />那些不理解fins得同学，可能是没做过ajax开发，或者ajax用的比较少，或者思想已经被&ldquo;server page &rdquo;方式禁锢。虽然本质上 jsf 还是&ldquo;server page&rdquo;，但确实比jsp、tag强很多；但是比起B/S完全分开得架构，jsf还是很丑陋。 我们公司有个项目组用得是SAP得WebDynpro，和jsf类似，要比jsf成熟，但实际开发起来也是很多问题。 <br /><br />个人认为，<span style="color: #ff0000;">fins设想的这种架构之所以未被普遍关注，是因为它损害了J2ee大厂商的商业利益，因此他们控制的主流IT媒体不愿宣传</span>。想想：如果Server只是用来接受数据-&gt;处理逻辑-&gt;返回数据，服务器端将非常<span style="color: #ff0000;">Lightweight和Performance</span>，大厂商的J2EE服务器还有高性能硬件还会有几个项目需要？&nbsp;</p>
<p>＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝</p>
<p>。。。正如我前面提到的，不理解fins的人大多是思想被&ldquo;server pages&rdquo;禁锢者，总认为浏览器只能负责 html render，其他一切都应该在服务器上完成，就如IT业早期的主机－字符终端模式。这种思想本质上是把浏览器看作21世纪的字符终端，完全忽略和闲置了目前运行浏览器pc的强大计算功能。 </p>
<p>。。。在一个server和UI无关的模式下（下面称为&ldquo;server business&rdquo;，与&ldquo;server pages&rdquo;对应），server只是一个business logic server（只接受UI请求改变或查询系统的state），大致相当于砍掉了J2EE的Web层，Lightweight是肯定的了，至于Performance，除了节省处理 <br />'UI layer'(NOT ONLY 'presentation layer') 的CPU开销，更重要的是大量节省服务器的出口带宽开销。</p>
<p>。。。&ldquo;server business&rdquo;模式(SB)与&ldquo;server pages&rdquo;模式(SP)本质上是不同的,SP下的'presentation layer'概念并不适合SB模式。</p>
<p>。。。将ajax单纯地视为transition technology本身也没什么错，虽然Ext等前端组件已经超越了这一概念。用Javascript framework称呼基于浏览器的&ldquo;全功能UI Layer"并不适当，后者 = HTML + DHTML(DOM) + JS，JS只是一个粘合剂，Ext等前端组件本质上只是扩展了HTML Element，假设HTML 6.0包含了功能强大的TreeView、ListView(GRID)等通用UI组件，则JS将回归为单纯的DHTML API操纵语言。 至于RIA(javaFx or Flex)，我认为它是侧重多媒体表现的&ldquo;全功能UI Layer"的等价物，它的发展前景取决于厂商对它的定位，如果你认同RIA，就没有理由不认同&ldquo;全功能UI Layer"的思路。<br />&nbsp;</p>
</td>
</tr>
</tbody>
</table>
</p>
<p><br /><br />JSF：基于服务器端的UI模型，Ext:基于浏览器端的UI模型。 <br /><br />很多人质疑以JavaScript为中心的UI开发，其实是对html/JavaScript的恐惧。 <br /><br />1、 不要把《 HTML(含CSS) + DHTML（或DOM）API + JavaScript开发》 简单理解成 JavaScript 开发。很多人觉得&ldquo;JavaScript&rdquo;难以掌握，是因为他们混淆了JavaScript脚本语言本身和它所要操纵的API：其实JavaScript本 身 非常简单，但它所要操纵的API&ldquo;非常复杂&rdquo;，因为HTML(含CSS) + DHTML（或DOM）API所涉及的API对象、属性、方法、事件数量巨大，可以说和Win32 API,JDK API（不单是swing/awt）同一个数量级。 <br /><br />2、HTML(含CSS)作为UI的表达语言，其&ldquo;潜在的&rdquo;界面表达能力应该说远远超越任何已有高端UI组件库（asp.net，jsf，Ext...）,因为它们本 身 都是基于HTML(含CSS)开发的，要想完整地（还不含绘图）、无障碍表达WebUI，掌握HTML(含CSS)及其API--DHTML（或DOM）是必由之路。 <br /><br />3、所有高端UI组件库的设计思想都是提高组件粒度，以掩盖HTML(含css)的复杂性。不同在于，服务器端UI组件（asp.net，jsf）试图&ldquo;彻底&rdquo;掩盖，它们排斥直接使用HTML(含css)，并且操纵UI组件的API面向后端语言（C#,VB,java）；而浏览器端UI组件（Ext等）是&ldquo;开放性的&rdquo;封装，允许直接操作HTML(含css)，操纵UI组件的API面向javascript。 <br /><br />4、服务器端UI组件的使用者，一般不太关心组件的具体实现，而且使用中也缺乏HTML+JS的训练，当组件功能满足不了要求时，自己扩展组件的难度很大，也就时说使用组件和开发组件之间存在巨大鸿沟。而浏览器端UI组件的使用者，一般会大致了解组件的实现，使用中频繁接触HTML，JS操纵能力也得到训练，因此他们会比较自然地形成组件改造扩展能力，使用组件和扩展组件之间得学习曲线是平滑的。 <br /><br />5、因此，从开发人员自身职业发展的角度看，要想成为无障碍的Web开发者，使用浏览器端UI组件模式应该是更好的选择。 <br /><br />作为Web开发者，必须热情拥抱HTML（css）和javascript，否则只能是半拉子开发人员。</p>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://www.javaeye.com/topic/185839#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><li><a href='/adverts/70' target='_blank'><span style="color:red;font-weight:bold;">第二届网络工程师侠客行大会5月24日杭州举行</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 23 Apr 2008 12:09:11 +0800</pubDate>
        <link>http://www.javaeye.com/topic/185839</link>
        <guid>http://www.javaeye.com/topic/185839</guid>
      </item>
          <item>
        <title>西方人通常发现不了的一个IE的bug</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://hax.javaeye.com">hax</a>&nbsp;
                    链接：<a href="http://www.javaeye.com/topic/191555" style="color:red;">http://www.javaeye.com/topic/191555</a>&nbsp;
          发表时间: 2008年05月09日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/>
          这个问题我大概在一年多以前在某个用到VML的页面中（当时倒是记录了<a href="http://blog.csdn.net/hax/archive/2006/11/23/1406679.aspx" target="_blank">VML的一个严重问题</a>）首次发现了这个Bug。经过一番狗狗之后，也未发现有同样的报告。后来我又逐渐在几种其他非VML的情形下重现了这个奇异的Bug。经过一番探究，我大致推断出了这个bug的原因。不过我一直没有公开发布过这个有趣的问题，只是跟少数同事提到过它。这个bug有个有趣的特点，就是西方人通常不会碰到这个bug。<br /><br />最近，<a href="http://realazy.org/blog/" target="_blank">真懒</a>同学（realazy）在<a href="http://realazy.org/blog/2008/03/29/understand-0-settimeout/#comment-63839" target="_blank">《认识延迟时间为 0 的 setTimeout》一文</a>中举例说明setTimeout的用途。代码大意如下：<br /><pre name="code" class="javascript">
$('myButton').onmousedown = function () {
  var input = document.createElement('input');
  input.type = 'text';
  $('myDiv').appendChild(input);
  input.focus();
}
</pre><br />在IE中，新创建的input没有如预期的获得焦点。<br /><br />如果把input.focus()放在一个setTimeout中延时执行，则就可以获得焦点。<br /><br />这个例子本身其实并不能证明realazy想要说明的观点，因为他不小心碰到了一个IE的微妙bug。在<a href="http://realazy.org/blog/2008/03/29/understand-0-settimeout/#comment-51746" target="_blank">留言</a>中，<a href="http://www.lunaticsun.com/" target="_blank">Lunatic Sun</a>倒是敏锐的判断出这是IE的bug，只是这个bug的本质不是那么容易认识到，它其实并不是onmousedown本身的bug。<br /><br />实际上，这是IE的focus机制的bug。<br /><br />IE中的所有元素其实并不是被凭空绘制出来的，而是统统基于已有的Windows控件之上。除了典型的按钮、下拉菜单等，普通的div其实也是一个textbox控件。<br /><br />所以IE的HTML focus等实际是被转换为windows控件的focus，于是在IE中存在两种不同层次的focus机制。理想上，HTML的focus应该被同步转换为windows控件的focus，然而IE可怜的代码导致这种转换存在许多bug。我们经常遇到的焦点虚线框丢失的问题其实就是一个例子。<br /><br />实际上，在上面的例子中，表面上input没有得到焦点，但是其实调用focus()之后，HTML focus确实已经到了新生成的input中，这一点你可以通过document.activeElement来验证，你也可以按tab和shift-tab观察焦点的切换来证明这一点。然而，由于mousedown事件默认会获得控件焦点，所以windows控件focus就跑回了你的按钮上面了。这里出现了windows focus机制和html focus机制的脱节。显然IE在focus上的同步代码实在是太脆弱了。<br /><br />事实上，IE对焦点的控制似乎本来就不和逻辑。所有的<a href="http://www.satzansatz.de/cssd/onhavinglayout.html" target="_blank">hasLayout</a>元素都能获得焦点！结果一个页面上大部分区域在mousedown之后焦点就不知跑到哪个元素上了——这显然不是我们想要的行为——合乎HTML规范逻辑的行为应该是只有交互控件，如表单控件和A元素等，才能获得焦点。这导致一个典型的用户体验问题：在一个限制高度的可卷动区域中（例如一个长表单），拖动scrollbar，控件焦点就丢失——实际焦点跑到scrollbar所在的元素（例如form元素）上了。最严重的是，body元素一般总会有scrollbar！为了缓解这个问题，微软为body元素打了补丁，使得body上的scrollbar不会抢走焦点。然而IE这个patch打得实在是太烂了，在标准模式下，canvas从body变成了html元素，所以页面scrollbar就到了html元素上，结果bug又回来了！<br /><br />撇开抢焦点问题，我们回到前面的话题。<br /><br />既然html focus还是在input元素上，那么当时windows控件焦点到底跑哪里去了？实际上这个焦点跑到了mousedown所发生的对象上。比如如果是一个input按钮，焦点就会在该input按钮实际对应的windows的Button控件上。不过button元素的实现和一般的input不同，所以button元素上的mousedown之后，windows控件焦点实际上会跑到button元素外层的那个元素所对应的windows控件（通常是TextBox控件）中。<br /><br />如何证明这一点？我过去用过一个调试工具可以显示出每个html控件实际的windows控件，也能查看实际的windows系统焦点。不过现在想不起来那个工具的名称了。搞笑的是，此处还会出现一个非常orz的症状——也就是本文标题所称的“西方人通常发现不了的一个IE的bug”——可以证明这一点。<br /><br />focus问题 + 西方人通常发现不了。各位是否已经猜到了呢？大家不妨用<a href="http://realazy.org/lab/settimeout.html" target="_blank">realazy的那个页面</a>中的第一个按钮来直接实验一把。<br /><br />我会在下篇blog中继续聊这个话题。
          <br/>
          <span style="color:red;">
            <a href="http://www.javaeye.com/topic/191555#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><li><a href='/adverts/70' target='_blank'><span style="color:red;font-weight:bold;">第二届网络工程师侠客行大会5月24日杭州举行</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 09 May 2008 20:00:12 +0800</pubDate>
        <link>http://www.javaeye.com/topic/191555</link>
        <guid>http://www.javaeye.com/topic/191555</guid>
      </item>
          <item>
        <title>Scala拾趣--从Java7说开来</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://eastsun.javaeye.com">Eastsun</a>&nbsp;
                    链接：<a href="http://www.javaeye.com/topic/190608" style="color:red;">http://www.javaeye.com/topic/190608</a>&nbsp;
          发表时间: 2008年05月07日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/>
          　　我们知道，关于当前正在进行中的Java7在Java社区有很多讨论。其焦点集中在要不要在Java7中引入一些新的语言特性，尤其是闭包：不仅有要不要加入闭包的争论，还有采用那种实现方式的问题。在<a href="http://www.javaeye.com/news/598" target="_blank">javapolis举行的关于JAVA7语言特性投票的结果</a>一文中列出了Java7中可能会加入的语言特性，那么我们先来看看在Scala中对于这些语言特性有何解决方式呢？<br />　　首先把闭包撇出来，因为对闭包不甚了解，所以就不多说。不过以我的看法，因为Scala本身就支持函数式编程，而Java还需要向后兼容性的考虑，所以我觉得Java7中无论以那种方式来实现闭包，也不太可能比Scala中的实现更加有效，或更加优雅。<br />　　下面我们就逐条来分析Java7中的十种语法提议：<br /><br /><span style="font-size: large"><span style="color: blue">1.Property declaration <br />2.Property access</span></span><br />　　这两条都是为了简化书写代码，这里用一个具体的例子来说明：<br />　　考虑一个Person类，这个类有两个属性，一个是表示姓名的forename，在JAVA7中可能的实现方式：<br /><br /><pre name="code" class="java">public class Person {   
 public property String forename;   
 public property int age;   
} </pre>  <br /><br />　　而使用Scala，则可以使用下列方式（参考资料:<a href="http://scala.sygneca.com/code/defining-bean-properties" target="_blank">Defining a BeanProperty</a>）：<br /><br /><pre name="code" class="java">class Person(@BeanProperty var forename:String,@BeanProperty var age:Int)</pre><br /><br />　　一行代码搞定！<br /><br /><br /><br /><span style="font-size: large"><span style="color: blue">3.Improve generics </span></span><br />　　Scala中也支持泛型，而且貌似比JAVA中的更灵活。不过其实现方式与目前的JAVA一样，都是使用擦除化。因此对于JAVA中泛型存在的一些问题，Scala中也存在（唔，至少这条提议中列出的两个问题在Scala中也同样存在。其他的我不太清楚，目前对Scala中的泛型理解尚浅）。<br /><br /><br /><span style="font-size: large"><span style="color: blue">4.Access List and Map using [] </span></span><br />　　在Scala中可以像下面一样使用Map：<br /><br /><pre name="code" class="java">import scala.collection.mutable.HashMap
object MapAccess extends Application{
    var map =new HashMap[Int,String]
    map(0) ="Zero"
    map += 1 -> "One"
    map += 2 -> "Two"
    var value =map(2)
    println(value)
}</pre><br />　　可以看到，在Scala中可以通过map(key)来读取value，通过map(key) =value来写入值对...<br />　　事实上，Scala中并没有对Map提供特别的语法支持。也就是说，对任意的类，你都可以像操作Map一样。只要你在一个类a中定义了apply方法，你就可以把这个类当作一个函数来使：<br />   　　a(something)<br />　　这等价于：<br />   　　a.apply(something)<br />　　如果你还定义了update方法，你就可以使用<br />   　　a(key) =value<br />　　这等价于<br />   　　a.update(key,value)<br />　　怎么样，不赖吧？<br /><br /><span style="font-size: large"><span style="color: blue">5,10.Null-handling and chaining </span></span><br />　　之所以有这两个提议，是由于在Java中存在null以及void类型的方法。在Java中，很多方法会返回一个null表示没有得到预期的结果或结果为空。事实上大部分情况这样做是不恰当的，这时抛出一个异常或返回一个表示“空”的对象（比如字符串“”，或空的List）可能更合理。而且这样造成的后果是对于很多方法调用后需要对其结果是否为null进行判断。<br />　　幸运的是，Scala中没有这些问题。<br />　　第一：在Scala中没有void，也就是说每个方法都会返回一个实实在在的对象（Java中的void在Scala中有一个对应的类Unit）。<br />　　第二：对于标准的Scala程序，null不应该出现。相对应的Scala中有个Option类来处理这种返回结果为“空”的情形。<br />　　这里我也不细说了，有兴趣的可以参看opensdp同学的 <a href="http://www.javaeye.com/topic/72517" target="_blank">用Scala语言中的 Option 对象来处理 null-like 返回值</a>。<br /><br /><br /><br /><span style="font-size: large"><span style="color: blue">6.Extension methods</span></span><br />　　这个有点像Ruby中的“open class”，就是允许在不修改原有代码的情况下给已有的类添加新的方法。<br />　　由于JAVA是静态类型语言，并且不允许两个名字完全一样的类的存在，所以在这一点的实现肯定会有很多限制，不可能做到像Ruby那样。目前有两种解决方案：<br /><div class="quote_title">Neal Gafter的提议 写道</div><div class="quote_div"><br />example:<br /><br />import static java.util.Collections.sort;<br />…<br />List&lt;String> list = …;<br />list.sort();</div><br /><div class="quote_title">Peter Ahé's 的提议 写道</div><div class="quote_div"><br />"Declaration-Site Extension Methods"<br /><br />example:<br /><br />package java.util;<br />interface List&lt;E> … {  <br />…  <br />void sort() import static java.util.Collections.sort;  <br />…<br />}</div><br />　　第一种实现可以看成是static import的一种扩展；第二种实现同样用到了static import，并且需要修改已有代码。<br />　　总之，这两种方案看起来都不甚优雅，甚至可以说丑陋。其意义也不大，并且对代码的可读性会造成一定的影响。<br />　　那么我们来看看Scala中能够如何解决这个问题。Scala中有一个功能很强大的机制：隐式类型转换。这个机制威力巨大，用处远不止Extension methods，这里我只举个例子说明如何解决Extension methods，有兴趣的可以参看fakechris同学写的<a href="http://www.javaeye.com/topic/107445" target="_blank">scala学习笔记(5) -- implicit type</a><br /><br />　　在Ruby中，我们可以通过如下方式向String类中添加print_self方法：<br /><br /><pre name="code" class="ruby">class String
  def print_self
    puts self
  end
end</pre><br />　　然后我们就可以对普通的String调用print_self方法：<br /><br /><pre name="code" class="ruby">"Daniel Spiewak".print_self    # prints my name</pre><br />　　那么Scala中怎末实现这个呢？很简单：<br /><br /><pre name="code" class="java">object StringTest extends Application{
    
    "Eastsun".printSelf         //对String调用printSelf方法
    
    implicit def stringWrapper(s:String) =new {
        def printSelf = println(s)
    }
}</pre><br />　　并且在类StringTest可见的范围内，这个方法都是有效的。（参考资料：<a href="http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-6" target="_blank">Getting Over Java</a>）<br /><br /><br /><br /><span style="font-size: large"><span style="color: blue">7.String switch </span></span><br />　　在Scala中没有switch关键词，但是有另一个强大得多的机制（不过，也复杂得多）：Pattern match。实现String switch功能，那只不过是小菜一根。<br /><br /><pre name="code" class="java">object MatchTest extends Application{
    
    test("hello")
    
    def test(obj:Any):Unit = obj match{
        case 1|2|3|4 => println("A integer between 1 and 4")
        case "hello" => println("Hi")
        case _       => println("something else")
    }
}</pre><br /><br /><br /><br /><span style="font-size: large"><span style="color: blue">8.Typedef </span></span><br />　　唔，对于这个功能...但是Scala中恰好就有这样一个关键词type，typedef就是这个关键词的作用之一：<br /><br /><pre name="code" class="java">import scala.collection.mutable.HashMap
object TypeTest extends Application{
    type ISMap =HashMap[Int,String]
    var map =new ISMap
    map(1) ="One"
}</pre><br /><br /><br /><br /><br /><span style="font-size: large"><span style="color: blue">9.Multi-catch </span></span><br />　　这里我们又可以来感受一下Scala中Pattern match的威力了：<br /><pre name="code" class="java">import java.io.IOException
object ExceptionCatch extends Application{
    
    exceptionCatch(ioexceptionThrow)
    
    def ioexceptionThrow():Unit = throw new IOException
    
    def exceptionCatch(func:()=>Unit) =
        try{
            func()
        }catch{
            case _:IllegalArgumentException|_:IllegalStateException => println("RuntimeException")
            case _:IOException => println("IOException")
            case _             => println("Something else")
        }
}</pre><br /><br />　　<span style="font-size: large"><span style="color: blue">结论</span>：</span>可以看到，目前在JAVA中想方设法想要加入的语言特性，在Scala中要么是根本不需要的东西，要么是以一种更加优雅的方式实现了。但同时，在学习Scala的过程中，一方面感受到Scala语法所带来的巨大便利与威力，另一方面其语法比起JAVA来复杂了许多。譬如在Java中粗略来说有interface,abstract class与普通的class。而Scala中除了普通的class与abstract class还有case class，sealed class,trait,object这些类型的类。更不用说Scala中那些函数式有关的语法了。<br />　　因此，可能有人会有疑问：有着这么复杂语法的语言有存在的必要吗？会不会成为下一个C++（恐龙）呢？Daniel Spiewak 在他的博客<a href="http://www.codecommit.com/blog/scala/is-scala-really-the-next-c" target="_blank">Is Scala Really the Next C++?</a>中也提出了这个问题，他的答案是否定的。主要的理由是：C++由于兼容的目的背上了C这个沉重的包袱，由于C的影响，很多语言特性不能很好的实现；而Scala不同，Scala与Java在源代码上是不兼容的，因此Scala可以更加自由的发挥。<br />　　我比较赞同这个观点，同时觉得Java7不应该添加太多的东西进去了。虽然现在Java代码相对其他新式语言来说显得啰嗦，但是Java简单，这就是它最大的优点。如果Java即要考虑向后兼容性，又想把新的特性一股脑加进去，到后来只可能走向C++的老路。这些新的特性应该由JVM上的其他语言来实现，比如Groovy，比如Scala。你觉得呢？<br />　　<span style="color: red">PS:</span>根据达尔文的理论，生物的进化包括<span style="color: blue">遗传</span>与<span style="color: blue">变异</span>。而目前的JAVA是只“遗传”不“变异”，这对于语言的进化也是很不好的。
          <br/>
          <span style="color:red;">
            <a href="http://www.javaeye.com/topic/190608#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/70' target='_blank'><span style="color:red;font-weight:bold;">第二届网络工程师侠客行大会5月24日杭州举行</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>Wed, 07 May 2008 15:02:14 +0800</pubDate>
        <link>http://www.javaeye.com/topic/190608</link>
        <guid>http://www.javaeye.com/topic/190608</guid>
      </item>
          <item>
        <title>AJAX不应该只是EXT</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://7thbyte.javaeye.com">7thbyte</a>&nbsp;
                    链接：<a href="http://www.javaeye.com/topic/184120" style="color:red;">http://www.javaeye.com/topic/184120</a>&nbsp;
          发表时间: 2008年04月18日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/>
          　　AJAX不应该只是EXT，就像Ruby不应该只是rails。<br /><br />　　从去年早些时候到现在，我的感想是，AJAX版面里，关于EXT的讨论渐渐增多，已经成为这个版面事实上绝对的主流话题。渐渐，很多朋友已经开始不再帖子里注明“这个帖子是针对EXT的”而直接开始讨论其中涉及的概念。仿佛默认地，一切这里的话题都是EXT一样。<br /><br />　　在我看来这是一个不太好的信号。当一个综合讨论版面的话题被某一方面的话题占据时，意味着其他的话题的空间渐渐缩小。社区变得渐渐失去活力，失去创新的氛围。<br /><br />　　或许我是杞人忧天，但是正如最近的置顶贴中说到的那样：<br /><br />　　<a href="http://www.javaeye.com/topic/179990" target="_blank">http://www.javaeye.com/topic/179990</a><br /><br /><div class="quote_title">引用</div><div class="quote_div">　　论坛上50％所提出的问题很大原因是对JavaScript知识不牢固所至，而不是EXT API的问题<br /></div><br /><br />　　诚然EXT是一个强大的组件库，充满作者努力和智慧的结晶。曾经基于prototype.js自己DIY过个人组件库的我，深知这其中需要多大的付出。作为如此优秀的产品，被广大开发者使用也是理所当然。<br /><br />　　然而EXT只是EXT，EXT只是Ajax，也许它能够解决大部分的问题，但是它不能解决所有的问题。<br />　　不仅仅是EXT，所有的组件库都不能解决所有的问题。<br /><br />　　对于大多数人而言，也许EXT的价值在于，能够更漂亮地把自己的界面需求制作出来，提供更好的交互性。<br /><br />　　可是当需求遭遇创新，没有良好的基础，没有适应创新的整体架构理念，创新无从谈起。<br /><br />　　使用EXT，无法搭建出Gmail，Google Reader，Google Calendar。因为他们是一体的，需要整体设计的产品方案，不仅是“前端使用某个组件库搭建”的东西。<br /><br />　　类似实现地图服务上面的跨浏览器的地图导航+路径绘制。或者实现在浏览器中如流程图绘制那样的富交互，并且与服务器端建立良好的关联模型，这些都不是一个框架，一个组件库能解决的问题。<br /><br />　　可以实践的东西实在很多：<br />　　javascript如何使用SVG/VML/Canvas统一封装的函数库进行浏览器绘图，这个过程中的实践；<br />　　如何同REST的服务架构结合，为web站点创造出一套更简洁的API，以利于数据源整合和mashup应用；<br />　　如何围绕客户端交互进行自动化测试；<br />　　浏览器的新特性对于客户端带来怎样的影响，围绕这些影响和变化的实践；<br />　　<br />　　……<br /><br />　　这些声音有，但还是太少。并不是责怪“来这里就是为了对付手头问题的人”。只是觉得为了论坛产生更多高质量讨论，需要更多的人在不同方面贡献出自己的实践。<br /><br />　　恰是这种实践，是到处都缺乏的东西。回避这种实践和探索，就只能沿着他人铺设的路线行走，创造不出自己的killer应用。也不能传播自己的理念。
          <br/>
          <span style="color:red;">
            <a href="http://www.javaeye.com/topic/184120#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/70' target='_blank'><span style="color:red;font-weight:bold;">第二届网络工程师侠客行大会5月24日杭州举行</span></a></li><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>Fri, 18 Apr 2008 15:18:29 +0800</pubDate>
        <link>http://www.javaeye.com/topic/184120</link>
        <guid>http://www.javaeye.com/topic/184120</guid>
      </item>
          <item>
        <title>open session and Hibernate事务处理机制</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://life4j.javaeye.com">ivorytower</a>&nbsp;
                    链接：<a href="http://www.javaeye.com/topic/186068" style="color:red;">http://www.javaeye.com/topic/186068</a>&nbsp;
          发表时间: 2008年04月23日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/>
          在没有使用Spring提供的Open Session In View情况下，因需要在service(or Dao)层里把session关闭，所以lazy loading 为true的话，要在应用层内把关系集合都初始化，如 company.getEmployees()，否则Hibernate抛session already closed Exception;    Open Session In View提供了一种简便的方法，较好地解决了lazy loading问题.     <br />    它有两种配置方式OpenSessionInViewInterceptor和OpenSessionInViewFilter(具体参看SpringSide)，功能相同，只是一个在web.xml配置，另一个在application.xml配置而已。     <br />     Open Session In View在request把session绑定到当前thread期间一直保持hibernate session在open状态，使session在request的整个期间都可以使用，如在View层里PO也可以lazy loading数据，如 ${ company.employees }。当View 层逻辑完成后，才会通过Filter的doFilter方法或Interceptor的postHandle方法自动关闭session。 <br />     OpenSessionInViewInterceptor配置<br /><pre name="code" class="xml">  
&lt;beans> 
&lt;bean name="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor"> 
&lt;property name="sessionFactory"> 
&lt;ref bean="sessionFactory"/> 
&lt;/property> 
&lt;/bean> 
&lt;bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> 
&lt;property name="interceptors"> 
&lt;list> 
&lt;ref bean="openSessionInViewInterceptor"/> 
&lt;/list> 
&lt;/property> 
&lt;property name="mappings"> 
... 
&lt;/property> 
&lt;/bean> ... &lt;/beans> 
</pre><br />OpenSessionInViewFilter配置<br /><pre name="code" class="xml"> 
&lt;web-app> 
... 
&lt;filter> 
&lt;filter-name>hibernateFilter&lt;/filter-name> 
&lt;filter-class> org.springframework.orm.hibernate3.support.OpenSessionInViewFilter &lt;/filter-class> 
&lt;!-- singleSession默认为true,若设为false则等于没用OpenSessionInView --> 
&lt;init-param> 
&lt;param-name>singleSession&lt;/param-name> 
&lt;param-value>true&lt;/param-value> 
&lt;/init-param> 
&lt;/filter> ... &lt;filter-mapping> 
&lt;filter-name>hibernateFilter&lt;/filter-name> 
&lt;url-pattern>*.do&lt;/url-pattern> 
&lt;/filter-mapping> ... &lt;/web-app> 
</pre><br />    很多人在使用OpenSessionInView过程中提及一个错误：<br />org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.NEVER) - turn your Session into FlushMode.AUTO or remove 'readOnly' marker from transaction definition <br />    看看OpenSessionInViewFilter里的几个方法 <br /><pre name="code" class="java">  

protected void doFilterInternal(HttpServletRequest request, 
		HttpServletResponse response,
		FilterChain filterChain) throws ServletException, IOException {　
	    SessionFactory sessionFactory = lookupSessionFactory();　
		logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");　
		Session session = getSession(sessionFactory);　
		TransactionSynchronizationManager.bindResource(　　
				sessionFactory, new SessionHolder(session));　
		try {　　
			filterChain.doFilter(request, response);　
			}　
		finally {　
			TransactionSynchronizationManager.unbindResource(sessionFactory);　
		logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");　
		closeSession(session, sessionFactory);　
		}
} 
protected Session getSession(SessionFactory sessionFactory)
                   throws DataAccessResourceFailureException {　
		Session session = SessionFactoryUtils.getSession(sessionFactory, true);　
		session.setFlushMode(FlushMode.NEVER);　
		return session;
}
protected void closeSession(Session session, 
		SessionFactory sessionFactory)throws CleanupFailureDataAccessException {　
	SessionFactoryUtils.closeSessionIfNecessary(session, sessionFactory);
}
</pre><br />     关于绑定session的方式，通过看spring里TransactionSynchronizationManager的实现，发现：它维护一个 <span style="color: red">java.lang.ThreadLocal类型</span>的resources，resources负责持有线程局部变量，这里resources持有的是一个 HashMap，通过TransactionSynchronizationManager.bindResource()方法在map里绑定和线程相关的所有变量到他们的标识上，包括如上所述的绑定在sessionFactory上的线程局部session。sessionHolder只不过是存放可以 hold一个session并可以和transtaction同步的容器。可以看到 OpenSessionInViewFilter在getSession的时候,会把获取回来的session的flush mode 设为FlushMode.NEVER。然后把该sessionFactory绑定到 TransactionSynchronizationManager，使request的整个过程都使用同一个session，在请求过后再接除该 sessionFactory的绑定，最后closeSessionIfNecessary根据该session是否已和transaction绑定来决定是否关闭session。绑定以后，就可以防止每次不会新开一个Session呢？看看HibernateDaoSupport的情况：<br /><pre name="code" class="java">  
 public final void setSessionFactory(SessionFactory sessionFactory) { 
 this.hibernateTemplate = new HibernateTemplate(sessionFactory); 
 }  
protected final HibernateTemplate getHibernateTemplate() {
 return hibernateTemplate;  
}         
</pre><br />     我们的DAO将使用这个template进行操作. <br /><pre name="code" class="java">   
public abstract class BaseHibernateObjectDao 
                extends HibernateDaoSupportimplements BaseObjectDao {     
protected BaseEntityObject getByClassId(final long id) {                
BaseEntityObject obj =(BaseEntityObject)getHibernateTemplate().execute(new HibernateCallback() {                        
public Object doInHibernate(Session session) 
         throws HibernateException{                                    
 return session.get(getPersistentClass(),new Long(id));                
       }                
    }
);                
return obj;      
}     
public void save(BaseEntityObject entity) {                  
       getHibernateTemplate().saveOrUpdate(entity);     
}     
public void remove(BaseEntityObject entity) {              
try {                     
       getHibernateTemplate().delete(entity);              
} catch (Exception e) {                      
       throw new FlexEnterpriseDataAccessException(e);             
       }      
}       
public void refresh(final BaseEntityObject entity) {               
       getHibernateTemplate().execute(new HibernateCallback(){                          
            public Object doInHibernate(Session session) 
           throws HibernateException   {                                
                 session.refresh(entity);                                      
                 return null;                          
            }               
       }
    );      
}      
public void replicate(final Object entity) {                
       getHibernateTemplate().execute(new HibernateCallback(){                          
             public Object doInHibernate(Session session)
                           throws HibernateException{                                      
                  session.replicate(entity,ReplicationMode.OVERWRITE);                 
                  eturn null;               
             }                
      });      
   }
}           
</pre>    <br />   而HibernateTemplate试图每次在execute之前去获得Session，执行完就力争关闭Session <br /><pre name="code" class="java">  
public Object execute(HibernateCallback action) throws DataAccessException {  
       Session session = (!this.allowCreate)SessionFactoryUtils.getSession(getSessionFactory(),
                         false);     
       SessionFactoryUtils.getSession(getSessionFactory(),
                                      getEntityInterceptor(), 
                                      getJdbcExceptionTranslator()));     
       boolean existingTransaction = TransactionSynchronizationManager.hasResource(
                                       getSessionFactory());   
       if (!existingTransaction && getFlushMode() == FLUSH_NEVER) {   
            session.setFlushMode(FlushMode.NEVER);  
}      
try {          
     Object result = action.doInHibernate(session);           
     flushIfNecessary(session, existingTransaction);           
     return result;    
}    
catch (HibernateException ex) {          
throw convertHibernateAccessException(ex);     
}     
finally {     
    SessionFactoryUtils.closeSessionIfNecessary(  
    session, getSessionFactory());     
    } 
}       
</pre><br />   而这个SessionFactoryUtils能否得到当前的session以及closeSessionIfNecessary是否真正关闭 session，取决于这个session是否用sessionHolder和这个sessionFactory在我们最开始提到的 TransactionSynchronizationManager绑定。      <br /><pre name="code" class="java">  
public static void closeSessionIfNecessary(Session session, 
                                           SessionFactory sessionFactory) 
                throws CleanupFailureDataAccessException { 
   if (session == null || TransactionSynchronizationManager.hasResource(sessionFactory)) { 
               return; 
} 
        logger.debug("Closing Hibernate session"); 
try { 
        session.close(); 
} catch (JDBCException ex) { // SQLException underneath
    throw new CleanupFailureDataAccessException("Could not close Hibernate session", 
                                             ex.getSQLException()); 
} catch (HibernateException ex) { 
    throw new CleanupFailureDataAccessException("Could not close Hibernate session", 
              ex); 
       } 
}     
</pre><br />     在这个过程中，若HibernateTemplate 发现自当前session有不是readOnly的transaction，就会获取到FlushMode.AUTO Session，使方法拥有<span style="color: red">写</span>权限。也即是，如果有不是readOnly的transaction就可以由Flush.NEVER转为 Flush.AUTO,拥有insert,update,delete操作权限，如果没有transaction，并且没有另外人为地设flush model的话，则doFilter的整个过程都是Flush.NEVER。所以受transaction保护的方法有写权限，没受保护的则没有。 <br />     可能的解決方式有：<br />1、将singleSession设为false，这样只要改web.xml，缺点是Hibernate Session的Instance可能会大增，使用的JDBC Connection量也会大增，如果Connection Pool的maxPoolSize设得太小，很容易就出问题。<br />2、在控制器中自行管理Session的FlushMode，麻烦的是每个有Modify的Method都要多几行程式。      <br />session.setFlushMode(FlushMode.AUTO);       <br />session.update(user);       <br />session.flush(); <br />3、Extend OpenSessionInViewFilter，Override protected Session getSession(SessionFactory sessionFactory)，将FlushMode直接改为Auto。<br />4、让方法受Spring的事务控制。这就是常使用的方法： 采用spring的事务声明,使方法受transaction控制  <br /><pre name="code" class="xml">   
&lt;bean id="baseTransaction" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"           
abstract="true">         
&lt;property name="transactionManager" ref="transactionManager"/>         
&lt;property name="proxyTargetClass" value="true"/>         
&lt;property name="transactionAttributes">             
&lt;props>                 
&lt;prop key="get*">PROPAGATION_REQUIRED,readOnly&lt;/prop>                 
&lt;prop key="find*">PROPAGATION_REQUIRED,readOnly&lt;/prop>                 
&lt;prop key="load*">PROPAGATION_REQUIRED,readOnly&lt;/prop>                 
&lt;prop key="save*">PROPAGATION_REQUIRED&lt;/prop>                 
&lt;prop key="add*">PROPAGATION_REQUIRED&lt;/prop>                 
&lt;prop key="update*">PROPAGATION_REQUIRED&lt;/prop>                 
&lt;prop key="remove*">PROPAGATION_REQUIRED&lt;/prop>             
&lt;/props>         
&lt;/property>     
&lt;/bean>     
&lt;bean id="userService" parent="baseTransaction">         
&lt;property name="target">             
&lt;bean class="com.phopesoft.security.service.impl.UserServiceImpl"/>         
&lt;/property>     
&lt;/bean> 
</pre><br />    对于上例，则以save,add,update,remove开头的方法拥有可写的事务，如果当前有某个方法，如命名为importExcel()，则因没有transaction而没有写权限，这时若方法内有insert,update,delete操作的话，则需要手动设置flush model为Flush.AUTO,如 session.setFlushMode(FlushMode.AUTO); session.save(user); session.flush();       <br />    尽管Open Session In View看起来还不错，其实副作用不少。看回上面OpenSessionInViewFilter的doFilterInternal方法代码，这个方法实际上是被父类的doFilter调用的，因此，我们可以大约了解的OpenSessionInViewFilter调用流程: request(请求)->open session并开始transaction->controller->View(Jsp)->结束transaction并 close session.      <br />    一切看起来很正确，尤其是在本地开发测试的时候没出现问题，但试想下如果流程中的某一步被阻塞的话，那在这期间connection就一直被占用而不释放。最有可能被阻塞的就是在写Jsp这步，一方面可能是页面内容大，response.write的时间长，另一方面可能是网速慢，服务器与用户间传输时间久。当大量这样的情况出现时，就有连接池连接不足，造成页面假死现象。 Open Session In View是个双刃剑，放在公网上内容多流量大的网站请慎用。   <span style="color: red">另外：这样会产生一点危险性，毕竟把数据库访问的环境放到了表现层。（：用VO） </span>                  <br />      Hibernate是对JDBC的轻量级对象封装，Hibernate本身是不具备Transaction处理功能的，Hibernate的 Transaction实际上是底层的JDBC Transaction的封装，或者是JTA Transaction的封装，下面我们详细的分析： 　　<br />      Hibernate可以配置为JDBCTransaction或者是JTATransaction，这取决于你在hibernate.properties中的配置: <br /><div class="quote_title">引用</div><div class="quote_div"> <br />#hibernate.transaction.factory_classnet.sf.hibernate.transaction.JTATransactionFactory #hibernate.transaction.factory_classnet.sf.hibernate.transaction.JDBCTransactionFactory<br /></div> 　　<br />     如果你什么都不配置，默认情况下使用JDBCTransaction，如果你配置为： <br /><div class="quote_title">引用</div><div class="quote_div">  <br />hibernate.transaction.factory_classnet.sf.hibernate.transaction.JTATransactionFactory <br /></div><br />     将使用JTATransaction，不管你准备让Hibernate使用JDBCTransaction，还是JTATransaction，我的忠告就是<span style="color: red">什么都不配</span>，将让它保持默认状态，如下： <br /><div class="quote_title">引用</div><div class="quote_div">   <br />#hibernate.transaction.factory_classnet.sf.hibernate.transaction.JTATransactionFactory #hibernate.transaction.factory_classnet.sf.hibernate.transaction.JDBCTransactionFactory 　　<br /></div><br />     在下面的分析中我会给出原因。 　　<br /><strong>一、JDBC Transaction </strong>　　<br />     看看使用JDBC Transaction的时候我们的代码例子： <br /><pre name="code" class="java">  
Session session = sf.openSession(); 
Transaction tx = session.beginTransactioin(); 
... session.flush(); 
tx.commit(); 
session.close(); 　　
</pre><br />     这是默认的情况，当你在代码中使用Hibernate的Transaction的时候实际上就是JDBCTransaction。那么JDBCTransaction究竟是什么东西呢？来看看源代码就清楚了： 　　Hibernate2.0.3源代码中的类net.sf.hibernate.transaction.JDBCTransaction: <br /><pre name="code" class="java">  
public void begin() throws HibernateException { 
... 
if (toggleAutoCommit) session.connection().setAutoCommit(false); 
... 
}　　
</pre><br />     这是启动Transaction的方法，看到 connection().setAutoCommit(false) 了吗？是不是很熟悉？ 　　<br />     再来看 <br /><pre name="code" class="java">   
public void commit() throws HibernateException { 
... 
try { 
 if ( 
 session.getFlushMode()!=FlushMode.NEVER ) 
session.flush(); 
try { 
session.connection().commit(); 
committed = true; 
} 
... 
toggleAutoCommit(); 
} 　　
</pre><br />     这是提交方法，看到connection().commit() 了吗？下面就不用我多说了，这个类代码非常简单易懂，通过阅读使我们明白Hibernate的Transaction都在干了些什么？我现在把用 Hibernate写的例子翻译成JDBC，大家就一目了然了： <br /><pre name="code" class="java">   
Connection conn = ...;               
&lt;--- session = sf.openSession(); 
conn.setAutoCommit(false);   
&lt;--- tx = session.beginTransactioin(); 
... 
&lt;--- ... conn.commit();                           
&lt;--- tx.commit(); 
(对应左边的两句) conn.setAutoCommit(true); 
conn.close();                              
&lt;--- session.close(); 　　
</pre><br />   看明白了吧，Hibernate的JDBCTransaction根本就是conn.commit而已，根本毫无神秘可言，只不过在Hibernate 中，Session打开的时候，就会自动conn.setAutoCommit(false)，不像一般的JDBC，默认都是true，所以你最后不写 commit也没有关系，由于Hibernate已经把AutoCommit给关掉了，所以用Hibernate的时候，你在程序中不写 Transaction的话，数据库根本就没有反应。  <br /><strong>二、JTATransaction </strong><br />     如果你在EJB中使用Hibernate，或者准备用JTA来管理跨Session的长事务，那么就需要使用JTATransaction，先看一个例子： <br /><pre name="code" class="java">  
javax.transaction.UserTransaction tx = new InitialContext().lookup("javax.transaction.UserTransaction"); 
Session s1 = sf.openSession(); 
... 
s1.flush(); 
s1.close(); 
... 
Session s2 = sf.openSession(); 
...
s2.flush(); 
s2.close();
tx.commit(); 
</pre><br />    这是标准的使用JTA的代码片断，Transaction是跨Session的，它的生命周期比Session要长。如果你在EJB中使用 Hibernate，那么是最简单不过的了，你什么Transaction代码统统都不要写了，直接在EJB的部署描述符上配置某某方法是否使用事务就可以了。 现在我们来分析一下JTATransaction的源代码， <br />net.sf.hibernate.transaction.JTATransaction: <br /><pre name="code" class="java">   
public void begin(InitialContext context, 
... 
... 
ut = (UserTransaction) context.lookup(utName); 
... 
</pre><br />    看清楚了吗？ 和我上面写的代码 “tx = new Initial Context?().lookup("javax.transaction.UserTransaction"); ”是不是完全一样？ <br /><pre name="code" class="java">  
public void commit() 
... 
... 
if (newTransaction) 
ut.commit();
 ... 
</pre><br />    JTATransaction的控制稍微复杂，不过仍然可以很清楚的看出来Hibernate是如何封装JTA的Transaction代码的。 但是你现在是否看到了什么问题？ 仔细想一下，Hibernate Transaction是从Session中获得的，tx = session.beginTransaction()，最后要先提交tx，然后再session.close，这完全符合JDBC的 Transaction的操作顺序，但是这个顺序是和JTA的Transactioin操作顺序彻底矛盾的！！！ JTA是先启动Transaction，然后启动Session，关闭Session，最后提交Transaction，因此当你使用JTA的 Transaction的时候，那么就千万不要使用Hibernate的Transaction，而是应该像我上面的JTA的代码片断那样使用才行。 <br />    <strong> 总结： </strong><br />1、在JDBC上使用Hibernate 必须写上Hibernate Transaction代码，否则数据库没有反应。此时Hibernate的Transaction就是Connection.commit而已; <br />2、在JTA上使用Hibernate 写JTA的Transaction代码，不要写Hibernate的Transaction代码，否则程序会报错;<br />3、在EJB上使用Hibernate 什么Transactioin代码都不要写，在EJB的部署描述符里面配置 <br />|---CMT(Container Managed Transaction) | <br />|---BMT(Bean Managed Transaction) | <br />|----JDBC Transaction | <br />|----JTA Transaction         <br />    <strong>关于session</strong>： <br />1.  servlet的session机制基于cookies,关闭浏览器的cookies则session失效即不能用网站的登录功能。<br />2.  Hibernate Session.       <br />       1>. session 清理缓存时，按照以下顺序执行SQL语句：             <br />session.save()的实体insert      <br />               实体的update             <br />               对集合的delete        <br />               集合元素的delete，update，insert             <br />               集合的insert             <br />session.delete()的先后，执行实体的delete        <br />       2>. 默认时，session在以下时间点清理缓存：               net.sf.hibernate.Transaction.commit():先清理缓存，再向数据库提交事务Session.find()或iterate()时，若缓存中持久化对象的属性发生了变化，就会先清缓存，以保证查询结果正确          <br />        3>.  Session的commit()和flush()的区别：<br />flush()只执行SQL语句，不提交事务；commit()先调用flush()，再提交事务        <br />        4>.  Session.setFlushMode()用于设定清理缓存的时间点： <br />清理缓存的模式 Session的查询方法 Session.commit() Session.flush() FlushMode.AUTO 清理清理清理 FlushMode.COMMIT 不清理清理清理 FlushMode.NEVER 不清理不清理清
          <br/>
          <span style="color:red;">
            <a href="http://www.javaeye.com/topic/186068#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/70' target='_blank'><span style="color:red;font-weight:bold;">第二届网络工程师侠客行大会5月24日杭州举行</span></a></li><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>Wed, 23 Apr 2008 21:19:19 +0800</pubDate>
        <link>http://www.javaeye.com/topic/186068</link>
        <guid>http://www.javaeye.com/topic/186068</guid>
      </item>
          <item>
        <title>Mnesia──一个用于电信应用系统的健壮的分布式DBMS</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://cryolite.javaeye.com">cryolite</a>&nbsp;
                    链接：<a href="http://www.javaeye.com/topic/186886" style="color:red;">http://www.javaeye.com/topic/186886</a>&nbsp;
          发表时间: 2008年04月25日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/>
          <h1 id="r23e">Mnesia──一个用于电信应用系统的健壮的分布式DBMS</h1>
<p>原文：http://www.erlang.se/publications/mnesia_overview.pdf</p>
<div id="yn1t">
<h2 id="gsql">摘要</h2>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;Mnesia
DBMS和拥有数据的应用系统运行在同一地址空间，然而应用系统不能销毁数据库的内容。Mnesia同时提供了快速存取的特性和很好的容错性，通常这两个
需求是相互矛盾的。Mnesia的实现是基于Erlang编程语言的特性，Mnesia也内嵌到Erlang中了。</div>
<div id="d2.0"><br id="tn-m" /></div>
<h2 id="zful">1. 介绍</h2>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;电信系统中数据的管理在许多方面（但也不是全部）与传统的商业DBMS（Database Manager
System）相同。尤其是对许多&ldquo;不停歇系统（nonstop
system）&rdquo;来说，在容错性上有非常高的要求，再加上需要有一个和应用系统运行在同一地址空间内的需求，导致我们设计了一个全新的DBMS。本文描述
了这个新的被称为Mnesia的DBMS的动机和相关设计。Mnesia由Erlang语言实现，与Erlang的关系非常紧密，Erlang为实现容错
性的电信系统提供了必要的功能。Mnesia是一个为了实现工业级电信应用系统而用Erlang编写的多用户的分布式DBMS，Erlang也是操作
Mnesia的理想语言。Mnesia试图涵盖所有的关于电信系统数据管理方面的问题，它有许多通常中传统数据库中不常见的特性。</div>
<div id="d2.0">&nbsp;&nbsp;&nbsp;&nbsp;电信应用中有许多不同于传统DBMS的特性需求。我们现在的用Erlang语言实现的应用系统需要有许多特性，这些特性是传统DBMS不能满足的，Mnesia根据如下需求设计：</div>
<div id="d2.0"><ol id="true">
<li id="wjok">快速实时的键/值查找；</li>
<li id="xawf">复杂的非实时性查询，主要是为了操作和维护；</li>
<li id="jbxj">由于分布式的应用导致的分布式的数据；</li>
<li id="wbug">高容错性；</li>
<li id="a.kb">动态重配置（Dynamic reconfiguration）；</li>
<li id="w-n:">复杂的对象。</li>
</ol></div>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;使Mnesia不同于其它DBMS的是，它是为电信应用系统中的数据管理问题设计的。Mnesia还将传统数据库中的许多概念与电信应用中的数据管理上
的概念结合在一起，前者包括事务和查询，后者包括极快速实时操作、容错性的可配置度（指复制）configurable degree of
fault tolerance（by means of
replication）、不停机或挂机而重新配置系统的能力。Mnesia与Erlang语言的紧耦合也使它看上去很有意思，它使得Erlang语言变
成了一门数据库编程语言。这带来很多好处，最主要的是通常由于DBMS中的数据格式与编程语言中的数据格式的不同带来的阻抗不匹配问题现在不存在了。</div>
<div id="d2.0">&nbsp;&nbsp; &nbsp;当前，Mnesia在Erisson中几乎所有的基于Erlang的工程中得到应用，从小规模的原型系统到大型交换机项目。</div>
<div id="d2.0">&nbsp;&nbsp; &nbsp;本文剩下部分如下组织：第2节是DBMS的简要概述，第3节列出了典型的DMBS功能，讨论了电信方面的功能以及Mnesia是如何提供这些功能的，第4节包括一些性能方面的测量，最后第5节总结。<br id="wgfa" /><br id="d:gq" />
</div>
<h2 id="o4vy">2. Mnesia简要概述</h2>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;Mnesia即是编程语言Erlang的扩展，也是一个Erlang应用程序。DBMS的组件，例如锁管理器、事务管理器、复制管理器、日志、主存储和
二级存储（primary and secondary memory
storage）、备份系统等等，这些都是由Erlang程序所实现的。然而，查询语言则是Erlang语法的一部分。Mnesia的数据模型是一种混合
类型：数据由record表组织，record表类似关系数据库中的关系（relation），但是record的属性（包括主键key）可以是任意复杂
的组合数据结构（如树、函数、闭包、代码等等）。这样，Mnesia也可以看做是所谓的对象关系DBMS。例如，我们定义人的record：</div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">-record(person, {name,&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;%% atomic，唯一性主键</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;data, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;%% 未指定的组合结构数据</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; married_to, &nbsp; &nbsp; &nbsp;%% 伴侣的名字，可以不指定（undefined）</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;children}). &nbsp; &nbsp; &nbsp;%% 孩子</span></div>
<div id="d2.0">有了这个定义，我们就可以用接下来的Erlang语法创建一个人的记录：</div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">X = #person{name = klacke,</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;data = {male, 36, 971191},</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; married_to = eva,</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;children = [marten, maja, klara]}.</span></div>
<div id="d2.0">将
变量X绑定到这个人的record。data域绑定到一个tuple：{male,36,971191}。这是一个复杂对象的例子，Mnesia对属性的
复杂性没有任何限制，我们甚至可以将函数对象作为属性值。变量X只是一个Erlang 项式（term），通过如下语句可以把它插入到数据库中：</div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">mnesia:write(X)</span></div>
<div id="d2.0">一系列Mnesia操作可以组织起来作为一个原子性的事务一起执行。为了让Mnesia执行一个事务，程序员必须首先构建一个函数对象，然后将其递交给Mnesia系统。我们通过一个例子解释，假设我们想写一个Erlang函数<span id="nh6:" style="font-family: Courier New;">divorce(Name)</span>，它接受一个人名，从数据库中查找这个人，将这个人和这个人的配偶的<span id="d3:0" style="font-family: Courier New;">married_to</span>域设为undefined值：</div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">divorce(Name) -&gt;</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;F = fun() -&gt;</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;case mnesia:read(Name) of</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;[] -&gt;</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;mnesia:abort(no_such_person);</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;Pers -&gt;</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;Partner = mnesia:read(Pers#person.married_to),</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;mnesia:write(Pers#person{married_to = undefined}),</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;mnesia:write(Partner#person{married_to = undefined})</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;end</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;end,</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;mnesia:transaction(F).</span></div>
<div id="d2.0"><span id="ds1-" style="font-family: Courier New;">divorce/1</span>函数由两条语句组成，第一条语句是 F = ...，用于创建一个函数对象，它什么都没执行，只是构建了一个匿名函数。第二条语句将这个函数交给Mnesia系统，它负责在一个事务的上行文中执行此函数，相当于传统的事务语法。</div>
<div id="d2.0">&nbsp;&nbsp; &nbsp;实际上函数F第一次执行一个读操作，用于查找给定名字Name的人，然后它执行第二个读操作以找到前者的配偶，最后执行两个写操作，将两条修改后的新记录（<span id="pd53" style="font-family: Courier New;">married_to</span>都已设为undefined）插入到数据库中，数据库中的旧值将新值被覆盖。函数<span id="flhc" style="font-family: Courier New;">divorce/1</span>将事务的值作为返回值，事务的值要么是<span id="kn9f" style="font-family: Courier New;">{aborted, Reason}</span>，要么是<span id="oy:v" style="font-family: Courier New;">{atomic, Value}</span>，这取决于事务是放弃了还是成功执行了。</div>
<div id="d2.0">&nbsp;&nbsp; &nbsp;Mnesia中的查询由表理解（list comprehension）语法表达[15]。一个用于查找所有生了超过X个孩子的人的名字的查询如下所示：</div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">query [P.name || P &lt; table(person),</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;length(P.children) &gt; X]</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">end</span></div>
<div id="d2.0">这被读作：组建一个P.name的列表，这里的P从person表中得到，而且每个P的children列表的长度超过X。将用户自定义的谓词混合中一个查询中也是可行而且自然的。例如有如下谓词</div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">maturep({Sex, Age, Phone}) when Age &gt; 30 -&gt;</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;true;</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">maturep({Sex, Age, Phone}) -&gt;</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;false;</span></div>
<div id="d2.0">查询可以是：</div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">query [P.name || P &lt;- table(person),</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;maturep(P.data),</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;length(P.children) &gt; X]</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">end</span></div>
<div id="d2.0">这个查询将提取出所有有超过X个小孩并且data域的第二个元素值大于30的人的名字。也可以用类似Datalog这样的嵌入式逻辑语言[16]的方式定义规则：</div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">oldies(Name) -&gt;</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;P &lt;- table(person),</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;maturep(P.data),</span></div>
<div id="d2.0"><span class="Apple-style-span" style="font-family: 'Courier New';">&nbsp;&nbsp; &nbsp;Name = P.name.</span></div>
<div id="d2.0">这
个规则用作是一个虚拟表，应用程序可以存取虚拟表oldies。虚拟的oldies表包含一个实际的person表的子集。这类似于关系数据库中视图
（view）的概念，但是功能更为强大。一个优化的查询编译器负责编译查询语句，该编译器已经集成到Erlang编译器中了。</div>
<div id="d2.0">&nbsp;&nbsp;&nbsp;&nbsp;数
据库表可以被复制到多个站点（或者节点）上，节点网络可以是异构网络。复制（Replication）是我们用于构建容错性系统的机制。对数据库表的访问
是位置透明的（location transparent)，也就是说，程序不需要知道数据的分布位置。一个数据库表有一个唯一的名字和一些相关的属性：</div>
<div id="d2.0">
<ul id="true">
<li id="vsot"><span class="Apple-style-span" style="font-family: 'Courier New';">type</span> 控制数据库表是set的还是bag的，set中key值是唯一的，而bag可以让多个对象有相同的key值；</li>
<li id="a7v:"><span class="Apple-style-span" style="font-family: 'Courier New';">ram_copies</span> 数据库表的复制品（replicas）所在的Mnesia节点仅将表保持在内存中；</li>
<li id="fl1e"><span class="Apple-style-span" style="font-family: 'Courier New';">disc_copies</span>&nbsp;数据库表的复制品（replicas）所在的Mnesia节点将表保持在内存中，但对表的所有更新操作都记录到磁盘中；</li>
<li id="n15_"><span class="Apple-style-span" style="font-family: 'Courier New';">disc_only_copies</span>&nbsp;数据库表的复制品（replicas）所在的Mnesia节点仅将表保持在磁盘上。显然这些复制品要比中内存中的复制品存取速度慢；</li>
<li id="eeba"><span class="Apple-style-span" style="font-family: 'Courier New';">index</span> 用于指定record中哪些属性需要做索引。所有的record总是自动为主键做索引；</li>
<li id="j433"><span class="Apple-style-span" style="font-family: 'Courier New';">snmp</span>&nbsp;是否需要通过SNMP协议操作。</li>
</ul>
</div>
<div id="d2.0">所
有表的描述信息都保持中数据库schema，Mnesia提供了许多函数用于动态的操作schema。表可以创建、移动、复制、改变、销毁......此
外，所有的系统活动都在背后执行，这就允许应用系统自己正在被修改时也可用（thus allows the application to
utilize the system as usual although the system itself is being
changed)。</div>
<div id="d2.0">&nbsp;&nbsp;&nbsp;&nbsp;可以通过备份创建整个分布式系统，这些备份将作为fallback安
装。（backups can be constructed of the entire distributed system, this
backups can be installed as
fallbacks.）这意味着系统如果崩溃了，数据库可以很快的从fallback自动重建。</div>
<div id="d2.0"><br id="v.j0" /></div>
<h2 id="tvwq">3. DBMS特性讨论</h2>
<div id="d2.0">&nbsp;&nbsp; &nbsp;不同的DBMS有着不同的特性特点。本节列出了不同的DBMS特点，并讨论了一些在我们电信系统中重要且必要的特性。</div>
<h3 id="qpwc">3.1 复杂值</h3>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;在DBMS中操作复杂值（如list、set、tree等）的能力可能是电信DBMS最重要的特性了。用于处理通信量（traffic）的电信应用系统
通常被到达系统的外部刺激(stimuli)所驱动，当这样一个刺激（stimuli）以PDU（Protocol Data
Unit）的形式到达电信系统时，PDU被解码，接着进行一系列操作，当PDU被解码后，系统通常提取出一些数据对象，可能是一个subscriber记
录，该记录用于决定哪些操作应该执行以响应收到的PDU。许多电信系统中，一个最重要的数据管理系统特性就是查找必须非常高效。DBMS允许数据以某种方
式组织和存储，这种方式使得一个简单的查找操作就能访问到数据。这个要求也使得为电信系统建模更加困难，以第三范式（甚至第一范式）组织电信数据通常不太
可行。</div>
<div id="d2.0">&nbsp;&nbsp; &nbsp;这也是为什么电信领域关注面向对象数据库系统的原因之一，相对于关系数据库，面向对象数据库允许数据以更灵活的方式组织。而Mnesia允许用户在数据库中使用任意复杂的对象作为属性值（attribute value），甚至是关键值（key value）。</div>
<h3 id="wqu5">3.2 数据格式和地址空间</h3>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;许多数据库使用一种内部的、语言独立的格式存储数据。由于前面提到的快速查找需求，这对于电信系统来说是非常不幸的。许多OODBMS（面向对象
DBMS）与一门程序语言（例如C++或Smalltalk）紧密耦合，这种在数据库中操作常规程序语言对象的能力使得阻抗不匹配消失了。这不仅使得
DBMS的操作更加容易，而且还提供了实现高效查找的机会，因为依靠使用的程序语言，一个查找操作能立即返回一个对象的指针。例如，如果我们想通过数据库
表实现一个路由表（routing
table），将路由数据从外部DBMS格式到我们需要的格式之间进行来回的转换是不现实的。此外，执行任何上下文切换和在另一个地址空间另一个进程中为
每个packet搜索相关数据也是不现实的（it is not realistic to perform any context
switches and search the relevant data for each packet in a process
executing in another address
sapce)。这就排除了所有不能直接链接到应用系统地址空间的DBMS，以及所有虽然链接到应用系统地址空间但是使用一种语言独立的格式存储数据的
DBMS。</div>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;让应用系统与DBMS运行在同一地址空间的最大缺点是如果应用系统由于程序的错误崩溃了，DBMS可能来不及在终结之前将重要数据存储到二级存储器
（secondary
storage）中。这意味着整个DBMS必须在再次启动之前进行恢复，而这通常都是一个非常耗时的过程，而在电信系统中当机时间必须尽可能短。应用系统
以及DBMS都是由Erlang实现的DBMS能避免这个问题。一个Erlang应用系统不能以影响DBMS的方式崩溃。应用系统和DBMS运行在同一地
址空间内，但Erlang保证一个应用系统的崩溃不会影响到另一个应用系统。Erlang进程有运行在同一地址空间的优点，但是这些进程不可能显式的读写
其它进程的内存。</div>
<h3 id="l4nd">3.3 容错性</h3>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;许多电信应用系统都是不可间断系统（nonstop
system），即使在发生硬件或软件错误的情况下系统仍然提供持续的访问。这个要求不仅是对DBMS的，也是对电信应用系统的。这影响到了整个应用系统
的设计，而DBMS必须为应用系统设计者提供一种能很好的设计容错系统的机制。Mnesia提供的这种机制就是将一张数据库表复制到多个节点上。一张
Mnesia表的所有复制品（replicas）都是等同的，在DBMS这一级别上没有主表和备用表的概念。如果一张表被复制了，一个事务中的所有的写操
作都会应用到所有的这些复制品（replicas）上，如果某些复制品（replicas）不可访问，写操作也能成功执行，而那些漏掉的复制品
（replicas）将在它们恢复后更新。这一机制使得设计一个不间断系统成为可能，该系统通过在不同地理区域上分布的系统之间的协作实现持续运行。许多
其它的高容错性系统（例如ClustRa[11]）也通过这种复制(replication)提供容错能力，然而它们没有和应用系统在同一地址空间内执行
的能力。</div>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;Mnesia能从灾难中部分的恢复，所有写入磁盘的对象能安全的与垃圾区分开来（objects are coded in such away
that it is possible to safely disinguish data from
garbage)。这使得扫描一个损害了的或者崩溃了的磁盘或文件系统，然后从崩溃了的磁盘中重新获取数据成为可能。</div>
<h3 id="si5e">3.4 分布（distribution）与位置透明</h3>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;Mnesia是一个真正的分布式DBMS，数据能被复制和远程存储，在这样的环境中，DBMS程序员无需了解数据的位置就能对其访问是非常重要的。也就
是说，数据的位置透明是非常重要的。另一方面，既然远程的数据访问非常昂贵，也需要应用系统程序员能显式的找到位置信息，这样就可以在数据所在的位置执行
程序。因此，我们需要能同时提供位置透明的能力和显式的定位数据位置的能力。不同的应用有着不同的需求。</div>
<div id="d2.0">&nbsp;&nbsp; &nbsp;Mnesia应用系统仅通过使用数据库表的名字（无需考虑表的位置）就能访问这些表。系统能明了数据都复制到了哪里。然而，它也能让Mnesia程序员通过系统查询到表的位置然后远程执行代码，可以将代码发送到远程站点上，或者那些代码已经在那里并加载了。</div>
<h3 id="fyzx">3.5 事务和ACID</h3>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;DBMS都有ACID特性，原子性、一致性、隔离性和持续性。这些特性通过Mnesia中的事务、writeahead
logging和恢复（recovery）实现。许多Mnesia事务包含了一系列仅在内存中（可能是复制的）的数据库表进行的操作，这些事务根本不与磁
盘存储系统打交道，因此对这些事务来说持久性特性没有实现。在电信系统中需要事务语义的一个例子就是当需要给系统添加一个新的subscriber时：当
我们进行此操作时，系统中会分配到一些资源，一些数据对象会被写入到系统内存中，所有的这些操作作为一个原子动作执行是至关重要的。不然这个系统就会出现
不一致的情况：可能某些资源没有释放。</div>
<h3 id="sb4e">3.6 绕过事务管理器的能力</h3>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;对电信通信量处理应用系统来说，事务的代价非常高，简单的通过事务系统访问数据是不可行的，因此绕过这类事务系统是很有用的。一个适合电信系统的
DBMS必须能够同时支持由一系列数据库操作组成的原子性事务，以及非常轻量级的对同一数据的锁定（very light weight
locking on the same
data）。前面的通信量处理系统由许多表组成，很多表很少写但是经常读。例如处理一个单独的呼叫（single
call）比添加一个subscriber更常见，路由一个PDU packet比修改路由表更常见。</div>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;当我们执行性能要求高的临界代码（critical
code）时，我们不想被强制使用事务，这些事务中只有只读操作。相反的，当路由表正被修改时从路由表中读取路由信息，一些数据包由于这种访问冲突丢失是
可以接受的。这里需要的是非常轻量级的锁定保护，这样应用系统进程可以访问数据表，并确定每个数据对象都是可读的，不会由于当前的写操作而混淆。
Mnesia通过所谓的脏接口（dirty
interface）支持这一特性。在一个事务中没有保护的读、写和搜索Mnesia表是可能的。这些脏操作是真正的实时DBMS操作：不管数据库有多
大，这些操作都能在可预期的时间内完成。</div>
<h3 id="ur8b">3.7 查询</h3>
<div id="d2.0">&nbsp;&nbsp; &nbsp;除了通信量的处理，电信系统还包含大量操作维护(O&amp;M)代码。例如，当从一个交换机系统中删除一个subscriber时，我们需要在好几张表中搜索与subscriber相关的数据，这就需要一个查询语言。操作和维护代码有如下特点：</div>
<div id="d2.0"><ol id="true">
<li id="g9tk">它没有或者有非常低的实时性要求；</li>
<li id="ri_z">它读取、搜索和操作大量的通信量数据；</li>
<li id="k18t">在系统的代码量上这些代码占了很大的比例（it constitutes a large part of the code volumn of the system）;</li>
<li id="s2p5">它很少执行，这取决于软件的好坏和bug的多少。</li>
</ol></div>
<div id="d2.0">这
样，一个强有力的执行在目标系统并能访问通信量表的查询语言能通过减少O&amp;M代码、通过声明（being
declarative）以及自动适应表的变化和网络拓扑结构。（a powerful query language which executes
on the target system and has complete access to all traffic tables, can
remedy by making the O&amp;M code smaller and by being declarative and
by being able to automatically adapt to chages in table layout or
network topology.）因为一个优化的编译器用于决定查询的执行顺序，O&amp;M代码可以变得更高效。</div>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;Mnesia查询语言基于表理解（list comprehension），这个想法在好几个其它的函数式DBMS（functional
DBMS），如[15]中得到过应用。表理解（list comprehension）的语法能与Erlang语言完美的结合在一起。</div>
<h3 id="zjmm">3.8 模式改建（Schema alteration）</h3>
<div id="d2.0">Erlang
语言有一个扩展的支持，它使得应用系统拥有不停止进程而修改正在执行中的代码的能力。这使得不停机而修改Erlang数据的发布和组织成为可能。也就是
说，它能在运行时修改Mnesia数据库模式（schema）而不必停掉系统。既然Mnesia是用来创建永不停机系统（nonstop
system）的，所有的系统活动如备份、修改模式（schema）、转储数据表表到二级存储器以及拷贝复制品（replicas）都可以在背后运行，而
且做这些事的同时应用系统依然能像平常一样访问和修改数据库表。</div>
<div id="d2.0"><br id="ws0b" /></div>
<h2 id="h.g9">4&nbsp;一些实施方面的问题</h2>
<div id="d2.0">&nbsp;&nbsp; &nbsp;Mnesia完全由Erlang实现，Erlang编程环境是一个实现分布式DBMS的理想工具，整个Mnesia的完整实现还包括系统从底层存储管理到查询优化编译器的所有方面，实现的代码很小，大概有2万行Erlang代码。</div>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;永久存储机制由操作系统的文件系统实现。不利的一面是这样的实现其性能取决于磁盘操作，好的一面是移植性不错。既然Mnesia主要是作为一个内存
DBMS（primary memory DBMS），我们觉得移植性更重要。主内存（primary memory）中的表和索引采用线性hash
list实现[13]，二级存储表（secondary storage tables）由具名文件（named
files）实现。每个文件被组织成一个线性hash list。（a linear hash list with a medium chain
length of the hash bucket set to a small value)。线性hash
list中查找操作上非常高效，在进行插入操作时效率也不错。文件和表的大小可以动态的伸缩。每个文件的空间管理由buddy算法实现。</div>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;Mnesia锁管理使用了许多传统技术。锁定（locking）是动态的，事务在需要时会得到一个锁。常规的两阶段锁定（regular
twophase locking）也有用到，死锁的预防是通过传统的waitdie[14]。waitdie算法的时间戳通过Lamport
clock得到，后者由每个节点上的事务管理器负责维护，当一个事务重启后，它的Lamport clock处于维护中，thus making
Mnesia live lock free as well.锁管理器还实现了多粒度的锁定。当一个事务提交时，事务管理器采用了传统的两阶段提交。</div>
<div id="d2.0">&nbsp;&nbsp;
&nbsp;通过关系数据库技术的操作符可以评估简单的查询，递归的查询可以通过SLG[3]进行评估。因为Mnesia运行在分布式Erlang上，其实现非常简
单。在一个分布式应用系统中有许多彼此隔离的Erlang节点运行在不同的机器上。Eralng负责运行在不同节点上的进程间的通信。分布式Erlang
可以透明的穿越不同endianism结构的机器，这样一个Mnesia系统就可以由许多异构计算机系统组成。进程和节点很容易被其它节点上的进程启动、
监视和停止。这消除了Mnesia和应用系统中许多通信的实现困难（This makes much of communication
implementation difficulties disappear for Mnesia as well as for
applications)。</div>
<div id="d2.0"><br id="w.mn" /></div>
<h2 id="r8jm">5 性能讨论</h2>
<div id="d2.0">我们在本节提供了一些Mnesia上的测量，图表清楚的显示了：</div>
<div id="d2.0">
<ul id="true">
<li id="igvt">相对于脏接口（dirty interface），使用事务系统的代价是相当大的。这一现象的正确解释是：脏接口快，事务系统慢；</li>
<li id="cw6:">复制的代价相当高。测试中的计算机使用的局域网是常规的10Mbit/sec；</li>
</ul>
</div>
<div id="d2.0">测试中的计算机是三台运行着Solaris 2.5的Sun UltraSparcs。所有的事务由一台167Mhz的UltraSparc负责初始化，其它两台是143Mhz。</div>
<div id="d2.0"><br id="zxxw" /></div>
<div id="pu.s">
<table id="qtho" style="height: 135px;" border="1" cellspacing="0" cellpadding="3" width="583">
<tbody id="mgh:">
<tr id="gg_o">
<td id="hgji" width="25%">复制品数量<br id="c-ny" />(number of replicas)</td>
<td id="a_8l" width="25%">1</td>
<td id="ckg9" width="25%">2</td>
<td id="h-.x" width="25%">3</td>
</tr>
<tr id="tr9b">
<td id="btd1" style="font-family: Courier New;" width="25%">divorce/1</td>
<td id="atuv" width="25%">1877</td>
<td id="pvf5" width="25%">5009</td>
<td id="vkh_" width="25%">13372</td>
</tr>
<tr id="l32m">
<td id="bi75" width="25%">使用wread的<span id="z3zx" style="font-family: Courier New;">divorce/1</span></td>
<td id="p:80" width="25%">1225</td>
<td id="romz" width="25%">4703</td>
<td id="m.t4" width="25%">12185</td>
</tr>
<tr id="sl1g">
<td id="mu1t" width="25%">dirty <span id="alp1" style="font-family: Courier New;">divorce/1</span></td>
<td id="afcz" width="25%">181</td>
<td id="kk6g" width="25%">592</td>
<td id="lk03" width="25%">1121</td>
</tr>
</tbody>
</table>
</div>
<div id="d2.0" style="text-align: center;">表1 不同配置执行divorce/1函数的wallclock，单位：毫秒（ms）</div>
<div id="d2.0">&nbsp;&nbsp;&nbsp;
第一行的数据是来自第2节的divorce/1函数运行的结果，第二行的数据是我们用Mnesia函数wread/1取代函数中的read/1后的运行结
果，wread函数通过设置一个写锁（write lock）而不是读锁（read
lock）读取数据，如果我们预先知道接下来的操作要将同一个对象写入的话，此函数效率要高一点，这样锁就不必从读锁更新成写锁。最后一行的数据来自我们
用脏函数（dirty functions）读写这些复制表，这就使用到了轻量级锁，并绕过了事务系统。<br id="qqzu" />
<h2 id="v5.j">6 结论</h2>
现在有大量的DBMS可供选择，包括许多可用的商业系统和无数的研究系统，似乎使用商业的DBMS是比较好的选择，但是如果考虑到第3节中提到的那些因素，没有一个合适的商业DBMS可用。我们认为我们的主要贡献在于：<br id="m82y" />
<ul id="izxg">
<li id="l2x1">通过组合许多已有的技术，我们实现了一个完整的分布式DBMS，许多研究组织只研究DBMS的某些方面，我们实现了一个完全的分布式DBMS，很少有这样的系统存在；</li>
<li id="ymir">我们展示了Erlang不仅适合电信系统而且也非常适合实现一个DBMS系统，例如Mnesia。就我们了解，这是第一次有人用一个符号编程语言(symbolic programming language)实现了一个分布式DBMS；</li>
<li id="t.sz">我们提供了一个全面的DBMS解决方案，至少在电信系统的数据管理方面是这样子。</li>
</ul>
今天Mnesia系统早已在Ericsson中用于构建真正的软件产品，Mnesia已经不再是原型系统，它已经成熟到可以贴上产品的标签了。通过http://www.ericsson.se/erlang可以了解该系统。<br id="j47r" />
<h2 id="gm9o">参考资料</h2>
<ol id="i47g">
<li id="v-pa">Armstrong,
J. L., Williams, M. C., Wikstrom, C. and Virding, S. R., Con current
Programming in Erlang, 2:nd ed. Prentice Hall (1995)</li>
<li id="o:26">Bernstein, P.A., Hadzilacos, V., Goodman, N. Concurrency Control and recovery in Database Systems Addison Wesley, 1987.</li>
<li id="s_e-">Chen,
A.W., Warren, D.S. Query Evaluation under the WellFounded Se mantics
Proc. ACM SIGACT-SIGMOD-SIGART Symp. on Principles of Database Sys.
Whashington, 1993.</li>
<li id="zt7g">Case, K. McCloghrie, M. Rose, S.
Waldbusser. Management Information Base for Version 2 of the Simple
Network Management Protocol (SNMPv2), Jan, 1996.</li>
<li id="bgur">Copeland,
G., Maier, D. Making Smalltalk a database system Proceedings of the
1984 ACM SIGMOD International Conference on Management of Data. pp.
316325. Boston 1984.</li>
<li id="x483">Eswaran, K.P., Grey, J.N.,
Lorie, R.A. and Traiger, I.L. The Notions of Consistence and Predicate
Locks in a Database system Communications of ACM, 19(11):624633,
November 1976.</li>
<li id="zwen">Faehndrich, M., Morrisett, G.,
Nettles, S., Wing, J. Extensions to Standard ML to Support Transactions
ACM SIGPLAN Workshop on ML and its Applications, June 2021, 1992.</li>
<li id="lyvq">Goetz, G. Query Evaluation Techniques for Large Databases ACMCS 2(25):73170, June 1993.</li>
<li id="p3st">Grey,
J.N. Notes on Database operating system: An advanced course Lecture
notes in Computer Science,Springer Verlag, Berlin. 1(60):393481, 1978.</li>
<li id="pjd7">Grey,
J.N., Lorie, R.A., Putzolo, G.R. and Traiger, I.L. Granularity of Locks
and degrees of consistency in a shared database IBM, Research report
RJ1654, September 1975.</li>
<li id="s7ij">Hvasshovd, SO., Torbjornsen,
O., Bratsberg, S.E., Holager, P. The ClustRa telecom database: High
availability, high throughput, and realtime response Proceedings of the
21st International Conference on Very Large Databases, Zurich,
Switzerland, pp. 469477, September 1995.</li>
<li id="mlhf">Lamport, L.
Time, clocks and the ordering of events i a distributed system ACM
Transactions on Programming Languages and Systems, 21(1):558565, July
1878.</li>
<li id="yj9d">Larsson, PA Larsson. Dynamic Hash tables Communications of the ACM, 31(4), 1988</li>
<li id="phku">Rosenkrantz,
D.J., Stearns, R.E. and Lewis, P.M. System Level Concurrency Control
for Distributed Databases ACM Transactions on Database Systems,
3(2):178198, June 1978.</li>
<li id="i_ef">Trinder, P.W. and Wadler, P.
List comprehensions and the Relational Cal culus Proceedings of the
Glasgow 1988 Workshop on functional programming, Rothesay , August
1988, pp 115123.</li>
<li id="sryk">Ullman, J. Principles of Database and KnowledgeBase Systems, vol 2. Computer Science press, 1989.</li>
</ol></div>
<div id="d2.0"><br id="pxup" /></div>
</div>
          <br/>
          <span style="color:red;">
            <a href="http://www.javaeye.com/topic/186886#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/70' target='_blank'><span style="color:red;font-weight:bold;">第二届网络工程师侠客行大会5月24日杭州举行</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>Fri, 25 Apr 2008 17:27:42 +0800</pubDate>
        <link>http://www.javaeye.com/topic/186886</link>
        <guid>http://www.javaeye.com/topic/186886</guid>
      </item>
          <item>
        <title>多线程是个不靠谱的东西</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://raylinn.javaeye.com">ray_linn</a>&nbsp;
                    链接：<a href="http://www.javaeye.com/topic/189994" style="color:red;">http://www.javaeye.com/topic/189994</a>&nbsp;
          发表时间: 2008年05月06日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/>
          这几天搞Parllel，才发现多线程远比想像中的困难，而不只是资源的冲突和锁定那么简单。利用多线程，首要目标是让任务并行，让数据的处理更有效率。但是，问题是什么样子的数据该并行处理？？<br /><br />首先我排除了大部分的文件读写操作，顺序读取会导致文件指针的移动，这显然会导致不确定数据结果，特别是要把数据填充到一系列结构（Struct)中去的时候，多线程产生了一堆错误的结果。<br /><br />其次内存中数据操作似乎也不好确定是否该用多线程，线程的生产、切换、消费都要消耗时间，在single core的机器上，多线程只会更没效率而不是更有效率。对于multi core的机器呢。。。情况似乎变得复杂，线程时间片和操作时间片的长度似乎是一个很关键的因素。对于“短”操作来说，花在线程消费上的开销，远远大于起并行带来的时间减少，因此，多线程只会更慢，而不是更快。但难点是，如何找到一个临界点，以作为是否引入多线程操作的阀值？ 线程消费的开销，可能随着机器的情况而变化，包括CPU的usage，CPU的核心数目。。。。<br /><br /><br />我翻了MC#(multi core C#)，PLinq，Parallel C#,谁也没告诉我答案，我们不缺工具，但缺best practise
          <br/>
          <span style="color:red;">
            <a href="http://www.javaeye.com/topic/189994#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/70' target='_blank'><span style="color:red;font-weight:bold;">第二届网络工程师侠客行大会5月24日杭州举行</span></a></li><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>Tue, 06 May 2008 09:07:34 +0800</pubDate>
        <link>http://www.javaeye.com/topic/189994</link>
        <guid>http://www.javaeye.com/topic/189994</guid>
      </item>
          <item>
        <title>我的第一个android小东西 mp3播放器</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://leiv.javaeye.com">leiv</a>&nbsp;
                    链接：<a href="http://www.javaeye.com/topic/186933" style="color:red;">http://www.javaeye.com/topic/186933</a>&nbsp;
          发表时间: 2008年04月25日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/>
          把它改了一下，添加了一些新的功能，包括设定编码和目录，保存和打开播放列表，添加文件和目录到播放列表，手动查找歌词。功能大概差不多了，但是bug非常多，以后有时间再改。<br />-------------------------------------------------<br />断断续续用了几个星期写了这个mp3播放器，基本实现的功能有：播放mp3；自动到百度查找歌词；在相同目录查找封面<br />实现方式是扫描/sdcard目录下的mp3文件，加入到数据库中，包括id,path,cover,lyric,name字段。查看详情时更新cover和lyric，如果不合适删除封面和歌词以后就不会再查找。<br />由于是边看资料边写的，代码很乱，特别是命名。有时候会出现莫名奇妙的错误，像播放时突然跳到歌曲的结尾，然后歌曲的当前播放位置一直增长。第一个作品，希望各位多多指教，呵呵。
          <br/>
          <span style="color:red;">
            <a href="http://www.javaeye.com/topic/186933#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><li><a href='/adverts/70' target='_blank'><span style="color:red;font-weight:bold;">第二届网络工程师侠客行大会5月24日杭州举行</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 25 Apr 2008 21:25:01 +0800</pubDate>
        <link>http://www.javaeye.com/topic/186933</link>
        <guid>http://www.javaeye.com/topic/186933</guid>
      </item>
          <item>
        <title>Amoeba for mysql 0.12发布（读写分离、负载均衡、Failover、数据切分） </title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://csrcom.javaeye.com">csrcom</a>&nbsp;
                    链接：<a href="http://www.javaeye.com/topic/188598" style="color:red;">http://www.javaeye.com/topic/188598</a>&nbsp;
          发表时间: 2008年05月01日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/>
          Amoeba 座落与Client、Database Server(s)之间。 具有负载均衡、高可用性、sql过滤、可承受高并发、读写分离、Query Route（解析sql query语句，并且根据条件与预先设定的规则，请求到指定的目标数据库。可并发请求多台数据库合并结果）、对客户端透明。  <br /><br />主要降低 数据切分带来的复杂多数据库结构、数据切分规则 给应用带来的影响。<br /><br /><br /><br />能够轻易实现读写分离 <br />Failover <br />负载均衡。  <br />能够帮助解决数据切分问题  <br /><br />目前amoeba 实现了 mysql 数据库的相关技术。<br /><br /><br /><strong>适用：</strong><br />mysql 4.1或者以上版本（mysql 协议版本：10）<br />暂时不支持事务、DDL语句目前只会分配给默认的数据库执行<br /><br /><strong>运行环境：</strong><br /><br />至少需要运行 mysql 4.1以上 服务一个；<br />Java 1.5或 以上版本<br /><br /><br /><br />中文文档地址： http://amoeba.sourceforge.net/doc  <br />文件下载： http://www.sourceforge.net/projects/amoeba
          <br/>
          <span style="color:red;">
            <a href="http://www.javaeye.com/topic/188598#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/70' target='_blank'><span style="color:red;font-weight:bold;">第二届网络工程师侠客行大会5月24日杭州举行</span></a></li><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>Thu, 01 May 2008 07:58:50 +0800</pubDate>
        <link>http://www.javaeye.com/topic/188598</link>
        <guid>http://www.javaeye.com/topic/188598</guid>
      </item>
          <item>
        <title>hibernate入门使用系列 4-- 关系映射篇（下）</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ryanpoy.javaeye.com">RyanPoy</a>&nbsp;
                    链接：<a href="http://www.javaeye.com/topic/190679" style="color:red;">http://www.javaeye.com/topic/190679</a>&nbsp;
          发表时间: 2008年05月07日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/>
          <p>接上篇 <a href="190597" target="_blank">hibernate入门使用系列 3-- 关系映射篇（中）</a>

<br />

<br />

<br />

<br />

开我写的前3篇中，分别讲了one-to-one, one--to-many, many-to-one 。<br />

<br />

这篇，主要讲的是 n：n 的关系。即：many-to-many。<br />

<br />

我们以老师和学生为例，一个老师可以交很多学生，同样一个学生可以拥有多个老师，所以，他们之间的关系就是n：n的。<br />

<br />

实体模型：<br />

<br />

<br />

<img src="../../../topics/download/7cd30464-261e-377c-8b88-5409eda5740f" alt="" />

<br />

从实体模型来看。有2个对象，但是为了在数据库中表示出2者的n:n的关系，我们还得引入一张表。所以，sql脚本如下：</p>
<pre name="code" class="sql"> use HibernateQuickUse;
drop table if exists teacher_student_relation;
drop table if exists Teacher;
drop table if exists Student;

create table Teacher (
	tid varchar(32) primary key,
	name varchar(32) not null
);

create table Student (
	sid varchar(32) primary key,
	name varchar(128) not null
);

create table teacher_student_relation (
	id integer auto_increment primary key,
	teacher_id varchar(32) not null,
	student_id varchar(32) not null,
	foreign key(teacher_id) references Teacher(tid),
	foreign key(student_id) references Student(sid)
);
</pre>
<p>
<br />

<br />

通过模型，创建java类如下：</p>
<p>Student.java</p>
<pre name="code" class="java">package org.py.hib.relation.many2many;

import java.util.HashSet;
import java.util.Set;

/**
 * Student entity.
 */

@SuppressWarnings(&quot;serial&quot;)
public class Student implements java.io.Serializable
{
	private String id;

	private String name;

	private Set&lt;Teacher&gt; teachers = new HashSet&lt;Teacher&gt;(0);

	public Student()
	{
	}

	public String getId()
	{
		return this.id;
	}

	public void setId(String id)
	{
		this.id = id;
	}

	public String getName()
	{
		return this.name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public Set&lt;Teacher&gt; getTeachers()
	{
		return teachers;
	}

	public void setTeachers(Set&lt;Teacher&gt; teachers)
	{
		this.teachers = teachers;
	}
}</pre>
<p>&nbsp;</p>
<p>Teacher.java:</p>
<pre name="code" class="java">package org.py.hib.relation.many2many;

import java.util.HashSet;
import java.util.Set;

/**
 * Teacher entity.
 */

@SuppressWarnings(&quot;serial&quot;)
public class Teacher implements java.io.Serializable
{
	private String id;

	private String name;

	private Set&lt;Student&gt; students = new HashSet&lt;Student&gt;(0);

	public Teacher()
	{
	}

	public String getId()
	{
		return this.id;
	}

	public void setId(String id)
	{
		this.id = id;
	}

	public String getName()
	{
		return this.name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public Set&lt;Student&gt; getStudents()
	{
		return students;
	}

	public void setStudents(Set&lt;Student&gt; students)
	{
		this.students = students;
	}

}</pre>
&nbsp;
<p>xml映射文件如下</p>
<p>Student.hbm.xml</p>
<pre name="code" class="xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;!DOCTYPE hibernate-mapping PUBLIC &quot;-//Hibernate/Hibernate Mapping DTD 3.0//EN&quot;
&quot;http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd&quot;&gt;

&lt;hibernate-mapping&gt;
	&lt;class name=&quot;org.py.hib.relation.many2many.Student&quot;
		table=&quot;student&quot;&gt;
		&lt;id name=&quot;id&quot; type=&quot;java.lang.String&quot; column=&quot;sid&quot; length=&quot;32&quot;&gt;
			&lt;generator class=&quot;uuid&quot; /&gt;
		&lt;/id&gt;

		&lt;property name=&quot;name&quot; type=&quot;java.lang.String&quot; column=&quot;name&quot;
			length=&quot;128&quot; not-null=&quot;true&quot; /&gt;

		&lt;set name=&quot;teachers&quot; table=&quot;teacher_student_relation&quot; cascade=&quot;save-update&quot; inverse=&quot;false&quot;&gt;
			&lt;key column=&quot;student_id&quot; not-null=&quot;true&quot; /&gt;

			&lt;many-to-many column=&quot;teacher_id&quot;
				class=&quot;org.py.hib.relation.many2many.Teacher&quot; 
				/&gt;
		&lt;/set&gt;
	&lt;/class&gt;
&lt;/hibernate-mapping&gt;
</pre>
<p><span style="color: #ff0000;">&nbsp;注意：</span>

</p>
<p>&nbsp;set中的 table 指向的是数据库中的关联表。</p>
<p>cascade 用的是<span style="color: #ff0000;">save-update</span>
, 且inverse用的是false，这样的话，当进行修改和保存和删除时，关联表中的记录也会删掉. </p>
<p>如果cascade 用的是 <span style="color: #ff0000;">all</span>
 那么连同student表中的记录也会被删除掉。</p>
<p>key中的column指的是： 关联表中与Student发生关系的字段。</p>
<p>而many-to-many中的column指的是：关联表中，与class(这里是：org.py.hib.relation.many2many.Teacher)发生关系的字段。</p>
<p>关于inverse，请参考上篇：<a href="190597" target="_blank">hibernate入门使用系列 3-- 关系映射篇（中）</a>

</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>Teacher.hbm.xml</p>
<pre name="code" class="xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;!DOCTYPE hibernate-mapping PUBLIC &quot;-//Hibernate/Hibernate Mapping DTD 3.0//EN&quot;
&quot;http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd&quot;&gt;

&lt;hibernate-mapping&gt;
	&lt;class name=&quot;org.py.hib.relation.many2many.Teacher&quot;
		table=&quot;teacher&quot;&gt;
		&lt;id name=&quot;id&quot; type=&quot;java.lang.String&quot; column=&quot;tid&quot;
			length=&quot;32&quot;&gt;
			&lt;generator class=&quot;uuid&quot; /&gt;
		&lt;/id&gt;

		&lt;property name=&quot;name&quot; type=&quot;java.lang.String&quot; column=&quot;name&quot;
			length=&quot;32&quot; not-null=&quot;true&quot; /&gt;

		&lt;set name=&quot;students&quot; table=&quot;teacher_student_relation&quot; cascade=&quot;save-update&quot;
			inverse=&quot;false&quot;&gt;
			&lt;key column=&quot;teacher_id&quot; not-null=&quot;true&quot; /&gt;
			&lt;many-to-many class=&quot;org.py.hib.relation.many2many.Student&quot;
				column=&quot;student_id&quot; /&gt;
		&lt;/set&gt;
	&lt;/class&gt;
&lt;/hibernate-mapping&gt;
</pre>
<p><span style="color: #ff0000;">&nbsp;
注意：</span>
</p>
<p>这里的inverse也采用了false，这样子的话，Teacher和Student都维护关系表中的关系。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>测试类，Many2ManyTest.java</p>
<pre name="code" class="java">package org.py.hib.relation.many2many;

import java.util.Iterator;
import java.util.List;
import java.util.Set;

import junit.framework.Assert;
import junit.framework.TestCase;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;

public class Many2ManyTest extends TestCase
{
	private SessionFactory factory;

	@Before
	public void setUp() throws Exception
	{
		Configuration conf = new Configuration().configure();
		factory = conf.buildSessionFactory();
	}

	/**
	 * 测试添加
	 * @throws Exception
	 */
	public void testSave() throws Exception
	{
		System.out.println(&quot;\n=== test save ===&quot;);

		Teacher teacher1 = new Teacher();
		teacher1.setName(&quot;teacher_1&quot;);

		Teacher teacher2 = new Teacher();
		teacher2.setName(&quot;teacher_2&quot;);

		Student stu1 = new Student();
		stu1.setName(&quot;student_1&quot;);

		Student stu2 = new Student();
		stu2.setName(&quot;student_2&quot;);

		stu1.getTeachers().add(teacher1);
		stu1.getTeachers().add(teacher2);

		stu2.getTeachers().add(teacher2);
		teacher1.getStudents().add(stu2);

		Session session = null;
		Transaction tran = null;
		try
		{
			session = factory.openSession();
			tran = session.beginTransaction();

			session.save(stu1);
			session.save(stu2);
			tran.commit();

			Assert.assertNotNull(teacher1.getId());
			Assert.assertNotNull(teacher2.getId());

			Assert.assertNotNull(stu1.getId());
			Assert.assertNotNull(stu2.getId());

		} catch (Exception ex)
		{
			tran.rollback();
			throw ex;
		} finally
		{
			if (session != null)
			{
				try
				{
					session.close();
				} catch (Exception ex)
				{
					// nothing to do
				} finally
				{
					if (session != null)
						session = null;
				}
			}
		}
	}

	/**
	 * 测试从Teacher查询
	 * @throws Exception
	 */
	@SuppressWarnings(&quot;unchecked&quot;)
	public void testFindFromTeacher() throws Exception
	{
		System.out.println(&quot;\n=== test find from Teacher ===&quot;);
		Session session = null;
		try
		{
			session = factory.openSession();
			Iterator&lt;Teacher&gt; iter = session.createQuery(&quot;from Teacher&quot;).iterate();
			while (iter.hasNext())
			{
				Teacher teacher = iter.next();
				Assert.assertNotNull(teacher.getId());
				String teacherName = teacher.getName();
				if (&quot;teacher_1&quot;.equals(teacherName))
				{
					Set&lt;Student&gt; stus = teacher.getStudents();
					Assert.assertEquals(stus.size(), 2);
					for (Student stu : stus)
					{
						String stuName = stu.getName();
						Assert.assertNotNull(stu.getId());
						Assert.assertTrue(stuName.equals(&quot;student_1&quot;) || stuName.equals(&quot;student_2&quot;));
					}
				} else if (&quot;teacher_2&quot;.equals(teacherName))
				{
					Set&lt;Student&gt; stus = teacher.getStudents();
					Assert.assertEquals(stus.size(), 2);

					for (Student stu : stus)
					{
						String stuName = stu.getName();
						Assert.assertNotNull(stu.getId());
						Assert.assertTrue(stuName.equals(&quot;student_1&quot;) || stuName.equals(&quot;student_2&quot;));
					}
				} else
				{
					throw new Exception(&quot;teacher name error exception.&quot;);
				}
			}
		} catch (Exception ex)
		{
			throw ex;
		} finally
		{
			if (session != null)
			{
				try
				{
					session.close();
				} catch (Exception ex)
				{
					// nothing to do
				} finally
				{
					if (session != null)
						session = null;
				}
			}
		}
	}

	/**
	 * 测试从Student查询
	 * @throws Exception
	 */
	@SuppressWarnings(&quot;unchecked&quot;)
	public void testFindFromStudent() throws Exception
	{
		System.out.println(&quot;\n=== test find from Student ===&quot;);
		Session session = null;
		try
		{
			session = factory.openSession();
			Iterator&lt;Student&gt; iter = session.createQuery(&quot;from Student&quot;).iterate();
			while (iter.hasNext())
			{
				Student stu = iter.next();
				Assert.assertNotNull(stu.getId());
				String stuName = stu.getName();
				if (&quot;student_1&quot;.equals(stuName))
				{
					Set&lt;Teacher&gt; teachers = stu.getTeachers();
					Assert.assertEquals(teachers.size(), 2);
					for (Teacher teacher : teachers)
					{
						String tName = teacher.getName();
						Assert.assertNotNull(teacher.getId());
						Assert.assertTrue(tName.equals(&quot;teacher_1&quot;) || tName.equals(&quot;teacher_2&quot;));
					}
				} else if (&quot;student_2&quot;.equals(stuName))
				{
					Set&lt;Teacher&gt; teachers = stu.getTeachers();
					Assert.assertEquals(teachers.size(), 2);
					for (Teacher teacher : teachers)
					{
						String tName = teacher.getName();
						Assert.assertNotNull(teacher.getId());
						Assert.assertTrue(tName.equals(&quot;teacher_1&quot;) || tName.equals(&quot;teacher_2&quot;));
					}
				} else
				{
					throw new Exception(&quot;student name error exception.&quot;);
				}
			}
		} catch (Exception ex)
		{
			throw ex;
		} finally
		{
			if (session != null)
			{
				try
				{
					session.close();
				} catch (Exception ex)
				{
					// nothing to do
				} finally
				{
					if (session != null)
						session = null;
				}
			}
		}
	}

	/**
	 * 测试修改
	 * @throws Exception
	 */
	public void testModify() throws Exception
	{
		System.out.println(&quot;\n=== test modify ===&quot;);
		Session session = null;
		Transaction tran = null;
		try
		{
			session = factory.openSession();
			tran = session.beginTransaction();

			Teacher t1 = (Teacher) session.createQuery(&quot;from Teacher t where t.name='teacher_1'&quot;).list().get(0);
			t1.setName(&quot;new_teacher_1&quot;); // 修改用户名 = m_name2.（原来用户名= m_name）

			Set&lt;Student&gt; stus = t1.getStudents();
			for (Student stu : stus)
			{
				if (stu.getName().equals(&quot;student_1&quot;))
				{
					stus.remove(stu);
					break;
				}
			}

			tran.commit();

		} catch (Exception ex)
		{
			throw ex;
		} finally
		{
			if (session != null)
			{
				try
				{
					session.close();
				} catch (Exception ex)
				{
					// nothing to do
				} finally
				{
					if (session != null)
						session = null;
				}
			}
		}

		/*
		 * 修改后再查询
		 */
		System.out.println(&quot;\n=== test find from Teacher after modify===&quot;);
		try
		{
			session = factory.openSession();
			Iterator&lt;Teacher&gt; iter = session.createQuery(&quot;from Teacher&quot;).iterate();
			while (iter.hasNext())
			{
				Teacher teacher = iter.next();
				Assert.assertNotNull(teacher.getId());
				String teacherName = teacher.getName();
				if (&quot;new_teacher_1&quot;.equals(