2005年12月05日

一、两种访问方法:

目前的kxml支持两种wap格式:WBXML/WML。
而有两种方法将解析WBXML:
1。使用j2me将WBXML转换到XML;
2。使用kxml直接解析WBXML流。下面我在这里讨论一下使用第二种方法实现client代码解析WBXML,当然要使用kxml了。

二、kxml实现方法:

首先需要位于web server的应用程序通过开放WAP网关(关于JWAP:详见http://jwap.sourceforge.net/)发送WML文件给j2me client。在WAP网关将数据发送j2me client之前WAP网关将WML文件转换为了WBXML文件。下面代码的展示了j2me client如何接收WBXML数据,解析数据,并显示有用的数据在手机屏幕上。

需要注意,在本例程中使用的kxml v1.0版本,kxml v2.0版本在使用上可能有所不同,开发者可以参考kxml2的手册


import java.io.*;
  
   import org.kxml.*;
   import org.kxml.parser.*;
   import org.kxml.wap.*;
  
   import javax.microedition.lcdui.*;
   import javax.microedition.midlet.*;
   import javax.microedition.io.*;
  
   public class WbxmlTest extends MIDlet implements CommandListener
   {
   private Display display = null;
   private List menu = null;
   private Form form = null;
   private String incomingText = "";
  
   static final Command okCommand  
      = new Command("Ok",     Command.OK,     1);
   static final Command exitCommand
      = new Command("Exit",   Command.EXIT,   0);
    
   // This is a hard coded WSP message that contains
   // address of web server where  our jsp page is located.
   byte[] message ={
      (byte)'1',(byte)0x40,(byte)0x3D,(byte)'h',(byte)'t',
      (byte)'t',(byte)'p',(byte)':',(byte)'/',(byte)'/',
      (byte)'l',(byte)'o',(byte)'c',(byte)'a',(byte)'l',
      (byte)'h',(byte)'o',(byte)'s',(byte)'t',(byte)':',
      (byte)'8',(byte)'0',(byte)'8',(byte)'0',(byte)'/',
      (byte)'e',(byte)'x',(byte)'a',(byte)'m',(byte)'p',
      (byte)'l',(byte)'e',(byte)'s',(byte)'/',(byte)'j',
      (byte)'s',(byte)'p',(byte)'/',(byte)'f',(byte)'i',
      (byte)'n',(byte)'a',(byte)'l',(byte)'f',(byte)'i',
      (byte)'l',(byte)'e',(byte)'s',(byte)'/',(byte)'D',
      (byte)'a',(byte)'t',(byte)'.',(byte)'j',(byte)'s',
      (byte)'p',(byte)0x80,(byte)0x94,(byte)0x88,(byte)0x81,
      (byte)0x6A,(byte)0x04,(byte)0x83,(byte)0x99
               };  
   // Memory space to receive message.
   byte[] msg = new byte [256];
    
  
   public void pauseApp() { /* ----- */ }
  
  
   public void destroyApp(boolean unconditional)
   { notifyDestroyed(); }
  
      
   public void startApp() {
      display = Display.getDisplay(this);
      this.mainMenu();
   }//startApp
  
  
   //Displays the menu screen
   private void mainMenu() {
      menu = new List(" Send Request", Choice.IMPLICIT);
      menu.append(" Send Message",null);
      menu.addCommand(okCommand);
      menu.setCommandListener(this);
      display.setCurrent(menu);
   }//mainMenu
  
  
   //Display the reply from WAPGateway (JWap).
   private void showReply()   {
      form = new Form( "Incoming Message" );
      form.append("The price = " + incomingText);
      form.addCommand(exitCommand);
      form.setCommandListener(this);
      display.setCurrent(form);
   }//showReply
  
  
   // Makes a WSP Connection with a WAPGateway,
   // Sends a message and receives the reply.
   public void getConnect() {
      Datagram dgram =null;
      DatagramConnection dc=null;
      try   {
         dc = (DatagramConnection)Connector.open ("datagram://127.0.0.1:9200");
         dgram = dc.newDatagram(message, message.length);
           try{
            dc.send(dgram);}
         catch (InterruptedIOException e){
            e.printStackTrace(); }
        
           dgram = dc.newDatagram (msg,msg.length);
           try{
            dc.receive(dgram);}
         catch (InterruptedIOException e){
            e.printStackTrace();}
         catch( IOException e){
            e.printStackTrace();}
  
         // This is the most interesting part.
         incomingText = this.getIncomingTextOfWmlc(dgram.getData());
         this.showReply();
           dc.close();
       }//try
      catch (IllegalArgumentException ie){
         ie.printStackTrace(); }
      catch (ConnectionNotFoundException cnf){  
         cnf.printStackTrace();   }
      catch (IOException e){e.printStackTrace();}
    }//getConnect()
  
  
    private String getIncomingTextOfWmlc ( byte[] wmlc ) {
    try {
      // Remove WSP header.
      // We know it is 19 bytes for our case.
      // But for real world applications,
      // this should be dynamically deteced.
  
      for ( int j = 0; j < wmlc.length-19; j++ )
                 wmlc[j] = wmlc[j+19];
          
      WmlParser parser = new WmlParser(new ByteArrayInputStream(wmlc));
      while (true) {
              try {
                 ParseEvent parseEvent = parser.read();
                 if ( parseEvent.getType() == Xml.START_TAG ) {
                    Attribute attr =
                parseEvent.getAttribute("value");
                    if ( attr != null )
                       return attr.getValue();
                 }//if
              }//try
      catch ( IOException e) {}
      }//while
          }//try
      catch ( IOException e) { e.printStackTrace();   }
      return "error";
   }//getIncomingTextOfWmlc
  
   public void commandAction(Command c, Displayable d) {
      String commandlabel = c.getLabel();
       if (commandlabel.equals("Exit"))
            destroyApp(false);
      else if (commandlabel.equals("Ok"))
            getConnect();
   }//commandAction
   }//class WbxmlTest



为了演示目的,除了建立一个web Server外,还要在本机建立一个JWAP Server。

三、代码说明:

上面的代码将数据连接请求发送到了本机的JWAP Server的URL:“datagram://127.0.0.1:9200”,并发送了一个硬编码的WSP(wireless Session Protocol)请求:http://localhost:8080/examples/jsp/finalfiles/Dat.jsp,然后等待并读取JWAP Server的回应,在接收到回应信息后使用kxml解析提取其中的数据(元素属性名为“value”的属性值)。在解析完成后,将数据显示于手机屏幕上。

代码中的getConnect 方法建立与JWAP Server的连接,并发送请求给JWAP Server,要求访问web Server上的http://localhost:8080/examples/jsp/finalfiles/Dat.jsp,在接收到JWAP Server发回的请求后,getConnect方法调用getIncomingTextOfWmlc方法提取接收到的WBXML数据。由于j2me client与JWAP Server之间的通讯使用了WAP协议堆栈,所以j2me client接收的数据中包含WSP头,在getIncomingTextOfWmlc方法中首先去掉了这个WSP头。

之后,getIncomingTextOfWmlc方法使用KXML的事件解析机制进行了4步操作:
1。传入保存WBXML数据的字节数组构造WmlParser 对象;
2。调用WmlParser的read方法,找到第一个TAG开始的地方;
3。读取“value”属性值;
4。回到第2步进行2、3之间的循环,直到找不到START_TAG。

四、数据流程:

而在JWAP网关接收到j2me client发来的硬编码请求后,将这个请求转发给了web Server,本例程中的web Server为http://localhost:8080。web Server接收到请求后,使用一个硬编码的WML文件作为回应:


<?xml version="1.0"?>
   <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">
   <%@ page language="java" contentType= "text/vnd.wap.wml" %>
  
   <wml>
     <card id="c0" newcontext="false" ordered="false">
    <input type="Price" value="15224" emptyok="false"/>
     </card>
   </wml>



当JWAP网关接收到这个web Server的WML文件后,将其转换为WBXML格式并修改其content-type编码为WBXML,最后将转换后的WBXML格式数据发给了j2me client。

五、总结:

使用kxml方法避免了XML与WBXML之间的相互转换,WBXML文件的格式减少了XML文件的大小,不仅可将WBXML用于WAP设备,也可以用于基于web的程序与无线设备之间的通讯和数据交换。

六、参考资源:
Compressing XML
jWAP
kxml

2005年09月06日

环境 linux mysql 4.1.10a apache 2 ———- 安装 php

1.下载软件包到 /usr/local/src
2.解压软件包
3.建立 php 的目录 #cd /usr/local #mkdir php
4.进入解压后的目录位置 #cd /usr/local/php-5.0.4
5.运行安装配置 这里的配置较为复杂: #./configure –prefix=/usr/local/php –with-apxs2=/usr/local/apache/bin/apxs –with-mysql=/usr/local –enable-track-vars –disable-debug 特别要指出的是这配置了 apache 自动加载 php 模块功能和 mysql 数据库支持功能
6.编译 php #make
7.安装 php #make install —————————— 其中第5步,可能出现undefined reference to `mysql_drop_db’的错误,只用把–with-mysql=/usr/local,改为–with-mysql=shared 结束服务器显示如下需要配置的 If you ever happen to want to link against installed libraries in a given directory, LIBDIR, you must either use libtool, and specify the full pathname of the library, or use the `-LLIBDIR’ flag during linking and do at least one of the following: – add LIBDIR to the `LD_LIBRARY_PATH’ environment variable during execution – add LIBDIR to the `LD_RUN_PATH’ environment variable during linking – use the `-Wl,–rpath -Wl,LIBDIR’ linker flag – have your system administrator add LIBDIR to `/etc/ld.so.conf’

2005年08月10日

诺基亚游戏事业部经理陈腾华在Chinajoy移动论坛上,为“手机游戏不日将大行其道”言词降温。游戏单价过低、手机平台开放系统缺乏兼容性等四个问题,使得诺基亚眼中的移动梦网游戏只是未来几年的高增长型行业,而目前仍处于早期阶段。

  由于国内庞大的手机用户群,及3G上线最后时间表的临近,使得国内围绕移动游戏“钱”景有多大、多美好的言论此起彼伏。做为参加本次Chinajoy移动论坛唯一手机终端厂商诺基亚认为,在移动游戏领域以下几个问题需要解决。

  (1)游戏单价偏低。据分析公司的资料,中国70%的消费者认为手机游戏的合理价格在1-3元人民币之间,而对照法国移动游戏下载价3欧元,中国手机市场只满足了移动游戏参与者的基数要求,但消费实际价与期待价相差很远。

  (2)用户活跃度比较低。CMCC Java业务2004年全年总用户数为720万,平均每月活跃用户数仅为27万,反复下裁游戏的用户很少,另外有专业人士认为,随着新手机用户增长放缓,移动游戏下裁用户的增长将减慢。

  (3)目前的开放系统缺乏兼容性。无论口号有多响亮,但终端下裁用户往往面临的真实情况却是,成百的手机型号、不同的在线游戏平台、多样的分销渠道、消费者因为不可预计的游戏品质而失望。手机厂商在内容提供上必须面对复杂兼容性问题和不断提高的开发成本。

  (4)移动游戏内容质量急需提升,这是个技术层面上的问题,背后包含的Java表现力等层次的提升内需。

经济学泰斗凯恩斯曾有过这么一句经典名言:“经济学家的思想无论对错,都比人们想象的有力量。那些自以为自己不受任何一种经济学思想支配的人,其实可能就是某个已故蹩脚经济学家的思想俘虏。”对于我们身边头戴棒球帽、身穿嘻哈装、脚蹬耐克鞋的新新人类而言,成为经济学家的思想俘虏也许是件很郁闷的事,但事实是,在商业利益驱动的经济社会中,潮流的走向中必然蕴含着经济助力。

要说流行走向,就不能不提现在如火中天的“魔兽世界”,这款由暴雪公司出品的大型网络游戏继在欧美、日韩掀起了一股前所未有的魔兽狂潮之后,迅速在中国受到了众多玩家的追捧和青睐。自2005年6月7日魔兽世界在中国启动商业化运营的一个月内,其国内付费用户的数量就已超过150万。

新新人类之所以钟爱魔兽世界这样的网络游戏,很重要的一个原因就是这种构建于网络平台之上的虚拟世界就像是与世隔绝的世外桃源,让想象丰富、渴望自由、偏爱叛逆的年轻人享受到了畅游乌托邦的快感。然而,略显扫兴的经济学不可避免地将触角伸到了虚拟世界,网络游戏内外都充斥着显而易见的经济经。

在网络游戏之内,玩家们无时无刻不面临着五花八门的经济学问题。首先,玩家碰到的第一个经济学问题就是选择。经济学是一门有关如何在局限条件下进行选择的科学,与现实中选择的无形化相比,游戏玩家在虚拟世界中经常遇到窗口模式的有形化选择。以魔兽世界为例,玩家在创造角色之时不仅需要在正义的联盟、邪恶的部落之间选择,还要在人类、精灵、兽人、亡灵等八大种族,战士、法师、术士等九大职业之间选择。选择的多样化必然带来了机会成本,在初始选择的效用递减到机会成本之下时,玩家往往不会放弃游戏,而是选择新的角色重新开始新鲜的体验,这从某种程度上给网络游戏运营的持久性提供了保障。

其次,玩家碰到的第二个经济学问题是分工。古典经济学家在解释经济增长时总是会将分工理论作为核心命题。在网络游戏之中,朴素的古典经济理论也被玩家们贯彻得极为彻底。在魔兽世界里,要想探索危险的游戏领域,打败恐怖的游戏魔王,玩家们需要组成种族、职业互补性极高的游戏队伍,在游戏过程中各司其责,充分利用专业化分工带来的整合优势。这种游戏分工增强了玩家互动,给网络游戏的趣味性、参与性提供了保障。

最后,玩家碰到的第三个经济学问题是分配。分配理论一直是经济学家最关心的命题之一。在网络游戏中,玩家对战利品、虚拟货币等经济产品的分配运用的是一套公平、有效的分配原则,即按劳分配基础上的按需分配。游戏中最根本的经济法则是多劳多得,越是努力的游戏玩家越是能够成为富甲一方的虚拟富翁,在此基础上,玩家通过合作获得的虚拟物品大多是根据职业需要进行分配,目的是在增强队伍凝聚力的同时最大化队伍的战斗能力,为“生产”更多的虚拟物品创造条件。这种制度经济学意义上的有效分配原则营造了公平、自由的游戏环境,为网络游戏的吸引力提供了保障。

在网络游戏之外,两大经济关键词也是引人注目。一个关键词是商业营销。与IT行业一样,网络游戏在诞生之时对营销概念也是置若罔闻,但随着网络经济概念的兴起,营销策略逐渐被网络游戏所吸收。在魔兽世界的成功之中,品牌效应、广告效应、联合效应等一系列营销策略就得到了充分运用,第九城市不惜重金引入世界顶尖游戏厂商的游戏产品是一个重要的战略手段,业内“暴雪出品、必属精品”的口碑为魔兽世界带来了高级的定位和广泛的受众,为魔兽世界在国内数百种网游中脱颖而出奠定了基础。而且,魔兽世界在运营之前和之中在各类媒体和专业网站都做足了广告,并和可口可乐进行了战略合作,邀请青少年喜爱的青春偶像SHE团体作为游戏形象代言,为魔兽世界成为时尚代名词创造了条件。此外,魔兽世界与各地网吧联手推出了一系列的主题活动,在利益共享的模式之下为魔兽世界的走红营造了合意的市场氛围。

网络游戏之外的另一经济关键词是地下经济。虚拟物品的现实交易由于安全性、合法性的争论一直备受非议。对于魔兽世界,由于生产商暴雪公司的明令禁止,虚拟物品现实交易就成了经济学中所谓的地下经济。地下经济是市场外部性的必然反映,当市场不能承载具有供需欲望的商品交易时,黑市就应运而生了。因此,网络游戏之外的地下经济是游戏市场的自然衍生,它在虚拟经济和实体经济之间架构了一条潜在的桥梁,掀起了网络游戏现实化的盖头。

无论如何,网络游戏内外的经济经都暗示着一种新兴经济力量的诞生和崛起,对于若隐若现的游戏经济,用魔兽世界的广告词作为结尾再合适不过了:一个世界在等待。(作者单位:复旦大学国际金融系)

2005年08月06日

注意:如果结果的长度大于服务器参数max_allowed_packet,字符串值函数返回NULL

对于针对字符串位置的操作,第一个位置被标记为1。

ASCII(str)
返回字符串str的最左面字符的ASCII代码值。如果str是空字符串,返回0。如果str是NULL,返回NULL。
mysql> select ASCII(‘2′);
-> 50
mysql> select ASCII(2);
-> 50
mysql> select ASCII(‘dx’);
-> 100
也可参见ORD()函数。

ORD(str)
如果字符串str最左面字符是一个多字节字符,通过以格式((first byte ASCII code)*256+(second byte ASCII code))[*256+third byte ASCII code...]返回字符的ASCII代码值来返回多字节字符代码。如果最左面的字符不是一个多字节字符。返回与ASCII()函数返回的相同值。
mysql> select ORD(‘2′);
-> 50

CONV(N,from_base,to_base)
在不同的数字基之间变换数字。返回数字N的字符串数字,从from_base基变换为to_base基,如果任何参数是NULL,返回NULL。参数N解释为一个整数,但是可以指定为一个整数或一个字符串。最小基是2且最大的基是36。如果to_base是一个负数,N被认为是一个有符号数,否则,N被当作无符号数。 CONV以64位点精度工作。
mysql> select CONV(“a”,16,2);
-> ‘1010′
mysql> select CONV(“6E”,18,8);
-> ‘172′
mysql> select CONV(-17,10,-18);
-> ‘-H’
mysql> select CONV(10+”10″+’10′+0xa,10,10);
-> ‘40′

BIN(N)
返回二进制值N的一个字符串表示,在此N是一个长整数(BIGINT)数字,这等价于CONV(N,10,2)。如果N是NULL,返回NULL。
mysql> select BIN(12);
-> ‘1100′

OCT(N)
返回八进制值N的一个字符串的表示,在此N是一个长整型数字,这等价于CONV(N,10,8)。如果N是NULL,返回NULL。
mysql> select OCT(12);
-> ‘14′

HEX(N)
返回十六进制值N一个字符串的表示,在此N是一个长整型(BIGINT)数字,这等价于CONV(N,10,16)。如果N是NULL,返回NULL。
mysql> select HEX(255);
-> ‘FF’

CHAR(N,…)
CHAR()将参数解释为整数并且返回由这些整数的ASCII代码字符组成的一个字符串。NULL值被跳过。
mysql> select CHAR(77,121,83,81,’76′);
-> ‘MySQL’
mysql> select CHAR(77,77.3,’77.3′);
-> ‘MMM’

CONCAT(str1,str2,…)
返回来自于参数连结的字符串。如果任何参数是NULL,返回NULL。可以有超过2个的参数。一个数字参数被变换为等价的字符串形式。
mysql> select CONCAT(‘My’, ‘S’, ‘QL’);
-> ‘MySQL’
mysql> select CONCAT(‘My’, NULL, ‘QL’);
-> NULL
mysql> select CONCAT(14.3);
-> ‘14.3′

LENGTH(str)
 
OCTET_LENGTH(str)
 
CHAR_LENGTH(str)
 
CHARACTER_LENGTH(str)
返回字符串str的长度。
mysql> select LENGTH(‘text’);
-> 4
mysql> select OCTET_LENGTH(‘text’);
-> 4

注意,对于多字节字符,其CHAR_LENGTH()仅计算一次。

LOCATE(substr,str)
 
POSITION(substr IN str)
返回子串substr在字符串str第一个出现的位置,如果substr不是在str里面,返回0.
mysql> select LOCATE(‘bar’, ‘foobarbar’);
-> 4
mysql> select LOCATE(‘xbar’, ‘foobar’);
-> 0

该函数是多字节可靠的。
LOCATE(substr,str,pos)
返回子串substr在字符串str第一个出现的位置,从位置pos开始。如果substr不是在str里面,返回0。
mysql> select LOCATE(‘bar’, ‘foobarbar’,5);
-> 7

这函数是多字节可靠的。

INSTR(str,substr)
返回子串substr在字符串str中的第一个出现的位置。这与有2个参数形式的LOCATE()相同,除了参数被颠倒。
mysql> select INSTR(‘foobarbar’, ‘bar’);
-> 4
mysql> select INSTR(‘xbar’, ‘foobar’);
-> 0

这函数是多字节可靠的。

LPAD(str,len,padstr)
返回字符串str,左面用字符串padstr填补直到str是len个字符长。
mysql> select LPAD(‘hi’,4,’??’);
-> ‘??hi’

RPAD(str,len,padstr)
返回字符串str,右面用字符串padstr填补直到str是len个字符长。
mysql> select RPAD(‘hi’,5,’?');
-> ‘hi???’

LEFT(str,len)
返回字符串str的最左面len个字符。
mysql> select LEFT(‘foobarbar’, 5);
-> ‘fooba’

该函数是多字节可靠的。

RIGHT(str,len)
返回字符串str的最右面len个字符。
mysql> select RIGHT(‘foobarbar’, 4);
-> ‘rbar’

该函数是多字节可靠的。

SUBSTRING(str,pos,len)
 
SUBSTRING(str FROM pos FOR len)
 
MID(str,pos,len)
从字符串str返回一个len个字符的子串,从位置pos开始。使用FROM的变种形式是ANSI SQL92语法。
mysql> select SUBSTRING(‘Quadratically’,5,6);
-> ‘ratica’

该函数是多字节可靠的。

SUBSTRING(str,pos)
 
SUBSTRING(str FROM pos)
从字符串str的起始位置pos返回一个子串。
mysql> select SUBSTRING(‘Quadratically’,5);
-> ‘ratically’
mysql> select SUBSTRING(‘foobarbar’ FROM 4);
-> ‘barbar’

该函数是多字节可靠的。

SUBSTRING_INDEX(str,delim,count)
返回从字符串str的第count个出现的分隔符delim之后的子串。如果count是正数,返回最后的分隔符到左边(从左边数) 的所有字符。如果count是负数,返回最后的分隔符到右边的所有字符(从右边数)。
mysql> select SUBSTRING_INDEX(‘www.mysql.com’, ‘.’, 2);
-> ‘www.mysql’
mysql> select SUBSTRING_INDEX(‘www.mysql.com’, ‘.’, -2);
-> ‘mysql.com’

该函数对多字节是可靠的。

LTRIM(str)
返回删除了其前置空格字符的字符串str。
mysql> select LTRIM(‘ barbar’);
-> ‘barbar’

RTRIM(str)
返回删除了其拖后空格字符的字符串str。
mysql> select RTRIM(‘barbar ‘);
-> ‘barbar’

该函数对多字节是可靠的。
TRIM([[BOTH | LEADING | TRAILING] [remstr] FROM] str)
返回字符串str,其所有remstr前缀或后缀被删除了。如果没有修饰符BOTH、LEADING或TRAILING给出,BOTH被假定。如果remstr没被指定,空格被删除。
mysql> select TRIM(‘ bar ‘);
-> ‘bar’
mysql> select TRIM(LEADING ‘x’ FROM ‘xxxbarxxx’);
-> ‘barxxx’
mysql> select TRIM(BOTH ‘x’ FROM ‘xxxbarxxx’);
-> ‘bar’
mysql> select TRIM(TRAILING ‘xyz’ FROM ‘barxxyz’);
-> ‘barx’

该函数对多字节是可靠的。

SOUNDEX(str)
返回str的一个同音字符串。听起来“大致相同”的2个字符串应该有相同的同音字符串。一个“标准”的同音字符串长是4个字符,但是SOUNDEX()函数返回一个任意长的字符串。你可以在结果上使用SUBSTRING()得到一个“标准”的 同音串。所有非数字字母字符在给定的字符串中被忽略。所有在A-Z之外的字符国际字母被当作元音。
mysql> select SOUNDEX(‘Hello’);
-> ‘H400′
mysql> select SOUNDEX(‘Quadratically’);
-> ‘Q36324′

SPACE(N)
返回由N个空格字符组成的一个字符串。
mysql> select SPACE(6);
-> ‘ ‘

REPLACE(str,from_str,to_str)
返回字符串str,其字符串from_str的所有出现由字符串to_str代替。
mysql> select REPLACE(‘www.mysql.com’, ‘w’, ‘Ww’);
-> ‘WwWwWw.mysql.com’

该函数对多字节是可靠的。

REPEAT(str,count)
返回由重复countTimes次的字符串str组成的一个字符串。如果count <= 0,返回一个空字符串。如果str或count是NULL,返回NULL。
mysql> select REPEAT(‘MySQL’, 3);
-> ‘MySQLMySQLMySQL’

REVERSE(str)
返回颠倒字符顺序的字符串str。
mysql> select REVERSE(‘abc’);
-> ‘cba’

该函数对多字节可靠的。

INSERT(str,pos,len,newstr)
返回字符串str,在位置pos起始的子串且len个字符长得子串由字符串newstr代替。
mysql> select INSERT(‘Quadratic’, 3, 4, ‘What’);
-> ‘QuWhattic’

该函数对多字节是可靠的。

ELT(N,str1,str2,str3,…)
如果N= 1,返回str1,如果N= 2,返回str2,等等。如果N小于1或大于参数个数,返回NULL。ELT()是FIELD()反运算。
mysql> select ELT(1, ‘ej’, ‘Heja’, ‘hej’, ‘foo’);
-> ‘ej’
mysql> select ELT(4, ‘ej’, ‘Heja’, ‘hej’, ‘foo’);
-> ‘foo’

FIELD(str,str1,str2,str3,…)
返回str在str1, str2, str3, …清单的索引。如果str没找到,返回0。FIELD()是ELT()反运算。
mysql> select FIELD(‘ej’, ‘Hej’, ‘ej’, ‘Heja’, ‘hej’, ‘foo’);
-> 2
mysql> select FIELD(‘fo’, ‘Hej’, ‘ej’, ‘Heja’, ‘hej’, ‘foo’);
-> 0

FIND_IN_SET(str,strlist)
如果字符串str在由N子串组成的表strlist之中,返回一个1到N的值。一个字符串表是被“,”分隔的子串组成的一个字符串。如果第一个参数是一个常数字符串并且第二个参数是一种类型为SET的列,FIND_IN_SET()函数被优化而使用位运算!如果str不是在strlist里面或如果strlist是空字符串,返回0。如果任何一个参数是NULL,返回NULL。如果第一个参数包含一个“,”,该函数将工作不正常。
mysql> SELECT FIND_IN_SET(‘b’,'a,b,c,d’);
-> 2

MAKE_SET(bits,str1,str2,…)
返回一个集合 (包含由“,”字符分隔的子串组成的一个字符串),由相应的位在bits集合中的的字符串组成。str1对应于位0,str2对应位1,等等。在str1, str2, …中的NULL串不添加到结果中。
mysql> SELECT MAKE_SET(1,’a',’b',’c');
-> ‘a’
mysql> SELECT MAKE_SET(1 | 4,’hello’,'nice’,'world’);
-> ‘hello,world’
mysql> SELECT MAKE_SET(0,’a',’b',’c');
-> ”

EXPORT_SET(bits,on,off,[separator,[number_of_bits]])
返回一个字符串,在这里对于在“bits”中设定每一位,你得到一个“on”字符串,并且对于每个复位(reset)的位,你得到一个“off”字符串。每个字符串用“separator”分隔(缺省“,”),并且只有“bits”的“number_of_bits” (缺省64)位被使用。
mysql> select EXPORT_SET(5,’Y',’N',’,',4)
-> Y,N,Y,N

LCASE(str)
 
LOWER(str)
返回字符串str,根据当前字符集映射(缺省是ISO-8859-1 Latin1)把所有的字符改变成小写。该函数对多字节是可靠的。
mysql> select LCASE(‘QUADRATICALLY’);
-> ‘quadratically’

UCASE(str)
 
UPPER(str)
返回字符串str,根据当前字符集映射(缺省是ISO-8859-1 Latin1)把所有的字符改变成大写。该函数对多字节是可靠的。
mysql> select UCASE(‘Hej’);
-> ‘HEJ’

该函数对多字节是可靠的。

LOAD_FILE(file_name)
读入文件并且作为一个字符串返回文件内容。文件必须在服务器上,你必须指定到文件的完整路径名,而且你必须有file权限。文件必须所有内容都是可读的并且小于max_allowed_packet。如果文件不存在或由于上面原因之一不能被读出,函数返回NULL。
mysql> UPDATE table_name
SET blob_column=LOAD_FILE(“/tmp/picture”)
WHERE id=1;

MySQL必要时自动变换数字为字符串,并且反过来也如此:

mysql> SELECT 1+”1″;
-> 2
mysql> SELECT CONCAT(2,’ test’);
-> ‘2 test’

如果你想要明确地变换一个数字到一个字符串,把它作为参数传递到CONCAT()。

如果字符串函数提供一个二进制字符串作为参数,结果字符串也是一个二进制字符串。被变换到一个字符串的数字被当作是一个二进制字符串。这仅影响比较。

2005年08月05日

摩托罗拉A6288、388型手机的登场,伴随着一个亮点:自身带有KJava虚拟机(KVM),能够通过多种方式下载且能够运行用户用KJava编写的符合MIDP1.0规范的应用程序。后来,又有多款手机支持KJava。实际上,KJava不仅能够为A6288等手机编写应用程序,而且还可以为其它一些移动信息设备编写应用程序。这类嵌入式程序和一般的Java程序相比,有它的特殊之处,本文将对KJava及其应用进行介绍。

1.KJava简介

Java语言最初是为嵌入式系统设计的一项产品,在Java 2中为了区分各种不同的应用,又细分成了Java 2 Enterprise Edition(J2EE)、Java 2 Standard Edition(J2SE)和Java 2 Micro Edition(J2ME)三种版本,其中J2ME又称作KJava。

在J2SE中,它定义了Java规范的核心类函数库(即Java.*)和扩展类函数库(即Javax.*),它主要定位在客户端的应用程序。J2EE主要定位于服务器端的应用程序,它除了支持J2SE所定义的核心类函数库之外,还增加了一些扩展函数类库,如支持Serverlet/JSP的Javax.servlet.*等。J2ME只支持J2SE所定义的核心函数类库的一部分,此外,在J2ME中还增加了一些支持嵌入式系统的扩展类函数库,如Javax.microedition.*等。

同样是嵌入式设备,它们在运算能力、电力供应等方面还是有很大的差别。Connected Limited Device Configuration(CLDC)规范描述的就是那些运算能力有限、电力供应有限的嵌入式设备,如手机等;而Connected Device Configuration(CDC)规范则描述的是像电视机顶盒这样运算能力较强、电力供应充足的系统。J2ME分别支持这两种不同的配置。在同一种配置下,J2ME又通过Profile来定义与特定嵌入式设备相关的扩展类函数库。

在传统的Java环境中,为了防止程序在传送途中被篡改以及其它一些安全上的考虑,当程序被类别载入器载入后,紧接着要进行Byte Code审核,审核通过以后才允许Java虚拟机执行它。这一操作在PC机上执行从速度上来看没有什么问题,但是在CLDC所描述的这类系统中,要想作完全相同的处理,从处理能力和速度上来看就显得有些力不从心了。为了解决这个问题,程序设计人员需要在程序设计结束之后额外再多做一件事:预先审核。通过预先审核,会在最终的类文件中加入一些特殊的符号,当该程序下载到目标平台上去执行时,可以以较快的速度完成审核操作。

2. Motorola SDK

下面以摩托罗拉A6288型手机为例来具体讲解KJava的程序设计。在摩托罗拉A6288中使用了两颗CPU,一颗是Dragon Ball VZ 33MHz,用于个人数字处理,另外一颗用于通讯的处理。系统为用户预留了约1M的内存空间,支持符合MIDP 1.0规范的KJava应用程序。我们可以到摩托罗拉的官方网站去下载开发工具:CodeWarrior(试用版),下面的内容就是基于这一开发工具。

在CodeWarrior中,它包含了摩托罗拉的SDK,它实现了CLDC和MIDP 1.0。它所实现的CLDC类库有Java.io.*、Java.lang.*、Java.util.*和Javax.microedition.io.*,实现的MIDP类库有Javax.microedition.lcdui.*、Javax.microedition.rms和Javax.micromedition.midlet。

我们在PC环境下编写的嵌入在浏览器中的Java程序叫做Applet,编写的程序必须要从Applet类扩展自己的类。而为手机这类移动信息设备编写的KJava程序叫做MIDlet,程序必须要从MIDlet类扩展自己的类。一个MIDlet程序具备如下的典型结构:

public class HelloWord extends MIDlet
{
HelloWord()
{……}

public void startApp()
{……}

public void pauseApp()
{……}

public void destroyApp(boolean unconditional)
{……}
}

当程序被启动时,startApp()被系统调用。(当程序刚开始运行时有启动过程,程序被暂停后也可以被再次启动)。当程序被暂时停止运行时,pauseApp()被调用。当程序执行结束时destroyApp()被调用。我们可以根据实际情况在各函数中进行相应的处理。

在摩托罗拉SDK中没有AWT或者SWING,因为它们的实现会耗费太多的资源,摩托罗拉SDK只是在Javax.microedition.lcdui中实现了基于LCD的一些比较简单的设计用户界面的功能。SDK中定义了各种Displable类(如Form),程序中当前显示的画面必须是某个Displable的实例,在该实例中可以再放置按钮、文本编辑框、单选框等UI。

3. 举例

下面是用KJava编写的一个可以在A6288型手机上运行的简单的例子。具体程序如下:

import Java.io.*;
import Javax.microedition.lcdui.*;
import Javax.microedition.midlet.*;

public class HelloWorld extends MIDlet implements CommandListener
{

private MainCanvas myCanvas;
private Display myDisplay;
private Command c1,c2,c3;

HelloWorld()
{
myCanvas=new MainCanvas();
myCanvas.addCommand(c1=new Command(“First”,Command.SCREEN,1));
myCanvas.addCommand(c2=new Command(“Second”,Command.SCREEN,1));
myCanvas.addCommand(c3=new Command(“Third”,Command.SCREEN,1));
myCanvas.setCommandListener(this);
myDisplay = Display.getDisplay(this);
myDisplay = Display.getDisplay(this);
}

public void startApp() throws MIDletStateChangeException
{ myCanvas.setUp(“first”);
myDisplay.setCurrent(myCanvas);
}

public void pauseApp() { }

public void destroyApp(boolean unconditional) { }

public void commandAction(Command c,Displayable d)
{ myCanvas.setUp(c.getLabel()); }

class MainCanvas extends Canvas
{ String ts;

MainCanvas()
{ super();
ts=new String(“first”);
}

public void setUp(String s)
{ ts=new String(s); }

public void paint(Graphics g)
{ g.setColor(0xFFFFFF);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(0);
g.drawString(“Current is the “+ts,getWidth()/2,60,
Graphics.HCENTER | Graphics.TOP);
}
}
}

程序运行后,可以根据用户选择的不同菜单项显示不同的内容。

2005年08月02日

赤裸,我们往往都会联想向一些不太好的地方。所以,当看到一款游戏叫做《赤裸特工》时,笔者自然第一时间下载了它。然而,当笔者欢快的运行本程序,想要瞻仰一下《赤裸特工》如何赤裸时,一道晴天霹雳击中了笔者。

  简单来说,这是一款女权主义的游戏,游戏中玩家控制一个身高八尺,腰围也八尺,远看貌似是个女性近看不知道是什么生物的角色进行游戏,在一幢永远都不会下到最底层的建筑物中,借助电梯肆无忌惮的蹂躏男性。游戏中,你可以通过电梯来升高或降低,并通过数字键“5”来发射粉色块状物攻击敌人。游戏的任务很多,分别是:消灭30个,60个,90个,120个,150个等等等等数量不等的敌人,如果单纯从游戏时间来说,这款游戏可谓非常超值,因为你可以持续玩这款游戏到天荒地老,直到你想要自杀或者把手机砸了为止。

  熟悉游戏的玩家一定马上会意识到,这是任天堂红白机上非常著名的游戏《电梯大战》的翻版。然而,如果是真的翻版,事情就不会这么糟糕了。事实上,这是一款上个世纪70年代的电子游戏的“简化移植”版,开发人员去掉了原作一切可以带来游戏乐趣的要素,并用一个大妈代替了原作中身手敏捷的干探。于是乎,游戏变成了一个出色的忍耐力考验工具,如果你能够坚持游戏10分钟而没有头晕,恶心,烦躁,具有强烈的攻击性等症状,那么贺喜你,天底下已经没什么事情能对你的情绪造成影响了。



非常“赤裸”的主人公——简单几个色块而已
其实这款游戏还可以叫做《美女大战电梯色狼》

 然而,就是这样一款游戏,却排到了百宝箱下载排行的第7位,这究竟是为什么呢?其实笔者在开篇就已经回答了这个问题——这完全是因为“赤裸”,“赤裸”是魔鬼呀,现实中的“赤裸”可以让一个自制力低下的人犯错误,而游戏中,“赤裸”也可以让一个玩家义无反顾的扔出自己的人民币。一切,都源于名字。事实上,现在百宝箱中的手机游戏大多还停留在“文字游戏”时代。所谓文字游戏,就是厂商们在游戏标题中冥思苦想大做文章,却对游戏品质不闻不问。似乎一个好听的名字要比这款游戏是否好玩更重要。我们经常可以看到一些掺杂了性信息的游戏标题,而往往就是这些标题占据了一个又一个百宝箱排行榜的位置。但是似乎就是这些名字也依然起的没有创造力,比如说,《赤裸特工》完全可以改名叫《古墓丽影》,当然这个名字不够“性感”,那么《性感美女历险记》如何?或者干脆叫《美女大战电梯色狼》不是更好?固然,玩家们会因为标题下载3~4个游戏,然而当他们下载到3~4个垃圾游戏的时候,他们还会下第5个,第六个吗?换个角度想,如果他们不是手机游戏的资深玩家,而是抱着“尝试”的心理来选择游戏,却被这些游戏伤透了心呢?他们也许会觉得手机游戏就是这个样子,于是再也不会来尝试,那么,请问这些只顾眼前利益的厂商们,当消费者对产业失去信心的时候,今后我们又拿什么来挣钱呢?每一个业内人士都会知道这样一个数据:目前国内手机游戏用户超过XXX万,然而知道并且下载过手机游戏的用户却寥寥无几,甚至不超过30%。然而就连着稀少的玩家我们都无法留住,那么我们便完全有理由为手机游戏的将来担心。


别以为这是结局,150个敌人之后还有180个呢

  因此,我们的游戏厂商们啊,请不要将利益只瞄准到眼前。中国手机游戏才刚刚起步,我们还在处于挖掘用户的时期,而就在这个时候我们却用一个又一个标榜着华丽名称的垃圾驱赶用户,这未免有些不智。一个持续的可发展的产业才是真正的金山,中国很早就有一个典故告诉我们,鱼和鱼竿送给你,要选择鱼竿,因为鱼早晚有吃完的时候。希望今后我们的手机游戏产品中,能够少一些赤裸,多一份真诚。

                                                                                             (神速蜗牛)

作者:Jason Brittain & Ian F. Darwin
出处:http://www.orielly.com
译者:陈光
2003-12-31

编者按:现在开发Java Web应用,建立和部署Web内容是一件很简单的工作。使用Jakarta Tomcat作为Servlet和JSP容器的人已经遍及全世界。Tomcat具有免费、跨平台等诸多特性,并且更新得很快,现在非常的流行。

你所需要做的就是:按照你的需求配置Tomcat,只要你正确配置,Tomcat一般都能适合你的要求。下面是一系列关于Tomcat的配置技巧,这些技巧源自于我的书:《Tomcat权威指南》,希望对你有所帮助。—— Jason Brittain

1. 配置系统管理(Admin Web Application)
大多数商业化的J2EE服务器都提供一个功能强大的管理界面,且大都采用易于理解的Web应用界面。Tomcat按照自己的方式,同样提供一个成熟的管理工具,并且丝毫不逊于那些商业化的竞争对手。Tomcat的Admin Web Application最初在4.1版本时出现,当时的功能包括管理context、data source、user和group等。当然也可以管理像初始化参数,user、group、role的多种数据库管理等。在后续的版本中,这些功能将得到很大的扩展,但现有的功能已经非常实用了。

Admin Web Application被定义在自动部署文件:CATALINA_BASE/webapps/admin.xml 。
(译者注:CATALINA_BASE即tomcat安装目录下的server目录)

你必须编辑这个文件,以确定Context中的docBase参数是绝对路径。也就是说,CATALINA_BASE/webapps/admin.xml 的路径是绝对路径。作为另外一种选择,你也可以删除这个自动部署文件,而在server.xml文件中建立一个Admin Web Application的context,效果是一样的。你不能管理Admin Web Application这个应用,换而言之,除了删除CATALINA_BASE/webapps/admin.xml ,你可能什么都做不了。

如果你使用UserDatabaseRealm(默认),你将需要添加一个user以及一个role到CATALINA_BASE/conf/tomcat-users.xml 文件中。你编辑这个文件,添加一个名叫“admin”的role 到该文件中,如下:

<role name="admin"/>

你同样需要有一个用户,并且这个用户的角色是“admin”。象存在的用户那样,添加一个用户(改变密码使其更加安全):

<user name="admin" password="deep_dark_secret" roles="admin"/>

当你完成这些步骤后,请重新启动Tomcat,访问http://localhost:8080/admin,你将看到一个登录界面。Admin Web Application采用基于容器管理的安全机制,并采用了Jakarta Struts框架。一旦你作为“admin”角色的用户登录管理界面,你将能够使用这个管理界面配置Tomcat。

2.配置应用管理(Manager Web Application)
Manager Web Application让你通过一个比Admin Web Application更为简单的用户界面,执行一些简单的Web应用任务。

Manager Web Application被被定义在一个自动部署文件中:

CATALINA_BASE/webapps/manager.xml 。

你必须编辑这个文件,以确保context的docBase参数是绝对路径,也就是说CATALINA_HOME/server/webapps/manager的绝对路径。
(译者注:CATALINA_HOME即tomcat安装目录)

如果你使用的是UserDatabaseRealm,那么你需要添加一个角色和一个用户到CATALINA_BASE/conf/tomcat-users.xml文件中。接下来,编辑这个文件,添加一个名为“manager”的角色到该文件中:

<role name=”manager”>

你同样需要有一个角色为“manager”的用户。像已经存在的用户那样,添加一个新用户(改变密码使其更加安全):

<user name="manager" password="deep_dark_secret" roles="manager"/>

然后重新启动Tomcat,访问http://localhost/manager/list,将看到一个很朴素的文本型管理界面,或者访问http://localhost/manager/html/list,将看到一个HMTL的管理界面。不管是哪种方式都说明你的Manager Web Application现在已经启动了。

Manager application让你可以在没有系统管理特权的基础上,安装新的Web应用,以用于测试。如果我们有一个新的web应用位于/home/user/hello下在,并且想把它安装到 /hello下,为了测试这个应用,我们可以这么做,在第一个文件框中输入“/hello”(作为访问时的path),在第二个文本框中输入“file:/home/user/hello”(作为Config URL)。

Manager application还允许你停止、重新启动、移除以及重新部署一个web应用。停止一个应用使其无法被访问,当有用户尝试访问这个被停止的应用时,将看到一个503的错误——“503 – This application is not currently available”。

移除一个web应用,只是指从Tomcat的运行拷贝中删除了该应用,如果你重新启动Tomcat,被删除的应用将再次出现(也就是说,移除并不是指从硬盘上删除)。

3.部署一个web应用
有两个办法可以在系统中部署web服务。
1> 拷贝你的WAR文件或者你的web应用文件夹(包括该web的所有内容)到$CATALINA_BASE/webapps目录下。
2> 为你的web服务建立一个只包括context内容的XML片断文件,并把该文件放到$CATALINA_BASE/webapps目录下。这个web应用本身可以存储在硬盘上的任何地方。

如果你有一个WAR文件,你若想部署它,则只需要把该文件简单的拷贝到CATALINA_BASE/webapps目录下即可,文件必须以“.war”作为扩展名。一旦Tomcat监听到这个文件,它将(缺省的)解开该文件包作为一个子目录,并以WAR文件的文件名作为子目录的名字。接下来,Tomcat将在内存中建立一个context,就好象你在server.xml文件里建立一样。当然,其他必需的内容,将从server.xml中的DefaultContext获得。

部署web应用的另一种方式是写一个Context XML片断文件,然后把该文件拷贝到CATALINA_BASE/webapps目录下。一个Context片断并非一个完整的XML文件,而只是一个context元素,以及对该应用的相应描述。这种片断文件就像是从server.xml中切取出来的context元素一样,所以这种片断被命名为“context片断”。

举个例子,如果我们想部署一个名叫MyWebApp.war的应用,该应用使用realm作为访问控制方式,我们可以使用下面这个片断:

<!–  
 Context fragment for deploying MyWebApp.war  
–>
<Context path="/demo" docBase="webapps/MyWebApp.war"
        debug="0" privileged="true">
 <Realm className="org.apache.catalina.realm.UserDatabaseRealm"                
        resourceName="UserDatabase"/>
</Context>

把该片断命名为“MyWebApp.xml”,然后拷贝到CATALINA_BASE/webapps目录下。

这种context片断提供了一种便利的方法来部署web应用,你不需要编辑server.xml,除非你想改变缺省的部署特性,安装一个新的web应用时不需要重启动Tomcat。


4.配置虚拟主机(Virtual Hosts)
关于server.xml中“Host”这个元素,只有在你设置虚拟主机的才需要修改。虚拟主机是一种在一个web服务器上服务多个域名的机制,对每个域名而言,都好象独享了整个主机。实际上,大多数的小型商务网站都是采用虚拟主机实现的,这主要是因为虚拟主机能直接连接到Internet并提供相应的带宽,以保障合理的访问响应速度,另外虚拟主机还能提供一个稳定的固定IP。

基于名字的虚拟主机可以被建立在任何web服务器上,建立的方法就是通过在域名服务器(DNS)上建立IP地址的别名,并且告诉web服务器把去往不同域名的请求分发到相应的网页目录。因为这篇文章主要是讲Tomcat,我们不准备介绍在各种操作系统上设置DNS的方法,如果你在这方面需要帮助,请参考《DNS and Bind》一书,作者是Paul Albitz and Cricket Liu (O’Reilly)。为了示范方便,我将使用一个静态的主机文件,因为这是测试别名最简单的方法。
在Tomcat中使用虚拟主机,你需要设置DNS或主机数据。为了测试,为本地IP设置一个IP别名就足够了,接下来,你需要在server.xml中添加几行内容,如下:

<Server port="8005" shutdown="SHUTDOWN" debug="0">
 <Service name="Tomcat-Standalone">
   <Connector className="org.apache.coyote.tomcat4.CoyoteConnector"
port="8080" minProcessors="5" maxProcessors="75"
enableLookups="true" redirectPort="8443"/>
   <Connector className="org.apache.coyote.tomcat4.CoyoteConnector"
port="8443" minProcessors="5" maxProcessors="75"
acceptCount="10" debug="0" scheme="https" secure="true"/>
     <Factory className="org.apache.coyote.tomcat4.CoyoteServerSocketFactory"
clientAuth="false" protocol="TLS" />
   </Connector>
   <Engine name="Standalone" defaultHost="localhost" debug="0">
     <!– This Host is the default Host –>
     <Host name="localhost" debug="0" appBase="webapps"
     unpackWARs="true" autoDeploy="true">
       <Context path="" docBase="ROOT" debug="0"/>
       <Context path="/orders" docBase="/home/ian/orders" debug="0"
                      reloadable="true" crossContext="true">
       </Context>
     </Host>

     <!– This Host is the first "Virtual Host": www.example.com –>
     <Host name="www.example.com" appBase="/home/example/webapp">
       <Context path="" docBase="."/>
     </Host>

   </Engine>
 </Service>
</Server>

Tomcat的server.xml文件,在初始状态下,只包括一个虚拟主机,但是它容易被扩充到支持多个虚拟主机。在前面的例子中展示的是一个简单的server.xml版本,其中粗体部分就是用于添加一个虚拟主机。每一个Host元素必须包括一个或多个context元素,所包含的context元素中必须有一个是默认的context,这个默认的context的显示路径应该为空(例如,path=””)。

5.配置基础验证(Basic Authentication)
容器管理验证方法控制着当用户访问受保护的web应用资源时,如何进行用户的身份鉴别。当一个web应用使用了Basic Authentication(BASIC参数在web.xml文件中auto-method元素中设置),而有用户访问受保护的web应用时,Tomcat将通过HTTP Basic Authentication方式,弹出一个对话框,要求用户输入用户名和密码。在这种验证方法中,所有密码将被以64位的编码方式在网络上传输。

注意:使用Basic Authentication通过被认为是不安全的,因为它没有强健的加密方法,除非在客户端和服务器端都使用HTTPS或者其他密码加密码方式(比如,在一个虚拟私人网络中)。若没有额外的加密方法,网络管理员将能够截获(或滥用)用户的密码。但是,如果你是刚开始使用Tomcat,或者你想在你的web应用中测试一下基于容器的安全管理,Basic Authentication还是非常易于设置和使用的。只需要添加<security-constraint>和<login-config>两个元素到你的web应用的web.xml文件中,并且在CATALINA_BASE/conf/tomcat-users.xml 文件中添加适当的<role>和<user>即可,然后重新启动Tomcat。

下面例子中的web.xml摘自一个俱乐部会员网站系统,该系统中只有member目录被保护起来,并使用Basic Authentication进行身份验证。请注意,这种方式将有效的代替Apache web服务器中的.htaccess文件。

<!–
 Define the Members-only area, by defining
 a "Security Constraint" on this Application, and
 mapping it to the subdirectory (URL) that we want
 to restrict.
–>
<security-constraint>
 <web-resource-collection>
   <web-resource-name>
     Entire Application
   </web-resource-name>
   <url-pattern>/members/*</url-pattern>
 </web-resource-collection>
 <auth-constraint>
     <role-name>member</role-name>
 </auth-constraint>
</security-constraint>
<!– Define the Login Configuration for this Application –>
<login-config>
 <auth-method>BASIC</auth-method>
 <realm-name>My Club Members-only Area</realm-name>
</login-config>

6.配置单点登录(Single Sign-On)
一旦你设置了realm和验证的方法,你就需要进行实际的用户登录处理。一般说来,对用户而言登录系统是一件很麻烦的事情,你必须尽量减少用户登录验证的次数。作为缺省的情况,当用户第一次请求受保护的资源时,每一个web应用都会要求用户登录。如果你运行了多个web应用,并且每个应用都需要进行单独的用户验证,那这看起来就有点像你在与你的用户搏斗。用户们不知道怎样才能把多个分离的应用整合成一个单独的系统,所有他们也就不知道他们需要访问多少个不同的应用,只是很迷惑,为什么总要不停的登录。

Tomcat 4的“single sign-on”特性允许用户在访问同一虚拟主机下所有web应用时,只需登录一次。为了使用这个功能,你只需要在Host上添加一个SingleSignOn Valve元素即可,如下所示:

<Valve className="org.apache.catalina.authenticator.SingleSignOn"
      debug="0"/>

在Tomcat初始安装后,server.xml的注释里面包括SingleSignOn Valve配置的例子,你只需要去掉注释,即可使用。那么,任何用户只要登录过一个应用,则对于同一虚拟主机下的所有应用同样有效。

使用single sign-on valve有一些重要的限制:
1> value必须被配置和嵌套在相同的Host元素里,并且所有需要进行单点验证的web应用(必须通过context元素定义)都位于该Host下。
2> 包括共享用户信息的realm必须被设置在同一级Host中或者嵌套之外。
3> 不能被context中的realm覆盖。
4> 使用单点登录的web应用最好使用一个Tomcat的内置的验证方式(被定义在web.xml中的<auth-method>中),这比自定义的验证方式强,Tomcat内置的的验证方式包括basic、digest、form和client-cert。
5> 如果你使用单点登录,还希望集成一个第三方的web应用到你的网站中来,并且这个新的web应用使用它自己的验证方式,而不使用容器管理安全,那你基本上就没招了。你的用户每次登录原来所有应用时需要登录一次,并且在请求新的第三方应用时还得再登录一次。当然,如果你拥有这个第三方web应用的源码,而你又是一个程序员,你可以修改它,但那恐怕也不容易做。
6> 单点登录需要使用cookies。

7.配置用户定制目录(Customized User Directores)
一些站点允许个别用户在服务器上发布网页。例如,一所大学的学院可能想给每一位学生一个公共区域,或者是一个ISP希望给一些web空间给他的客户,但这又不是虚拟主机。在这种情况下,一个典型的方法就是在用户名前面加一个特殊字符(~),作为每位用户的网站,比如:

http://www.cs.myuniversity.edu/~username
http://members.mybigisp.com/~username

Tomcat提供两种方法在主机上映射这些个人网站,主要使用一对特殊的Listener元素。Listener的className属性应该是org.apache.catalina.startup.UserConfig,userClass属性应该是几个映射类之一。如果你的系统是Unix,它将有一个标准的/etc/passwd文件,该文件中的帐号能够被运行中的Tomcat很容易的读取,该文件指定了用户的主目录,使用PasswdUserDatabase 映射类。

<Listener className="org.apache.catalina.startup.UserConfig"
directoryName="public_html"
userClass="org.apache.catalina.startup.PasswdUserDatabase"/>

web文件需要放置在像/home/users/ian/public_html 或者 /users/jbrittain/public_html一样的目录下面。当然你也可以改变public_html 到其他任何子目录下。

实际上,这个用户目录根本不一定需要位于用户主目录下里面。如果你没有一个密码文件,但你又想把一个用户名映射到公共的像/home一样目录的子目录里面,则可以使用HomesUserDatabase类。

<Listener className="org.apache.catalina.startup.UserConfig"
directoryName="public_html" homeBase="/home"
userClass="org.apache.catalina.startup.HomesUserDatabase"/>

这样一来,web文件就可以位于像/home/ian/public_html 或者 /home/jasonb/public_html一样的目录下。这种形式对Windows而言更加有利,你可以使用一个像c:\home这样的目录。

这些Listener元素,如果出现,则必须在Host元素里面,而不能在context元素里面,因为它们都用应用于Host本身。


8.在Tomcat中使用CGI脚本
Tomcat主要是作为Servlet/JSP容器,但它也有许多传统web服务器的性能。支持通用网关接口(Common Gateway Interface,即CGI)就是其中之一,CGI提供一组方法在响应浏览器请求时运行一些扩展程序。CGI之所以被称为通用,是因为它能在大多数程序或脚本中被调用,包括:Perl,Python,awk,Unix shell scripting等,甚至包括Java。当然,你大概不会把一个Java应用程序当作CGI来运行,毕竟这样太过原始。一般而言,开发Servlet总要比CGI具有更好的效率,因为当用户点击一个链接或一个按钮时,你不需要从操作系统层开始进行处理。

Tomcat包括一个可选的CGI Servlet,允许你运行遗留下来的CGI脚本。

为了使Tomcat能够运行CGI,你必须做如下几件事:
1. 把servlets-cgi.renametojar (在CATALINA_HOME/server/lib/目录下)改名为servlets-cgi.jar。处理CGI的servlet应该位于Tomcat的CLASSPATH下。
2. 在Tomcat的CATALINA_BASE/conf/web.xml 文件中,把关于<servlet-name> CGI的那段的注释去掉(默认情况下,该段位于第241行)。
3. 同样,在Tomcat的CATALINA_BASE/conf/web.xml文件中,把关于对CGI进行映射的那段的注释去掉(默认情况下,该段位于第299行)。注意,这段内容指定了HTML链接到CGI脚本的访问方式。
4. 你可以把CGI脚本放置在WEB-INF/cgi 目录下(注意,WEB-INF是一个安全的地方,你可以把一些不想被用户看见或基于安全考虑不想暴露的文件放在此处),或者你也可以把CGI脚本放置在context下的其他目录下,并为CGI Servlet调整cgiPathPrefix初始化参数。这就指定的CGI Servlet的实际位置,且不能与上一步指定的URL重名。
5. 重新启动Tomcat,你的CGI就可以运行了。

在Tomcat中,CGI程序缺省放置在WEB-INF/cgi目录下,正如前面所提示的那样,WEB-INF目录受保护的,通过客户端的浏览器无法窥探到其中内容,所以对于放置含有密码或其他敏感信息的CGI脚本而言,这是一个非常好的地方。为了兼容其他服务器,尽管你也可以把CGI脚本保存在传统的/cgi-bin目录,但要知道,在这些目录中的文件有可能被网上好奇的冲浪者看到。另外,在Unix中,请确定运行Tomcat的用户有执行CGI脚本的权限。

9.改变Tomcat中的JSP编译器(JSP Compiler)
在Tomcat 4.1(或更高版本,大概),JSP的编译由包含在Tomcat里面的Ant程序控制器直接执行。这听起来有一点点奇怪,但这正是Ant有意为之的一部分,有一个API文档指导开发者在没有启动一个新的JVM的情况下,使用Ant。这是使用Ant进行Java开发的一大优势。另外,这也意味着你现在能够在Ant中使用任何javac支持的编译方式,这里有一个关于Apache Ant使用手册的javac page列表。使用起来是容易的,因为你只需要在<init-param> 元素中定义一个名字叫“compiler”,并且在value中有一个支持编译的编译器名字,示例如下:

<servlet>
   <servlet-name>jsp</servlet-name>
   <servlet-class>
     org.apache.jasper.servlet.JspServlet
   </servlet-class>
   <init-param>
     <param-name>logVerbosityLevel</param-name>
     <param-value>WARNING</param-value>
   </init-param>
   <init-param>
     <param-name>compiler</param-name>
     <param-value>jikes</param-value>
   </init-param>
   <load-on-startup>3</load-on-startup>
</servlet>

当然,给出的编译器必须已经安装在你的系统中,并且CLASSPATH可能需要设置,那处决于你选择的是何种编译器。

10.限制特定主机访问(Restricting Access to Specific Hosts)
有时,你可能想限制对Tomcat web应用的访问,比如,你希望只有你指定的主机或IP地址可以访问你的应用。这样一来,就只有那些指定的的客户端可以访问服务的内容了。为了实现这种效果,Tomcat提供了两个参数供你配置:RemoteHostValve 和RemoteAddrValve。

通过配置这两个参数,可以让你过滤来自请求的主机或IP地址,并允许或拒绝哪些主机/IP。与之类似的,在Apache的httpd文件里有对每个目录的允许/拒绝指定。
例如你可以把Admin Web application设置成只允许本地访问,设置如下:

<Context path="/path/to/secret_files" …>
 <Valve className="org.apache.catalina.valves.RemoteAddrValve"
        allow="127.0.0.1" deny=""/>
</Context>

如果没有给出允许主机的指定,那么与拒绝主机匹配的主机就会被拒绝,除此之外的都是允许的。与之类似,如果没有给出拒绝主机的指定,那么与允许主机匹配的主机就会被允许,除此之外的都是拒绝的。

————————————–

作者简介:
Jason Brittain是CollabNet公司的一名资深软件工程师,主要负责软件底层架构的开发。他已经为Apache Jakarta项目做了很多贡献,多年以来,他一直是一名积极的开源软件开发者。

Ian F. Darwin已经在计算机行业工作了30年:从1980年开始使用Unix,从1995年开始使用Java,从1998年开始使用OpenBSD。他是两本Oreilly图书的作者:Checking C Programs with lint 和 Java Cookbook,还与Jason Brittain合著了Tomcat: The Definitive Guide。

译者简介:
陈光(Holen Chen),J2EE项目经理,熟悉知识管理及电子政务,致力于Apache Jakarta项目在国广的推广及深层次应用,可通过 holen@263.net与作者联系。
2005年07月30日

1、自动关闭停止响应程序
  有些时候,XP会提示你某某程序停止响应,很烦,通过修改注册表我们可以让其自行关闭,在HKEY_CURRENT_USER\Control Panel\Desktop中将字符健值是AutoEndTasks的数值数据更改为1,重新注销或启动即可。
2、清除内存中不被使用的DLL文件
  在注册表的HKKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion,在Explorer增加一个项AlwaysUnloadDLL,默认值设为1。注:如由默认值设定为0则代表停用此功能。
3、优化网上邻居
  Windows XP网上邻居在使用时系统会搜索自己的共享目录和可作为网络共享的打印机以及计划任务中和网络相关的计划任务,然后才显示出来,这样速度显然会慢的很多。这些功能对我们没多大用的话,可以将其删除。在注册表编辑器中找到HKEY_LOCAL_MACHINE\sofeware\Microsoft\Windows\Current Version\Explore\RemoteComputer\NameSpace,删除其下的{2227A280-3AEA-1069-A2DE08002B30309D}(打印机)和{D6277990-4C6A-11CF8D87- 00AA0060F5BF}(计划任务),重新启动电脑,再次访问网上邻居,你会发现快了很多。
4、加快启动速度
  要加快Windows XP的启动速度。可以通过修改注册表来达到目的,在注册表编辑器,找到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\MemoryManagement\PrefetchParameters,在右边找到EnablePrefetcher主键,把它的默认值3改为1,这样滚动条滚动的时间就会减少;
5、加快开关机速度
  在Windows XP中关机时,系统会发送消息到运行程序和远程服务器,告诉它们系统要关闭,并等待接到回应后系统才开始关机。加快开机速度,可以先设置自动结束任务,首先找到HKEY_CURRENT_USER\Control Panel\Desktop,把AutoEndTasks的键值设置为1;然后在该分支下有个“HungAppTimeout”,把它的值改为“4000(或更少),默认为50000;最后再找到HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control,把WaitToKillServiceTimeout设置为“4000”;通过这样设置关机速度明显快了不少。
6、加快菜单显示速度
  为了加快菜单的显示速度,我们可以按照以下方法进行设置:我们可以在HKEY_CURRENT_USER\Control Panel\Desktop下找到“MenuShowDelay”主键,把它的值改为“0”就可以达到加快菜单显示速度的效果。
7.让Windows XP Professional的上网速率提升20% 
 Windows XP Professional有一个名为服务质量信息计划的功能。此功能为保证重要事件的安全执行,有意为他们保留了一部分频宽,以备不时之需。这一功能对于很多商业用户来说是至关重要的,因为很多商业处理过程不允许有一点差错。但对于普通家庭用户来说,这一功能就显得有点讨厌了。因为对他们来说,更快的浏览网页和下载速度才是最重要的,那么我们怎么来提升另外20%的频宽呢?
   解决方法A :
  一、首先必须以系统管理员身分登入系统
  二、其实点击开始→运行 。
  在出现的运行对话框中输入“gpedit.msc”按确定。
   然后在出现的屏幕下,依次双击“本地计算机策略”下的“计算机配置”→“管理模板”→“网络”→“QoS数据包调度程序”。在屏幕右边会出现“QoS数据包调度程序”项目的设置。接着点击右边子项目的“限制可保留带宽”。这时,左边会显示“限制可保留带宽”的详细描述。从这里我们可了解到“限制可保留带宽”的一些基本情况。
  了解之后我们就可以对“限制可保留带宽”进行设置了。点击“限制可保留带宽”下“显示”旁边的“属性”(或者选择子项目“限制可保留带宽”,在点击右键→“属性”也可),出现“限制可保留带宽”对话框,先点击“说明”,再进一步了解“限制可保留带宽”确定系统可保留的连接带宽的百分比情况。
  之后我们就可以对另外20%带宽进入设置了。点击“设置”。“设置”为我们提供了三个选择(未配置、已启用、已禁用),选择“已启用”,接着再将带宽限制旁边的%设置为0%即可,然后按确定退出。
  三、点击开始→连接到→显示所有连接
   选中你所建立的连接,右键点击属性,在出现的连接属性中点击网络,在显示的网络对话框中,检查“此连接使用下列项目”中“QoS数据包调度程序”是否已打了勾,没问题就按确定退出。
  四、最后重新重新启动系统便完成对另外20%的频宽利用了
   需要注意的是:此方法只对Windows XP Professional版有效。   
解决方法B: 
据说XP的一个系统服务Qos,这个调度要占用一定的网络带宽,像我这样的一毛不拔的人是无法忍受的,去掉方法是:开始菜单→运行→键入gpedit.msc,出现“组策略”窗口,展开"管理模板”→“网络”,展开"QoS数据包调度程序",在右边窗右键单击“限制可保留带宽",在属性中的“设置”中有“限制可保留带宽",选择“已禁用”,确定即可。当上述修改完成并应用后,用户在网络连接的属性对话框内的一般属性标签栏中如果能够看到"QoS Packet Scheduler(QoS数据包调度程序)"。说明修改成功,否则说明修改失败,顺便把网络属性中的那个Qos协议也一起干掉(卸载)吧。
8、减少启动时加载项目
  许多应用程序在安装时都会自作主张添加至系统启动组,每次启动系统都会自动运行,这不仅延长了启动时间,而且启动完成后系统资源已经被消耗掉!
  启动“系统配置实用程序”,在“启动”项中列出了系统启动时加载的项目及来源,仔细查看你是否需要它自动加载,否则清除项目前的复选框,加载的项目愈少,启动的速度自然愈快。此项需要重新启动方能生效。 
9、修改注册表的run键,取消那几个不常用的东西,比如Windows Messenger。启用注册表管理器:开始→运行→Regedit→找到“HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run\MSMSGS”/BACKGROUND这个键值,右键→删除,世界清静多了,顺便把那几个什么cfmon的都干掉吧。
10、修改注册表来减少预读取,减少进度条等待时间,效果是进度条跑一圈就进入登录画面了,开始→运行→regedit启动注册表编辑器,找HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Memory\Management\PrefetchParameters,有一个键EnablePrefetcher把它的数值改为“1”就可以了。另外不常更换硬件的朋友可以在系统属性中把总线设备上面的设备类型设置为none(无)。
11、关闭系统属性中的特效,这可是简单有效的提速良方。点击开始→控制面板→系统→高级→性能→设置→在视觉效果中,设置为调整为最佳性能→确定即可。这样桌面就会和win2000很相似的,我还是挺喜欢XP的蓝色窗口,所以在“控制面板”→“显示”→“外观”→“在窗口和按钮”选择Windows XP样式,这样既能看到漂亮的蓝色界面,又可以加快速度。
12、我用Windows commadner+Winrar来管理文件,Win XP的ZIP支持对我而言连鸡肋也不如,因为不管我需不需要,开机系统就打开个zip支持,本来就闲少的系统资源又少了一分,点击开始→运行,敲入:“regsvr32 /u zipfldr.dll”双引号中间的,然后回车确认即可,成功的标志是出现个提示窗口,内容大致为:zipfldr.dll中的Dll UnrgisterServer成功。  
13、关掉调试器Dr. Watson
  我好像从win95年代开始一次也没用过这东西,可以这样取消:打开册表,找到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug子键分支,双击在它下面的Auto键值名称,将其“数值数据”改为0,最后按F5刷新使设置生效,这样就取消它的运行了。沿用这个思路,我们可以把所有具备调试功能的选项取消,比如蓝屏时出现的memory.dmp,在“我的电脑→属性→高级→设置→写入调试信息→选择无”等等。 
14.关闭自动更新
我们要对XP的“自动更新”功能下手,“自动更新”是Windows XP为了方便用户升级系统而推出的一种新功能,这种功能可以在微软推出系统升级补丁或系统安全补丁的时候,自动提醒用户升级自己的系统,不过这种功能有一个要求,就是用户必须时时在线,但是对于我们这些缺铁少银的“穷人”来说,这个要求未免苛刻,所以我们把“自动升级”功能关闭掉,改为“手动升级”。
  具体操作为:右键单击“我的电脑”,点击属性,点击“自动更新”,在“通知设置”一栏选择“关闭自动更新。我将手动更新计算机”一项。
15.关闭系统还原
  我们要对形如鸡肋的“系统还原”功能下手,系统还原功能是微软的一个很富有想象力的创意,不过微软没有能够很好的实现这种创意,所以做出来的系统还原功能只能使食之无味,弃之可惜的鸡肋之作。对用户来说,没什么太大作用,所以我们决定要关闭它以节约内存。
  具体操作为:右键单击“我的电脑”,点击属性,会弹出来系统属性对话框,点击“系统还原”,在“在所有驱动器上关闭系统还原”选项上打勾。
16.关闭远程桌面 
 关闭“远程桌面”功能,这个功能它的一个特点就是可以让别人在另一台机器上访问你的桌面。在局域网中,这个功能很有用。比如你有问题了可以向同事求助,他可以不用到你的跟前,直接通过“远程桌面”来访问你的机器帮你解决问题。但是对于我们只有一台计算机的普通用户来说这个功能就显得多余了,所以我们把它关掉,不让它在那儿白白浪费内存。
17.关闭自动发送错误
  关闭“自动发送错误”功能,大家在Window XP中肯定有这样的经历,一旦一个程序异常终止,系统就会自动跳出一个对话框问你是否将错误发送给微软,这就是XP中的“自动发送错误”功能,可这样的功能有什么用呢?除了浪费电话费外,对我们而言没有任何用处,所以我们应该义无反顾的把这项功能关掉。右键单击“我的电脑”,点击属性,点击“高级”→“错误汇报”,选择“禁用错误汇报”功能。
18.关闭Internet时间同步
  关闭“Internet时间同步”功能,“Internet时间同步”,就是使你的计算机时钟每周和Internet时间服务器进行一次同步,这样你的系统时间就会是精确的,不过这个功能对我们来说用处不大,而且还可能引起死机的问题。所以我们要把它关掉。进入“控制面板”→“日期、时间、语言和区域选项”,然后单击“日期和时间”→“Internet时间”。
19.关闭多余的服务
关闭多余的服务,Windows XP和Windows 2000一样可以作为诸如http服务器、邮件服务器、ftp服务器,所以每当Windows XP启动时,随之也启动了许多服务,有很多服务对于我们这些普通用户来说是完全没用的,所以关掉它们是一个很好的选择。
你可以将以下服务启动方式安全的修改为手动:ClipBook;Error Reporting Service;Fast User Switching Compatibility;Indexing Service;Remote Registry;Smart Card;SSDP Discovery Service;Terminal Services;Universal Plug and Play Device Host。
还可根据自己的情况更改以下服务: 
(1)、Clipbook Server:该服务允许网络上的其他用户看到本机的文件夹。建议改为手动启动。 
(2)、Messenger:在网络上发送和接收信息。若用户关闭了Alerter,可以安全地把它改为手动启动。 
(3)、Printer Spooler:打印后台处理程序。若用户没有配置打印机,建议改为手动启动。 
(4)、Error Reporting Service:服务和应用程序在非标准环境下运行时提供错误报告。建议改为手动启动。 
(5)、Fast User Switching Compatibility:快速用户切换兼容性。建议改为手动启动。 
(6)、Automatic Updates:自动更新。可以改为手动启动。 
(7)、Net Logon:网络注册用于处理象注册信息那样的网络安全功能。可以改为手动启动。 
(8)、Network DDE和Network DDE DSDM:动态数据交换。除非用户准备在网上共享自己的Office,否则应该把它改为手动启动。注:这和在通常的商务设定中使用Office不同。 
(9)、NT LM Security Support:NT LM安全支持提供商用于在网络应用中提供安全保护。建议改为手动启动。 
(10)、Remote Desktop Help Session Manager:远程桌面帮助会话管理器。建议改为手动启动。 
(11)、Remote Registry:远程注册表使远程用户能修改此计算机上的注册表设置。建议改为手动启动。 
(12)、Task Scheduler:任务调度程序使用户能在计算机上配置和制定自动任务的日程。建议改为手动启动。 
(13)、Uninterruptible Power Supply:UPS不间断电源用于管理用户的UPS。如果使用者没有UPS,把它改为手动启动。 
(14)、Windows Image Acquisition (WIA):Windows 图像获取为扫描仪和照相机提供图像捕获。如果使用者没有这些设备,建议改为手动启动。 

说明:复制表(只复制结构,源表名:a 新表名:b)
SQL: select * into b from a where 1<>1

说明:拷贝表(拷贝数据,源表名:a 目标表名:b)
SQL: insert into b(a, b, c) select d,e,f from b;

说明:显示文章、提交人和最后回复时间
SQL: select a.title,a.username,b.adddate from table a,(select max(adddate) adddate from table where table.title=a.title) b

说明:外连接查询(表名1:a 表名2:b)
SQL: select a.a, a.b, a.c, b.c, b.d, b.f from a LEFT OUT JOIN b ON a.a = b.c


说明:日程安排提前五分钟提醒
SQL:  select * from 日程安排 where datediff(‘minute’,f开始时间,getdate())>5

说明:两张关联表,删除主表中已经在副表中没有的信息
SQL: 
delete from info where not exists ( select * from infobz where info.infid=infobz.infid )

说明:–
SQL: 
SELECT A.NUM, A.NAME, B.UPD_DATE, B.PREV_UPD_DATE
  FROM TABLE1,
    (SELECT X.NUM, X.UPD_DATE, Y.UPD_DATE PREV_UPD_DATE
        FROM (SELECT NUM, UPD_DATE, INBOUND_QTY, STOCK_ONHAND
                FROM TABLE2
              WHERE TO_CHAR(UPD_DATE,’YYYY/MM’) = TO_CHAR(SYSDATE, ‘YYYY/MM’)) X,
            (SELECT NUM, UPD_DATE, STOCK_ONHAND
                FROM TABLE2
              WHERE TO_CHAR(UPD_DATE,’YYYY/MM’) =
                    TO_CHAR(TO_DATE(TO_CHAR(SYSDATE, ‘YYYY/MM’) ¦¦ ‘/01′,’YYYY/MM/DD’) – 1, ‘YYYY/MM’) ) Y,
        WHERE X.NUM = Y.NUM (+)
          AND X.INBOUND_QTY + NVL(Y.STOCK_ONHAND,0) <> X.STOCK_ONHAND ) B
WHERE A.NUM = B.NUM

说明:–
SQL: 
select * from studentinfo where not exists(select * from student where studentinfo.id=student.id) and 系名称=’"&strdepartmentname&"’ and 专业名称=’"&strprofessionname&"’ order by 性别,生源地,高考总成绩

说明:
从数据库中去一年的各单位电话费统计(电话费定额贺电化肥清单两个表来源)
SQL: 
SELECT a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, ‘yyyy’) AS telyear,
      SUM(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘01′, a.factration)) AS JAN,
      SUM(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘02′, a.factration)) AS FRI,
      SUM(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘03′, a.factration)) AS MAR,
      SUM(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘04′, a.factration)) AS APR,
      SUM(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘05′, a.factration)) AS MAY,
      SUM(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘06′, a.factration)) AS JUE,
      SUM(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘07′, a.factration)) AS JUL,
      SUM(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘08′, a.factration)) AS AGU,
      SUM(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘09′, a.factration)) AS SEP,
      SUM(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘10′, a.factration)) AS OCT,
      SUM(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘11′, a.factration)) AS NOV,
      SUM(decode(TO_CHAR(a.telfeedate, ‘mm’), ‘12′, a.factration)) AS DEC
FROM (SELECT a.userper, a.tel, a.standfee, b.telfeedate, b.factration
        FROM TELFEESTAND a, TELFEE b
        WHERE a.tel = b.telfax) a
GROUP BY a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, ‘yyyy’)

说明:四表联查问题:
SQL: select * from a left inner join b on a.a=b.b right inner join c on a.a=c.c  inner join d on a.a=d.d where …..

说明:得到表中最小的未使用的ID号
SQL:
SELECT (CASE WHEN EXISTS(SELECT * FROM Handle b WHERE b.HandleID = 1) THEN MIN(HandleID) + 1 ELSE 1 END) as HandleID
 FROM  Handle
 WHERE NOT HandleID IN (SELECT a.HandleID – 1 FROM Handle a)