Author Archive

搬家杂记

上周末,之前“蹭租”的房子到期,遂搬至另一个小区,周末两天的时间全花在搬东西,打扫卫生上了。不过在躺在床上那一霎,感觉还是值了,毕竟作为北漂一族,如何能使自己感到有“家”的感觉才是最真切的,哪怕这个“家”非常临时。而我也告别了之前住大房间——客厅的历史。

说到之前住的客厅,可能这是我7月份来北京之后比较失误的决定吧。当时正赶上奥运,房子不是很好找,就蹭在了三个本科同学租的三室一厅的客厅里。由于客厅的缘故,加上彼此之间很熟,所以自己的空间很小,以致于除了工作以外做的技术积累很少。可能帮小花做的GEF小项目算是一些,其他都很少。有时候在看书的时候,由于客厅没有书桌,只能到同学的房间,如果同学回来了也只能让出桌子。现在回头看,当时就很欠考虑,因为有一个自己独立的空间是非常重要的。年前有个上海autodesk的同学辞职,来北京找工作。到的前几天跟我们一起住,不过在一星期内就在外面租了房子,我们几个都说没必要这么急找房子,先一起住着嘛。不过他非常坚持,其实也就是空间的缘故了,如果跟我们一起住的话,供他梳理和积累知识以找工作的空间会非常有限。

除了搬家,这星期比较重要的事情就是拿到DB2某部门的口头offer了。上周五两小时的笔试,昨天上午三个技术官一个半小时的面试,再加上昨天下午一线经理全英文的面试,很tough的一个过程。自己的表现也只能说正常,算不上好或者完美,因为对笔试和面试都没有作太多的准备,一些只要准备就能答上的问题最后让自己都感到无奈了,比如问你Hibernate对实体的继承设计有哪些实现支持,这个问题我做Hibernate sharing的时候都谈到过,可当时就是记不住具体哪几种了,非常非常的尴尬。笔试面试还是得好好的准备,否则这个知识点答不全,那个也答不全,你的印象分也扣得差不多了。

不过这个offer也不能算很保险,因为最后一线说他那边没什么问题了,HC也不会有问题,不过最终得美国那边批,所以可能美国那边还会有电话面试。不管怎么样,保持一颗平常心吧,如果真有什么意外也不算是世界末日。记得10月底在面完自己部门的一线二线时,感觉offer就是囊中之物了。结果一线一封“Please seek other opportunities besides GBSC”的邮件彻底让我体检到了“人间悲剧”。。。

浅析java字符编码(在socket游走之间)

在引子中,介绍了一些在文件操作时需要注意的java字符编码细节。而在实际工程中,对java字符的操作更多情况下是在网络上以流的方式进行编解码。这篇文章就尝试着记录一些这方面的心得,与大家分享。

一般来说,工程中免不了与第三方厂商或系统交互,有时候你是作为发送方,有时候你是作为接收方。如果不采用类似于MQ这样的消息中间件,那么对于消息的交互细节,有一些是必须注意的。

自己动手,丰衣足食

为了完成一些基本的接收和发送动作,我们大可以自己动手,利用java中的socket api,实现一个如下的客户端和服务端。

Client:

   1: InetAddress addr = InetAddress.getLocalHost();
   2: int port = 9876;
   3: Socket socket = new Socket();
   4: SocketAddress socketAddr = new InetSocketAddress(addr, port);
   5:  
   6: String data = "中文123";
   7: socket.connect(socketAddr);
   8:  
   9: String path = "/";
  10: BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
  11: bw.write("POST " + path + " HTTP/1.0\r\n");
  12: bw.write("Content-Length: " + data.getBytes("UTF-8").length + "\r\n");
  13: bw.write("\r\n");
  14:  
  15: // send request with the data
  16: bw.write(data);
  17: bw.flush();
  18:  
  19: // get response
  20: BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
  21: String line = "";
  22: while ((line = br.readLine()) != null) {
  23:     System.out.println(line);
  24: }
  25: bw.close();
  26: br.close();

