亚洲精品影院久久久久久,亚洲人xxx日本人18,亚洲精品国产福利在线观看http://www.tkk7.com/xixidabao/zh-cnSat, 10 May 2025 23:51:17 GMTSat, 10 May 2025 23:51:17 GMT60mysql數據導入導出總結http://www.tkk7.com/xixidabao/archive/2009/10/23/299487.htmlJAVA之路JAVA之路Fri, 23 Oct 2009 07:53:00 GMThttp://www.tkk7.com/xixidabao/archive/2009/10/23/299487.htmlmysql文件導入導出有2種方式:
1. 導入導出操作SQL文件,通過執行SQL語句來完成操作,文件中包含建表語句和一系列的插入語句;
2. 導入導出操作特定格式的文本數據,該方式文件中僅包含數據,注意文件的編碼和數據庫編碼要兼容,文件編碼應該和character_set_database一致,而與set names charaset的charset無任何關系;

這里只是方式2的操作方法:
導出到文件:
select * from custom into outfile 'e:\custom.txt' FIELDS TERMINATED BY ',' optionally enclosed by '"';

從文件導入: 
load data infile 'e:\custom.txt' replace into table custom;

 load data infile 'e:\custom.txt' replace into table custom fields terminated by ',' optionally enclosed by '"';


從庫中其他表導入:
 insert into tablename1 select * from tablename2;

///////////// 導入導出給定列 ///////////

 load data infile 'e:\custom.txt' replace into table custom fields terminated by ',' optionally enclosed by

'"'(customid,name,sex);

 select customid,name,sex from custom into outfile 'e:\acustom.txt' fields terminated by ',' optionally enclosed

by '"';

////////////////////////

MYSQL的主鍵是放主存的,第一次的時候執行max獲取最大編號,如果插入的時候沒有就自增,如果插入的時候指定了主鍵,則判

斷是否大于max,是則設置主鍵為max,并插入記錄,否則替換或出錯;如果自增主鍵為null,則仍是自增;
所以導入的時候,自增主鍵也可以直接導入;

如果導入的時候出現:truncated字樣,則是SQL_MODE問題,修改sql_mode就可以了;

show variables like '%sql_mode%';
set sql_mode='no_auto_create_user,no_engine_substitution';

如出現錯誤:ERROR 1262 (01000): Row 6737 was truncated; it contained more data than there were input columns.
如:文件中出現,,空字符,正常情況下會出錯,需要修改sql_mode后才能導入(會有正常警告);



JAVA之路 2009-10-23 15:53 發表評論
]]>
解析表達式http://www.tkk7.com/xixidabao/archive/2009/04/08/264538.htmlJAVA之路JAVA之路Wed, 08 Apr 2009 14:16:00 GMThttp://www.tkk7.com/xixidabao/archive/2009/04/08/264538.htmlpackage com.sunyard.lmas.expression;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;

import com.sunyard.lmas.Base;

public abstract class ExpreCountAmt extends Base {
 private ArrayList<String> middle = new ArrayList<String>();// 存儲中序表達式
 private ArrayList<String> right = new ArrayList<String>();// 存儲右序表達式 
 protected String getExpression(){
  return middle.toString();
 }
 protected String getRight(){
  return right.toString();
 }
 /**
  * 獲取中序表達式
  */
 private void setMiddle(String expression)throws Exception{
  middle.clear();
  right.clear();
  expression = expression==null ? "" : expression.trim();
  if(expression.length()>0&&Calculate.isOperator(expression.substring(0,1))){
   middle.add("0");
  }
  StringTokenizer st = new StringTokenizer(expression, "+-*/()%", true);
  while (st.hasMoreElements()) {
   String item = st.nextToken();
   if(Calculate.isOperator(item))
//   if(item.endsWith("+")||item.endsWith("-")||item.endsWith("*")||item.endsWith("/")||item.endsWith("(")||item.endsWith(")")||item.endsWith("%"))
    middle.add(item);
   else
    middle.add(getAmt(item).toString());
  }
 }
 /**
  * 將中序表達式轉換為右序表達式
  */
 private void toRight() {
  if(middle.size()<=0)
   return;
  Stacks aStack = new Stacks();
  String operator;
  int position = 0;
  while (true) {
   if (Calculate.isOperator((String) middle.get(position))) {
    if (aStack.top == -1
      || ((String) middle.get(position)).equals("(")) {
     aStack.push(middle.get(position));
    } else {
     if (((String) middle.get(position)).equals(")")) {
      if (!((String) aStack.top()).equals("(")) {
       operator = (String) aStack.pop();
       right.add(operator);
      }
     } else {
      if (Calculate.priority((String) middle
        .get(position)) <= Calculate
        .priority((String) aStack.top())
        && aStack.top != -1) {
       operator = (String) aStack.pop();
       if (!operator.equals("("))
        right.add(operator);
      }
      aStack.push(middle.get(position));
     }
    }
   } else
    right.add(middle.get(position));
   position++;
   if (position >= middle.size())
    break;
  }
  while (aStack.top != -1) {
   operator = (String) aStack.pop();
   right.add(operator);
  }
 }
 /**
  * 獲取結果
  * @return
  */
 private double getResult(){
  if(right.size()==0)
   return 0.0;
  String result;
  Stacks aStack = new Stacks();
  String op1, op2, is = null;
  Iterator<String> it = right.iterator();
  while (it.hasNext()) {
   is = (String) it.next();
   if (Calculate.isOperator(is)) {
    op1 = (String) aStack.pop();
    op2 = (String) aStack.pop();
    aStack.push(Calculate.twoResult(is, op1, op2));
   } else
    aStack.push(is);
  }
  result = (String) aStack.pop();
  return Double.parseDouble(result);
 }
 public final double getCountAmt(String expression)throws Exception{  
  setMiddle(expression);
  toRight();
  return getResult();
 }
 public abstract Double getAmt(String subjectno)throws Exception;
}



JAVA之路 2009-04-08 22:16 發表評論
]]>
職場新人必看:如何在三個月掌握三年的經驗(轉載)http://www.tkk7.com/xixidabao/archive/2008/01/26/177926.htmlJAVA之路JAVA之路Sat, 26 Jan 2008 08:17:00 GMThttp://www.tkk7.com/xixidabao/archive/2008/01/26/177926.html
  我一直有個感覺,在“模仿中成長,在創新中成功”,其實在真正的職業工作中,大多數的工作都是模仿重復,強調的是工作效率,而不是創新。對于企業而言,過度的創新必然導致過多的失敗,以及效率的低下。

  以下方式是我的成長中曾經做過的,也是我用來訓練新員工的方案。你們也可以試試。

  看到很多談應聘技巧的帖子,其實并不實用,有菜譜并不代表能做出好菜,能不能做出好菜仍要看你天天炒,日日炒,炒出來的本事。

  所以,我這里要強調的一點是,你收集到的任何資料都不能只是看看,而必須自己手把手,動手去整理、去歸類,去建立新的結構,這個信息收集與處理的過程甚至比你最后總結成文的文字更重要。

  何謂“學習”?學習學習,學而習,習而成習慣。光學不習,那知識還只是書上的,老師教的,不是你自己的,只有你重復練習了,經過量變,才會有質變,當你形成條件反射時,你就真正掌握這個東西了。

  這個過程需要維持兩至三個月的時間,一定要堅持下去,你會看到自己的變化的。否則,你會用你最青春的兩三年來慢慢沉淀出這些你兩三個月就能掌握的東西。

  一切一切,其實,你們比的不是其它的東西,只是比的速度。

  這也是為什么我那么強調基本功的原因。
    
    
    1. 職業分析:
    A. 分析性格——分析長處和短處——分析大家都有的長處——確定自己最終發展的專業
    B. 確定興趣——分析競爭的激烈程度和發展的空間大小——尋找相對優勢—確定自己最終進入的行業
    C. 確定行業內自己的專業方向,繼續保持自身的專業優勢。
     /* 性格決定專業,興趣決定行業,行業>專業,某個行業會包括很多專業。 */

    2. 編寫行業報告——著重對行業全面性的把握。
    A. 通過上網查詢和購買行業報刊,收集不少于三十萬字的行業、重點企業的有效資料,在電腦中進行資料分析、分類、匯總。
    B. 參考同類行業書籍,確定寫作提綱,確定文章結構和邏輯方向,培養文字表達能力和邏輯能力,以及熟練的電腦使用技能。
    C. 將三十萬字資料濃縮成十至十五萬字,寫成一本符合出版行文格式要求的行業報告。如果選題好,還真的有出版的可能性。如果有一定的獨特見解,也可以寫成文章爭取在專業刊物上發表,樹立個人專業形象。
    
    3. 編寫講座報告——著重對專業系統性的把握。
    A. 根據你希望從事的專業崗位,從報告中選擇兩到三個重點,將書稿壓縮成兩萬字的講座稿(按每分鐘150字的演講速度,即兩個小時)。
    B. 將演講稿再濃縮成兩千字的提綱和重要內容,使用PPT軟件編成演講用演示文件,并根據相關內容配以精彩圖片。
    C. 培養職業化的公眾表達能力和表達方式,練習普通話,使用講座稿進行互動講座和演講練習,只到脫口而出。
    

  告訴大家兩個名人是這么成長的.
  
  一個是教英語的李陽,他讀大學時成績不好,英語不及格,然后他做什么去了?他跑到沒人的地方大聲喊英語去了.
  
  一個是做廣告的葉茂中,他賣廣告賣不出去了,他跑回家寫書.別人看到的和他自己說的是拿著書出版出了名,發達了.其實做過這個事的人才會知道,當他把這本書寫出來時,能不能出版已經不重要的,因為他知道他變化了.
  
  我當時也是沒辦法了,把所有的錢買了臺電腦,在家里做了三個月這個事,三個月后的變化是驚人的,我的父母、我兼職的公司的老總,最重要的是我自己,都感覺到了自己的變化。
  
  完全不同了。

  其實我寫的已經不是理論了,其實什么都沒有技巧的,只是多看書,然后多做,硬磕,堅持下去,剛開始覺得沒變化,沒感覺,很累,堅持不下去,然后做著做著,就越來越快了,然后慢慢的有變化.

  而且有意思的是,我在家呆了三個月,做的事其實根本與我所從事的工作沒有一點關系.只是這三個月的訓練,對于我的邏輯、結構、全局性、文字表達能力、口頭表達能力有了極大的提升。

  至于收入翻5翻,當年一個月也就八百塊錢,然后做完這個訓練后整個人的狀態都變了,有自信了,然后寫了一個方案去應聘,結果進了一家大公司,當然,開始我還不想去,因為對方只給我800/月,還要自己租房子,吃飯,覺得不好,但是對方連續四個月三次打電話找我,于是我去了,結果去了就后悔了,真正好的公司根本不在乎工資的,重要的是你自己的能力。第一個月,我就掙了八千塊,我以前想都不敢想的。然后兩個月就轉了正,而有一個有關系的同事,呆了一年還沒能轉正。然后每個月的收入超過工資幾倍,還有年終獎兩萬,出國旅游,其實也不累,我到這個家公司的同時,還到另一家廣告公司兼職,呵呵,很回憶的過去。

  現在看到太多的人談工資,我確實不喜歡,我這幾年都不和老板談工資的,因為說出來好笑,帳面工資高了,還要多扣稅.

  我只在意公司的分配方式,怎么樣算提成和獎金,年薪.
  
  上個月有一個和我同齡的名牌大學MBA來我現在所在的小公司應聘,不愿意和人事小姐談,老板不在,我就來談了,我說好呀,以你的資歷我不能和你談給誰做副手的問題了,我跟你談談公司的分配方式吧,其實我們公司普通員工的收入都不高的,長沙平均水平,只是不忙,周末休兩天,工作滿一年還有一個星期的年休假.
  
  但是公司幾個部門負責人還是有錢的,象我三十歲,一年18萬左右的年薪,其它的我就不清楚了,有幾個我一個星期才見一次的,比我還小,只怕拿得比我還多.你應該也是這樣的吧.
  
  他要求6千一個月的月薪,我說這倒不重要,重要的是公司不會給你安排業務的,你自己找業務回來,公司給你平臺,給你配團隊,能掙多少錢是你的本事.
  
  我說完了,問,你有什么想法嗎?他說沒想法,起身走人.
  
  太有意思了,你在長沙想拿六千一個月,你等別人找事給你做,你為什么不能自己找到項目呀?六千是底薪呀,差不多7萬2千的底薪,如果是這樣的,那我自己算我應該拿到二十五萬以上的年薪了.
  
  從來拿底薪和拿年薪的人就是不一樣的.
  
  如果你不敢拿年薪,你就不要想著談什么老板給你少了.
  
  企業是要盈利的,資本家是要剝削的.問題是,如果你是一個真正能創造價值的人,你自己所創造的價值你是可以拿到手的.
  
  大學畢業生,如果什么經驗也沒有,只有知識,沒有技能,能找到一個給你幾百塊錢,讓你在這里呆著學東西的企業就應該感謝了,如果你覺得這種企業不是你所向往的,你在上大學時就老老實實努力學,少玩,多練.
  
  我工作有一個總結,錢永遠不會是目標,但是它會是結果.

  談到職業規劃,有人說過職業可以規劃的,我也相信未來可以計劃的,問題是,你是不是這個能不能計劃出你未來的人,以及,你身邊有沒有熟悉你的高 人 指 點,如果沒有,那你自己都不會明白你自己的未來是什么的,就象象你去做所謂的性向測試,說不定是你自己在自欺欺人了,這種事多了,沒人會把自己算成一個壞人的。

  所以重要的還是那一句話,復雜的生活簡單過,簡單的事情重復做。
  
  你是中文系的,如果你的年紀還不是很大,建議你憑你自己的能力,哪怕是工資少點,你都要進最好的廣告公司,去呆上一年半載,按我說的方法偷師,基本能力提升了,慢慢的你會遇到一些貴人的,還有你會涉及一些行業,慢慢的,你會發覺你內心深處喜歡的行業。
  
  呵呵,特別是哦,女孩子,只有努力才能進大公司,只有進了大公司才能遇到優秀的男生。好男生都關在寫字樓里上班下班加班的,呵呵。生活圈子都小的,你選擇的工作圈在你努力的階段就是你的生活圈。

  在你的成長過程中,有五個人非常重要。
    
  第一個,導師,教練。
  他教給你實用的技巧、一定的工作經驗,而不是知識。他可以給你指明方向。
  這個人可能是你的上司、前輩、學長。
    
  第二個,陪練,同路人。
  任何人的成長都不是學出來的,而是學而習,習而成習慣,練出來的。在這個練的過程中,是一件很苦的過程,是一系列簡單動作的重復重復再重復,由量變到質變的過程,在這個過程中,一個人很難堅持下來,這時你需要一個同路人。
  他可以是和你共同興趣,共同目標的朋友,最好是你生命中所愛的人。
    
  第三個,榜樣,他是你人生的標桿。
  在你一生中,在不同階段,會有不同的標桿,你向他學習,受他鼓舞,一步一步向他靠擾。
  最重要的是那個你看得到摸得著的人,你知道,不需要通過機遇,只需要通過努力就可以達到的榜樣。
    
  第四個,敵人,看不起你的人,拒絕過你的人。
  人不到絕境是不會有斗志的,你要證明他是錯的,他會給你真正的動力。
    
  第五個,最重要的是第五個,你們覺得第五個人是你自己。
  世界上沒有救世主,任何希望當別人救世主的人不是瘋子就是傻子,只有自己才可以救自己。
  這個世界上,失敗的人除了天分太差之外,只有以下幾點,懶,方向不對,方法不對,沒有堅持。
  如果你自己做不到,你不要怪別人。

  基本功是你自己的,細節所積累下來的,能讓你迅速融入新環境.
  /* 人和人最終的差距就是在基本功上面,是否迅速。 */
  
  不知道怎么跟大家談基本功這個問題.

  很多東西大家都沒把它當基本功了.

  比如說,我想要的人,他打字很快,他很少很少寫錯別字,有豐富的詞匯量,邏輯很清晰,用詞很準確,這些看上去難不難?
  
  但是在我這兩年見過的應聘的策劃文案來看,只有兩個人做到了.一個是做了三年文案的女孩子,慢慢磨的.一個是中文碩士生,還沒畢業.
  
  其實大學到底教給大家什么了?
  知識?
  
  大學階段必須打好你的基本功,這些決定了你就業后的學習能力,階層簡單工作的工作效率.
  
  如果誰還說打字、排版是文員做的事,那只能說他是真正不明白真正的職場需要。
  
  你們在大學所學到的知識,都是同質化的了,如果將知識變為通用的、標準化的技能才是重要的。
  
  既然學的東西沒用,那在大學還要不要認真學習呢?
  
  當然要,因為這些東西是系統性的,這個學習過程能培養你的學習能力。
  
  知識不能改變你的命運了,但是它可以改變你的氣質。
  
  如果你讀個四年大學出來,你的氣質還不能好一點,那你的大學就真的白讀了。
  
  經常有人在問面試穿什么衣服呀?
  
  穿什么衣服重要嗎?
  
  重要的是什么人在穿這些衣服。
  
  重要的是你的精氣神,你的氣質。
  
  有一天有一個應聘文案的來了,我叫設計總監先和他聊聊。
  
  聊完了,我說這個人不行吧,設計總監說為什么?
  
  我說我們調性不符,我們多少都有點書卷氣,而他是一臉的江湖氣。
  
  果然,呵呵。
  
  招聘方當然是要看應聘者的外形條件的,但并不是丑的就不招,重要的是能力和你的氣質,是不是符合公司要求的。

  重要的是興趣。
  
  然后是狂練基本功,簡單重復積累。
  
  學打拳,你先站三個月樁再說。
  
  面對新人,我說很多東西,你會發現,每個字你都認識,每句話你都看得懂,但是你理解嗎?
  
  領悟,是教不了的。
  
  自己努力吧,自己重復做,再會明白自己最想要的是什么。
  
  你考公員員如果死活考不上,那你應該去想想,這種機械性的考試你都過不了,那是不是學習方法,或者興趣不對呀?
  
  做銷售,同樣的,從基階做起吧。
  
  你的財政學對你有沒有幫助?
  
  當然有,你對銷售的認識會不同的。
  
  象十年前我賣保險,人人都跟銀行比,算利息,都算得沒有銀行高,只能說死了人有賠了。
  
  而我是怎么算呢?我用遞增,還是增減年金公式算,呵呵,比銀行高呢。
  
  另外,別人說死了人有賠,最多是說得婉轉點。
  
  我可沒把它當死人賣呀,我把它當禮物賣,當成父母送給孩子的禮物賣,賣得可好了,呵呵。
  
  現在哪個做人壽險的人敢說他一年做兩百多單?
  
  呵呵,我好象一年做了二百四十單左右,全是年繳哦。

  這個世界上最窮的和最富的人都在做銷售.
  
  做銷售的人底薪很低的,大多數人拼的只是體力罷了,如果你想做好,你多花心思就可以了.多想多跑,還是在一個行業里多堅持,找到高手做師父帶你.

  我說說當年我混日子的時候怎么過來的.
  
  那年頭電腦還緊俏,我只要一有機會就到別人電腦上練東西,終于練成了今天的電腦基本功,一方面要多學,一方面要多用心.
  
  然后,我每天做記錄,記下工作的流程,記下別人說過的工作中重要的話,其實什么叫行業經驗,很多老手隨便說的話,都是行話了,有它的意思的,聽了就要想,就要去查,很多東西就知道了.
  
  為什么要記錄,因為什么叫職業化?職業化就是標準化、流程化,模式化,你多看多記多想就能明白了,這些東西在很多地方都是通用的。
  /* 職業精神 */

  有一點,如果這里收入還可以的話,你好好學吧,任何工作都要呆一兩年,你才會有認識的,跳來跳去的對你不好,真的,你還在磨性情的時候,只要你保持學習的能力,別下班玩去了就可以了,有壓力才有動力,好好留心心儀的公司招聘的要求,按那個要求去做一個一年的訓練與學習計劃,一年后,那個公司在等你。

