论坛首页 Java企业应用论坛

元数据、开放数据模型及动态系统--形而下学篇

浏览 15852 次
该帖已经被评为精华帖
作者 正文
   发表时间:2007-01-17  

普适数据容器



数据集(DataSet)是一个普适的,弱类型的,开放的数据容器。普适是指它可以适应任何基于名值对集合或名值表的数据形式,比如Properties,Database Tables, XML, CSV, JSON, LDAP, ...。弱类型是指所有的容器都用同一个数据结构,存放在容器中的只有两种对象:数值和子结构。数值一律用字符串表示,时间日期用XML标准形式来表示,子结构一律用数据集表示。有关定义域的元数据存放在专门的数据结构中。如果需要类型转换,借助元数据,可以很容易地做到。所以为了突出本质,我宁愿把数据转换的代码放到AccessorWrapper里。事实上在我们的项目里,根本就没有用到类型转换。所谓开放,就是前面说过的,容器可以适应数据模型的变化,以不变应万变。

下面给出两个主要的接口。DataSet继承Iterator和Map,即是为了表明概念继承上的渊源,也是为了与Web模板结合。数据集在元数据只含有字段名,并且不具有滚动性质的情形下,退化为Map。

java 代码
 
  1. public interface DataSet extends Iterator, Map  
  2. {  
  3.     public abstract String getDataSetName();  
  4.     public abstract void setDataSetName(String name);  
  5.     public abstract DataSetMetaData getMetaData();  
  6.     public abstract void setMetaData(DataSetMetaData md);  
  7.   
  8.     public abstract DataSet getDataSet(int column);  
  9.     public abstract void setDataSet(int column, DataSet ds);  
  10.     public abstract DataSet getDataSet(String path);  
  11.     public abstract void setDataSet(String path, DataSet ds);  
  12.   
  13.     public abstract String getValue(int column);  
  14.     public abstract void setValue(int column, String value);  
  15.     public abstract String getValue(String path);  
  16.     public abstract void setValue(String path, String value);  
  17. }  
  18.   
  19. public interface DataSetMetaData  
  20. {  
  21.     public abstract int getColumnCount();  
  22.     public abstract void setColumnCount(int count);  
  23.   
  24.     public abstract int getColumnDisplaySize(int column);       // optional  
  25.     public abstract String getColumnLabel(int column);          // optional  
  26.     public abstract String getColumnName(int column);  
  27.     public abstract int getColumnType(int column);  
  28.     public abstract String getColumnTypeName(int column);  
  29.     public abstract int getPrecision(int column);               // optional  
  30.     public abstract int getScale(int column);                   // optional  
  31.     public abstract boolean isAutoIncrement(int column);        // default "no"  
  32.     public abstract boolean isCaseSensitive(int column);        // default "yes"  
  33.     public abstract int isNullable(int column);                 // default "yes"  
  34.     public abstract void setAutoIncrement(int columnIndex, boolean property);  
  35.     public abstract void setCaseSensitive(int columnIndex, boolean property);  
  36.     public abstract void setColumnDisplaySize(int columnIndex, int size);  
  37.     public abstract void setColumnLabel(int columnIndex, String label);  
  38.     public abstract void setColumnName(int columnIndex, String columnName);  
  39.     public abstract void setColumnType(int columnIndex, int DataType);  
  40.     public abstract void setColumnTypeName(int columnIndex, String typeName);  
  41.     public abstract void setNullable(int columnIndex, int property);  
  42.     public abstract void setPrecision(int columnIndex, int precision);  
  43.     public abstract void setScale(int columnIndex, int scale);  
  44. }  

 

数据集的辅助工具

为了便于应用,需要有一整套的工具类。下面列出与数据集应用有关的核心类和辅助类,结合后面的用例,可以让你对数据集的应用方式有一个大概的了解。
核心接口
    DataSet
    DataSetMetaData
    DataSetException
    ...
核心实现:
    DataSetBaseImpl
    DataSetMetaDataBaseImpl
    DataType
    TypeFormat
    ...