这里需要注意的是有几点:

1. 第12行Content-Length的大小一定要设置正确,这里的长度是POST正文数据的byte大小。一会联系server端的代码详细解释这个属性。

2. Content-Type作为Http header并不是必需的。有时候我们会发现有些流Content-Type是写着UTF-8,但实际上它的内容不是按照UTF-8编码的。所以这个属性对内容数据并没有约束力。

3. 第10行至第13行用了BufferedWriter进行数据发送,这里可以有多种选择,比如PrintStream。

Server:

刚才说到了Client发送的Content-Length指定了发送内容的byte大小,那么这个值在server端如何取得呢?一般的做法是通过一个BufferedReader对socket.getInputStream()进行adapt,然后就可以用readline()方法一行一行地将头信息读出来,由于Http Header都是ASCII字符,由于无论哪种解码方式Content-Length的值都能正确得到。

接下来读取body信息的方式就有一些陷阱在里面了。

陷阱1. 网络上的字符流不同于文件中固定的字符流,它是没有EOF的。所以什么时候停止是由你来决定的,如果你一直readLine()它是不会终止的。

陷阱2. 刚才已经得到了Content-Length,那么你是不是会尝试以下的代码呢?

   1: for (int i = 0; i < contentLength; i++) {
   2:     sb.append((char) br.read());

这段代码的结果在处理刚才发送的“中文123”时便会读了5个字符后无法往下读,这是由于br是按char来读,而并非byte,那么在读完5个字符后便停止了(但如同readLine()读完后一样,它并不会返回一个EOF)。而contentLength是9。

如果你说为什么不在发送时将contentLength指定为5呢?这就纯粹是为了解决问题而解决问题,更违背了HTTP的RFC规范。

规范的做法是首先不对socket.getInputStream()进行任何的adapt,即不进行decoding,先将body的byte[]数组完整取出来,再对其decoding。

Solution1. 如果socket的inputstream你只需要遍历一遍,那么用inputstream的read()方法先得到contentLength,就能顺利得到body的byte[]数组。

Solution2. 如果socket的inputstream你需要遍历多遍,比如首先要计算body的某一部分的md5值,那么通过socket的inputstream首先构造一个ByteArrayInputStream是更好的选择。不但可以调用mark()和reset()方法,更可以利用其有EOF直接调用readLine()方法。

   1: InputStream input = socket.getInputStream();
   2: byte[] buffer = new byte[1024];
   3: int readNum = input.read(buffer);
   4:  
   5: // wrap a local stream
   6: ByteArrayInputStream ins = new ByteArrayInputStream(buffer, 0, readNum);
   7: BufferedReader br = new BufferedReader(new InputStreamReader(ins, "UTF-8"));    // UTF-8 is necessary
   8: String line = "";
   9: long contentLength = 0;
  10: while ((line = br.readLine()) != null) {
  11:     if (line.startsWith("Content-Length")) {
  12:         contentLength = Long.parseLong(line.substring(line.indexOf(':') + 1).trim());
  13:     }
  14:     if (line.equals(""))
  15:         break;
  16: }
  17:  
  18: StringBuffer sb = new StringBuffer();
  19:  
  20: while ((line = br.readLine()) != null)
  21:     sb.append(line);
  22:  
  23: PrintStream out = new PrintStream(socket.getOutputStream());
  24: out.println("HTTP/1.0 200 OK");
  25: out.println("Content-Type: text/html;charset=UTF-8");
  26: out.println(); // blank will end http-header
  27: out.write("123中文".getBytes("UTF-8"));
  28:  
  29: socket.close();

几点说明:

1. 第2行到第3行的代码需要些小改动以适应大数据量post的情况。

2. 第23行到第27的代码与Client端利用BufferedWriter输出的情况类似,一些细节可以自行比较。

3. 你可能会注意到一个细节,为什么Client在read response(Client代码20-24行)可以毫无忌惮地使用BufferedReader来读?因为在Client端的read动作是在Server端发出close()动作之后,而这同样可以解释为什么在Server端socket.getInpuStream()没有EOF标志。

下一节将介绍一下client端及server端代表了先进生产力的API,自己制造粗糙的轮子并不是一件好事,嗯。

Category: java  Leave a Comment

浅析java字符编码(引子)

最近的项目由于要和多方欧洲ISV集成,进行数据报文的接收和发送,对于java字符编码以及处理方式略有一些心得,遂记之。

这篇引子先用一些直观的代码说话,给出一些直观的结果。

1. 写文件

如果要写一个简单的文件,就会有以下的代码:

BufferedWriter bw = new BufferedWriter(new OutputStreamWriter( 
                            new FileOutputStream(fileName), charset)); 
bw.write("中文123"); 
bw.flush(); 
bw.close();

其中charset主要有几类:ISO-8859-1(ASCII),UTF-8(Unicode),UTF-16(Unicode),GB-18030(ANSI local),GB2312(ANSI local)等等。

PS1: 如果OutputStreamWriter换成无charset构造方法,则charset默认为JVM的编码集。

本地的JVM编码集可通过System.getProperty("file.encoding")得知。我公司的台式机是GB18030(环境是xp英文,改了默认语言),而家里的笔试本是GBK(环境是xp中文)。所以这个值是由多方配置一起决定的,比如JVM setting,OS,OS setting等。

如果你的机器是ISO-8859-1,那么写入中文应该是会乱码的。

PS2: GB18030从字符集上来看基本是与Unicode一一对应的,但是从写入文件系统的种类和用途来分,它更多是用作本地ANSI。

PS3: 这里的中文字符串直接写到了程序中,并不是一种优雅的方法,通常的I18N做法是把涉及到国际化的文字写到资源文件里。

这个写文件的过程首先构造了一个JVM编码集的字符串,然后将它以某种charset转换,最后以byte[]并写到文件系统中。

2. 读文件

java i/o库提供两种读的方式,基于byte和char。而String也就是char[],所以java i/o提供的类似readLine()这样的方法实际上做了一个从byte到char的转换。

2.1 以byte方式读文件

int length = 0;
InputStream ins = new FileInputStream(fileName);
while (ins.read() != -1) {
    length++;
}
System.out.println(length);

注:此处虽然用ins.available()也可以得出同样的结果(因为文本很小,基本可以认为ins.available()就是inputstream的字节大小)。而实际上这个方法只是返回在无阻塞情况下当前所能读的长度,譬如大于8k的文件它也只会返回8192。

         
             length(byte)

charset

123

total

UTF-8

3

3

3

9

UTF-16

2

2

6

10+2

GB18030

2

2

3

7

default(JVM charset)

2

2

3

7

 

以byte方式读文件并不涉及解码过程,故这个结果是文件中字符串的真实存储长度。

其中UTF-8对中文字符以3byte编码,而对ASCII前128个字符优化处理,以1byte表示。UTF-16标准就是通常我们说的Unicode,对每个字符都采用2byte编码。GB18030是本地ANSI编码,中文字符占2byte,ASCII占1byte。default因机器而异。

PS: 你可能注意到这里有个10+2,+2的问题与Big Endian和Little Endian(字节序)有关,我们等到以后再说:D

2.2 以char方式读文件

BufferedReader br = new BufferedReader(new InputStreamReader( 
                    new FileInputStream(fileName), charset)); 
char[] buffer = new char[1024]; 
int readNum = 0; 
StringBuffer sb = new StringBuffer(); 
while ((readNum = br.read(buffer)) != -1) 
    sb.append(buffer, 0, readNum); 
String s = sb.toString();
 
System.out.println(s.length()); 
System.out.println(s.getBytes().length); 
System.out.println(s.getBytes(charset).length);

其实读文件的本质还是以byte流读,只不过byte到char用了一个InputStreamReader这么一个Adapter,并指定了解码方式。需要注意的是InputStreamReader的这个charset指定的是从文件读取byte[]的解码方式,也就是说它只是保证以byte流的方式把文件内容正确读出来,而之后读出的byte[]转换到char[](String)的过程依旧是通过JVM charset转换的。所以这段代码的输出结果就是:s.length()输出char[]的长度为5;s.getBytes().length输出JVM charset decoded byte[]的长度为7(因为我本机是GB18030);而s.getBytes(charset).length则输出JVM_charset decoded byte[] –> charset decoded byte[]的长度。当然这些结果都是基于正确解码的基础,否则会抛sun.io.MalformedInputException或者解出乱码(例如以utf8编码,又以GB18030解码)。

Category: java  Leave a Comment

Was 6.1.0.19的一个bug

现在的项目服务器环境是我配的,Was和Db2都update到了最新的fixpack。Was更新到了今年9月份的一个版本,就是这个6.1.0.19了。可是最近项目在服务器上测试经常down,运行一段时间就基本卡死,不过本机确没有这个问题。

最后怀疑是Was版本问题,于是本地Was也pack到了6.1.0.19。debug一下,发现很奇怪的异常:基本上每一次数据库查询就会出现以下错误:

[08-11-20 9:26:01:484 CST] 00000025 SystemErr     R Exception in thread “WebContainer : 1″ java.lang.RuntimeException: java.lang.NullPointerException
[08-11-20 9:26:01:484 CST] 00000025 SystemErr     R     at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:793)
[08-11-20 9:26:01:484 CST] 00000025 SystemErr     R     at com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:873)
[08-11-20 9:26:01:484 CST] 00000025 SystemErr     R     at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1473)
[08-11-20 9:26:01:484 CST] 00000025 SystemErr     R Caused by: java.lang.NullPointerException
    at com.ibm.io.async.AsyncFuture.completed(AsyncFuture.java:111)
    at com.ibm.io.async.ResultHandler.complete(ResultHandler.java:195)
    at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:743)
    at com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:873)
    at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1473)