JAVA之路 2008-01-26 16:17 發表評論
]]>
MessageDigest對密碼進行加密http://www.tkk7.com/xixidabao/archive/2008/01/26/177892.htmlJAVA之路JAVA之路Sat, 26 Jan 2008 04:16:00 GMThttp://www.tkk7.com/xixidabao/archive/2008/01/26/177892.html 比如,有明文密碼如下:
String originalPwd = "mypassword";

應用報文摘要方法,得到單向的加密字符串

//MD5是16位,SHA是20位(這是兩種報文摘要的算法)
//MessageDigest md= MessageDigest.getInstance("MD5");
MessageDigest messageDigest=MessageDigest.getInstance("SHA-1");
messageDigest.update(originalPwd.getBytes());
//String digestedPwdString = new String(messageDigest.digest());
String digestedPwdString = new String(Base64.encode(messageDigest.digest()));
System.out.println("pwd:" + digestedPwdString);
這樣,就得到密碼的報文摘要,把此摘要保存到數據庫,
以后用戶登陸時,用相同的算法算出摘要,和數據庫中的比較,如果一致,則密碼正確。

注意:
byte[] digest = messageDigest.digest();
得到的是個二進制byte數組,有可能某些byte是不可打印的字符。
所以用Base64.encode把它轉化成可打印字符。

也可以把digest的每個byte轉化成hex(16進制)保存。
MessageDigest messageDigest=MessageDigest.getInstance("SHA-1");
messageDigest.update(originalPwd.getBytes());
byte[] bin = messageDigest.digest();
再調用下面的方法生產hex(16進制)保存。


二行制轉hex字符串的方法如下:
private static String byte2hex(byte[] b){
    String hs="";
    String stmp="";
    for (int n=0; n<b.length; n++){
        stmp=(java.lang.Integer.toHexString(b[n] & 0xFF));
        if (stmp.length()==1) hs=hs+"0"+stmp;
            else hs=hs+stmp;
    }
    return hs;
}

或者:
private static String byto2hex2(byte[] bin){
    StringBuffer buf = new StringBuffer();
    for (int i = 0; i < bin.length; ++i) {
        int x = bin[i] & 0xFF, h = x >>> 4, l = x & 0x0F;
        buf.append((char) (h + ((h < 10) ? '0' : 'a' - 10)));
        buf.append((char) (l + ((l < 10) ? '0' : 'a' - 10)));
    }
    return buf.toString();
}

或者:
干脆直接用下面的方法生成,用到第三方包:
public static String encryptPwd(String pwd, String algorithm){
    //String a = org.apache.catalina.realm.RealmBase.Digest(pwd,"SHA-1");
    return org.apache.catalina.realm.RealmBase.Digest(pwd, algorithm);
}

 

http://www.ibm.com/developerworks/cn/java/l-security/index.html

JAVA之路 2008-01-26 12:16 發表評論
]]>
Java NIO API詳解http://www.tkk7.com/xixidabao/archive/2008/01/18/176208.htmlJAVA之路JAVA之路Fri, 18 Jan 2008 06:47:00 GMThttp://www.tkk7.com/xixidabao/archive/2008/01/18/176208.html閱讀全文

JAVA之路 2008-01-18 14:47 發表評論
]]>
SQL語句處理的過程http://www.tkk7.com/xixidabao/archive/2007/12/20/169007.htmlJAVA之路JAVA之路Thu, 20 Dec 2007 05:30:00 GMThttp://www.tkk7.com/xixidabao/archive/2007/12/20/169007.html 本節介紹了SQL語句處理的基本過程,主要包括:
  ·        查詢語句處理
  ·        DML語句處理(insert, update, delete)
  ·        DDL 語句處理(create .. , drop .. , alter .. , )
  ·        事務控制(commit, rollback)

  SQL 語句的執行過程(SQL Statement Execution)
            
   圖3-1 概要的列出了處理和運行一個sql語句的需要各個重要階段。在某些情況下,Oracle運行sql的過程可能與下面列出的各個階段的順序有所不同。如DEFINE階段可能在FETCH階段之前,這主要依賴你如何書寫代碼。

  對許多oracle的工具來說,其中某些階段會自動執行。絕大多數用戶不需要關心各個階段的細節問題,然而,知道執行的各個階段還是有必要的,這會幫助你寫出更高效的SQL語句來,而且還可以讓你猜測出性能差的SQL語句主要是由于哪一個階段造成的,然后我們針對這個具體的階段,找出解決的辦法。

  圖 3-1  SQL語句處理的各個階段

  DML語句的處理
      
  本節給出一個例子來說明在DML語句處理的各個階段到底發生了什么事情。假設你使用Pro*C程序來為指定部門的所有職員增加工資。程序已經連到正確的用戶,你可以在你的程序中嵌入如下的SQL語句:
EXEC SQL UPDATE employees
SET salary = 1.10 * salary WHERE department_id = :var_department_id; var_department_id是程序變量,里面包含部門號,我們要修改該部門的職員的工資。當這個SQL語句執行時,使用該變量的值。

  每種類型的語句都需要如下階段:
  ·        第1步: Create a Cursor     創建游標
  ·        第2步: Parse the Statement  分析語句
  ·        第5步: Bind Any Variables    綁定變量
  ·        第7步: Run the Statement    運行語句
  ·        第9步: Close the Cursor     關閉游標

  如果使用了并行功能,還會包含下面這個階段:
  ·        第6步: Parallelize the Statement   并行執行語句

  如果是查詢語句,則需要以下幾個額外的步驟,如圖 3所示:
  ·        第3步: Describe Results of a Query   描述查詢的結果集
  ·        第4步: Define Output of a Query      定義查詢的輸出數據
  ·        第8步: Fetch Rows of a Query        取查詢出來的行

  下面具體說一下每一步中都發生了什么事情:.

  第1步: 創建游標(Create a Cursor)


        由程序接口調用創建一個游標(cursor)。任何SQL語句都會創建它,特別在運行DML語句時,都是自動創建游標的,不需要開發人員干預。多數應用中,游標的創建是自動的。然而,在預編譯程序(pro*c)中游標的創建,可能是隱含的,也可能顯式的創建。在存儲過程中也是這樣的。

  第2步:分析語句(Parse the Statement)
 
  在語法分析期間,SQL語句從用戶進程傳送到Oracle,SQL語句經語法分析后,SQL語句本身與分析的信息都被裝入到共享SQL區。在該階段中,可以解決許多類型的錯誤。

  語法分析分別執行下列操作:
l        翻譯SQL語句,驗證它是合法的語句,即書寫正確
l        實現數據字典的查找,以驗證是否符合表和列的定義
l        在所要求的對象上獲取語法分析鎖,使得在語句的語法分析過程中不改變這些對象的定義
l        驗證為存取所涉及的模式對象所需的權限是否滿足
l        決定此語句最佳的執行計劃
l        將它裝入共享SQL區
l        對分布的語句來說,把語句的全部或部分路由到包含所涉及數據的遠程節點
      
  以上任何一步出現錯誤,都將導致語句報錯,中止執行。

  只有在共享池中不存在等價SQL語句的情況下,才對SQL語句作語法分析。在這種情況下,數據庫內核重新為該語句分配新的共享SQL區,并對語句進行語法分析。進行語法分析需要耗費較多的資源,所以要盡量避免進行語法分析,這是優化的技巧之一。

  語法分析階段包含了不管此語句將執行多少次,而只需分析一次的處理要求。Oracle只對每個SQL語句翻譯一次,在以后再次執行該語句時,只要該語句還在共享SQL區中,就可以避免對該語句重新進行語法分析,也就是此時可以直接使用其對應的執行計劃對數據進行存取。這主要是通過綁定變量(bind variable)實現的,也就是我們常說的共享SQL,后面會給出共享SQL的概念。

  雖然語法分析驗證了SQL語句的正確性,但語法分析只能識別在SQL語句執行之前所能發現的錯誤(如書寫錯誤、權限不足等)。因此,有些錯誤通過語法分析是抓不到的。例如,在數據轉換中的錯誤或在數據中的錯(如企圖在主鍵中插入重復的值)以及死鎖等均是只有在語句執行階段期間才能遇到和報告的錯誤或情況。

  查詢語句的處理
      
  查詢與其它類型的SQL語句不同,因為在成功執行后作為結果將返回數據。其它語句只是簡單地返回成功或失敗,而查詢則能返回一行或許多行數據。查詢的結果均采用表格形式,結果行被一次一行或者批量地被檢索出來。從這里我們可以得知批量的fetch數據可以降低網絡開銷,所以批量的fetch也是優化的技巧之一。

       有些問題只與查詢處理相關,查詢不僅僅指SELECT語句,同樣也包括在其它SQL語句中的隱含查詢。例如,下面的每個語句都需要把查詢作為它執行的一部分:
INSERT INTO table SELECT...
UPDATE table SET x = y WHERE...
DELETE FROM table WHERE...
CREATE table AS SELECT...

  具體來說,查詢