辅助工具包:   

    SAX 2扩展包。
        DataReader < org.xml.sax.XMLReader
            DefaultReader <~ JAXP Default Reader
            DataSetReader
            DBTableReader
            DBLobReader
            JndiReader
            TeeFilter
            ObjectReader
            ResultSetReader
            ...
        DataSource < org.xml.sax.InputSource
        DataSink ~ org.apache.xml.serializer.Serializer
            DefaultSink ~ org.apache.xml.serializer.Serializer
            DataSetSink
            DBTableSink
            DBLobSink
            ObjectSink
        DataPipeFactory
    IO扩展包
        InputStreamBuffer, OutputStreamBuffer
        NullInputStream, NullOutputStream
        ByteMessageInputStream, ByteMessageOutputStream
        TextMessageInputStream, TextMessageOutputStream
        SmartZipInputStream, SmartZipOutputStream
        RewindableInputStream
        FanOutputStream
        StreamCopier
    组件框架
        略过

数据集用例

在应用上,数据集的应用模式,遵循了SAX 2的标准模式。

SAX 2的标准模式

java 代码
 
  1. XMLReader reader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();  
  2. //获取一个InputStream实例is,然后  
  3. InputSource source = new InputSource(is);  
  4. //获取一个ContentHandler实例handler,然后  
  5. reader.setContentHandler(handler);  
  6. reader.parse(source);  

数据集的例子
java 代码
 
  1. EndPoint source_ep = new EndPoint("jdbc://MyDataSource/MySchema/MyName");  
  2. DataReader reader = DataPipeFactory.createDataReader(source_ep);  
  3. DataSource source = DataPipeFactory.createDataSource(source_ep);  
  4. EndPoint sink_ep = new EndPoint("object:///net/hyperdigital/dataset/DataSet");  
  5. DataSink sink = DataPipeFactory.createDataSource(sink_ep);  
  6. DataHandler handler = sink.asContentHandler();  
  7. reader.setContentHandler(handler);  
  8. reader.parse(source);  
  9. DataSet ds = handler.getContent();  

上面这段代码将执行“select * from MySchema.MyName",并将数据读入一个数据集,数据集的名字是MyName。如果我想执行一个特定的SQL语句,我只要在上面第一行后加上一行:
java 代码
 
  1. source_ep.setProperty("read""select xxx,xxx, xxx from xxx where ...");  

相应的代码会执行给定的SELECT语句,并把结果读入一个数据集,数据集的的名字属性是MyName。

如果我想把结果存放在一个XML文件中,可以这样做:

java 代码
 
  1. EndPoint source_ep = new EndPoint("jdbc://MyDataSource/MySchema/MyName");  
  2. DataReader reader = DataPipeFactory.createDataReader(source_ep);  
  3. DataSource source = DataPipeFactory.createDataSource(source_ep);  
  4. EndPoint sink_ep = new EndPoint("file:///absolution/file/path/MyFileName.xml");  
  5. DataSink sink = DataPipeFactory.createDataSource(sink_ep);  
  6. DataHandler handler = sink.asContentHandler();  
  7. reader.setContentHandler(handler);  
  8. reader.parse(source);  

和前面的代码相比,不同的仅仅是数据汇(数据流向的终点)的定义。如果要把数据存作XML并且发到消息队列上,同样只要改动数据汇:
java 代码
 
  1. EndPoint sink_ep = new EndPoint("queue://MyQCF/MyQueue");  

XML是假定的媒介形式,我可以以通过指定端点的属性来改变媒介形式。

如果我要把数据存入数据库表,假定数据集里的字段名和数据库表中的字段名相同,(convention over configuration),相应的代码如下:
java 代码
 
  1. EndPoint source_ep = new EndPoint("object://java/net/hyperdigital/dataset/DataSet");  
  2. DataReader reader = DataPipeFactory.createDataReader(source_ep);  
  3. DataSource source = DataPipeFactory.createDataSource(source_ep, dataset);  
  4. EndPoint sing_ep = new EndPoint("jdbc://MyDataSource/MySchema/MyTable");  
  5. DataSink sink = DataPipeFactory.createDataSource(sink_ep);  
  6. DataHandler handler = sink.asContentHandler();  
  7. reader.setContentHandler(handler);  
  8. reader.parse(source);  

数据迁移是小菜一碟:
java 代码
 
  1. EndPoint sing_ep = new EndPoint("jdbc://MyDataSource/MySchema/MyTable");      
  2. DataReader reader = DataPipeFactory.createDataReader(source_ep);      
  3. DataSource source = DataPipeFactory.createDataSource(source_ep, dataset);      
  4. EndPoint sing_ep = new EndPoint("jdbc://YourDataSource/YourSchema/YourTable");      
  5. DataSink sink = DataPipeFactory.createDataSource(sink_ep);      
  6. DataHandler handler = sink.asContentHandler();      
  7. reader.setContentHandler(handler);      
  8. reader.parse(source);    