[08-11-20 9:26:01:484 CST] 00000025 SystemErr     R     at com.ibm.io.async.AsyncFuture.completed(AsyncFuture.java:111)
[08-11-20 9:26:01:484 CST] 00000025 SystemErr     R     at com.ibm.io.async.ResultHandler.complete(ResultHandler.java:195)
[08-11-20 9:26:01:484 CST] 00000025 SystemErr     R     at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:743)
[08-11-20 9:26:01:484 CST] 00000025 SystemErr     R     … 2 more

之后的查询这个WebContainer的number就会++,这不down就奇怪了。

不过从异常信息来看,应该和代码逻辑没有太大关系,最后google到了这是Was 6.1.0.19的一个Bug,temp的fix方案是这样,打开Was控制台,在Application servers > server1 > Web container > Custom Properties里new以下的属性:

Name : com.ibm.ws.webcontainer.channelwritetype
Value : sync

重新启动Was即可。

Category: java  Leave a Comment

几本值得关注的eclipse书籍

以下几本都是eclipse的经典书籍,均尚未出版.

Eclipse: Building Commercial-Quality Plug-ins, Third Edition
EMF: Eclipse Modeling Framework, Second Edition
Eclipse Modeling Project: A Domain-Specific Language Toolkit

其中第三本应该是GMF的第一本权威书籍,zx的blog中还提到过.