·        要求讀一致性
·        可能使用回滾段作中間處理
·        可能要求SQL語句處理描述、定義和取數據階段

  第3步: 描述查詢結果(Describe Results of a Query)
 
  描述階段只有在查詢結果的各個列是未知時才需要;例如,當查詢由用戶交互地輸入需要輸出的列名。在這種情況要用描述階段來決定查詢結果的特征(數據類型,長度和名字)。

  第4步: 定義查詢的輸出數據(Define Output of a Query)  
      
  在查詢的定義階段,你指定與查詢出的列值對應的接收變量的位置、大小和數據類型,這樣我們通過接收變量就可以得到查詢結果。如果必要的話,Oracle會自動實現數據類型的轉換。這是將接收變量的類型與對應的列類型相比較決定的。

  第5步: 綁定變量(Bind Any Variables)
      
  此時,Oracle知道了SQL語句的意思,但仍沒有足夠的信息用于執行該語句。Oracle 需要得到在語句中列出的所有變量的值。在該例中,Oracle需要得到對department_id列進行限定的值。得到這個值的過程就叫綁定變量(binding variables)

  此過程稱之為將變量值捆綁進來。程序必須指出可以找到該數值的變量名(該變量被稱為捆綁變量,變量名實質上是一個內存地址,相當于指針)。應用的最終用戶可能并沒有發覺他們正在指定捆綁變量,因為Oracle 的程序可能只是簡單地指示他們輸入新的值,其實這一切都在程序中自動做了。因為你指定了變量名,在你再次執行之前無須重新捆綁變量。你可以改變綁定變量的值,而Oracle在每次執行時,僅僅使用內存地址來查找此值。如果Oracle 需要實現自動數據類型轉換的話(除非它們是隱含的或缺省的),你還必須對每個值指定數據類型和長度。關于這些信息可以參考oracle的相關文檔,如Oracle Call Interface Programmer's Guide

  第6步: 并行執行語句(Parallelize the Statement )
     
  ORACLE 可以在SELECTs, INSERTs, UPDATEs, MERGEs, DELETEs語句中執行相應并行查詢操作,對于某些DDL操作,如創建索引、用子查詢創建表、在分區表上的操作,也可以執行并行操作。并行化可以導致多個服務器進程(oracle server processes)為同一個SQL語句工作,使該SQL語句可以快速完成,但是會耗費更多的資源,所以除非很有必要,否則不要使用并行查詢。

  第7步: 執行語句(Run the Statement)
      
  到了現在這個時候,Oracle擁有所有需要的信息與資源,因此可以真正運行SQL語句了。如果該語句為SELECT查詢或INSERT語句,則不需要鎖定任何行,因為沒有數據需要被改變。然而,如果語句為UPDATE或DELETE語句,則該語句影響的所有行都被鎖定,防止該用戶提交或回滾之前,別的用戶對這些數據進行修改。這保證了數據的一致性。對于某些語句,你可以指定執行的次數,這稱為批處理(array processing)。指定執行N次,則綁定變量與定義變量被定義為大小為N的數組的開始位置,這種方法可以減少網絡開銷,也是優化的技巧之一。

  第8步: 取出查詢的行(Fetch Rows of a Query)
      
  在fetch階段,行數據被取出來,每個后續的存取操作檢索結果集中的下一行數據,直到最后一行被取出來。上面提到過,批量的fetch是優化的技巧之一。

  第9步: 關閉游標(Close the Cursor)
      
  SQL語句處理的最后一個階段就是關閉游標

  DDL語句的處理(DDL Statement Processing)
     
  DDL語句的執行不同與DML語句和查詢語句的執行,這是因為DDL語句執行成功后需要對數據字典數據進行修改。對于DDL語句,語句的分析階段實際上包括分析、查找數據字典信息和執行。事務管理語句、會話管理語句、系統管理語句只有分析與執行階段,為了重新執行該語句,會重新分析與執行該語句。

  事務控制(Control of Transactions)
      
  一般來說,只有使用ORACLE編程接口的應用設計人員才關心操作的類型,并把相關的操作組織在一起,形成一個事務。一般來說,我門必須定義事務,這樣在一個邏輯單元中的所有工作可以同時被提交或回滾,保證了數據的一致性。一個事務應該由邏輯單元中的所有必須部分組成,不應該多一個,也不應該少一個。
  ·        在事務開始和結束的這段時間內,所有被引用表中的數據都應該在一致的狀態(或可以被回溯到一致的狀態)
  ·        事務應該只包含可以對數據進行一致更改(one consistent change to the data)的SQL語句

  例如,在兩個帳號之間的轉帳(這是一個事務或邏輯工作單元),應該包含從一個帳號中借錢(由一個SQL完成),然后將借的錢存入另一個帳號(由另一個SQL完成)。這2個操作作為一個邏輯單元,應該同時成功或同時失敗。其它不相關的操作,如向一個帳戶中存錢,不應該包含在這個轉帳事務中。

  在設計應用時,除了需要決定哪種類型的操作組成一個事務外,還需要決定使用BEGIN_DISCRETE_TRANSACTIO存儲過程是否對提高小的、非分布式的事務的性能有作用。

JAVA之路 2007-12-20 13:30 發表評論
]]>
SQL觸發器語法參考 http://www.tkk7.com/xixidabao/archive/2007/12/07/165947.htmlJAVA之路JAVA之路Fri, 07 Dec 2007 01:13:00 GMThttp://www.tkk7.com/xixidabao/archive/2007/12/07/165947.htmlSQL觸發器語法參考

CREATE TRIGGER trigger_name
ON { table | view
}
[ WITH ENCRYPTION ]
{

    { { FOR | AFTER | INSTEAD OF } { [ INSERT ] [ , ] [ UPDATE ] }
        [ WITH APPEND ]
        [ NOT FOR REPLICATION ]
        AS
        [ { IF UPDATE ( column )
            [ { AND | OR } UPDATE ( column ) ]
                [ ...n ]
        | IF ( COLUMNS_UPDATED ( ) { bitwise_operator } updated_bitmask )
                { comparison_operator } column_bitmask [ ...n ]
        } ]
        sql_statement [ ...n ]
    }
}

參數

trigger_name

是觸發器的名稱。觸發器名稱必須符合標識符規則,并且在數據庫中必須唯一。可以選擇是否指定觸發器所有者名稱。

Table | view

是在其上執行觸發器的表或視圖,有時稱為觸發器表或觸發器視圖。可以選擇是否指定表或視圖的所有者名稱。

WITH ENCRYPTION

加密 syscomments 表中包含 CREATE TRIGGER 語句文本的條目。使用 WITH ENCRYPTION 可防止將觸發器作為 SQL Server 復制的一部分發布。

AFTER

指定觸發器只有在觸發 SQL 語句中指定的所有操作都已成功執行后才激發。所有的引用級聯操作和約束檢查也必須成功完成后,才能執行此觸發器。

如果僅指定 FOR 關鍵字,則 AFTER 是默認設置。

不能在視圖上定義 AFTER 觸發器。

INSTEAD OF

指定執行觸發器而不是執行觸發 SQL 語句,從而替代觸發語句的操作。

在表或視圖上,每個 INSERT、UPDATE 或 DELETE 語句最多可以定義一個 INSTEAD OF 觸發器。然而,可以在每個具有 INSTEAD OF 觸發器的視圖上定義視圖。

INSTEAD OF 觸發器不能在 WITH CHECK OPTION 的可更新視圖上定義。如果向指定了 WITH CHECK OPTION 選項的可更新視圖添加 INSTEAD OF 觸發器,SQL Server 將產生一個錯誤。用戶必須用 ALTER VIEW 刪除該選項后才能定義 INSTEAD OF 觸發器。

{ [DELETE] [,] [INSERT] [,] [UPDATE] }

是指定在表或視圖上執行哪些數據修改語句時將激活觸發器的關鍵字。必須至少指定一個選項。在觸發器定義中允許使用以任意順序組合的這些關鍵字。如果指定的選項多于一個,需用逗號分隔這些選項。

對于 INSTEAD OF 觸發器,不允許在具有 ON DELETE 級聯操作引用關系的表上使用 DELETE 選項。同樣,也不允許在具有 ON UPDATE 級聯操作引用關系的表上使用 UPDATE 選項。

WITH APPEND

指定應該添加現有類型的其它觸發器。只有當兼容級別是 65 或更低時,才需要使用該可選子句。如果兼容級別是 70 或更高,則不必使用 WITH APPEND 子句添加現有類型的其它觸發器(這是兼容級別設置為 70 或更高的 CREATE TRIGGER 的默認行為)。有關更多信息,請參見 sp_dbcmptlevel

WITH APPEND 不能與 INSTEAD OF 觸發器一起使用,或者,如果顯式聲明 AFTER 觸發器,也不能使用該子句。只有當出于向后兼容而指定 FOR 時(沒有 INSTEAD OF 或 AFTER),才能使用 WITH APPEND。以后的版本將不支持 WITH APPEND 和 FOR(將被解釋為 AFTER)。

NOT FOR REPLICATION

表示當復制進程更改觸發器所涉及的表時,不應執行該觸發器。

AS

是觸發器要執行的操作。

sql_statement

是觸發器的條件和操作。觸發器條件指定其它準則,以確定 DELETE、INSERT 或 UPDATE 語句是否導致執行觸發器操作。

當嘗試 DELETE、INSERT 或 UPDATE 操作時,Transact-SQL語句中指定的觸發器操作將生效。

觸發器可以包含任意數量和種類的 Transact-SQL 語句。觸發器旨在根據數據修改語句檢查或更改數據;它不應將數據返回給用戶。觸發器中的 Transact-SQL 語句常常包含控制流語言。CREATE TRIGGER 語句中使用幾個特殊的表:

  • deletedinserted 是邏輯(概念)表。這些表在結構上類似于定義觸發器的表(也就是在其中嘗試用戶操作的表);這些表用于保存用戶操作可能更改的行的舊值或新值。例如,若要檢索 deleted 表中的所有值,請使用:
    SELECT *
        FROM deleted
        
  • 如果兼容級別等于 70,那么在 DELETE、INSERT 或 UPDATE 觸發器中,SQL Server 將不允許引用 inserteddeleted 表中的 textntextimage 列。不能訪問 inserteddeleted 表中的 textntextimage 值。若要在 INSERT 或 UPDATE 觸發器中檢索新值,請將 inserted 表與原始更新表聯接。當兼容級別是 65 或更低時,對 inserteddeleted 表中允許空值的textntextimage 列,將返回空值;如果這些列不可為空,則返回零長度字符串。

    當兼容級別是 80 或更高時,SQL Server 允許在表或視圖上通過 INSTEAD OF 觸發器更新 textntextimage 列。

n

是表示觸發器中可以包含多條 Transact-SQL 語句的占位符。對于 IF UPDATE (column) 語句,可以通過重復 UPDATE (column) 子句包含多列。

IF UPDATE (column)

測試在指定的列上進行的 INSERT 或 UPDATE 操作,不能用于 DELETE 操作。可以指定多列。因為在 ON 子句中指定了表名,所以在 IF UPDATE 子句中的列名前不要包含表名。若要測試在多個列上進行的 INSERT 或 UPDATE 操作,請在第一個操作后指定單獨的 UPDATE(column) 子句。在 INSERT 操作中 IF UPDATE 將返回 TRUE 值,因為這些列插入了顯式值或隱性 (NULL) 值。

說明  IF UPDATE (column) 子句的功能等同于 IF、IF...ELSE 或 WHILE 語句,并且可以使用 BEGIN...END 語句塊。有關更多信息,請參見控制流語言

 

可以在觸發器主體中的任意位置使用 UPDATE (column)。

column

是要測試 INSERT 或 UPDATE 操作的列名。該列可以是 SQL Server 支持的任何數據類型。但是,計算列不能用于該環境中。有關更多信息,請參見數據類型

IF (COLUMNS_UPDATED())

測試是否插入或更新了提及的列,僅用于 INSERT 或 UPDATE 觸發器中。COLUMNS_UPDATED 返回 varbinary 位模式,表示插入或更新了表中的哪些列。

COLUMNS_UPDATED 函數以從左到右的順序返回位,最左邊的為最不重要的位。最左邊的位表示表中的第一列;向右的下一位表示第二列,依此類推。如果在表上創建的觸發器包含 8 列以上,則 COLUMNS_UPDATED 返回多個字節,最左邊的為最不重要的字節。在 INSERT 操作中 COLUMNS_UPDATED 將對所有列返回 TRUE 值,因為這些列插入了顯式值或隱性 (NULL) 值。

可以在觸發器主體中的任意位置使用 COLUMNS_UPDATED。

bitwise_operator

是用于比較運算的位運算符。

updated_bitmask

是整型位掩碼,表示實際更新或插入的列。例如,表 t1 包含列 C1C2C3C4C5。假定表 t1 上有 UPDATE 觸發器,若要檢查列 C2、C3 C4 是否都有更新,指定值 14;若要檢查是否只有列 C2 有更新,指定值 2。

comparison_operator

是比較運算符。使用等號 (=) 檢查 updated_bitmask 中指定的所有列是否都實際進行了更新。使用大于號 (>) 檢查 updated_bitmask 中指定的任一列或某些列是否已更新。

column_bitmask

是要檢查的列的整型位掩碼,用來檢查是否已更新或插入了這些列。

注釋

觸發器常常用于強制業務規則和數據完整性。SQL Server 通過表創建語句(ALTER TABLE 和 CREATE TABLE)提供聲明引用完整性 (DRI);但是 DRI 不提供數據庫間的引用完整性。若要強制引用完整性(有關表的主鍵和外鍵之間關系的規則),請使用主鍵和外鍵約束(ALTER TABLE 和 CREATE TABLE 的 PRIMARY KEY 和 FOREIGN KEY 關鍵字)。如果觸發器表存在約束,則在 INSTEAD OF 觸發器執行之后和 AFTER 觸發器執行之前檢查這些約束。如果違反了約束,則回滾 INSTEAD OF 觸發器操作且不執行(激發)AFTER 觸發器。

可用 sp_settriggerorder 指定表上第一個和最后一個執行的 AFTER 觸發器。在表上只能為每個 INSERT、UPDATE 和 DELETE 操作指定一個第一個執行和一個最后一個執行的 AFTER 觸發器。如果同一表上還有其它 AFTER 觸發器,則這些觸發器將以隨機順序執行。

如果 ALTER TRIGGER 語句更改了第一個或最后一個觸發器,則將除去已修改觸發器上設置的第一個或最后一個特性,而且必須用 sp_settriggerorder 重置排序值。

只有當觸發 SQL 語句(包括所有與更新或刪除的對象關聯的引用級聯操作和約束檢查)成功執行后,AFTER 觸發器才會執行。AFTER 觸發器檢查觸發語句的運行效果,以及所有由觸發語句引起的 UPDATE 和 DELETE 引用級聯操作的效果。

觸發器限制

CREATE TRIGGER 必須是批處理中的第一條語句,并且只能應用到一個表中。

觸發器只能在當前的數據庫中創建,不過觸發器可以引用當前數據庫的外部對象。

如果指定觸發器所有者名稱以限定觸發器,請以相同的方式限定表名。

在同一條 CREATE TRIGGER 語句中,可以為多種用戶操作(如 INSERT 和 UPDATE)定義相同的觸發器操作。

如果一個表的外鍵在 DELETE/UPDATE 操作上定義了級聯,則不能在該表上定義 INSTEAD OF DELETE/UPDATE 觸發器。

在觸發器內可以指定任意的 SET 語句。所選擇的 SET 選項在觸發器執行期間有效,并在觸發器執行完后恢復到以前的設置。

與使用存儲過程一樣,當觸發器激發時,將向調用應用程序返回結果。若要避免由于觸發器激發而向應用程序返回結果,請不要包含返回結果的 SELECT 語句,也不要包含在觸發器中進行變量賦值的語句。包含向用戶返回結果的 SELECT 語句或進行變量賦值的語句的觸發器需要特殊處理;這些返回的結果必須寫入允許修改觸發器表的每個應用程序中。如果必須在觸發器中進行變量賦值,則應該在觸發器的開頭使用 SET NOCOUNT 語句以避免返回任何結果集。

DELETE 觸發器不能捕獲 TRUNCATE TABLE 語句。盡管 TRUNCATE TABLE 語句實際上是沒有 WHERE 子句的 DELETE(它刪除所有行),但它是無日志記錄的,因而不能執行觸發器。因為 TRUNCATE TABLE 語句的權限默認授予表所有者且不可轉讓,所以只有表所有者才需要考慮無意中用 TRUNCATE TABLE 語句規避 DELETE 觸發器的問題。

無論有日志記錄還是無日志記錄,WRITETEXT 語句都不激活觸發器。

觸發器中不允許以下 Transact-SQL 語句:

ALTER DATABASE CREATE DATABASE DISK INIT
DISK RESIZE DROP DATABASE LOAD DATABASE
LOAD LOG RECONFIGURE RESTORE DATABASE
RESTORE LOG    

說明  由于 SQL Server 不支持系統表中的用戶定義觸發器,因此建議不要在系統表中創建用戶定義觸發器。

 

多個觸發器

SQL Server 允許為每個數據修改事件(DELETE、INSERT 或 UPDATE)創建多個觸發器。例如,如果對已有 UPDATE 觸發器的表執行 CREATE TRIGGER FOR UPDATE,則將創建另一個更新觸發器。在早期版本中,在每個表上,每個數據修改事件(INSERT、UPDATE 或 DELETE)只允許有一個觸發器。

說明  如果觸發器名稱不同,則 CREATE TRIGGER(兼容級別為 70)的默認行為是在現有的觸發器中添加其它觸發器。如果觸發器名稱相同,則 SQL Server 返回一條錯誤信息。但是,如果兼容級別等于或小于 65,則使用 CREATE TRIGGER 語句創建的新觸發器將替換同一類型的任何現有觸發器,即使觸發器名稱不同。有關更多信息,請參見 sp_dbcmptlevel

 