不厌其烦贴了那么多,是为了显示在发布系统中任意两个端点间的数据通讯,都是同一模式。现在让我们来看一看EndPoint是个什么东西。java.net.URI是不可继承的,所以下面extends的意思是EndPoint包括URI的所有方法。
java 代码
 
  1. public class EndPoint extends URI  
  2. {  
  3.     public EndPoint(String endpoint) {}  
  4.     public EndPoint(String context, String relative) {}  
  5.     public EndPoint(URI uri) {}  
  6.     public EndPoint(URI context, String relative) {}  
  7.    
  8.     public String getContextPath() {}  
  9.     public String getRelative() {}  
  10.     public String getTextType() {}  
  11.     public URI getURI() {}  
  12.   
  13.     public String getProperty(String name) {}  
  14.     public void setProperty(String name, String property) {}  
  15.     public Set getPropertyNames() {}  
  16. }  

主要的属性(Property)有方法(method)和文本类型(text_type)。方法可以是Read, Write, Append, Delete, Get == Reader, Insert == Put == Write, Post == Update。Read是假定的也是唯一的源方法,Write是假定的汇方法。文本类型的值可以是text, xml, html,这些是Xalan中已经实现的,也可以很容易地增加cvs, json等等类型。EndPoint里的URI,基本上都是URL。EndPoint支持的传输协议,包括所有java.netURLConnection支持的协议,再加上jdbc(数据库表和LOB),jndi, queue,topic,object。Object的主要支持字符串、数据集、Map、。。。,字符串里的内容,可以是任何文本类型(text_type)。

再回过头去看上面的代码片断,设想一下各种数据源和数据汇的组合,上面的代码,远远超过了仅仅作数据库访问的意义,它可以用于网络上任意两个组件或者服务之间的数据传输,它可以看成是一个分布式数据访问和数据传输的框架。

数据集的应用模式

对数据的操作可以有两种模式:一种是OO模式,另一种我暂且称之为数据集管道模式,是我要介绍的模式。

在管道模式看来,数据集(DataSet)代表一个向量或者一个向量的序列。那么,对数据的操作就是作用于这个向量的一个矩阵,而ContentHandler就是一个事件驱动的矩阵实现。普通的ContentHandler更适于同一空间中的线性变换(实际上不一定线性),而Transformer对应于不同空间之间的变换。数学上,矩阵是可以连乘的,在SAX 2,Transformer是可以像管道一样串连的,二者一一对应。这种管道化的特性,提供了又一个分解复杂逻辑的手段,使我们能够把复杂逻辑分步骤实现,每一个子逻辑块实现一个明确的子目标。

数据集的管道模式借用了SAX 2的管道模式。上面的几个例子显示了数据传输的应用,它们是最短的管道系统,一个输入缓冲池(DataSource),一个泵(DataReader),一个输出缓冲池(DataSink)。系统把流体(数据)从输入缓冲池运送到输出缓冲池,不做任何处理。如果要对数据进行处理,只要在泵和输出缓冲池之间接上一个或几个过滤器(XMLFilter/Transformer)就可以了。过滤器可以是自己实现的ContentHandler,也可以是由XSLT产生的Transformer。

管道模式实际上就是用XML Schema实现领域模型,用ContentHandler/XSLT实现领域逻辑的模式。管道模式的好处是:1。最弱的类型依赖,所有的数值都是字符串类型,程序员不必费心类型转换。2。最佳的动态性能。XML Schema和XSLT都是文本文件,可以动态部署,动态修改。领域模型和领域逻辑的改变后,代码不必跟着修改。坏处是你得学XML和SAX 2。较之于一大堆繁不胜繁,遮遮掩掩,修修补补而又欲盖弥彰的设计模式起来,XML要简单直白多了。另一个坏处是,XSLT的功能不够强大,不足以应付在某些复杂情况。不过,某些OO模型下看来很复杂的问题,用XSLT来实现的话,也可能很简单。