Category: java  Leave a Comment

搜狗五笔

从上周开始,由于固话号被撤,一直上不了网,周末这两天顿时觉得很无聊…于是周六晚上跑到老刘家蹭网蹭饭.

无意间老刘说搜狗好像出了款五笔输入法,于是上官网看了眼,基于五笔86,好像非常不错.自从用电脑以来,输入法一直忠于最原始的五笔86输入法,其他的基于此的诸如万能,陈桥,个人感觉用户体验都不是很好.而今天试用的搜狗五笔,非常非常爽,以致于使用之后有种"我找你找了好久"的感觉.

以下是一些我的个人配置,可能跟我一样用了十几年的原版五笔的用户也会做这些配置.

调频:

image

中文默认的标点使用半角:

image

还有就是搜狗五笔的词库比原版大了很多,这样诸如"奭"这种原版词库没有的字能轻松地打出来.但同时却有些"幸福的烦恼":由于词组比原来扩充了很多,这样一些原版是唯一的字或词组却有多个选项,这些不必要的选择就是annoying的事情了.

例如输入rtfp,你只是想直接输出"挺"字.可以却出现如下的画面导致你还要多做一次选择:image
sogou允许删除自定义的词组,但我觉得非自定义的词组也应该能被删除.

另外,还有一些诸如与个人帐户同步词库等功能.总之,就是很好很强大了.