遞歸觸發器

當在 sp_dboption 中啟用 recursive triggers 設置時,SQL Server 還允許觸發器的遞歸調用。

遞歸觸發器允許發生兩種類型的遞歸:

  • 間接遞歸

  • 直接遞歸

使用間接遞歸時,應用程序更新表 T1,從而激發觸發器 TR1,該觸發器更新表 T2。在這種情況下,觸發器 T2 將激發并更新 T1

使用直接遞歸時,應用程序更新表 T1,從而激發觸發器 TR1,該觸發器更新表 T1。由于表 T1 被更新,觸發器 TR1 再次激發,依此類推。

下例既使用了間接觸發器遞歸,又使用了直接觸發器遞歸。假定在表 T1 中定義了兩個更新觸發器 TR1TR2。觸發器 TR1 遞歸地更新表 T1。UPDATE 語句使 TR1TR2 各執行一次。而 TR1 的執行將觸發 TR1(遞歸)和 TR2 的執行。給定觸發器的 inserteddeleted 表只包含與喚醒調用觸發器的 UPDATE 語句相對應的行。

說明  只有啟用 sp_dboptionrecursive triggers 設置,才會發生上述行為。對于為給定事件定義的多個觸發器,并沒有確定的執行順序。每個觸發器都應是自包含的。

 

禁用 recursive triggers 設置只能禁止直接遞歸。若要也禁用間接遞歸,請使用 sp_configurenested triggers 服務器選項設置為 0。

如果任一觸發器執行了 ROLLBACK TRANSACTION 語句,則無論嵌套級是多少,都不會進一步執行其它觸發器。

嵌套觸發器

觸發器最多可以嵌套 32 層。如果一個觸發器更改了包含另一個觸發器的表,則第二個觸發器將激活,然后該觸發器可以再調用第三個觸發器,依此類推。如果鏈中任意一個觸發器引發了無限循環,則會超出嵌套級限制,從而導致取消觸發器。若要禁用嵌套觸發器,請用 sp_configure nested triggers 選項設置為 0(關閉)。默認配置允許嵌套觸發器。如果嵌套觸發器是關閉的,則也將禁用遞歸觸發器,與 sp_dboptionrecursive triggers 設置無關。

延遲名稱解析

SQL Server 允許 Transact-SQL 存儲過程、觸發器和批處理引用編譯時不存在的表。這種能力稱為延遲名稱解析。但是,如果 Transact-SQL 存儲過程、觸發器或批處理引用在存儲過程或觸發器中定義的表,則只有當兼容級別設置(通過執行 sp_dbcmptlevel 設置)等于 65 時,才會在創建時發出警告。如果使用批處理,則在編譯時發出警告。如果引用的表不存在,將在運行時返回錯誤信息。有關更多信息,請參見延遲名稱解析和編譯

權限

CREATE TRIGGER 權限默認授予定義觸發器的表所有者、sysadmin 固定服務器角色成員以及 db_ownerdb_ddladmin 固定數據庫角色成員,并且不可轉讓。

若要檢索表或視圖中的數據,用戶必須在表或視圖中擁有 SELECT 語句權限。若要更新表或視圖的內容,用戶必須在表或視圖中擁有 INSERT、DELETE 和 UPDATE 語句權限。

如果視圖中存在 INSTEAD OF 觸發器,用戶必須在該視圖中有 INSERT、DELETE 和 UPDATE 特權,以對該視圖發出 INSERT、DELETE 和 UPDATE 語句,而不管實際上是否在視圖上執行了這樣的操作。

示例
A. 使用帶有提醒消息的觸發器

當有人試圖在 titles 表中添加或更改數據時,下例將向客戶端顯示一條消息。

說明  消息 50009 是 sysmessages 中的用戶定義消息。有關創建用戶定義消息的更多信息,請參見 sp_addmessage

 

USE pubs
IF EXISTS (SELECT name FROM sysobjects
WHERE name = 'reminder' AND type = 'TR')
DROP TRIGGER reminder
GO
CREATE TRIGGER reminder
ON titles
FOR INSERT, UPDATE
AS RAISERROR (50009, 16, 10)
GO
B. 使用帶有提醒電子郵件的觸發器

titles 表更改時,下例將電子郵件發送給指定的人員 (MaryM)。

USE pubs
IF EXISTS (SELECT name FROM sysobjects
WHERE name = 'reminder' AND type = 'TR')
DROP TRIGGER reminder
GO
CREATE TRIGGER reminder
ON titles
FOR INSERT, UPDATE, DELETE
AS
EXEC master..xp_sendmail 'MaryM',
'Don''t forget to print a report for the distributors.'
GO
C. 在 employee 和 jobs 表之間使用觸發器業務規則

由于 CHECK 約束只能引用定義了列級或表級約束的列,表間的任何約束(在下例中是指業務規則)都必須定義為觸發器。

下例創建一個觸發器,當插入或更新雇員工作級別 (job_lvls) 時,該觸發器檢查指定雇員的工作級別(由此決定薪水)是否處于為該工作定義的范圍內。若要獲得適當的范圍,必須引用 jobs 表。

USE pubs
IF EXISTS (SELECT name FROM sysobjects
WHERE name = 'employee_insupd' AND type = 'TR')
DROP TRIGGER employee_insupd
GO
CREATE TRIGGER employee_insupd
ON employee
FOR INSERT, UPDATE
AS
/* Get the range of level for this job type from the jobs table. */
DECLARE @min_lvl tinyint,
@max_lvl tinyint,
@emp_lvl tinyint,
@job_id smallint
SELECT @min_lvl = min_lvl,
@max_lvl = max_lvl,
@emp_lvl = i.job_lvl,
@job_id = i.job_id
FROM employee e INNER JOIN inserted i ON e.emp_id = i.emp_id
JOIN jobs j ON j.job_id = i.job_id
IF (@job_id = 1) and (@emp_lvl   10)
BEGIN
RAISERROR ('Job id 1 expects the default level of 10.', 16, 1)
ROLLBACK TRANSACTION
END
ELSE
IF NOT (@emp_lvl BETWEEN @min_lvl AND @max_lvl)
BEGIN
RAISERROR ('The level for job_id:%d should be between %d and %d.',
16, 1, @job_id, @min_lvl, @max_lvl)
ROLLBACK TRANSACTION
END
D. 使用延遲名稱解析

下例創建兩個觸發器以說明延遲名稱解析。

USE pubs
IF EXISTS (SELECT name FROM sysobjects
WHERE name = 'trig1' AND type = 'TR')
DROP TRIGGER trig1
GO
-- Creating a trigger on a nonexistent table.
CREATE TRIGGER trig1
on authors
FOR INSERT, UPDATE, DELETE
AS
SELECT a.au_lname, a.au_fname, x.info
FROM authors a INNER JOIN does_not_exist x
ON a.au_id = x.au_id
GO
-- Here is the statement to actually see the text of the trigger.
SELECT o.id, c.text
FROM sysobjects o INNER JOIN syscomments c
ON o.id = c.id
WHERE o.type = 'TR' and o.name = 'trig1'
-- Creating a trigger on an existing table, but with a nonexistent
-- column.
USE pubs
IF EXISTS (SELECT name FROM sysobjects
WHERE name = 'trig2' AND type = 'TR')
DROP TRIGGER trig2
GO
CREATE TRIGGER trig2
ON authors
FOR INSERT, UPDATE
AS
DECLARE @fax varchar(12)
SELECT @fax = phone
FROM authors
GO
-- Here is the statement to actually see the text of the trigger.
SELECT o.id, c.text
FROM sysobjects o INNER JOIN syscomments c
ON o.id = c.id
WHERE o.type = 'TR' and o.name = 'trig2'
E. 使用 COLUMNS_UPDATED

下例創建兩個表:一個 employeeData 表和一個 auditEmployeeData 表。人力資源部的成員可以修改 employeeData 表,該表包含敏感的雇員薪水信息。如果更改了雇員的社會保險號碼 (SSN)、年薪或銀行帳戶,則生成審核記錄并插入到 auditEmployeeData 審核表。

通過使用 COLUMNS_UPDATED() 功能,可以快速測試對這些包含敏感雇員信息的列所做的更改。只有在試圖檢測對表中的前 8 列所做的更改時,COLUMNS_UPDATED() 才起作用。

USE pubs
IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'employeeData')
DROP TABLE employeeData
IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'auditEmployeeData')
DROP TABLE auditEmployeeData
GO
CREATE TABLE employeeData (
emp_id int NOT NULL,
emp_bankAccountNumber char (10) NOT NULL,
emp_salary int NOT NULL,
emp_SSN char (11) NOT NULL,
emp_lname nchar (32) NOT NULL,
emp_fname nchar (32) NOT NULL,
emp_manager int NOT NULL
)
GO
CREATE TABLE auditEmployeeData (
audit_log_id uniqueidentifier DEFAULT NEWID(),
audit_log_type char (3) NOT NULL,
audit_emp_id int NOT NULL,
audit_emp_bankAccountNumber char (10) NULL,
audit_emp_salary int NULL,
audit_emp_SSN char (11) NULL,
audit_user sysname DEFAULT SUSER_SNAME(),
audit_changed datetime DEFAULT GETDATE()
)
GO
CREATE TRIGGER updEmployeeData
ON employeeData
FOR update AS
/*Check whether columns 2, 3 or 4 has been updated. If any or all of columns 2, 3 or 4 have been changed, create an audit record. The bitmask is: power(2,(2-1))+power(2,(3-1))+power(2,(4-1)) = 14. To check if all columns 2, 3, and 4 are updated, use = 14 in place of >0 (below).*/
IF (COLUMNS_UPDATED() & 14) > 0
/*Use IF (COLUMNS_UPDATED() & 14) = 14 to see if all of columns 2, 3, and 4 are updated.*/
BEGIN
-- Audit OLD record.
INSERT INTO auditEmployeeData
(audit_log_type,
audit_emp_id,
audit_emp_bankAccountNumber,
audit_emp_salary,
audit_emp_SSN)
SELECT 'OLD',
del.emp_id,
del.emp_bankAccountNumber,
del.emp_salary,
del.emp_SSN
FROM deleted del
-- Audit NEW record.
INSERT INTO auditEmployeeData
(audit_log_type,
audit_emp_id,
audit_emp_bankAccountNumber,
audit_emp_salary,
audit_emp_SSN)
SELECT 'NEW',
ins.emp_id,
ins.emp_bankAccountNumber,
ins.emp_salary,
ins.emp_SSN
FROM inserted ins
END
GO
/*Inserting a new employee does not cause the UPDATE trigger to fire.*/
INSERT INTO employeeData
VALUES ( 101, 'USA-987-01', 23000, 'R-M53550M', N'Mendel', N'Roland', 32)
GO
/*Updating the employee record for employee number 101 to change the salary to 51000 causes the UPDATE trigger to fire and an audit trail to be produced.*/
UPDATE employeeData
SET emp_salary = 51000
WHERE emp_id = 101
GO
SELECT * FROM auditEmployeeData
GO
/*Updating the employee record for employee number 101 to change both the bank account number and social security number (SSN) causes the UPDATE trigger to fire and an audit trail to be produced.*/
UPDATE employeeData
SET emp_bankAccountNumber = '133146A0', emp_SSN = 'R-M53550M'
   WHERE emp_id = 101
GO
SELECT * FROM auditEmployeeData
GO
F. 使用 COLUMNS_UPDATED 測試 8 列以上

如果必須測試影響到表中前 8 列以外的列的更新時,必須使用 UBSTRING 函數測試由 COLUMNS_UPDATED 返回的適當的位。下例測試影響 Northwind.dbo.Customers 表中的第 3、第 5 或第 9 列的更新。

USE Northwind
DROP TRIGGER  tr1
GO
CREATE TRIGGER tr1 ON Customers
FOR UPDATE AS
IF ( (SUBSTRING(COLUMNS_UPDATED(),1,1)=power(2,(3-1))
+ power(2,(5-1)))
AND (SUBSTRING(COLUMNS_UPDATED(),2,1)=power(2,(1-1)))
)
PRINT 'Columns 3, 5 and 9 updated'
GO
UPDATE Customers
SET ContactName=ContactName,
Address=Address,
Country=Country
GO


JAVA之路 2007-12-07 09:13 發表評論
]]>
JDBC連接數據庫經驗技巧http://www.tkk7.com/xixidabao/archive/2007/10/12/152343.htmlJAVA之路JAVA之路Fri, 12 Oct 2007 06:36:00 GMThttp://www.tkk7.com/xixidabao/archive/2007/10/12/152343.html閱讀全文

JAVA之路 2007-10-12 14:36 發表評論
]]>
Hibernate實踐 http://www.tkk7.com/xixidabao/archive/2007/08/29/140929.htmlJAVA之路JAVA之路Wed, 29 Aug 2007 07:18:00 GMThttp://www.tkk7.com/xixidabao/archive/2007/08/29/140929.html閱讀全文

JAVA之路 2007-08-29 15:18 發表評論
]]>
XXXX項目緩存方案總結http://www.tkk7.com/xixidabao/archive/2007/08/28/140497.htmlJAVA之路JAVA之路Tue, 28 Aug 2007 06:35:00 GMThttp://www.tkk7.com/xixidabao/archive/2007/08/28/140497.html轉:http://dev.csdn.net/author/bromon/8737868d27c74af7b4dbfc4d0497f4fa.html



XXXX項目緩存方案總結
 
       XXXX項目是目前在實際工作中正在做的事情,該項目是一個大型系統的內容管理內核,負責最核心的meta data的集中管理,性能有較高的要求,設計初期就要求能夠支持cluster。項目使用hibernate 3.2,針對開發過程中對于各種緩存的不同看法,撰寫了本文。重點在于澄清一些hibernate的緩存細節,糾正一些錯誤的緩存用法。
 
一、hibernate的二級緩存
       如果開啟了二級緩存,hibernate在執行任何一次查詢的之后,都會把得到的結果集放到緩存中,緩存結構可以看作是一個hash table,key是數據庫記錄的id,value是id對應的pojo對象。當用戶根據id查詢對象的時候(load、iterator方法),會首先在緩存中查找,如果沒有找到再發起數據庫查詢。但是如果使用hql發起查詢(find, query方法)則不會利用二級緩存,而是直接從數據庫獲得數據,但是它會把得到的數據放到二級緩存備用。也就是說,基于hql的查詢,對二級緩存是只寫不讀的。
 
       針對二級緩存的工作原理,采用iterator取代list來提高二級緩存命中率的想法是不可行的。Iterator的工作方式是根據檢索條件從數據庫中選取所有目標數據的id,然后用這些id一個一個的到二級緩存里面做檢索,如果找到就直接加載,找不到就向數據庫做查詢。因此假如iterator檢索100條數據的話,最好情況是100%全部命中,最壞情況是0%命中,執行101條sql把所有數據選出來。而list雖然不利用緩存,但是它只會發起1條sql取得所有數據。在合理利用分頁查詢的情況下,list整體效率高于iterator。
 
       二級緩存的失效機制由hibernate控制,當某條數據被修改之后,hibernate會根據它的id去做緩存失效操作。基于此機制,如果數據表不是被hibernate獨占(比如同時使用jdbc或者ado等),那么二級緩存無法得到有效控制。
 
       由于hibernate的緩存接口很靈活,cache provider可以方便的切換,因此支持cluster環境不是大問題,通過使用swarmcache、jboss cache等支持分布式的緩存方案,可以實現。但是問題在于:
1、 分布式緩存本身成本偏高(比如使用同步復制模式的jboss cache)
2、 分布式環境通常對事務控制有較高要求,而目前的開源緩存方案對事務緩存(transaction cache)支持得不夠好。當jta事務發生會滾,緩存的最后更新結果很難預料。這一點會帶來很大的部署成本,甚至得不償失。
 
結論:XXXX不應把hibernate二級緩存作為優化的主要手段,一般情況下建議不要使用。
 
原因如下:
1、 XXXX的DAO類大部分是從1.0升級過來,由于1.0采用的是hibernate 2.1,所以在批量刪除數據的時候采用了native sql的方式。雖然XXXX2.0已經完全升級到hibernate 3.2,支持hibernate原生的批量刪改,但是由于hibernate批量操作的性能不如sql,而且為了兼容1.0的dao類,所以很多地方保留了sql操作。哪些數據表是單純被hibernate獨占無法統計,而且隨著將來業務的發展可能會有很大變數。因此不宜采用二級緩存。
2、 針對系統業務來說,基于id檢索的二級緩存命中率極為有限,hql被大量采用,二級緩存對性能的提升很有限。
3、 hibernate 3.0在做批量修改、批量更新的時候,是不會同步更新二級緩存的,該問題在hibernate 3.2中是否仍然存在尚不確定。
 
 
二、hibernate的查詢緩存
 
       查詢緩存的實現機制與二級緩存基本一致,最大的差異在于放入緩存中的key是查詢的語句,value是查詢之后得到的結果集的id列表。表面看來這樣的方案似乎能解決hql利用緩存的問題,但是需要注意的是,構成key的是:hql生成的sql、sql的參數、排序、分頁信息等。也就是說如果你的hql有小小的差異,比如第一條hql取1-50條數據,第二條hql取20-60條數據,那么hibernate會認為這是兩個完全不同的key,無法重復利用緩存。因此利用率也不高。
 
       另外一個需要注意的問題是,查詢緩存和二級緩存是有關聯關系的,他們不是完全獨立的兩套東西。假如一個查詢條件hql_1,第一次被執行的時候,它會從數據庫取得數據,然后把查詢條件作為key,把返回數據的所有id列表作為value(請注意僅僅是id)放到查詢緩存中,同時整個結果集放到class緩存(也就是二級緩存),key是id,value是pojo對象。當你再次執行hql_1,它會從緩存中得到id列表,然后根據這些列表一個一個的到class緩存里面去找pojo對象,如果找不到就向數據庫發起查詢。也就是說,如果二級緩存配置了超時時間(或者發呆時間),就有可能出現查詢緩存命中了,獲得了id列表,但是class里面相應的pojo已經因為超時(或發呆)被失效,hibernate就會根據id清單,一個一個的去向數據庫查詢,有多少個id,就執行多少個sql。該情況將導致性能下降嚴重。
 
       查詢緩存的失效機制也由hibernate控制,數據進入緩存時會有一個timestamp,它和數據表的timestamp對應。當hibernate環境內發生save、update等操作時,會更新被操作數據表的timestamp。用戶在獲取緩存的時候,一旦命中就會檢查它的timestamp是否和數據表的timestamp匹配,如果不,緩存會被失效。因此查詢緩存的失效控制是以數據表為粒度的,只要數據表中任何一條記錄發生一點修改,整個表相關的所有查詢緩存就都無效了。因此查詢緩存的命中率可能會很低。
 
結論:XXXX不應把hibernate二級緩存作為優化的主要手段,一般情況下建議不要使用。
 
原因如下:
1、 XXXX的上層業務中檢索條件都比較復雜,尤其是涉及多表操作的地方。很少出現重復執行一個排序、分頁、參數一致的查詢,因此命中率很難提高。
2、 查詢緩存必須配合二級緩存一起使用,否則極易出現1+N的情況,否則性能不升反降
3、 使用查詢緩存必須在執行查詢之前顯示調用Query.setCacheable(true)才能激活緩存,這勢必會對已有的hibernate封裝類帶來問題。
 
 
 
總結
       詳細分析hibernate的二級緩存和查詢緩存之后,針對XXXX項目的具體情況做出結論,在底層使用通用緩存方案的想法基本上是不可取的。比較好的做法是在高層次中(業務邏輯層面),針對具體的業務邏輯狀況手動使用數據緩存,不僅可以完全控制緩存的生命周期,還可以針對業務具體調整緩存方案提交命中率。Cluster中的緩存同步可以完全交給緩存本身的同步機制來完成。比如開源緩存swarmcache采用invalidate的機制,可以根據用戶指定的策略,在需要的時候向網絡中的其他swarmcache節點發送失效消息,這一機制和XXXX1.0中已經采用的MappingCache的同步方案基本一致。建議采用。


JAVA之路 2007-08-28 14:35 發表評論
]]>
如何在oracle使用blob,clobhttp://www.tkk7.com/xixidabao/archive/2007/08/22/138584.htmlJAVA之路JAVA之路Wed, 22 Aug 2007 05:05:00 GMThttp://www.tkk7.com/xixidabao/archive/2007/08/22/138584.html轉:http://blog.tostudy.com.cn/blog/show_930.html 


一.寫入BLOB

1.先在blob中插入empty_blob()
2.獲得對剛剛插入記錄的引用
BLOB blob = (BLOB) rs.getBlob("你的blob字段名稱");
3.寫入
OutputStream out = blob.getBinaryOutputStream();
out.write(ENCYPWD);//注意這里
二.讀出BLOB

1.blob  = rs.getBlob("你的blob字段名稱");
2.
InputStream is = blob.getBinaryStream();
int length = (int) blob.length();
byte[] buffer = new byte[length];
is.read(buffer);
is.close();
3.你有了is就隨便處理了
比如說輸出到一個文件
FileOutputStream fo = new FileOutputStream(filename);//數據到的文件名
fo.write(buffer);
fo.close();

環境:
Database: Oracle 9i
App Server: BEA Weblogic 8.14
表結構:
CREATE TABLE TESTBLOB (ID Int, NAME Varchar2(20), BLOBATTR Blob)
CREATE TABLE TESTBLOB (ID Int, NAME Varchar2(20), CLOBATTR Clob)   JAVA可以通過JDBC,也可以通過JNDI訪問并操作數據庫,這兩種方式的具體操作存在著一些差異,由于通過App Server的數據庫連接池JNDI獲得的數據庫連接提供的java.sql.Blob和java.sql.Clob實現類與JDBC方式提供的不同,因此在入庫操作的時候需要分別對待;出庫操作沒有這種差異,因此不用單獨對待。
一、BLOB操作
1、入庫
(1)JDBC方式
    //通過JDBC獲得數據庫連接
    Class.forName("oracle.jdbc.driver.OracleDriver");
    Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:testdb", "test", "test");
    con.setAutoCommit(false);
    Statement st = con.createStatement();
    //插入一個空對象empty_blob()
    st.executeUpdate("insert into TESTBLOB (ID, NAME, BLOBATTR) values (1, "thename", empty_blob())");
    //鎖定數據行進行更新,注意“for update”語句
    ResultSet rs = st.executeQuery("select BLOBATTR from TESTBLOB where ID=1 for update");
    if (rs.next())
    {
        //得到java.sql.Blob對象后強制轉換為oracle.sql.BLOB
        oracle.sql.BLOB blob = (oracle.sql.BLOB) rs.getBlob("BLOBATTR");
        OutputStream outStream = blob.getBinaryOutputStream();
        //data是傳入的byte數組,定義:byte[] data
        outStream.write(data, 0, data.length);
    }
    outStream.flush();
    outStream.close();
    con.commit();
    con.close(); (2)JNDI方式
    //通過JNDI獲得數據庫連接
    Context context = new InitialContext();
    ds = (DataSource) context.lookup("ORA_JNDI");
    Connection con = ds.getConnection();
    con.setAutoCommit(false);
    Statement st = con.createStatement();
    //插入一個空對象empty_blob()
    st.executeUpdate("insert into TESTBLOB (ID, NAME, BLOBATTR) values (1, "thename", empty_blob())");
    //鎖定數據行進行更新,注意“for update”語句
    ResultSet rs = st.executeQuery("select BLOBATTR from TESTBLOB where ID=1 for update");
    if (rs.next())
    {
        //得到java.sql.Blob對象后強制轉換為weblogic.jdbc.vendor.oracle.OracleThinBlob(不同的App Server對應的可能會不同)
        weblogic.jdbc.vendor.oracle.OracleThinBlob blob = (weblogic.jdbc.vendor.oracle.OracleThinBlob) rs.getBlob("BLOBATTR");
        OutputStream outStream = blob.getBinaryOutputStream();
        //data是傳入的byte數組,定義:byte[] data
        outStream.write(data, 0, data.length);
    }
    outStream.flush();
    outStream.close();
    con.commit();
    con.close(); 2、出庫
    //獲得數據庫連接
    Connection con = ConnectionFactory.getConnection();
    con.setAutoCommit(false);
    Statement st = con.createStatement();
    //不需要“for update”
    ResultSet rs = st.executeQuery("select BLOBATTR from TESTBLOB where ID=1");
    if (rs.next())
    {
        java.sql.Blob blob = rs.getBlob("BLOBATTR");
        InputStream inStream = blob.getBinaryStream();
        //data是讀出并需要返回的數據,類型是byte[]
        data = new byte[input.available()];
        inStream.read(data);
        inStream.close();
    }
    inStream.close();
    con.commit();
    con.close();   二、CLOB操作
1、入庫
(1)JDBC方式
    //通過JDBC獲得數據庫連接
    Class.forName("oracle.jdbc.driver.OracleDriver");
    Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:testdb", "test", "test");
    con.setAutoCommit(false);
    Statement st = con.createStatement();
    //插入一個空對象empty_clob()
    st.executeUpdate("insert into TESTCLOB (ID, NAME, CLOBATTR) values (1, "thename", empty_clob())");
    //鎖定數據行進行更新,注意“for update”語句
    ResultSet rs = st.executeQuery("select CLOBATTR from TESTCLOB where ID=1 for update");
    if (rs.next())
    {
        //得到java.sql.Clob對象后強制轉換為oracle.sql.CLOB
        oracle.sql.CLOB clob = (oracle.sql.CLOB) rs.getClob("CLOBATTR");
        Writer outStream = clob.getCharacterOutputStream();
        //data是傳入的字符串,定義:String data
        char[] c = data.toCharArray();
        outStream.write(c, 0, c.length);
    }
    outStream.flush();
    outStream.close();
    con.commit();
    con.close();
(2)JNDI方式
    //通過JNDI獲得數據庫連接
    Context context = new InitialContext();
    ds = (DataSource) context.lookup("ORA_JNDI");
    Connection con = ds.getConnection();
    con.setAutoCommit(false);
    Statement st = con.createStatement();
    //插入一個空對象empty_clob()
    st.executeUpdate("insert into TESTCLOB (ID, NAME, CLOBATTR) values (1, "thename", empty_clob())");
    //鎖定數據行進行更新,注意“for update”語句
    ResultSet rs = st.executeQuery("select CLOBATTR from TESTCLOB where ID=1 for update");
    if (rs.next())
    {
        //得到java.sql.Clob對象后強制轉換為weblogic.jdbc.vendor.oracle.OracleThinClob(不同的App Server對應的可能會不同)
        weblogic.jdbc.vendor.oracle.OracleThinClob clob = (weblogic.jdbc.vendor.oracle.OracleThinClob) rs.getClob("CLOBATTR");
        Writer outStream = clob.getCharacterOutputStream();
        //data是傳入的字符串,定義:String data
        char[] c = data.toCharArray();
        outStream.write(c, 0, c.length);
    }
    outStream.flush();
    outStream.close();
    con.commit();
    con.close(); 2、出庫
    //獲得數據庫連接
    Connection con = ConnectionFactory.getConnection();
    con.setAutoCommit(false);
    Statement st = con.createStatement();
    //不需要“for update”
    ResultSet rs = st.executeQuery("select CLOBATTR from TESTCLOB where ID=1");
    if (rs.next())
    {
        java.sql.Clob clob = rs.getClob("CLOBATTR");
        Reader inStream = clob.getCharacterStream();
        char[] c = new char[(int) clob.length()];
        inStream.read(c);
        //data是讀出并需要返回的數據,類型是String
        data = new String(c);
        inStream.close();
    }
    inStream.close();
    con.commit();
    con.close();   需要注意的地方:
1、java.sql.Blob、oracle.sql.BLOB、weblogic.jdbc.vendor.oracle.OracleThinBlob幾種類型的區別
2、java.sql.Clob、oracle.sql.CLOB、weblogic.jdbc.vendor.oracle.OracleThinClob幾種類型的區別

JAVA之路 2007-08-22 13:05 發表評論
]]>
java實現的ftp文件上傳例題 http://www.tkk7.com/xixidabao/archive/2007/08/15/136861.htmlJAVA之路JAVA之路Wed, 15 Aug 2007 04:03:00 GMThttp://www.tkk7.com/xixidabao/archive/2007/08/15/136861.html
 轉:http://www.128kj.com/article/article5/4C981624F129B81E393E4DFE72AC9096.htm?id=1820


前幾天寫過一編"關于java的http協議文件上傳實用例題"的文章;今天還想寫編關于java用ftp上傳文件的內容。我來說說2者的優缺點;
    1:用http協議上傳更適合web編程的方便;傳小于1M文件速度要比用ftp協議上傳文件略快。安全性好;不像ftp那樣;必須要啟動一個ftp服務才行。

    2:用ftp協議上傳文件大于1M的文件速度比http快;文件越大;上傳的速度就比http上傳快的倍數越大。而且用java編寫程序;ftp比http方便。好,廢話少說;我們先搭建一個實例來理性認識一下用java編寫ftp上傳文件的技術。

  首先在本機啟動一個ftp服務,ftp的用戶:"IUSR_ZJH" 密碼:"123";隨后在ftp主目錄下建一個名為upftp的子目錄;下面有4個文件就可啟動這個例題了。

文件1:MainCtrl.java(servlet文件)內容如下:

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;

import java.io.FileInputStream;
import java.io.IOException;

import sun.net.TelnetOutputStream;
import sun.net.ftp.FtpClient;


public class MainCtrl extends HttpServlet {
 
   private FtpClient ftpClient;
  
 public void doPost(HttpServletRequest req, HttpServletResponse resp)
 throws ServletException, IOException {

   resp.setContentType("text/html; charset=UTF-8");

   try {
    //連接ftp服務器
    connectServer("127.0.0.1", "IUSR_ZJH", "123", "upftp");
  //上傳文件;并返回上傳文件的信息
    req.setAttribute("inf", upload(req.getParameter("file_name")));
   } catch (Exception e) {
  System.out.println(e.toString());
  req.setAttribute("inf", e.toString());
  req.getRequestDispatcher("view_inf.jsp").forward(req, resp);
  return;
   } finally {
    if (ftpClient != null) {
     ftpClient.closeServer();    
    }
      }
   req.getRequestDispatcher("view_inf.jsp").forward(req, resp);
  }
 
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
   doPost(req, resp);
  }
//連接ftp服務器
 private void connectServer(String server, String user, String password,
   String path) throws IOException {
  // server:FTP服務器的IP地址;user:登錄FTP服務器的用戶名
  // password:登錄FTP服務器的用戶名的口令;path:FTP服務器上的路徑
  ftpClient = new FtpClient();
  ftpClient.openServer(server);
  ftpClient.login(user, password);
  //path是ftp服務下主目錄的子目錄
  if (path.length() != 0)
   ftpClient.cd(path);
  //用2進制上傳
  ftpClient.binary();
 }

 //上傳文件;并返回上傳文件的信息
 private String upload(String filename) throws Exception {
  TelnetOutputStream os = null;
  FileInputStream is = null;
  try {
   //"upftpfile"用ftp上傳后的新文件名
   os = ftpClient.put("upftpfile");
   java.io.File file_in = new java.io.File(filename);
   if (file_in.length()==0) {
    return "上傳文件為空!";
   }
   is = new FileInputStream(file_in);
   byte[] bytes = new byte[1024];
   int c;
   while ((c = is.read(bytes)) != -1) {
    os.write(bytes, 0, c);
   }
  } finally {
   if (is != null) {
    is.close();
   }
   if (os != null) {
    os.close();
   }
  }
  return "上傳文件成功!";
 }

}

文件2:upftp.htm(前臺操作頁面)內容如下:

 

文件3:view_inf.jsp(信息提示頁面)和upftp.htm一樣放在context的根目錄下,

內容如下:

<%@page contentType="text/html;charset=UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style type="text/css">
th
{
background-color: #4455aa;
color: white;
font-size: 14px;
font-weight:bold;
}
td.TableBody1
{
background-color: #FFFFF0;
color: white;
font-size: 14px;
font-weight:bold;
font-color: red;
}
.tableBorder1
{
width:97%;
border: 1px;
background-color: #6595D6;
}
</style>
</head>
<body>
<%String inf = (String) request.getAttribute("inf");
   if (inf == null) {
    inf = request.getParameter("inf");
   }%>
<table class="tableborder1" style="width: 75%;" align="center"
 cellpadding="3" cellspacing="1">
 <tbody>
  <tr align="center">
   <th colspan="2" height="25" width="100%">信 息 提 示:</th>
  </tr>
  <tr align="center">
   <td class="tablebody1" colspan="2" width="100%" height="200"><%=inf%></td>
  </tr>
  <tr align="center">
   <td><input name="back" value="返 回" onclick="history.back();"
    type="button" /></td>
  </tr>
 </tbody>
</table>
</body></html>

文件4:web.xml(j2ee的備置文件)放在WEB-INF目錄下,

內容如下:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
 version="2.4">
 <!--Servlet name-->
 <servlet>
  <servlet-name>MainCtrl</servlet-name>
  <servlet-class>MainCtrl</servlet-class>
 </servlet>
 <!--Servlet mapping-->
 <servlet-mapping>
  <servlet-name>MainCtrl</servlet-name>
  <url-pattern>/MainCtrl</url-pattern>
 </servlet-mapping>
</web-app>

 



JAVA之路 2007-08-15 12:03 發表評論
]]>
有關亂碼的處理http://www.tkk7.com/xixidabao/archive/2007/08/14/136660.htmlJAVA之路JAVA之路Tue, 14 Aug 2007 06:08:00 GMThttp://www.tkk7.com/xixidabao/archive/2007/08/14/136660.html
  為什么說亂碼是中國程序員無法避免的話題呢?這個首先要從編碼機制上說起,大家都是中文和英文的編碼格式不是一樣,解碼也是不一樣的!如果中國的程序員不會遇到亂碼,那么只有使用漢語編程。漢語編程是怎么回事我也不大清楚,應該是前年吧,我一朋友給我介紹漢語編程,怎么不錯不錯?當時因為學習忙沒去關注這個,等我閑了,那個朋友不弄這個,問他他也不說不大清楚,最后自己對這個學習也不了了之了。

今天我寫這個不是講解中英文之間的差距,解碼等,我是將我在這幾年工作遇到各種各樣的亂碼的解決方法,總結一樣,也希望大家能把自己暈倒解決亂碼的方法都說出來,咱們弄一個解決亂碼的“葵花寶典”。

  對于Java由于默認的編碼方式是 UNICODE,所以用中文也易出問題,常見的解決是
String s2 = new String(s1.getBytes(“ISO-8859-1”),”GBK”);

  1、utf8解決JSP中文亂碼問題

  一般說來在每個頁面的開始處,加入:

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>

<%
request.setCharacterEncoding("UTF-8");
%>

  charset=UTF-8 的作用是指定JSP向客戶端輸出的編碼方式為“UTF-8”

  pageEncoding="UTF-8" 為了讓JSP引擎能正確地解碼含有中文字符的JSP頁面,這在LINUX中很有效

  request.setCharacterEncoding("UTF-8"); 是對請求進行了中文編碼

  有時,這樣仍不能解決問題,還需要這樣處理一下:

String msg = request.getParameter("message");
String str=new String(msg.getBytes("ISO-8859-1"),"UTF-8");
out.println(st);

  2、Tomcat 5.5 中文亂碼

  只要把%TOMCAT安裝目錄%/ webapps\servlets-examples\WEB-INF\classes\filters\SetCharacterEncodingFilter.class文件拷到你的webapp目錄/filters下,如果沒有filters目錄,就創建一個。

  2)在你的web.xml里加入如下幾行:

<filter>
<filter-name>Set Character Encoding</filter-name>
<filter-class>filters.SetCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>GBK</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Set Character Encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

  3)完成.

  2 get方式的解決辦法

  1) 打開tomcat的server.xml文件,找到區塊,加入如下一行:

URIEncoding=”GBK”

  完整的應如下:
 
<Connector
port="80" maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
debug="0" connectionTimeout="20000"
disableUploadTimeout="true"
URIEncoding="GBK"
/>

 2)重啟tomcat,一切OK。

  3、xmlHttpRequest中文問題

  頁面jsp用的GBK編碼

<%@ page contentType="text/html; charset=GBK"%>

  javascript部分

function addFracasReport() {
var url="controler?actionId=0_06_03_01&actionFlag=0010";
var urlmsg="&reportId="+fracasReport1.textReportId.value; //故障報告表編號

var xmlHttp=Common.createXMLHttpRequest();
xmlHttp.onreadystatechange = Common.getReadyStateHandler(xmlHttp, eval("turnAnalyPage"));
xmlHttp.open("POST",url,true);
xmlHttp.setRequestHeader( " Content-Type " , " application/x-www-form-urlencoded);
xmlHttp.send(urlmsg);

}

  后臺java中獲得的reportId是亂碼,不知道該怎么轉,主要是不知道xmlHttp.send(urlmsg); 以后是什么編碼?在后面用java來轉,試了幾種,都沒有成功,其中有:

public static String UTF_8ToGBK(String str) {
try {
return new String(str.getBytes("UTF-8"), "GBK");
} catch (Exception ex) {
return null;
}
}

public static String UTF8ToGBK(String str) {
try {
return new String(str.getBytes("UTF-16BE"), "GBK");
} catch (Exception ex) {
return null;
}
}

public static String GBK(String str) {
try {
return new String(str.getBytes("GBK"),"GBK");
} catch (Exception ex) {
return null;
}
}
public static String getStr(String str) {
try {
String temp_p = str;
String temp = new String(temp_p.getBytes("ISO8859_1"), "GBK");
temp = sqlStrchop(temp);
return temp;
} catch (Exception e) {
return null;
}
}

  4、JDBC ODBC Bridge的Bug及其解決方法

  在編寫一數據庫管理程序時,發現JDBC-ODBC Bridge存在不易發現的Bug。在向數據表插入數據時,如果為英文字符,存儲內容完全正確,如果存入中文字符,部分數據庫只能存儲前七八個中文字符,其他內容被截去,導致存儲內容的不完整(有些數據庫不存在這個問題,如Sybase SQL Anywhere 5.0。JDBC-ODBC Bridge還存在無法建表的Bug)。

  對于廣大需要存儲中文信息的Java程序員來說,這可是一個不好的消息。要么改用其他語言編程,要么選擇其他價格昂貴的數據庫產品。“一次編寫,到處運行”的目標,也大打折扣。能不能采用變通的方法,將中文信息進行處理后再存儲來解決這個問題呢?答案是肯定的。

  解決問題的具體思路、方法

  Java采用Unicode碼編碼方式,中英文字符均采用16bit存儲。既然存儲英文信息是正確的,根據一定規則,將中文信息轉換成英文信息后存儲,自然不會出現截尾現象。讀取信息時再進行逆向操作,將英文信息還原成中文信息即可。由GB2312編碼規則可知,漢字一般為二個高位為1的ASCII碼,在轉換時將一個漢字的二個高位1去掉,還原時再將二個高位1加上。為了處理含有英文字符的中文字串,對英文字符則需要加上一個Byte 0標記。以下提供的兩個公用靜態方法,可加入任何一個類中使用。

  將中英文字串轉換成純英文字串

  public static String toTureAsciiStr(String str){

  StringBuffer sb = new StringBuffer();

  byte[] bt = str.getBytes();

  for(int i =0 ; i〈bt.length; i++){

  if(bt[i]〈0){

  //是漢字去高位1

  sb.append((char)(bt[i]&&0x7f));

   }else{//是英文字符 補0作記錄

  sb.append((char)0);

  sb.append((char)bt[i]);

   }

   }

  return sb.toString();

  }

  將經轉換的字串還原

  public static String unToTrueAsciiStr(String str){

   byte[] bt = str.getBytes();

   int i,l=0,length = bt.length,j=0;

   for(i = 0; i〈length; i++){

   if(bt[i] == 0){

   l++;

   }

   }

   byte []bt2 = new byte[length-l];

   for(i =0 ; i〈length; i++){

   if(bt[i] == 0){

   i++;

   bt2[j] = bt[i];

   }else{

   bt2[j] = (byte)(bt[i]|0x80);

   }

   j++;

   }

  String tt = new String(bt2);

  return tt;

  }

  上例在實際編程中效果很好,只是存儲的中文信息需要經過同樣處理,才能被其他系統使用。而且如果中文字串出現英文字符,實際上增加了額外的存儲空間。

  5、Solaris下Servlet編程的中文問題及解決辦法

  在使用Java開發Internet上的一個應用系統時,發現在Windows下調試完全正常的Servlet,上傳到Solaris 服務器上,運行卻出現故障——返回的網頁不能顯示中文,應為中文的信息全為亂碼;用中文信息做關鍵字,不能正確檢索數據庫。后來采用加入檢查代碼等方法探知故障原因如下:

  顯示亂碼主要是因為通過類 HttpServletResponse提供的方法setContentType 無法改變返回給客戶的數據的編碼方式,正確的編碼方式應為GB2312或者GBK,而事實上為缺省的ISO8859-1。無法檢索中文信息則是因為,客戶提交的中文信息經瀏覽器編碼到達服務器后,Servlet無法將其正確解碼。

  舉例說明顯示亂碼解決方法

  Servlet 一般通常做法如下:

  public class ZldTestServlet extends HttpServlet {

  public void doGet (HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{

  //在使用 Writer向瀏覽器返回數據前,設置 content-type header ,在這里設置相應的字符集gb2312

  response.setContentType("text/html; charset=gb2312");

  PrintWriter out = response.getWriter(); //*

  // 正式返回數據

  out.println("〈html〉〈head〉〈title〉Servlet test〈/title〉〈/head〉" );

  out.println("這是一個測試頁!");

  out.println("〈/body〉〈/html〉");

  out.close();

  }

   ...

  }

  解決頁面顯示亂碼問題,需將*處代碼換成如下內容:

  PrintWriter out = new PrintWriter(new OutputStreamWriter(response.getOutputStream(),"gb2312"));

  Solaris中文信息檢索問題的解決
  瀏覽器利用表單向服務器提交信息時,一般采用x-www-form-urlencoded 的MIME格式對數據進行編碼。如果使用get方法,參數名稱和參數值經編碼后附加在URL后,在Java中稱作查詢串(query string)。

  在Servlet程序中,如果采用ServletRequest的方法getParameter取得參數值,在Solaris環境下,對漢字卻不能正確解碼。因而無法正確檢索數據庫。

  在Java 1.2的包——java.net中提供了URLEncode和URLDecode類。類URLEncode提供了按x-www-form-urlencoded格式對給定串進行轉換的方法。類URLEncode則提供了逆方法。

  6、Common Mail亂碼問題

  common mail是一個小而方便的mail包,他實現了對Java Mail的封裝,使用起來十分的方便,但是我在使用他的時候發現,使用純文本的內容發送,結果是亂碼,代碼如下:

public class TestCommonMail {
public static void main(String[] args) throws EmailException, MessagingException {
SimpleEmail email = new SimpleEmail();
email.setCharset("GB2312");
email.setHostName("smtp.163.com");
email.setSubject("test");
email.addTo("test@163.com");
email.setFrom("test@163.com");
email.setMsg("我的測試");
email.setAuthentication("test", "test");
email.send();
}
}

分析了一下commons mail的源碼找到了原因。源碼如下:

public class SimpleEmail extends Email
{
public Email setMsg(String msg) throws EmailException, MessagingException
{
if (EmailUtils.isEmpty(msg))
{
throw new EmailException("Invalid message supplied");
}

setContent(msg, TEXT_PLAIN);
return this;
}
}

Email代碼片段

public void setContent(Object aObject, String aContentType)
{
this.content = aObject;
if (EmailUtils.isEmpty(aContentType))
{
this.contentType = null;
}
else
{
// set the content type
this.contentType = aContentType;

// set the charset if the input was properly formed
String strMarker = "; charset=";
int charsetPos = aContentType.toLowerCase().indexOf(strMarker);
if (charsetPos != -1)
{
// find the next space (after the marker)
charsetPos += strMarker.length();
int intCharsetEnd =
aContentType.toLowerCase().indexOf(" ", charsetPos);

if (intCharsetEnd != -1)
{
this.charset =
aContentType.substring(charsetPos, intCharsetEnd);
}
else
{
this.charset = aContentType.substring(charsetPos);
}
}
}
}

email.send(); 的send方法將調用
public void buildMimeMessage() throws EmailException
{
try
{
this.getMailSession();
this.message = new MimeMessage(this.session);

if (EmailUtils.isNotEmpty(this.subject))
{
if (EmailUtils.isNotEmpty(this.charset))
{
this.message.setSubject(this.subject, this.charset);
}
else
{
this.message.setSubject(this.subject);
}
}

// ========================================================
// Start of replacement code
if (this.content != null)
{
this.message.setContent(this.content, this.contentType);
}
// end of replacement code
// ========================================================
else if (this.emailBody != null)
{
this.message.setContent(this.emailBody);
}
else
{
this.message.setContent("", Email.TEXT_PLAIN);
}

if (this.fromAddress != null)
{
this.message.setFrom(this.fromAddress);
}
else
{
throw new EmailException("Sender address required");
}

if (this.toList.size() + this.ccList.size() + this.bccList.size() == 0)
{
throw new EmailException(
"At least one receiver address required");
}

if (this.toList.size() > 0)
{
this.message.setRecipients(
Message.RecipientType.TO,
this.toInternetAddressArray(this.toList));
}

if (this.ccList.size() > 0)
{
this.message.setRecipients(
Message.RecipientType.CC,
this.toInternetAddressArray(this.ccList));
}

if (this.bccList.size() > 0)
{
this.message.setRecipients(
Message.RecipientType.BCC,
this.toInternetAddressArray(this.bccList));
}

if (this.replyList.size() > 0)
{
this.message.setReplyTo(
this.toInternetAddressArray(this.replyList));
}

if (this.headers.size() > 0)
{
Iterator iterHeaderKeys = this.headers.keySet().iterator();
while (iterHeaderKeys.hasNext())
{
String name = (String) iterHeaderKeys.next();
String value = (String) headers.get(name);
this.message.addHeader(name, value);
}
}

if (this.message.getSentDate() == null)
{
this.message.setSentDate(getSentDate());
}

if (this.popBeforeSmtp)
{
Store store = session.getStore("pop3");
store.connect(this.popHost, this.popUsername, this.popPassword);
}
}
catch (MessagingException me)
{
throw new EmailException(me);
}
}
由代碼可以知道純文本方式最終調用了Java Mail的
message.setContent(this.content, this.contentType);
content是內容
contentType是類型,如text/plain,
(我們可以試試直接用Java mail發郵件,設置文本內容不使用setText方法,也使用setContent("測試", "text/plain")方式,你可以看到內容也是亂碼)

  關鍵就在于text/plain,我們改成text/plain; charset=gb2312,ok亂碼解決了。在commons mail我們看SimpleEmail 類中setMsg方法調用的就是 setContent(msg, TEXT_PLAIN); 我們只需要將Email類中的常量TEXT_PLAIN修改一下加入 charset=你的字符集 ,重新打包jar,這樣就可以了

  7、toad的字符集的設置與oracle的安裝

  oracle數據庫服務器的安裝一般是中文字符集,有時安裝在不同的平臺下,設置為ISO編碼,toad是oracle開發的最好工具,不是我說的,可是中文環境下安裝的toad,打開英文字符的oracle時,中文全是亂碼。必須進行設置

環境變量---〉系統變量

NLS_lANG=SIMPLIFIED CHINESE_CHINA.ZHS16GBK

NLS_lANG=AMERICAN_AMERICA.WE8ISO8859P1

AMERICAN_AMERICA.WE8MSWIN1252

或者

打開注冊表,點擊HKEY_LOCAL_MATHINE
再點擊Software,再點擊ORACLE
在點擊HOME(ORACLE所在目錄)
在注冊表的右半面有NLS_LANG,
雙擊它,將你想要的覆蓋掉原來的就可以了
最好記下舊的,以便可以改回來。

connect sys/chang_on_install
update props$
set value$='ZHS16CGB231280'
where name='NLS_CHARACTERSET';
commit;
這樣就OK了

  8、如何解決GWT(google web toolkit)中文的問題

  GWT 中文亂碼解決方法

1.把你要顯示的中文“測試字符串”輸入到一個文件,如:1.txt
2.進入命令行,進入1.txt所在的目錄,敲入以下命令:native2ascii.exe 1.txt 2.txt 回車。這樣就生成了另外一個文件2.txt。
3.2.txt的內容如下:\u6d4b\u8bd5\u5b57\u7b26\u4e32
4.然后用上面的編碼,在gwt中使用,就可以了.

  9、xmlHttp得到的網頁怎么是亂碼?

  (1)在服務器端使用WebRequest而不是xmlHttp
  (2) 將

StreamReader sr = new StreamReader(stream);

  對于簡體中文改成:

StreamReader sr = new StreamReader(stream , Encoding.Default );

  對于utf-8改成:

StreamReader sr = new StreamReader(stream , Encoding.UTF8 );

  當然,Encoding枚舉還有很多其他的成員,對于不同的編碼content-type可以有選擇的應用

  (3)后來我發現無論是content-type是gb2312還是utf-8,用

StreamReader sr = new StreamReader(stream , Encoding.Default );

  都可以返回正常的漢字,所以統一的改成Encoding.Default




--------------------------------------------------------------------------------

最后,在服務器端從一個url獲得網頁的源代碼的代碼如下:



/// <summary>
/// post一個指定的url,獲得網頁的源代碼(用WebRequest實現)
/// </summary>
/// <param name="url"></param>
/// <returns>
/// 如果請求失敗,返回null
/// 如果請求成功,返回網頁的源代碼
/// </returns>
public static string GetContentFromUrl2( string url )
{
//變量定義
string respstr;

WebRequest myWebRequest=WebRequest.Create(url);
// myWebRequest.PreAuthenticate=true;
// NetworkCredential networkCredential=new NetworkCredential( username , password , domain );
// myWebRequest.Credentials=networkCredential;

// Assign the response object of 'WebRequest' to a 'WebResponse' variable.
WebResponse myWebResponse=myWebRequest.GetResponse();
System.IO.Stream stream = myWebResponse.GetResponseStream();
StreamReader sr = new StreamReader(stream , Encoding.Default );
//以字符串形式讀取數據流
respstr = sr.ReadToEnd();
sr.Close();

return respstr;

}

JAVA之路 2007-08-14 14:08 發表評論
]]>
寫得蠻好的linux學習筆記http://www.tkk7.com/xixidabao/archive/2007/08/14/136537.htmlJAVA之路JAVA之路Mon, 13 Aug 2007 16:21:00 GMThttp://www.tkk7.com/xixidabao/archive/2007/08/14/136537.html
linux目錄架構
/   根目錄
/bin    常用的命令 binary file 的目錄
/boot   存放系統啟動時必須讀取的檔案,包括核心 (kernel) 在內
     /boot/grub/menu.lst   GRUB設置
     /boot/vmlinuz   內核
     /boot/initrd     核心解壓縮所需 RAM Disk
/dev    系統周邊設備    
/etc    系統相關設定文件
     /etc/DIR_COLORS   設定顏色
     /etc/HOSTNAME   設定用戶的節點名
     /etc/NETWORKING   只有YES標明網絡存在
     /etc/host.conf 文件說明用戶的系統如何查詢節點名
     /etc/hosts 設定用戶自已的IP與名字的對應表
     /etc/hosts.allow 設置允許使用inetd的機器使用
     /etc/hosts.deny 設置不允許使用inetd的機器使用
     /etc/hosts.equiv 設置遠端機不用密碼
     /etc/inetd.conf 設定系統網絡守護進程inetd的配置
     /etc/gateways 設定路由器
     /etc/protocols 設定系統支持的協議
     /etc/named.boot 設定本機為名字服務器的配置文件
     /etc/sysconfig/network-scripts/ifcfg-eth0   設置IP
     /etc/resolv.conf    設置DNS 
     /etc/X11  X Window的配置文件,xorg.conf XF86Config 這兩個 X Server 的設定檔
     /etc/fstab    記錄開機要mount的文件系統
     /etc/inittab 設定系統啟動時init進程將把系統設置成什么樣的runlevel
     /etc/issue 記錄用戶登錄前顯示的信息
     /etc/group 設定用戶的組名與相關信息
     /etc/passwd 帳號信息
     /etc/shadow 密碼信息
     /etc/sudoers 可以sudo命令的配置文件
     /etc/securetty 設定哪些終端可以讓root登錄
     /etc/login.defs 所有用戶登錄時的缺省配置
     /etc/exports 設定NFS系統用的
     /etc/init.d/   所有服務的預設啟動 script 都是放在這裡的,例如要啟動或者關閉
     /etc/xinetd.d/  這就是所謂的 super daemon 管理的各項服務的設定檔目錄
     /etc/modprobe.conf   內核模塊額外參數設定
     /etc/syslog.conf   日志設置文件
/home   使用者家目錄
/lib    系統會使用到的函數庫
     /lib/modules   kernel 的相關模塊
     /var/lib/rpm   rpm套件安裝處
/lost+found    系統不正常產生錯誤時,會將一些遺失的片段放置於此目錄下
/mnt     外設的掛載點
/media   /mnt類似
/opt     主機額外安裝的軟件
/proc    虛擬目錄,是內存的映射
      /proc/version   內核版本
       /proc/sys/kernel   系統內核功能
/root    系統管理員的家目錄
/sbin    系統管理員才能執行的指令
/srv     一些服務啟動之後,這些服務所需要取用的資料目錄
/tmp     一般使用者或者是正在執行的程序暫時放置檔案的地方
/usr     最大的目錄,存許應用程序和文件
    /usr/X11R6   X-Window目錄
    /usr/src    Linux源代碼
    /usr/include:系統頭文件
    /usr/openwin 存放SUNOpenWin
    /usr/man 在線使用手冊
    /usr/bin           使用者可執行的 binary file 的目錄
    /usr/local/bin     使用者可執行的 binary file 的目錄
    /usr/lib           系統會使用到的函數庫
    /usr/local/lib     系統會使用到的函數庫
    /usr/sbin          系統管理員才能執行的指令
    /usr/local/sbin    系統管理員才能執行的指令
/var   日志文件
    /var/log/secure    記錄登入系統存取資料的檔案,例如 pop3, ssh, telnet, ftp 等都會記錄在此檔案中
    /var/log/wtmp      記錄登入者的訊息資料, last
    /var/log/messages  幾乎系統發生的錯誤訊息
    /var/log/boot.log  記錄開機或者是一些服務啟動的時候,所顯示的啟動或關閉訊息
    /var/log/maillog   紀錄郵件存取或往來( sendmail pop3 )的使用者記錄
    /var/log/cron      記錄 crontab 這個例行性服務的內容
    /var/log/httpd, /var/log/news, /var/log/mysqld.log, /var/log/samba, /var/log/procmail.log
    分別是幾個不同的網路服務的記錄檔
 
一些常用的基本命令:
uname -a    查看內核版本      
ls -al    顯示所有文件的屬性
pwd         顯示當前路徑       
cd -    返回上一次目錄     cd ~    返回主目錄
date s      設置時間、日期         
cal      顯示日歷     cal 2006
bc          計算器具              
man  & info     幫助手冊
locale     顯示當前字體     locale -a    所有可用字體     /etc/sysconfig/i18n設置文件
LANG=en    使用英文字體           
sync       將數據同步寫入硬盤       
shutdonw -h now & half & poweroff  關機
reboot     重啟                  
startx  &  init 5   進入圖形介面
/work  & ?work    向上、下查找文檔內容
chgrp      改變檔案群組  chgrp testing install.log   
chown     改變所屬人   chown root:root install.log
chmod      改變屬性     chmod 777 install.log     read=4  write=2  execute=1
cp   復制   cp filename
rm   刪除文件  rm -rf filename   強制刪除文件
rmdir   刪除文件夾
mv  移動    mv 123.txt 222.txt  重命名
mkdir     創建文件夾
touch     創建文件  更新當前時間
cat       由第一行開始顯示     cat |more  分頁
nl        在內容前加行號
more  &  less   一面一面翻動
head -n filename   顯示第N行內容
tail -n filename  顯示后N行內容
od        顯示非純文檔
df -h 顯示分區空間
du  顯示目錄或文件的大小
fdisk   分區設置    fdisk -l /dev/hda  顯示硬盤分區狀態
mkfs    建立各種文件系統  mkfs -t ext3  /dev/ram15  
fsck    檢查和修復LINUX檔案
ln      硬鏈接   ln -s  軟件鏈接
whereis   查找命令
locate    查找
find      查找   find / -name "***.***"
which     查看工具
whoami    顯示當前用戶
gcc -v    查看GCC版本
chattr +i filename  禁止刪除   chattr -i filename  取消禁止
lsattr    顯示隱藏檔屬性
updatedb  更新資料庫
mke2fs    格式化   mkfs -t ext3
dd if=/etc/passwd of=/tmp/passwd.bak    備份
mount     列出系統所有的分區
mount -t iso9660 /dev/cdrom /mnt/cdrom   掛載光盤
mount -t vfat /dev/fd0 /mnt/floppy       掛載軟盤
mount -t vfat -o iocharset=utf8,umask=000 /dev/hda2 /mnt/hda2   掛載fat32分區
mount -t ntfs -o nls=utf8,umask=000 /dev/hda3 /mnt/hda3         掛載ntfs分區
Linux-NTFS Project: http://linux-ntfs.sourceforge.net/
umount /mnt/hda3  缷載
ifconfig   顯示或設置網絡設備
service network restart   重啟網卡 
ifdown eth0  關閉網卡
ifup eth0    開啟網卡
clear    清屏
history    歷史記錄       !55  執行第55個指令
stty   設置終端    stty -a
fdisk /mbr   刪除GRUB
at     僅進行一次的工作排程
crontab   循環執行的例行性命令    [e]編輯,[l]顯示,[r]刪除任務
&       后臺運行程序    tar -zxvf 123.tar.gz & --------->后臺運行
jobs    觀看后臺暫停的程序   jobs -l
fg      將后臺程序調到前臺   fg n ------>n是數字,可以指定進行那個程序
bg      讓工作在后臺運行
kill    結束進程    kill -9 PID     [9]強制結束,[15]正常結束,[l]列出可用的kill信號
ps aux  查看后臺程序  
top     查看后臺程序   top -d 2    每兩秒更新一次        top -d 2 -p10604   觀看某個PID
        top -b -n 2 > /tmp/top.txt -----> top 的資訊進行 2 次,然後將結果輸出到 /tmp/top.txt   
pstree   以樹狀圖顯示程序    [A] ASCII 來連接, [u]列出PID, [p]列出帳號
killall   要刪除某個服務    killall -9 httpd
free      顯示內存狀態     free -m  -------->M為單位顯示
uptime    顯示目前系統開機時間
netstat   顯示網絡狀態    netstat -tulnp------>找出目前系統上已在監聽的網路連線及其 PID
dmesg     顯示開機信息    demsg | more
nice      設置優先權      nice -n -5 vi & -----> root 給一個 nice 植為 -5 ,用於執行 vi
renice    調整已存在優先權
runlevel  顯示目前的runlevel
depmod    分析可載入模塊的相依性
lsmod     顯示已載入系統的模塊
modinfo   顯示kernel模塊的信息
insmod    載入模塊
modprobe   自動處理可載入模塊
rmmod     刪除模塊
chkconfig   檢查,設置系統的各種服務     chkconfig --list ----->列出各項服務狀態
ntsysv     設置系統的各種服務
cpio      備份文件
 

壓縮命令:
 *.Z      compress 程式壓縮的檔案;
 *.bz2    bzip2 程式壓縮的檔案;
 *.gz     gzip 程式壓縮的檔案;
 *.tar    tar 程式打包的資料,並沒有壓縮過;
 *.tar.gz tar 程式打包的檔案,其中並且經過 gzip 的壓縮
compress filename  壓縮文件  [-d]解壓  uncompress
gzip filename   壓縮  [-d]解壓  zcat 123.gz 查看壓縮文件內容
bzip2 -z filename  壓縮  [-d]解壓   bzcat filename.bz2  查看壓縮文件內容
tar -cvf /home/123.tar /etc  打包,不壓縮
tar -xvf 123.tar   解開包
tar -zxvf /home/123.tar.gz  gzip解壓
tar -jxvf /home/123.tar.bz2  bzip2解壓
tar -ztvf /tmp/etc.tar.gz   查看tar內容
cpio -covB  > [file|device]   份份
cpio -icduv < [file|device]   還原
 
vi一般用法
一般模式              編輯模式                  指令模式
h                a,i,r,o,A,I,R,O             :w 保存
j                 進入編輯模式                :w! 強制保存
k                 dd 刪除光標當前行           :q! 不保存離開
l                 ndd 刪除n                 :wq! 保存后離開
0 移動到行首        yy 復制當前行                :e! 還原原始檔
$ 移動到行尾        nyy 復制n                  :w filename 另存為
H 屏幕最上          p,P 粘貼                     :set nu 設置行號
M 屏幕中央          u  撤消                      :set nonu 取消行號
L 屏幕最下          [Ctrl]+r 重做上一個動作       ZZ 保存離開
G 檔案最后一行      [ctrl]+z 暫停退出            :set nohlsearch   永久地關閉高亮顯示
/work 向下搜索                                   :sp 同時打開兩個文檔
?work 向上搜索                                   [Ctrl]+w 兩個文檔設換
gg 移動到檔案第一行                              :nohlsearch    暫時關閉高亮顯示
 
認識SHELL
alias    顯示當前所有的命令別名      alias lm="ls -al"   命令別名    unalias lm 取消命令別名
type      類似which
exprot    設置或顯示環境變量
exprot PATH="$PATH":/sbin  添加/sbinPATH路徑
echo $PATH    顯示PATH路徑
bash      進入子程序
name=yang     設定變量
unset name    取消變量
echo $name    顯示變量的內容
myname="$name its me"   &   myname='$name its me'     單引號時$name失去變量內容
ciw=/etc/sysconfig/network-scripts/     設置路徑
env      列出所有環境變量
echo $RANDOM    顯示隨意產生的數
set      設置SHELL
PS1='[\u@\h \w \A #\#]\$ '     提示字元的設定
   [root@linux ~]# read [-pt] variable     -----------讀取鍵盤輸入的變量
   參數:
   -p  :後面可以接提示字元!
   -t  :後面可以接等待的『秒數!』
declare    聲明 shell 變量
ulimit -a   顯示所有限制資料
 ls /tmp/yang && echo "exist" || echo "not exist"
 意思是說,當 ls /tmp/yang 執行後,若正確,就執行echo "exist" ,若有問題,就執行echo "not exist"
 echo $PATH | cut -d ':' -f 5       :為分隔符,讀取第5段內容
 export | cut -c 10-20      讀取第1020個字節的內容
 last | grep 'root'    搜索有root的一行,[-v]反向搜索
 cat /etc/passwd | sort    排序顯示
 cat /etc/passwd | wc      顯示『行、字數、字節數』
正規表示法
[root@test root]# grep [-acinv] '搜尋字串' filename
       參數說明:
       -a :將 binary 檔案以 text 檔案的方式搜尋資料
       -c :計算找到 '搜尋字串' 的次數
       -i :忽略大小寫的不同,所以大小寫視為相同
       -n :順便輸出行號
       -v :反向選擇,亦即顯示出沒有 '搜尋字串' 內容的那一行!
 grep -n 'the' 123.txt     搜索the字符 -----------搜尋特定字串      
 grep -n 't[ea]st' 123.txt    搜索testtaste兩個字符---------利用 [] 來搜尋集合字元
 grep -n '[^g]oo' 123.txt     搜索前面不為goo-----------向選擇 [^]
 grep -n '[0-9]' 123.txt  搜索有0-9的數字
 grep -n '^the' 123.txt 搜索以the為行首-----------行首搜索^
 grep -n '^[^a-zA-Z]' 123.txt  搜索不以英文字母開頭
 grep -n '[a-z]$' 123.txt    搜索以a-z結尾的行---------- 行尾搜索$
 grep -n 'g..d' 123.txt     搜索開頭g結尾d字符----------任意一個字元 .
 grep -n 'ooo*' 123.txt     搜索至少有兩個oo的字符---------重複字元 *
sed    文本流編輯器    利用腳本命令來處理文本文件
awd    模式掃描和處理語言
 nl 123.txt | sed '2,5d'   刪除第二到第五行的內容
diff     比較文件的差異
cmp      比較兩個文件是否有差異
patch    修補文件
pr       要打印的文件格式化
 

帳號管理
/etc/passwd    系統帳號信息
/etc/shadow    帳號密碼信息    MD5 32位加密
     在密碼欄前面加『 * 』『 ! 』禁止使用某帳號
/etc/group     系統群組信息
/etc/gshadow
newgrp    改變登陸組
useradd  &  adduser    建立新用戶  ---------> useradd -m test  自動建立用戶的登入目錄
          useradd -m -g pgroup test --------->指定所屬級
/etc/default/useradd   相關設定
/etc/login.defs       UID/GID 有關的設定
passwd    更改密碼 -----------> passwd test
usermod   修改用戶帳號
userdel   刪除帳號 ----------->userdel -r test
chsh      更換登陸系統時使用的SHELL   [-l]顯示可用的SHELL;[-s]修改自己的SHELL
chfn      改變finger指令顯示的信息
finger    查找并顯示用戶信息
id        顯示用戶的ID ----------->  id test
groupadd   添加組
groupmod   usermod類似
groupdel   刪除組
su test    更改用戶   su -    進入root,且使用root的環境變量
sudo       以其他身份來執行指令
visudo     編輯/etc/sudoers      加入一行『 test ALL=(ALL) ALL
           %wheel ALL = (ALL) ALL               系統里所有wheel群組的用戶都可用sudo
           %wheel ALL = (ALL) NOPASSWD: ALL     wheel群組所有用戶都不用密碼NOPASSWD
       User_Alias ADMPW = vbird, dmtsai, vbird1, vbird3         加入ADMPW
       ADMPW ALL = NOPASSWD: !/usr/bin/passwd, /usr/bin/passwd [A-Za-z]*, \
       !/usr/bin/passwd root      可以更改使用者密碼,但不能更改root密碼 (在指令前面加入 ! 代表不可)
PAM (Pluggable Authentication Modules, 嵌入式模組)
who & w     看誰在線                    
last        最近登陸主機的信息
lastlog     最近登入的時間    讀取 /var/log/lastlog
talk        與其他用戶交談
write       發送信息    write test   [ctrl]+d 發送
mesg        設置終端機的寫入權限    mesg n 禁止接收     mesg y
wall        向所有用戶發送信息    wall this is q test
mail        mail  
/etc/default/useradd    家目錄默認設置
quota      顯示磁盤已使用的空間與限制     quota -guvs ----->秀出目前 root 自己的 quota 限制值
           quota -vu   查詢
quotacheck   檢查磁盤的使用空間與限制     quotacheck -avug  ----->將所有的在 /etc/mtab 內,含有 quota 支援的 partition 進行掃瞄
             [-m] 強制掃描 
     quota一定要是獨立的分區,要有quota.userquota.group兩件文件,/etc/fstab添加一句:
     /dev/hda3 /home ext3 defaults,usrquota,grpquota 1 2
     chmod 600 quota*         設置完成,重啟生效
edquota    編輯用戶或群組的quota  [u]用戶,[g]群組,[p]復制,[t]設置寬限期限
           edquota -a yang       edquota -p yang -u young ----->復制   
quotaon    開啟磁盤空間限制     quotaon -auvg -------->啟動所有的具有 quota filesystem
quotaoff   關閉磁盤空間限制     quotaoff -a  -------->關閉了 quota 的限制
repquota -av     查閱系統內所有的具有 quota filesystem 的限值狀態
Quota 從開始準備 filesystem 的支援到整個設定結束的主要的步驟大概是:
1、設定 partition filesystem 支援 quota 參數:
由於 quota 必須要讓 partition 上面的 filesystem 支援才行,一般來說, 支援度最好的是 ext2/ext3
其他的 filesystem 類型鳥哥我是沒有試過啦! 啟動 filesystem 支援 quota 最簡單就是編輯 /etc/fstab
使得準備要開放的 quota 磁碟可以支援 quota 囉;
2、建立 quota 記錄檔:
剛剛前面講過,整個 quota 進行磁碟限制值記錄的檔案是 aquota.user/aquota.group
要建立這兩個檔案就必須要先利用 quotacheck 掃瞄才行喔!
3、編輯 quota 限制值資料:
再來就是使用 edquota 來編輯每個使用者或群組的可使用空間囉;
4、重新掃瞄與啟動 quota
設定好 quota 之後,建議可以再進行一次 quotacheck ,然後再以 quotaon 來啟動吧!

開機流程簡介
1、載入 BIOS 的硬體資訊,並取得第一個開機裝置的代號;
2、讀取第一個開機裝置的 MBR boot Loader (亦即是 lilo, grub, spfdisk 等等) 的開機資訊;
3、載入 Kernel 作業系統核心資訊, Kernel 開始解壓縮,並且嘗試驅動所有硬體裝置;
4Kernel 執行 init 程式並取得 run-level 資訊;
5init 執行 /etc/rc.d/rc.sysinit 檔案;
6、啟動核心的外掛模組 (/etc/modprobe.conf)
7init 執行 run-level 的各個批次檔( Scripts )
8init 執行 /etc/rc.d/rc.local 檔案;
9、執行 /bin/login 程式,並等待使用者登入;
10、登入之後開始以 Shell 控管主機。
/etc/rc.d/rc3.d,S開頭的為開機啟動,K開頭的為關閉,接著的數字代表執行順序
GRUB vga設定
彩度\解析度  640x480  800x600  1024x768  1280x1024   bit
    256        769      771      773       775      8 bit
   32768       784      787      790       793     15 bit
   65536       785      788      791       794     16 bit
   16.8M       786      789      792       795     32 bit

./configure    檢查系統信息       ./configure --help | more  幫助信息
make clean     清除之前留下的文件
make           編譯
make install   安裝
rpm -q  ----->查詢是否安裝             rpm -ql ------>查詢該套件所有的目錄
rpm -qi ----->查詢套件的說明資料       rpm -qc[d] ----->設定檔與說明檔
rpm -ivh  ---->安裝                    rpm -V  -------->查看套件有否更動過
rpm -e  ------>刪除                    rpm -Uvh ------->升級安裝 
--nodeps ----->強行安裝                --test ----->測試安裝


JAVA之路 2007-08-14 00:21 發表評論
]]>
Java實現隨機驗證碼功能實例http://www.tkk7.com/xixidabao/archive/2007/08/09/135382.htmlJAVA之路JAVA之路Wed, 08 Aug 2007 16:07:00 GMThttp://www.tkk7.com/xixidabao/archive/2007/08/09/135382.html                                                   Java實現隨機驗證碼功能實例
                                                                                  2007-08-08 來自:lizhe1985  


現在許多系統的注冊、登錄或者發布信息模塊都添加的隨機碼功能,就是為了避免自動注冊程序或者自動發布程序的使用。

驗證碼實際上就是隨機選擇一些字符以圖片的形式展現在頁面上,如果進行提交操作的同時需要將圖片上的字符同時提交,如果提交的字符與服務器session保存的不同,則認為提交信息無效。為了避免自動程序分析解析圖片,通常會在圖片上隨機生成一些干擾線或者將字符進行扭曲,增加自動識別的難度。

在這里,我們使用servlet來實現隨機驗證碼的實現。

package com.servlet;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
* 生成隨機驗證碼
* @author bitiliu
*
*/
public class ValidateCodeServlet extends HttpServlet
{

private static final long serialVersionUID = 1L;

//驗證碼圖片的寬度。
private int width=60;
//驗證碼圖片的高度。
private int height=20;
//驗證碼字符個數
private int codeCount=4;


private int x=0;
//字體高度
private int fontHeight;
private int codeY;

char[] codeSequence = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };

/**
* 初始化驗證圖片屬性
*/
public void init() throws ServletException
{
//從web.xml中獲取初始信息
//寬度
String strWidth=this.getInitParameter("width");
//高度
String strHeight=this.getInitParameter("height");
//字符個數
String strCodeCount=this.getInitParameter("codeCount");

//將配置的信息轉換成數值
try
{
if(strWidth!=null && strWidth.length()!=0)
{
width=Integer.parseInt(strWidth);
}
if(strHeight!=null && strHeight.length()!=0)
{
height=Integer.parseInt(strHeight);
}
if(strCodeCount!=null && strCodeCount.length()!=0)
{
codeCount=Integer.parseInt(strCodeCount);
}
}
catch(NumberFormatException e)
{}

x=width/(codeCount+1);
fontHeight=height-2;
codeY=height-4;

}

protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, java.io.IOException {

//定義圖像buffer
BufferedImage buffImg = new BufferedImage(
width, height,BufferedImage.TYPE_INT_RGB);
Graphics2D g = buffImg.createGraphics();

//創建一個隨機數生成器類
Random random = new Random();

//將圖像填充為白色
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);

//創建字體,字體的大小應該根據圖片的高度來定。
Font font = new Font("Fixedsys", Font.PLAIN, fontHeight);
//設置字體。
g.setFont(font);

//畫邊框。
g.setColor(Color.BLACK);
g.drawRect(0, 0, width - 1, height - 1);

//隨機產生160條干擾線,使圖象中的認證碼不易被其它程序探測到。
g.setColor(Color.BLACK);
for(int i = 0; i < 160; i++)
{
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x, y, x + xl, y + yl);
}

//randomCode用于保存隨機產生的驗證碼,以便用戶登錄后進行驗證。
StringBuffer randomCode = new StringBuffer();
int red = 0, green = 0, blue = 0;

//隨機產生codeCount數字的驗證碼。
for (int i = 0; i < codeCount; i++) {
//得到隨機產生的驗證碼數字。
String strRand = String.valueOf(codeSequence[random.nextInt(36)]);
//產生隨機的顏色分量來構造顏色值,這樣輸出的每位數字的顏色值都將不同。
red = random.nextInt(255);
green = random.nextInt(255);
blue = random.nextInt(255);

//用隨機產生的顏色將驗證碼繪制到圖像中。
g.setColor(new Color(red, green, blue));
g.drawString(strRand, (i + 1) * x, codeY);

//將產生的四個隨機數組合在一起。
randomCode.append(strRand);
}
// 將四位數字的驗證碼保存到Session中。
HttpSession session = req.getSession();
session.setAttribute("validateCode", randomCode.toString());

// 禁止圖像緩存。
resp.setHeader("Pragma", "no-cache");
resp.setHeader("Cache-Control", "no-cache");
resp.setDateHeader("Expires", 0);

resp.setContentType("image/jpeg");

//將圖像輸出到Servlet輸出流中。
ServletOutputStream sos = resp.getOutputStream();
ImageIO.write(buffImg, "jpeg", sos);
sos.close();
}

}

需要在web.xml中聲明servlet

<servlet>
<servlet-name>ValidateCodeServlet</servlet-name>
<servlet-class>com.servlet.ValidateCodeServlet</servlet-class>
<init-param>
<param-name>width</param-name>
<param-value>200</param-value>
</init-param>
<init-param>
<param-name>height</param-name>
<param-value>80</param-value>
</init-param>
<init-param>
<param-name>codeCount</param-name>
<param-value>5</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>ValidateCodeServlet</servlet-name>
<url-pattern>/validateCodeServlet</url-pattern>
</servlet-mapping>

用戶提交后就可以將用戶輸入的驗證碼與session中保存的字符串進行比對,達到驗證的效果

JAVA之路 2007-08-09 00:07 發表評論
]]>
主站蜘蛛池模板: 黄色成人网站免费无码av| 真人无码作爱免费视频| 亚洲卡一卡2卡三卡4卡无卡三| 无码不卡亚洲成?人片| 国产人妖ts在线观看免费视频| 巨胸喷奶水视频www网免费| 999国内精品永久免费视频| 亚洲免费观看网站| 国产免费AV片在线播放唯爱网| 中文字幕无码播放免费| 免费看韩国黄a片在线观看| 99re热免费精品视频观看 | 亚洲人色大成年网站在线观看| 亚洲制服中文字幕第一区| 亚洲国产成人久久精品影视| 亚洲国产成人久久综合一| 亚洲成人福利在线观看| 亚洲AV成人噜噜无码网站| 亚洲午夜无码久久| 国产91成人精品亚洲精品| 一级毛片人与动免费观看| 一级成人a做片免费| 久久国产乱子伦精品免费午夜| 中国好声音第二季免费播放| 免费成人在线电影| 在线视频精品免费| 爽爽日本在线视频免费| 国产女高清在线看免费观看| 四虎影视在线永久免费观看| 伊人久久综在合线亚洲91| 亚洲s色大片在线观看| 亚洲综合久久久久久中文字幕| 国产亚洲精品bv在线观看| 亚洲精品无码久久久久秋霞 | 亚洲国产成人久久精品99 | 成人au免费视频影院| 免费观看午夜在线欧差毛片| 中文字幕人成人乱码亚洲电影 | 亚洲中文字幕无码爆乳av中文| 久久亚洲精品成人777大小说| 亚洲制服丝袜一区二区三区|