OO模式也可以由两种做法。一种是传统的做法。一种是在数据集的基础上构造领域模型,将领域模型封闭在领域逻辑层。传统的领域模型+ORM的模式,不可避免地会把领域模型的细节暴露给领域逻辑层以外,而造成系统对领域模型不必要的依赖,大大降低系统的稳定性和动态性。我对传统的领域模型+ORM产生的大量的空壳类和映射文件十分厌恶,在我看来,它们就像一具具没有灵魂,没有血肉的骷髅,可以用尸横遍野来形容。

从对数据所负的责任来看,系统的参与者(actor)可以分为两类。一类是中介(agent),一类是客户(client)。客户是数据的最终消费者,负责对数据的解读和处理加工。数据只有对客户才有逻辑的和物理的意义。而中介只是传递数据,数据对于中介来说是没有意义的毛数据(raw data),中介的责任是将毛数据不失真的传递到客户的手中。从系统的壳层(layer)结构来看,客户位于领域逻辑层内部而中介位于领域逻辑层外部,因此中介们不必要也不应该知道领域模型的细节。这才符合编程的基本道理。通常的Domain Model+ORM的模式,就是因为违背了这个基本的道理,才有了种种的弊端。

 

小结

  • 数据集是一个普适的,开放的数据容器。数据集可以用来承载任何基于名值对的数据媒介。
  • 在数据集中,数值和元数据是分离的。元数据,除了字段名以外,可以在需要的时候延迟加载,不需要就不必加载。
  • 用数据集构造的系统,在领域逻辑层外,数据被视为毛数据,不再有类型的桎梏。
  • 在纵向,数据集可以应用于系统的各层之间,以来取代大量的XO(DTO,PO,VO,...)。
  • 在横向,已经开发出的工具可以使数据方便地在组件和服务之间传递。
  • 系统的配置信息,也是基于名值对的持久数据,当然也可以用数据集以及相关的技术。
  • 简单一致的数容器和传输模式,大大简化了组件之间的连通和阻抗匹配问题,开发团队可以集中精力解决领域逻辑问题。
  • 服务数据库表和服务本地XML文件的代码几乎是一样的,所以只需截取几条典型记录,存作XML,就可以用于离线测试。
  • 因为输入输出都是纯数据,都是同一个类型,Mock Object不再需要,大大简化了单元测试。
  • 易于离线开发和离线测试。我做J2EE项目,开发中基本不需要EJB容器。结果我的离线开发工具/环境被其他团队拿去作为正式的离线工具(standalone tool)。离线系统和在线系统具有完全相同的功能,唯一的差别是单线程和多线程。
  • 用数据集构造的系统,可以实现简单项目到复杂项目的平滑过渡,或者说复杂项目各阶段之间的平滑过渡。
  • 用数据集构造的系统,符合REST和SOA的要求。这里就不展开了。
   发表时间:2007-01-17  
DataSetMetaData也相当于一个配置文件了
0 请登录后投票
   发表时间:2007-01-17  
嗯, 可以看出来这个架构有很强的XSLT传承, 非常适合 流式的 批量 数据处理.

不过在OLTP方面可能就相对比较弱, 数据集 因为本身只是数据载体, 缺少 数据库管理系统 的 事务 和 并发控制 机制, 对 数据集 的复杂 统计 和 查询 效率上也会有障碍, 如果说改为通过数据库进行查询, 查询结果先封装成结果集再由程序处理的话, 那就回到ORM的思路上去了, 而且效果可能还不如ORM.

另外 数据集 也损失了 OO 理念里同时用 数据 和 行为 来建模解决问题的优势. 不过在异构系统里想全盘 OO 也是不太可能的, 所以在不同平台之间只是传递数据也不失为实践中的好方法.
0 请登录后投票
   发表时间:2007-01-17  
我在 WoW http://wow.dev.java.net 里实现了一个  XT (XML Based Transmission) 机制, 可以通过 XML 流传递对象, 这样就同时承载了 数据 和 逻辑. 不过编程接口不是那么友好, 需要应用类实现 XSendable 和 XReceivable 接口, 也要先理解掌握 SAX 原理, 再去在适当的Method里实现传递逻辑.

public interface XSendable
{
    /**
     * The xid must be valid xml local tag name.
     */
    String getXID();

    void send2(XCarrier carrier) throws SAXException;
}

public interface XCarrier
{
    void sendable(XSendable sendable) throws SAXException;

    void startElement(String name) throws SAXException;

    void attribute(String name, String value) throws SAXException,
            IllegalArgumentException;

    void text(String text) throws SAXException;