Anything is contribution

事隔三年,再次接触eclipse plugin开发,免不了有很多的感触,so log it.

  • contribution
    想必看过<contribution to eclipse>这本书的人都不会对这个词陌生.contribution也是eclipse开发的一种普遍的开发思想.
    例如你想在别人开发的插件上加上自己的东西怎么办?首先要扩展一个eclipse或是其他插件定义的扩展点,然后作objectContribution或者viewContribution.前者指某个对象被选中时,你所扩展的动作被触发.viewContribution指的在activited的view上做扩展,小三角的下拉菜单,和view并排的toolbar等等.
    image 
    如果你想覆写这个插件的某个部分,那么只作object或view的contribution是实现不了的.这需要将需要覆写的这个部分抽出一个扩展点,然后相应的实现这个扩展点.
    contribution是一个非常重要的概念,因为无论你是在别人的插件做扩展还是自己从头写一个插件,都是一步步的迭代,一步步的扩展,最后你写完的N个小插件完成一个整体功能.如果你最后写出来的插件只是一个jar,我觉得并不能算是一个合格的contribution,起码有点bad smell,因为你自己都没在自己的插件做扩展,那么别人更不可能在你的插件上做扩展了.
    eclipse3.4将以前一个单独发展的插件eclipse spy打包进了pde的runtime,所以你可以直接在3.4中点击Alt+Shift+F1查看激活窗口的plugin信息.
  • model, EMF与MDA
    model在软件开发中的作用不言而喻.数据库设计中有这么一句话:数据存在的时间比利用这些数据开发出来的系统要长得多.而model比数据更进了一层,有点抽象到底的味道.
    现在公司做的这个项目有web和plugin两部分,其实两部分用的模型是基本一致的.在web上以数据库的形式展现,而在plugin中以EMF的方式展现.而开发方式都是model driven的.web中以model生成结合相应的ORM框架生成DAO基础代码,而在plugin中生成核心的model代码,model.edit代码.
    EMF确实是一个精心设计的框架,生成的代码质量很高,比如它的edit代码,提供了相应的模型元素的provider,当然你得在你的代码里做一些转换才能利用.
  • lightning的记忆
    lightning是三年前做的一个plugin,曾经靠这个项目拿了很多奖.更重要的是在这个项目中,熟识了一批对我影响很大的朋友.比起浮云般的奖项,这才是最为珍贵的.
    现在回过头来看,lightning很多地方需要改进.
    首先就是版本问题,lightning至今仍只能跑在eclipse3.0上,这一点是比较致命的.从开源软件的角度来说,尾数是偶数的版本都是比较stable的.eclipse也不例外3.0,3.2,3.4都是非常稳定的版本,比起上一个版本也有很多的新特性.所以lightning如果要做扩展,就非常困难.比如用CNF针对模型做一个viewer tree,那么不在3.2以上的版本就无从谈起.
    其次是SWT/JFace中的一些best practice,这部分当时的布局是用的GridLayout.但是从今天我所实践的来看,FormLayout应该是最提倡使用的,而实际上也确实如此.FormLayout基本上是万能的,能很好的设计各种布局需求.如果继承的父类定义了**layout,那么子类最基础构造的composite必须用**layoutdata,这点是必须遵守的.比如一个dialog继承了TitleAreaDialog,那么这个dialog最外围的composite需要setLayoutData(new GridData(GridData.FILL_BOTH)).
    而对于父与子关系的composite,也是这个道理,父类设定的**layout,子类必须设定相应的**layoutdata才能生效.
    最后就是模型和性能上的改进.当时FLASH的模型全是自己设计,持久化的.主要是用java2D画基本的图形,然后定义转换算法,做出变化的过程.其实当时做到一半的时候,听说了GEF和EMF,但考虑到当时的资料很少,而且java2D已经研究有了一定经验,最后还是放弃了GEF和EMF.而从今天来看,这两者可以很好的配合,并解决lightning的需求,而且java2D是基本AWT的,相应的图形放在eclipse基于SWT的view上,必须用SWT_AWT这样的转换操作,而这在一定程度是有性能问题的.

Category: java  Leave a Comment

Olympic Football Final

昨天终于在奥运即将结束的时候,有幸去目睹了一下鸟巢的风采.票是梓特同学的GF弄的,RMB 800,我和涛哥抱着"今生能看几回奥运"的心理,欣然前往.

票是足球决赛,阿根廷对尼日利亚,中午12点.这个时间真是很尴尬,但是我发现2000年和2004的足球决赛都是艳阳高照下进行的,不知道是为什么.

早上九点多出门,13号线,10号线,8号线,凭着当日门票坐城铁或公交都是免费的.比较假的是在10号线转8号线的时候偶遇杜伊.

final1 

更比较假的是旁边一个哥们从后面赶上来,直接把他价值不菲的单反塞给我们,让帮拍张,然后头也不回的去追杜伊了.orz…就不怕我们直接掉头走了么,哈哈.

终于来到了传说中的鸟巢,座位是3层的20排,好远啊.看不清球员的脸,不过能第一次从全景欣赏一场足球决赛,也是相当激动的了.
开场后场面略显沉闷,梅西和里克尔梅还是阿根廷最有威胁的球员,第一次欣赏里克尔梅从容的背身拿球,摆脱,组织进攻,大师级的优雅一览无遗.梅西个人能力摆在那儿呢,无论是右边路的突破还是中路和队友踢墙二过一,都能激发起观众的呐喊.没办法,作为一场奥运会的决赛,就是看球星来的.不过梅西和队友的配合还算不是默契,几次二过一都差那么一点儿.

final2

进球终于来到了,58′,Di Maria,这位在半决赛对巴西就表现得很抢眼的左前卫接梅西的直塞,一脚精彩的吊射打破了僵局.

DiMaria
DSC02980

进球之后双后都还有不少机会,但是阿根廷表现的更像是一支国家队,战术素养,大局观和对场面节奏的控制.尤其是马斯切拉诺这道铁闸,没有给尼日利亚太多机会.最后,阿根廷如愿卫冕,比分也和四年前一样,只是进球者从特维斯换成了迪马利亚.

DSC02981 

颁奖仪式,巴西的众多大牌也到场了.

collage 

最后,给个五星红旗的特写.

DSC02989

Category: dailylife  2 Comments

MOTD归来

就当奥运如火如荼地进行之时,新赛季的英超联赛上周末已经鸣金开战.so,每周末又可以下载高清的BBC Match of the Day(简称MOTD)欣赏了.

昨天晚上看了Day1,第N次感叹英超已经全方位将其他联赛甩在身后.随便聊聊看完的感受吧:

保罗因斯入主布莱克本,感觉执教能力和经验还差马克休斯一筹,本特利出走白鹿巷,实力整体有所减弱,不过圣克鲁兹还是可以依赖的.加上Paul Ince,曾经在MU效力过的英超现役主帅已经有4名了,还有Mark Hughes, Roy Keane和Steve Bruce(拜托布鲁斯同学不要再将名字缩写印在教练外套上了).

富勒姆首轮就输给了升班马,他们本赛季还是为保级而战,不知道本赛季是否还有上赛季最后一轮挤掉雷丁的运气.

阿森纳本赛季中场人员流失严重,而纳斯里的到来从任何方面来说,都是非常正确的选择.假以时日,"皮雷二世"将横空出世.

罗比基恩以2000万英镑的身代来到安菲尔德,我觉得这么高的价格有待商榷,尽管高佬已经出走朴茨茅斯.从第一轮的表现来看,小基恩表现得很积极,甚至有点过了头,将托雷斯一个空门补射给挡出…非常尴尬.托雷斯还是红军最可以依赖的,他已经不再是马竞那个hell boy了.

再来说说热刺吧.热刺一直是我非常欣赏的球队,老板非常有进取心,从这几年的经营,买人就可以看出,他们不甘居于第二阵营,不过打破传统四强的垄断,还是有很长的路要走.今年从阵容上补充了相当一部分有实力的球员,莫德里奇,本特利,多斯桑托斯.不过对热刺最伤的就是小基恩和贝巴托夫(虽然还未正式离开)的出走,而达伦本特不能被寄予过多期待.

最后来看看贝赫拉米.是的,这位前拉齐奥的右中场已经转会铁锤帮.我并不是说贝赫拉米有多强,但这种其他联赛准强队的绝对主力流失到英超的现象,却是耐人寻味的.而这种例子其实有很多了,上赛季乌迪内斯的蒙塔里就跳到了朴茨茅斯.这种信号已经很明显了,就看其他联赛能不能忘记历史,重新迎头赶上了.

伪问题 or not

来公司之后接到的第一个任务是把原来的一个数据库重构,中间遇到两个小问题.算不算问题?这是一个问题.

第一个问题可以抽象成这样:
表p(erson)和表a(ddress),他们原来都没有主键,表p的属性有p_name,表a也有p_name,是直接引用表p的p_name,然后还有些type,phone之类的.
现在要做的事情就是把两表都加上id主键,然后把表a的p_name改名为p_id,并通过其以外键的形式和表p关联起来.虽然我不知道出于什么原因这些表设计之初没有主键,无所谓是否把它看成一个伪问题,反正只是一条update语句搞定的事情.alter之后的表看起来像这样:

表person  
p_id p_name


表address      
a_id p_id(p_name renamed) type phone

注意现在address的p_id是原来的p_name改名改过来的,数据还是person的p_name值.
我首先想到的是update person, address set address.p_id = person.p_id where address.p_id = person.p_name.
悲剧,update操作后只能接一个表.所以想当然的把select中的思维拿来过做update是不行的.
再次试验,update address set p_id = (select p.p_id from person p, address a where a.p_id = p.p_name).
再次悲剧,提示update操作set的值必须是唯一的.
括号里的子查询结果确实是得到了正确的结果集,不过这样赋给p_id是不允许的.
最后的语句是这样的:
update address a set a.p_id = (select p.p_id from person p where a.p_id = p.p_name fetch first 1 row only).
特别注意这里有个first 1 row only是db2的方言,相应数据库应该换成取头一条记录的相应语句.

第二个问题就是在原来的表上更新id字段,因为以varchar作主键无法自动增长,所以先设成default为0.
这个问题你当然可以说改成integer或者bigint就完全不存在这个问题,不过牢骚归牢骚,问题还是得解决.
首先想到的是能不能用一条语句就搞定了.尝试了一下,也搜了一些db2的函数,不是那么容易,要做+1操作还得做转成数字的操作.一条语句应该是不行,或许写个存储过程能搞定.
然后想到的就是用N条update语句做了,因为update每次只能set一个value.手写N条语句是不可能了,最后实现的方法是用JDBC先把所有id为0的取出来,然后遍历,同时构造相应的domain对象,将其加入一个list.然后把数据库的id为0的纪录清空,遍历list,将每个domain对象插入.其实最后用的是insert操作而不是update,update的话还是有很多限制.
虽然这个方法看起来比较诡异,anyway,还是解决了需求.

最后来看看这两个问题,虽然你有一万个理由置疑这个问题为什么会出现,并牢骚满腹地抱怨为什么会有这种伪问题.但是,一再坚称伪问题也只能说明你不敢面对.只有当你如庖丁解牛地分析各种伪问题,并给出solution,才说明水平到了新的层次.