    void endElement(String name) throws SAXException;
}

public interface XReceivable
{
    /**
     * The xid must be valid xml local tag name.
     */
    String getXID();

    void startReceivingFromX();

    void receiveXAttribute(String name, String value);

    void startReceivingXContent();

    void receiveXText(String text);

    void startReceivingXNestedElement(Stack<String> elements);

    void receiveXNestedElementAttribute(Stack<String> elements, String name,
            String value);

    void startReceivingXNestedElementContent(Stack<String> elements);

    void receiveXNestedElementText(Stack<String> elements, String text);

    void endReceivingXNestedElement(Stack<String> elements);

    XReceivable mapNestedXReceivable(String xid, Stack<String> elements);

    void nestedXReceivableStartedReceiving(XReceivable receivable);

    void nestedXReceivableEndedReceiving(XReceivable receivable);

    void endReceivingFromX();
}


在两边同是Java环境的情况下逻辑上比较清晰, 比如这个 请求即时消息存档 的任务对象:
public class RequestArchivedIMsgs extends Submission
{

    // shared code

    private transient long tid;

    private transient long beforeID;

    private transient long beforeTime;

    private transient int max;

    // traverser side code

    public RequestArchivedIMsgs(long tid, long beforeID, long beforeTime,
            int max)
    {
        this.tid = tid;
        this.beforeID = beforeID;
        this.beforeTime = beforeTime;
        this.max = max;
    }

    @Override
    protected void sendAttributes2(XCarrier carrier) throws SAXException
    {
        super.sendAttributes2(carrier);
        carrier.attribute("tid", String.valueOf(tid));
        carrier.attribute("beforeID", String.valueOf(beforeID));
        carrier.attribute("beforeTime", String.valueOf(beforeTime));
        carrier.attribute("max", String.valueOf(max));
    }

    // scener side code

    public RequestArchivedIMsgs()
    {
    }

    @Override
    public void receiveXAttribute(String name, String value)
    {
        if ("tid".equals(name))
            tid = Long.parseLong(value);
        else if ("beforeID".equals(name))
            beforeID = Long.parseLong(value);
        else if ("beforeTime".equals(name))
            beforeTime = Long.parseLong(value);
        else if ("max".equals(name))
            max = Integer.parseInt(value);
        else
            super.receiveXAttribute(name, value);
    }

    @Override
    public void realize() throws AccessDeniedException, TheObjectException
    {
        Persistent<? extends TheTopic> topic = scener.getApp().getTOB()
                .get(tid);
        if (topic == null || !(topic.o instanceof TheTopic)) return;
        scener.getConnection().postGuidance(
                new SendArchivedIMsgs(
                        scener.getUserAccount().o.getOwningUser(), topic,
                        beforeID, beforeTime, max));
    }

}

它发送到服务器端执行时, 其实是构造一个 SendArchivedIMsgs 对象, 也是通过 XML 流传递回去, 在客户端把自己承载的数据填入到客户端缓存, 然后激发用户界面更新.

对动态结构化的数据, SAX 方式的传递和处理是最高效的. 另外因为是XML, 也有可能用于异构系统.
美国那边已经有开发者在用Flash做WoW的浏览器端界面, 也算是 XT 在异构系统上的尝试的起步了.

楼主如果有兴趣可以下载 WoW 的源码看看, 感兴趣的问题共同研究讨论.
0 请登录后投票
   发表时间:2007-01-18  
看完了上下篇。有几点感想及疑问。
1.为什么一定要使用XML作为中间数据格式?既然所有使用的情形都是JAVA,为什么不用JAVA类呢?更快,更自然些。可能会说,为以后跟异构系统交互?那么是否又经过了足够的考虑呢。
2.我觉得用法还算比较复杂的。可能为了这个普适性,必须要付出的代价,关键是你们用到了这个普适习性么,如果是,或者你们的应用就是要这么复杂,我觉得尚可理解。
3.我认为用XML做持久层来替代DB,以实现离线测试的想法难以理解。如果仅是存、取单条记录,做这类非常简单的测试那自然可以,但是对于给出SQL稍复杂的查询应该就无能为力了,如果非要做什么离线或者简化测试,似乎不如用一个HSQLDB来做,(当然,这并不是一个简单的路子,我觉得还是在线测试好了)

总结一下,你的架构估计是为了SOA一类的大型系统设计的。我对那些并不熟悉,不过我怀疑的是你们是否值得用这种复杂的结构,不要说太远的未来,或者噱头的问题。

另外一点意见,我对你表述的语言有点搞不清,比如什么矩阵连乘什么的(一看就是学院派的,高!),我的数学不好,是不是可以用简单直白的语言表达呢?搞个大伙都明白的方法。
0 请登录后投票
   发表时间:2007-01-18  
引用

另外 数据集 也损失了 OO 理念里同时用 数据 和 行为 来建模解决问题的优势. 不过在异构系统里想全盘 OO 也是不太可能的, 所以在不同平台之间只是传递数据也不失为实践中的好方法.

我想你没有理解我的意思。我没有明言背景是分布系统。我说的是在需要模型的地方(领域层)建模,在除此以外的任何地方,数据就是(毛)数据。

引用

1.为什么一定要使用XML作为中间数据格式?既然所有使用的情形都是JAVA,为什么不用JAVA类呢?更快,更自然些。可能会说,为以后跟异构系统交互?那么是否又经过了足够的考虑呢。

我考虑的是:开放,动态,简单。有了类,就有了约束,就有了耦合,就不开放,不动态。

引用

2.我觉得用法还算比较复杂的。可能为了这个普适性,必须要付出的代价,关键是你们用到了这个普适习性么,如果是,或者你们的应用就是要这么复杂,我觉得尚可理解。

我们的项目是一个类似MS BizTalk或BEA Integration Server的项目。我们实现的是一个引擎,下游的团队用它来开发他们的项目。随着下游项目的加入,Domain Model就要修改,平均一个月改一次。所以项目的性质决定了它必须是动态的开放的。在项目的初始阶段,我们请了BEA来做POC,结果Integration Server不能满足我们的动态需求,另外运行效率也不好。BizTalk也一样。这之后,我们才决定自己做的。

最后的结果是,我们不仅满足了系统对于需求的稳定性,而且我们的系统至少比BEA Integration Server快25倍,比BizTalk快15倍。

那些例子,就是REST Style的WebSerivce。如果你觉得用法复杂,那是我写作的失败。

引用

总结一下,你的架构估计是为了SOA一类的大型系统设计的。我对那些并不熟悉,不过我怀疑的是你们是否值得用这种复杂的结构,不要说太远的未来,或者噱头的问题。

我们的系统已经运行三年多了。第二版是我一个人写的,就用这里说的方法,将整个系统重构。代码是第一版的一半,运行效率是第一版的4倍。第二版也已经运行两年多了,我离开那家公司都快一年了,系统照样在转。

0 请登录后投票
   发表时间:2007-01-18  
嗯, 分布的组件之间只传数据进行互操作的设计现状, 也是传统技术框架造成的, 以后 HBI (Hosting Based Interfacing) 发展起来, 传递带逻辑的数据对象就会很自然了.

从应用上来看, 这个系统结构起到的好像是 ESB (Enterprise Service Bus) 的作用, 兼有一些企业组件开发最佳实践的指导意义. 如果花心思规整包装一下, 差不多可以作为一个SOA方案卖了.
0 请登录后投票
   发表时间:2007-01-18  
pojo 写道

引用

总结一下,你的架构估计是为了SOA一类的大型系统设计的。我对那些并不熟悉,不过我怀疑的是你们是否值得用这种复杂的结构,不要说太远的未来,或者噱头的问题。

我们的系统已经运行三年多了。第二版是我一个人写的,就用这里说的方法,将整个系统重构。代码是第一版的一半,运行效率是第一版的4倍。第二版也已经运行两年多了,我离开那家公司都快一年了,系统照样在转。


对于你说的成就,我很欣赏。
但似乎你并没有回答我的问题。

我已经大概知道你一个人做了这个系统,然后系统性能比这个那个的商业版本高很多倍。
但是我仍然不清楚你的软件是在什么样的环境下产生的,为了解决什么为题而做。
脱离了这些,我想我们理解也困难。
先告诉我们需求是什么,再说怎么做,为什么。

我猜了一下,既然你提到了Biztalk之类的,那么你做的是否是一个完整的EAI软件呢?或者是一部分?

0 请登录后投票
   发表时间:2007-02-06  
潜水多年,忍不住大吼一声:真不赖
0 请登录后投票
   发表时间:2007-02-06  
数据集的概念很类似dotnet的ado机制的嘛
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics