2008年1月10日
程序代碼自動排版:Ctrl+Shift+F,會自動把代碼進行格式化的排版,非常方便
快速執行程序:Ctrl + F11第一次執行時,它會詢問您執行模式,設置好后,以后只要按這個熱鍵,它就會快速執行。
Ctrl+Shift+/ 加上段注釋/**/
Ctrl+Shift+\ 取消段注釋/**/
Ctrl+/ 加上行注釋或取消行注釋
自動匯入所需要的類別:Ctrl+Shift+O
取消自動validation:
取消方法: windows-->perferences-->myeclipse-->validation
除開Manual下面的復選框全部選中之外,其他全部不選
手工驗證方法:
在要驗證的文件上,單擊鼠標右鍵-->myeclipse-->run validation
按new Remote Site,Name填 svn , URL填http://subclipse.tigris.org/update,一直next到finished為止
1.FindBugs:查錯
目前版本0.9.1,有for eclipse的插件. 網址是
http://findbugs.sourceforge.net.
工作原理:檢查程序生成的class的工具.
界面:獨立運行的提供圖形界面,很友好,有bug報告.
可用性:大多數提示有用,值得改
插件:
可以設置基本和檢查的錯誤類別.
插件保存設置有問題,我是關閉項目后臺修改了配置文件,在裝入才成功改了配置的.
bug臨時解決: 使用獨立的findbugs設置規則,然后到C:\Documents and Settings\XXX\下找.Findbugs_prefs,然后改名覆蓋eclipse project下的.fbprefs (先關閉你的project)
配置沒有查找功能,不過縮寫能讓我們很快找到某個規則
2.PMD:主要是查錯
目前版本3.2,有for eclipse以及其他ide的插件.網址是
http://pmd.sourceforge.net
工作原理:檢查源碼
可用性:一部分值得修改,有些過于嚴格
界面:獨立運行的是命令行界面,命令比較簡單.
插件:可以配置規則,有一個獨立的窗口顯示提示,分5級提示,很友好
使用:建立自己的規范,然后用于實際使用中.
3.CheckStyle:主要查代碼規范
目前版本4.0 beta 5,有for eclipse的插件.網址是
http://checkstyle.sourceforge.net.
工作原理:檢查源碼,對javadoc,書寫格式等進行檢查.
規則定義:默認的規則是sun的編碼規范.不過按照sun的規則則過于嚴格,而且每個公司也有自己的規范,和sun的不同,所以需要自定義規范.
4.JTest 重量級的商業工具
目前版本7.0.7,有for eclipse的插件.網址是http://www.parasoft.com/
不推薦使用,不過功能強大,可以進行代碼檢查,可以自動生成單元測試和進行單元測試.(不過就是太慢了,而且生成的單元測試沒太大用途)
使用感覺:
安裝上插件后,對自己的項目進行檢查,發現警告太多了,有點發蒙的感覺.不過把警告看一遍,覺得都很有道理,有些也確實是一些錯誤.
當然PMD和CheckStyle的規范太嚴格,最后還是配置了一下.
通過改正警告,感覺還是不錯,至少可以說自己的代碼可以通過工具的檢測了.
當然基礎代碼和項目代碼還是不一樣的,基礎代碼往往比較復雜,所以和普通項目代碼的規范應該有所不同.有些規則只能用在普通代碼上,用在基礎類代碼上往往沒法處理.
其他
代碼查錯推薦使用Findbugs和PMD,代碼書寫規范推薦使用CheckStyle進行檢查.這樣不僅能查出一些基本的錯誤,也能提高項目的代碼質量.對提高自己的代碼水平也是非常好.
推薦項目組建立統一的規則,代碼復查的時候就使用這些工具,省時省力.
實乃居家旅行,殺人越貨必備之工具也.(因為肯定有人要罵你,呵呵,也是你找"差"的工具)
Lucene是一個全文檢索的引擎,目前有Java和.Net 等幾個版本.Java版本的網址是
http://lucene.apache.org.相關的一個項目是車東的WebLucene:
http://sourceforge.net/projects/weblucene.
首先,基于一個簡單的新聞系統,要想做全文檢索.新聞系統的管理等在這里不在具體提出,下面列出新聞對象的類:
注:程序用會到一些工具類,不在此列出,用戶可以自己實現.
package com.jscud.website.newsinfo.bean;
import java.sql.Timestamp;
import com.jscud.util.DateTime;
import com.jscud.util.StringFunc;
import com.jscud.website.newsinfo.NewsConst;
/**
* 一個新聞.
*
* @author scud(飛云小俠) http://www.jscud.com
*
*/
public class NewsItem
{
private int nid; //新聞編號
private int cid; //類別編號
private String title;//標題
private int showtype; //內容類型:目前支持url和html
private String content;//內容
private String url;//對應網址,如果內容類型是url的話
private Timestamp addtime; //增加時間
private int click; //點擊數
//對應的get,set函數,較多不在列出,可以使用工具生成
//......
/**
* 按照類型格式化
*/
public String getShowContent()
{
String sRes = content;
if(showtype == NewsConst.ShowType_HTML)
{
}
return sRes;
}
public String getTarget()
{
if(showtype == NewsConst.ShowType_URL)
{
return "_blank";
}
else
return "";
}
/**
* 靜態Html文件的路徑及其名字
*/
public String getHtmlFileName()
{
int nYear = DateTime.getYear_Date(getAddtime());
int nMonth = DateTime.getMonth_Date(getAddtime());
String sGeneFileName =
"/news/" + getCid() + "/" + nYear + "/" + nMonth +"/" + getNid() + ".htm";
return sGeneFileName;
}
/**
* 靜態Html文件的路徑
*/
public String getHtmlFilePath()
{
int nYear = DateTime.getYear_Date(getAddtime());
int nMonth = DateTime.getMonth_Date(getAddtime());
String sGeneFilePath =
getCid() + "_" + nYear + "_" + nMonth;
return sGeneFilePath;
}
}
|
可以看到,我們需要對標題和內容進行檢索,為了這個目的,我們首先需要來研究一下lucene.
在Lucene中,如果要進行全文檢索,必須要先建立索引然后才能進行檢索,當然實際工作中還會有刪除索引和更新索引的工作.
在此之前,介紹一個最基本的類(摘抄自http://www.aygfsteel.com/cap/archive/2005/07/17/7849.html):
Analyzer 文件的分析器(聽起來別扭,還是叫Analyzer好了)的抽象,這個類用來處理分詞(對中文尤其重要,轉換大小寫(Computer->computer,實現查詢大小寫無關),轉換詞根(computers->computer),消除stop words等,還負責把其他格式文檔轉換為純文本等.
在lucene中,一般會使用StandardAnalyzer來分析內容,它支持中文等多字節語言,當然可以自己實現特殊的解析器.StandardAnalyzer目前對中文的處理是按照單字來處理的,這是最簡單的辦法,但是也有缺點,會組合出一些沒有意義的結果來.
首先我們來了解建立索引,建立索引包含2種情況,一種是給一條新聞建立索引,另外的情況是在開始或者一定的時間給批量的新聞建立索引,所以為了通用,我們寫一個通用的建立索引的函數:
(一般一類的索引都放在一個目錄下,這個配置可以在函數中定義,也可以寫在配置文件中,通過參數傳遞給函數.)
/**
* 生成索引.
*
* @param doc 目標文檔
* @param indexDir 索引目錄
*/
public static void makeIndex(Document doc, String indexDir)
{
List aList = new ArrayList();
aList.add(doc);
makeIndex(aList, indexDir);
}
/**
* 生成索引.
*
* @param doc 生成的document.
* @param indexDir 索引目錄
*/
public static void makeIndex(List docs, String indexDir)
{
if (null == docs)
{
return;
}
boolean indexExist = indexExist(indexDir);
IndexWriter writer = null;
try
{
StandardAnalyzer analyzer = new StandardAnalyzer();
//如果索引存在,就追加.如果不存在,就建立新的索引.lucene要是自動判決就好了.
if(indexExist)
{
writer = new IndexWriter(indexDir, analyzer, false);
}
else
{
writer = new IndexWriter(indexDir, analyzer, true);
}
//添加一條文檔
for (int i = 0; i < docs.size(); i++)
{
Document doc = (Document) docs.get(i);
if (null != doc)
{
writer.addDocument(doc);
}
}
//索引完成后的處理
writer.optimize();
}
catch (IOException e)
{
LogMan.warn("Error in Make Index", e);
}
finally
{
try
{
if (null != writer)
{
writer.close();
}
}
catch (IOException e)
{
LogMan.warn("Close writer Error");
}
}
}
|
可以看到,建立索引用到類是IndexWrite,它可以新建索引或者追加索引,但是需要自己判斷.判斷是通過IndexReader這個類來實現的,函數如下:
/**
* 檢查索引是否存在.
* @param indexDir
* @return
*/
public static boolean indexExist(String indexDir)
{
return IndexReader.indexExists(indexDir);
}
|
如果每次都是新建索引的話,會把原來的記錄刪除,我在使用的時候一開始就沒有注意到,后來觀察了一下索引文件,才發現這個問題.
還可以看到,建立索引是給用戶的Document對象建立索引,Document表示索引中的一條文檔記錄.那么我們如何建立一個文檔那?以新聞系統為例,代碼如下:
/**
* 生成新聞的Document.
*
* @param aNews 一條新聞.
*
* @return lucene的文檔對象
*/
public static Document makeNewsSearchDocument(NewsItem aNews)
{
Document doc = new Document();
doc.add(Field.Keyword("nid", String.valueOf(aNews.getNid())));
doc.add(Field.Text("title", aNews.getTitle()));
//對Html進行解析,如果不是html,則不需要解析.或者根據格式調用自己的解析方法
String content = parseHtmlContent(aNews.getContent());
doc.add(Field.UnStored("content", content));
doc.add(Field.Keyword("addtime", aNews.getAddtime()));
//可以加入其他的內容:例如新聞的評論等
doc.add(Field.UnStored("other", ""));
//訪問url
String newsUrl = "/srun/news/viewhtml/" + aNews.getHtmlFilePath() + "/" + aNews.getNid()
+ ".htm";
doc.add(Field.UnIndexed("visiturl", newsUrl));
return doc;
}
|
通過上面的代碼,我們把一條新聞轉換為lucene的Document對象,從而進行索引工作.在上面的代碼中,我們又引入了lucene中的Field(字段)類.Document文檔就像數據庫中的一條記錄,它有很多字段,每個字段是一個Field對象.
從別的文章摘抄一段關于Field的說明(摘抄自http://www.aygfsteel.com/cap/archive/2005/07/17/7849.html):
[quote]
類型 Analyzed Indexed Stored 說明
Field.Keyword(String,String/Date) N Y Y 這個Field用來儲存會直接用來檢索的比如(編號,姓名,日期等)
Field.UnIndexed(String,String) N N Y 不會用來檢索的信息,但是檢索后需要顯示的,比如,硬件序列號,文檔的url地址
Field.UnStored(String,String) Y Y N 大段文本內容,會用來檢索,但是檢索后不需要從index中取內容,可以根據url去load真實的內容
Field.Text(String,String) Y Y Y 檢索,獲取都需要的內容,直接放index中,不過這樣會增大index
Field.Text(String,Reader) Y Y N 如果是一個Reader, lucene猜測內容比較多,會采用Unstored的策略.
[/quote]
我們可以看到新聞的編號是直接用來檢索的,所以是Keyword類型的字段,新聞的標題是需要檢索和顯示用的,所以是Text類型,而新聞的內容因為是Html格式的,所以在經過解析器的處理用,使用的UnStored的格式,而新聞的時間是直接用來檢索的,所以是KeyWord類型.為了在新聞索引后用戶可以訪問到完整的新聞頁面,還設置了一個UnIndexed類型的訪問地址字段.
(對Html進行解析的處理稍后在進行講解)
為一條新聞建立索引需要兩個步驟:獲取Document,傳給makeIndex函數,代碼如下:
public static void makeNewsInfoIndex(NewsItem aNews)
{
if (null == aNews)
{
return;
}
makeIndex(makeNewsSearchDocument(aNews),indexDir);
} |
建立索引的工作就進行完了,只要在增加新聞后調用 makeNewsInfoIndex(newsitem); 就可以建立索引了.
如果需要刪除新聞,那么也要刪除對應的索引,刪除索引是通過IndexReader類來完成的:
/**
* 刪除索引.
* @param aTerm 索引刪除條件
* @param indexDir 索引目錄
*/
public static void deleteIndex(Term aTerm, String indexDir)
{
List aList = new ArrayList();
aList.add(aTerm);
deleteIndex(aList, indexDir);
}
/**
* 刪除索引.
*
* @param aTerm 索引刪除條件.
* @param indexDir 索引目錄
*
*/
public static void deleteIndex(List terms, String indexDir)
{
if (null == terms)
{
return;
}
if(!indexExist(indexDir)) { return; }
IndexReader reader = null;
try
{
reader = IndexReader.open(indexDir);
for (int i = 0; i < terms.size(); i++)
{
Term aTerm = (Term) terms.get(i);
if (null != aTerm)
{
reader.delete(aTerm);
}
}
}
catch (IOException e)
{
LogMan.warn("Error in Delete Index", e);
}
finally
{
try
{
if (null != reader)
{
reader.close();
}
}
catch (IOException e)
{
LogMan.warn("Close reader Error");
}
}
}
|
刪除索引需要一個條件,類似數據庫中的字段條件,例如刪除一條新聞的代碼如下:
public static void deleteNewsInfoIndex(int nid)
{
Term aTerm = new Term("nid", String.valueOf(nid));
deleteIndex(aTerm,indexDir);
} |
通過新聞的ID,就可以刪除一條新聞.
如果需要更新新聞,如何更新索引哪? 更新索引需要先刪除索引然后新建索引2個步驟,其實就是把上面的代碼組合起來,例如更新一條新聞:
public static void updateNewsInfoIndex(NewsItem aNews)
{
if (null == aNews)
{
return;
}
deleteNewsInfoIndex(aNews.getNid());
makeNewsInfoIndex(aNews);
}
|
至此,索引的建立更新和刪除就告一段落了.其中批量更新新聞的代碼如下:
(批量更新應該在訪問人數少或者后臺程序在夜間執行)
public static void makeAllNewsInfoIndex(List newsList)
{
List terms = new ArrayList();
List docs = new ArrayList();
for (int i = 0; i < newsList.size(); i++)
{
NewsItem aitem = (NewsItem) newsList.get(i);
if (null != aitem)
{
terms.add(new Term("nid", String.valueOf(aitem.getNid())));
docs.add(makeNewsSearchDocument(aitem));
}
}
deleteIndex(terms,indexDir);
makeIndex(docs,indexDir);
}
|
最近在研究lucene的全文檢索,在很多地方需要解析或者說分析Html內容或者Html頁面,Lucene本身的演示程序中也提供了一個Html Parser,但是不是純Java的解決方案.于是到處搜索,在網上找到了一個"HTMLParser".
網址是: http://htmlparser.sourceforge.net ,當前版本為1.5.
下載下來,試用一番,感覺不錯,完全能滿足lucene解析Html的需求.
過幾天貼出lucene進行全文檢索的代碼.(檢索本站的文章等).
試用代碼如下,供大家參考:
package com.jscud.test;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import org.htmlparser.Node;
import org.htmlparser.NodeFilter;
import org.htmlparser.Parser;
import org.htmlparser.filters.NodeClassFilter;
import org.htmlparser.filters.OrFilter;
import org.htmlparser.nodes.TextNode;
import org.htmlparser.tags.LinkTag;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
import org.htmlparser.visitors.HtmlPage;
import org.htmlparser.visitors.TextExtractingVisitor;
import com.jscud.util.LogMan; //一個日志記錄類
/**
* 演示了Html Parse的應用.
*
* @author scud http://www.jscud.com
*/
public class ParseHtmlTest
{
public static void main(String[] args) throws Exception
{
String aFile = "e:/jscud/temp/test.htm";
String content = readTextFile(aFile, "GBK");
test1(content);
System.out.println("====================================");
test2(content);
System.out.println("====================================");
test3(content);
System.out.println("====================================");
test4(content);
System.out.println("====================================");
test5(aFile);
System.out.println("====================================");
//訪問外部資源,相對慢
test5("
System.out.println("====================================");
}
/**
* 讀取文件的方式來分析內容.
* filePath也可以是一個Url.
*
* @param resource 文件/Url
*/
public static void test5(String resource) throws Exception
{
Parser myParser = new Parser(resource);
//設置編碼
myParser.setEncoding("GBK");
HtmlPage visitor = new HtmlPage(myParser);
myParser.visitAllNodesWith(visitor);
String textInPage = visitor.getTitle();
System.out.println(textInPage);
}
/**
* 按頁面方式處理.對一個標準的Html頁面,推薦使用此種方式.
*/
public static void test4(String content) throws Exception
{
Parser myParser;
myParser = Parser.createParser(content, "GBK");
HtmlPage visitor = new HtmlPage(myParser);
myParser.visitAllNodesWith(visitor);
String textInPage = visitor.getTitle();
System.out.println(textInPage);
}
/**
* 利用Visitor模式解析html頁面.
*
* 小優點:翻譯了<>等符號
* 缺點:好多空格,無法提取link
*
*/
public static void test3(String content) throws Exception
{
Parser myParser;
myParser = Parser.createParser(content, "GBK");
TextExtractingVisitor visitor = new TextExtractingVisitor();
myParser.visitAllNodesWith(visitor);
String textInPage = visitor.getExtractedText();
System.out.println(textInPage);
}
/**
* 得到普通文本和鏈接的內容.
*
* 使用了過濾條件.
*/
public static void test2(String content) throws ParserException
{
Parser myParser;
NodeList nodeList = null;
myParser = Parser.createParser(content, "GBK");
NodeFilter textFilter = new NodeClassFilter(TextNode.class);
NodeFilter linkFilter = new NodeClassFilter(LinkTag.class);
//暫時不處理 meta
//NodeFilter metaFilter = new NodeClassFilter(MetaTag.class);
OrFilter lastFilter = new OrFilter();
lastFilter.setPredicates(new NodeFilter[] { textFilter, linkFilter });
nodeList = myParser.parse(lastFilter);
Node[] nodes = nodeList.toNodeArray();
for (int i = 0; i < nodes.length; i++)
{
Node anode = (Node) nodes[i];
String line = "";
if (anode instanceof TextNode)
{
TextNode textnode = (TextNode) anode;
//line = textnode.toPlainTextString().trim();
line = textnode.getText();
}
else if (anode instanceof LinkTag)
{
LinkTag linknode = (LinkTag) anode;
line = linknode.getLink();
//@todo 過濾jsp標簽:可以自己實現這個函數
//line = StringFunc.replace(line, "<%.*%>", "");
}
if (isTrimEmpty(line))
continue;
System.out.println(line);
}
}
/**
* 解析普通文本節點.
*
* @param content
* @throws ParserException
*/
public static void test1(String content) throws ParserException
{
Parser myParser;
Node[] nodes = null;
myParser = Parser.createParser(content, null);
nodes = myParser.extractAllNodesThatAre(TextNode.class); //exception could be thrown here
for (int i = 0; i < nodes.length; i++)
{
TextNode textnode = (TextNode) nodes[i];
String line = textnode.toPlainTextString().trim();
if (line.equals(""))
continue;
System.out.println(line);
}
}
/**
* 讀取一個文件到字符串里.
*
* @param sFileName 文件名
* @param sEncode String
* @return 文件內容
*/
public static String readTextFile(String sFileName, String sEncode)
{
StringBuffer sbStr = new StringBuffer();
try
{
File ff = new File(sFileName);
InputStreamReader read = new InputStreamReader(new FileInputStream(ff),
sEncode);
BufferedReader ins = new BufferedReader(read);
String dataLine = "";
while (null != (dataLine = ins.readLine()))
{
sbStr.append(dataLine);
sbStr.append("\r\n");
}
ins.close();
}
catch (Exception e)
{
LogMan.error("read Text File Error", e);
}
return sbStr.toString();
}
/**
* 去掉左右空格后字符串是否為空
* @param astr String
* @return boolean
*/
public static boolean isTrimEmpty(String astr)
{
if ((null == astr) || (astr.length() == 0))
{
return true;
}
if (isBlank(astr.trim()))
{
return true;
}
return false;
}
/**
* 字符串是否為空:null或者長度為0.
* @param astr 源字符串.
* @return boolean
*/
public static boolean isBlank(String astr)
{
if ((null == astr) || (astr.length() == 0))
{
return true;
}
else
{
return false;
}
}
}
|
1.資料
2.本地事務與分布式事務
- 本地事務
完全依賴于DB、JMS自身,,如直接調用jdbc中的conn.commit();這里沒應用服務器什么事,所以也不支持多數據源的全局事務。
- 分布式事務
在JavaEE世界的事務在JTA、JTS規范和XA Sources之上實現。
JTA是用戶編程接口,JTS是服務器底層服務,兩者一般由應用服務器自帶實現,而atomikos
、JOTM
和JBoss Transaction
是專門搞局搶生意的。
XA Sources其實先于JavaEE而存在,JDBC driver必須有javax.sql.XADataSource接口的實現類,否則所謂二階段提交就是個偽能力。
JavaEE除了支持JDBC和JMS外,還引入了JCA模型。JCA可以說是目前唯一可移植的插入JavaEE事務的資源模型,因此像JDO這類框架/Server就是靠乖乖出自己的JCA連接器來參與JavaEE事務的。
3.編程式模型
手工調用jdbc的connection事務方法和使用JTA接口都屬于編程式開發,在EJB中叫BMT(Bean管理事務)。
JTA最重要的接口就是UserTransaction和它的六個方法-begin,commit,rollback,getStatus,setRollbackonly,setTransactionTimeout。
程序需要UserTransaction時可以從JNDI領取,不過JNDI名隨應用服務器不同而不同。EJB3里可以直接用個@Resource注入。
4.宣告式模型
前面都是鋪墊,這個才是主打的事務模型,如EJB的CMT(容器管理事務)和Sprin。
其中EJB2.0,Spring1.0在部署描述符和applicationContext.xml中定義,而EJB3.0和Spring2.0則采用annotation。
4.1 事務類型
這里JavaEE與Spring的定義基本相同:
- Required:如果Context中有事務就加入,沒有就自己創建一個。(最常用設置)
- Mandatory:永遠加入一個事務。如果當前Context沒有事務,拋出異常。(那些不打算自己負責rollback事務的方法,必須加入到別人的事務,由別人來控制rollback)
- RequiresNew:永遠新建一個事務。(那些不管別人如何,自己必須提交事務的方法,比如審計信息是一定要寫的)
- Supports:如果有事務就加入,如果沒有就算了。永遠不會創建新事務。(一般用于只讀方法,不會主動創建事務,但如果當前有事務就加入,以讀到事務中未提交的數據)
- NotSupported:永遠不使用事務,如果當前有事務,掛起事務。(那些有可能拋異常但異常并不影響全局的方法)
- Never:不能在有當前事務的情況下調用本方法。(生人勿近?)
可見,Required是默認的設置,Supports是只讀方法的最佳選擇。
4.2 事務隔離級別
- ReadUncommited:本事務可以看到另一事務未提交的數據。臟讀。
- ReadCommited:本事務只可以看到另一事務已提交的數據。不可重復讀。
- RepeatableRead:可重復讀。在一個事務內,第一次讀到的數據,在本事務沒有提交前,無論另一個事務如何提交數據,本事務讀到的數據都是不變的。
- Serializable:串行化,同時只有一個事務能讀相同的數據。
級別越低越安全效率也越低。隔離級別需要相關資源支持,如重復讀在Oracle里會降級為ReadCommited。Spring里默認的Default級別完全看數據源的臉色行事。
4.3 關于Rollback
EJB里,想rollback只能sessionContext.setRollbackOnly(),或者拋出EJBException。(EJB3還可以annotation設置某些自定義Exception可以觸發rollback)
在Spring里,同樣只會rollback unchecked exception(RuntimeExcption及子類),而checked exception(Exception及子類)是不會rollback的,除非你特別聲明。
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW,rollbackFor = {MyException1.class,MyException2.class})
因此所有在service層方法中用throws定義的Exception,都必須在事務定義中進行rollback設定。(請勿善忘)
所有在service層方法中c被atch處理了的異常,又希望容器輔助rollback的話,必須重拋一個預定義的RuntimeException的子類。(請勿回望)
4.4 關于Spring
Spring不希望編程式事務管理。
Spring也不希望使用EJB CMT--CMT依賴于EJB而無法用于POJO,依賴于JTA全局事務對單數據源場景造成了浪費,而且rollback機制比較麻煩(必須為EJBException或手工setRollbackOnly())。
因此Spring通過AOP實現了對POJO的整套宣告式事務體系;對jdbc,hibernate,jpa,jms等local數據源和JTA實現了統一的事務管理機制,而且支持本地資源與JTA在配置文件級的切換,而且改進了rollback機制。
1)一個本地事務管理器:
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean>
2)Spring就會把請求都轉發到應用服務器的JTA對象上(注意此時數據源也需要改為用JNDI從應用服務器獲取)。
<bean id="myTxManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
3)應用服務器專有的類型的JTA事務管理器:
<bean id="myTxManager" class="org.springframework.transaction.jta.WebLogicJtaTransactionManager"/>
在Web開發中,經常需要使用Session來保存特定用戶的信息,在我們的程序中很多地方散落著類似下面的語句:
int userAge = (int)this.Session["UserAge"];
我們知道,Session中存放的是鍵值對,鍵是string類型的,如果我們一不小心把上面的語句寫成這樣:
int userAge = (int)this.Session["UseAge"];
編譯期不會發現這個錯誤,但運行時一定會拋出異常,這是在程序中直接操作Session可能引發的問題之一。另外,每次獲取userAge的時候都要寫代碼進行強制轉換,感覺很繁瑣。我們需要一個解決方案來解決這些問題。我的做法是引入一個Session的包裝,使之對象化、強類型化。就像接下來的例子一樣:
public class SessionHelper
{
private HttpSessionState curSession;
public SessionHelper(HttpSessionState session)
{
this.curSession = session;
}
public static SessionHelper CreateInstance(HttpSessionState session)
{
return new SessionHelper(session);
}
public string UserID
{
get
{
return this.curSession["UserID"].ToString();
}
set
{
this.curSession["UserID"] = value ;
}
}
public int UserAge
{
get
{
return (int)this.curSession["UserAge"];
}
set
{
this.curSession["UserAge"] = value ;
}
}
//某用戶上傳的所有圖片
public ArrayList PicList
{
get
{
if (this.curSession["PicList"] == null)
{
this.curSession["PicList"] = new ArrayList();
}
return (ArraayList)this.curSession["PicList"];
}
}
//清空圖片列表
public void ClearAllPics()
{
this.PicList.Clear();
}
}
這樣,我們用起來就非常方便了:
SessionHelper sessionHelper = SessionHelper.CreateInstance(this.Session);
ArrayList picList = sessionHelper.PicList;
//
處理picList中的圖片
sessionHelper.ClearAllPics();
引入這一層包裝,可以使我們的程序的可讀性、可維護性更好,而且將原來的一些運行期的錯誤提前到了編譯期,這也是強類型帶來的好處。
最近一個項目要用Java做,一點都不熟啊。沒辦法,只好硬著頭皮啃了,花了大半天的時間,終于在Eclipse上完成了第一個Hibernate例子。下面記錄關鍵的步驟,權作筆記,以備日后查看。
(1)下載Hibernate,并向項目中導入Hibernate。
Project->Properies->Java Build Path->Libraries->Add External JARs...,選擇Hibernate根目錄下的hibernate3.jar,添加到項目中。
接著,要將Hibernate下的lib文件夾下的所有文件都作為一個User Library添加到項目中,否則,如果僅僅添加hibernate3.jar,編譯可以通過,運行卻會拋出ClassNotDef的異常,因為hibernate3.jar依賴于Hibernate下的lib文件夾下的文件。
2)我們的應用的后臺數據庫使用的是Oracle,所以首先要在例子項目中引入含有Oracle jdbc driver的包,classes12.jar。該jar文件位于oracle安裝目錄的jdbc\lib目錄下。
在Eclipse中,Project->Properies->Java Build Path->Libraries->Add External JARs...,選擇classes12.jar,將其添加到項目中。
(3)生成hibernate.cfg.xml文件。
通常Hibernate的配置文件和.hbm.xml文件都可以自動生成,這種自動生成的工具很多,我使用的是HibernateSynchronizer,它可以作為一個插件添加到Eclipse中。當HibernateSynchronizer插件正確加載后,我們可以向當前項目中添加Hibernate配置文件:File->New->Other->Hibernate->Hibernate Configuration File,出現如下界面:
注意,Driver Class要選擇針對Oracle的oracle.jdbc.driver.OracleDriver,而且Database URL的格式也要正確,如:
jdbc:oracle:thin:@10.8.8.221:1521:ORCL
最好將hibernate.cfg.xml文件存放于項目的根目錄下。
4)生成.hbm.xml文件。File->New->Other->Hibernate->Hibernate Mapping File,出現如下界面:

在填寫完Password后,點擊Refresh按鈕,就會在Tables中列出所有可以訪問的數據庫表,然后選中要為其生成.hbm.xml文件的表,點擊Finish,即會生成對應的.hbm.xml文件,比如我上面選擇的是Mobileuser表,就會生成Mobileuser.hbm.xml文件。
(5)從.hbm.xml文件自動生成實體類。
在Package Explorer中選中Mobileuser.hbm.xml文件,右鍵->Hibernate Synchronizer->Synchronize Files ,即可生成對應的實體類和DAO類。如果你僅僅想要實體類,那么可以在Project->Properies->Hibernate Synchronizer->Data Access Objects ,將“I would like to have DAOs created for me”的鉤選項去掉即可。
(6)在hibernate.cfg.xml文件中添加對應的mapping resource。
在Package Explorer中選中Mobileuser.hbm.xml文件,右鍵->Hibernate Synchronizer->Add Mapping Reference,即會在
hibernate.cfg.xml中自動生成如下配置:
<mapping resource="HibernateTest/Mobileuser.hbm.xml" />
(7)修改自動生成的hibernate.cfg.xml文件。需要在hibernate.cfg.xml文件的首部添加:
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
比較繁瑣的是,每次自動修改hibernate.cfg.xml文件后,都要重新添加這個xml片斷。
萬事具備,現在可以寫個測試來檢驗一下了:
//僅僅作為示例,沒有進行異常處理
public static void main(String[] args)
{
Configuration cfg = new Configuration().configure() ;
SessionFactory sFactory = cfg.buildSessionFactory() ;
Session session = sFactory.openSession() ;
Transaction tx = session.beginTransaction();
Mobileuser user = (Mobileuser)session.load(Mobileuser.class , new Integer(2)) ;
String age = user.getMobilenumber() ;
System.out.println(age) ;
tx.commit();
session.close() ;
}
在.NET上用的VS.NET+Spring.net+Nhibernate,到了Java平臺上,自然對應著Eclipse+Spring+Hibernate。
上一篇文章介紹了如何在Eclipse上使用Hibernate的入門,本文就簡單介紹一下如何在Eclipse使用Spring。
(1)首先,是下載Spring,可以從sourceforge上下載,
http://sourceforge.net/projects/springframework。目前的最新的可以下載 spring-framework-1.2.8-with-dependencies.zip 。
(2)然后,可以將Spring引入到你的項目中。
先將spring-framework-1.2.8-with-dependencies.zip解壓,將其中的spring.jar(dist目錄中)、commons-logging.jar(lib\jakarta-commons目錄)、log4j-1.2.13.jar(lib\log4j目錄)這三個文件復制到的”D:\java\Spring\lib" 目錄中,然后在Eclipse中建立一個“Spring”庫,將那三個文件添加進“Spring”庫中。
(3)測試一下:
新建兩個類,Student和Book。
public class Book
{
private int id = 0 ;
private String bookName ;
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
public class Student
{
private int age = 0;
private String name ;
private Book book ;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
public String GetBookName()
{
return this.book.getBookName() ;
}
}
然后添加Spring配置文件bean.xml(bean.xml必須在CLASSPATH可以存取到的目錄中):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="student" class="com.springTest.Student">
<property name="age">
<value>22</value>
</property>
<property name="name">
<value>Sky</value>
</property>
<property name="book" ref="book">
</property>
</bean>
<bean id="book" class="com.springTest.Book">
<property name="id">
<value>1000</value>
</property>
<property name="bookName">
<value>戰爭與和平</value>
</property>
</bean>
</beans>
最后的主程序:
public static void main(String[] args)
{
Resource res = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(res);
Student stu = (Student) factory.getBean("student");
System.out.println(stu.GetBookName());
}
運行后可以看到控制臺輸出--“戰爭與和平”。
與Spring.net的使用基本完全一致(包括配置文件、BeanFactory的獲取等),所以熟悉Spring.net的你過渡到Spring是非常平滑的。
最后,Java中的屬性實在是沒有C#中的簡潔,呵呵。
終于,使用Java完成了一個WebService的例子,其中的一個非常小的問題,折騰了我將近一天的時間。下面給出步驟,說明在Java平臺上如何開發WebService。
采用的工具:Eclipse3.1.2 + Tomcat5.5 + XFire1.1 。使用XFire開發WebService應該說非常的容易,只需要按照下面例子的步驟來做:
(1)在Eclipse中新建一個dynamic Web Project ,假設名為XFireZhuweiTest。
(2)導入XFire用戶庫。該庫中應包含xfire-1.1目錄下的xfire-all-1.1.jar文件,以及
xfire-1.1\lib目錄下的所有文件。
(3)將上述的XFire用戶庫中的所有文件拷貝到XFireZhuweiTest項目的
WebContent\WEB-INF\lib目錄下。
(4)修改
WebContent\WEB-INF\web.xml配置文件的內容,下面是修改后web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" 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">
<display-name>
XFireZhuweiTest</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>XFireServlet</servlet-name>
<servlet-class>
org.codehaus.xfire.transport.http.XFireConfigurableServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>XFireServlet</servlet-name>
<url-pattern>/servlet/XFireServlet/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>XFireServlet</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
</web-app>
web.xml中添加的servlet映射表明,所有匹配“/services/*”的url請求全部交給org.codehaus.xfire.transport.http.XFireConfigurableServlet來處理。
(5)編寫需要發布為WebService的Java類,這個例子中是一個非常簡單的MathService.java。
package com.zhuweisky.xfireDemo;
public class MathService
{
public int Add(int a ,int b)
{
return a+b ;
}
}
(6)在WebContent\META-INF目錄下新建xfire文件夾,然后在xfire目錄下添加一個XFire使用的配置文件services.xml,該配置文件中的內容反映了要將哪些java類發布為web服務。本例中的services.xml內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xfire.codehaus.org/config/1.0">
<service>
<name>MathService</name>
<namespace>http://com.zhuweisky.xfireDemo/MathService</namespace>
<serviceClass>com.zhuweisky.xfireDemo.MathService</serviceClass>
</service>
</beans>
XFire會借助Spring來解析services.xml,從中提取需要發布為WebService的配置信息。
很多文章介紹到這里就完了,然而當我按照他們所說的啟動WebService ,然后通過
http://localhost:8080/XFireZhuweiTest/services/MathService?wsdl 來訪問服務描述時,卻拋出了異常,說services.xml文件不存在--
“org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from class path resource [META-INF/xfire/services.xml]; nested exception is java.io.FileNotFoundException: class path resource [META-INF/xfire/services.xml] cannot be opened because it does not exist”。
(7)非常關鍵的一點,就是這個小難題花費了我將近一天的時間。
在
WebContent\WEB-INF目錄下新建
classes文件夾,然后需要將
WebContent下的整個
META-INF文件夾剪切到新建的classes文件夾下。
到這里,項目的完整目錄結構如下:
(8)在Package Explorer中選中XFireZhuweiTest項目,右鍵->Run As ->Run On Server,關聯到你機器上的TomCat,然后會啟動Tomcat,以啟動web服務。(注意,在進行此步驟之前,請先停止TomCat)
(9)在IE中輸入 http://localhost:8080/XFireZhuweiTest/services/MathService?wsdl 會得到正確的web服務描述文檔。
(10)測試剛發布的webService。我使用C#動態調用Web服務:
//C#
string url = "http://localhost:8080/XFireZhuweiTest/services/MathService" ;
object[] args ={1,2} ;
object result = ESFramework.WebService.WebServiceHelper.InvokeWebService(url ,"Add" ,args) ;
MessageBox.Show(result.ToString());
(關于C#動態調用Web服務,請參見這里)
執行后,彈出對話框,顯示結果是3。
這是我項目中使用的一個分頁存儲過程,具有很強的通用性。配合前臺ASP.NET使用50萬條數據基本感不到延遲。數據庫為SQLServer2000。
1.分頁存儲過程
CREATE procedure pagination
@str_sql varchar(1000) = '*', -- 執行的SQL 不含Order by 內容
@str_orderfield varchar(255)='''', -- 排序的字段名
@page_size int = 10, -- 頁大小
@page_index int = 0, -- 頁碼
@order_type int, -- 設置排序類型, 非 -1 值則降序
@total_count int output -- 返回記錄總數, 非 0 值則返回
as
---------------------
-- 獲取指定頁的數據--
---------------------
declare @strsql varchar(5000) -- 主語句
declare @strtmp varchar(5000) -- 臨時變量
declare @strorder varchar(400) -- 排序字串
declare @cruRow int -- 當前行號
--執行總數統計
exec getRowCount @str_sql,@total_count output
set @strtmp = ' select * from ' +
' (select top ' + convert(varchar(10),@page_size) + ' * from ' +
' (select top ' + convert(varchar(10),(@page_index + 1) * @page_size) +' * from '+ -- N+1頁
' ('+ @str_sql +') Src '
--排序方向
if @order_type !=0
begin
set @strsql= @strtmp +
' order by @str_orderfield asc) a ' +
' order by @str_orderfield desc)b' +
' order by @str_orderfield asc'
end
else
begin
set @strsql= @strtmp +
' order by @str_orderfield desc) a ' +
' order by @str_orderfieldasc)b' +
' order by @str_orderfield desc'
end
exec (@strsql)
GO
----------------------------------------------------------------------------
2.分頁存儲過程執行中用到的行數統計
create procedure getRowCount
@sql nvarchar(2000),
@count int output
as
begin
--------------------
-- 獲取數據總行數 --
--------------------
declare @tmpsql nvarchar(2000)
set @tmpsql='select @count=count(*) from ('+ @sql +') a'
execute sp_executesql @tmpsql,N'@count int output',@count output
end
GO
31 構造器Constructor是否可被override?
構造器Constructor不能被繼承,因此不能重寫Overriding,但可以被重載Overloading。
32 是否可以繼承String類?
String類是final類故不可以繼承。
33 當一個線程進入一個對象的一個synchronized方法后,其它線程是否可進入此對象的其它方法?
不能,一個對象的一個synchronized方法只能由一個線程訪問。
33 try {}里有一個return語句,那么緊跟在這個try后的finally {}里的code會不
會被執行,什么時候被執行,在return前還是后?
會執行,在return前執行。
34 編程題: 用最有效率的方法算出2乘以8等於幾?
2 << 3
35 兩個對象值相同(x.equals(y) == true),但卻可有不同的hash code,這句話對不對?
不對,有相同的hash code。
36 當一個對象被當作參數傳遞到一個方法后,此方法可改變這個對象的屬性,并可返回變化后的結果,那么這里到底是值傳遞還是引用傳遞?
是值傳遞。Java編程語言只由值傳遞參數。當一個對象實例作為一個參數被傳遞到方法中時,參數的值就是對該對象的引用。對象的內容可以在被調用的方法中改變,但對象的引用是永遠不會改變的。
37 swtich是否能作用在byte上,是否能作用在long上,是否能作用在String上?
switch(expr1)中,expr1是一個整數表達式。因此傳遞給 switch 和 case 語句的參數應該是 int、 short、 char 或者 byte。long,string 都不能作用于swtich。
38 Hashtable和HashMap
Hashtable繼承自Dictionary類,而HashMap是Java1.2引進的Map interface的一個實現
HashMap允許將null作為一個entry的key或者value,而Hashtable不允許
還有就是,HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因為contains方法容易讓人引起誤解。
最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在
多個線程訪問Hashtable時,不需要自己為它的方法實現同步,而HashMap就必須為之提供外同步。
Hashtable和HashMap采用的hash/rehash算法都大概一樣,所以性能不會有很大的差異。
21 數組有沒有length()這個方法? String有沒有length()這個方法?
數組沒有length()這個方法,有length的屬性。
String有有length()這個方法。
22 Overload和Override的區別。Overloaded的方法是否可以改變返回值的類型?
方法的重寫Overriding和重載Overloading是Java多態性的不同表現。重寫Overriding是父類與子類之間多態性的一種表現,重載Overloading是一個類中多態性的一種表現。如果在子類中定義某方法與其父類有相同的名稱和參數,我們說該方法被重寫(Overriding)。子類的對象使用這個方法時,將調用子類中的定義,對它而言,父類中的定義如同被“屏蔽”了。如果在一個類中定義了多個同名的方法,它們或有不同的參數個數或有不同的參數類型,則稱為方法的重載(Overloading)。
Overloaded的方法是可以改變返回值的類型。
23 Set里的元素是不能重復的,那么用什么方法來區分重復與否呢? 是用==還是equals()? 它們有何區別?
Set里的元素是不能重復的,那么用iterator()方法來區分重復與否。equals()是判讀兩個Set是否相等。 equals()和==方法決定引用值是否指向同一對象equals()在類中被覆蓋,為的是當兩個分離的對象的內容和類型相配的話,返回真值。
24最常見到的runtime exception。
ArithmeticException, ArrayStoreException, BufferOverflowException, BufferUnderflowException, CannotRedoException, CannotUndoException, ClassCastException, CMMException, ConcurrentModificationException, DOMException,EmptyStackException, IllegalArgumentException, IllegalMonitorStateException, IllegalPathStateException, IllegalStateException,ImagingOpException, IndexOutOfBoundsException, MissingResourceException, NegativeArraySizeException, NoSuchElementException, NullPointerException, ProfileDataException, ProviderException, RasterFORMatException, Secur
ityException, SystemException, UndeclaredThrowableException, UnmodifiableSetException, UnsupportedOperationException
25 error和exception有什么區別?
error 表示恢復不是不可能但很困難的情況下的一種嚴重問題。比如說內存溢出。不可能指望程序能處理這樣的情況。
exception 表示一種設計或實現問題。也就是說,它表示如果程序運行正常,從不會發生的情況。
26 List, Set, Map是否繼承自Collection接口?
List,Set是
Map不是
27 abstract class和interface有什么區別?
聲明方法的存在而不去實現它的類被叫做抽象類(abstract class),它用于要創建一個體現某些基本行為的類,并為該類聲明方法,但不能在該類中實現該類的情況。不能創建abstract 類的實例。然而可以創建一個變量,其類型是一個抽象類,并讓它指向具體子類的一個實例。不能有抽象構造函數或抽象靜態方法。Abstract 類的子類為它們父類中的所有抽象方法提供實現,否則它們也是抽象類為。取而代之,在子類中實現該方法。知道其行為的其它類可以在類中實現這些方法。
接口(interface)是抽象類的變體。在接口中,所有方法都是抽象的。多繼承性可通過實現這樣的接口而獲得。接口中的所有方法都是抽象的,沒有一個有程序體。接口只可以定義static final成員變量。接口的實現與子類相似,除了該實現類不能從接口定義中繼承行為。當類實現特殊接口時,它定義(即將程序體給予)所有這種接口的方法。然后,它可以在實現了該接口的類的任何對象上調用接口的方法。由于有抽象類,它允許使用接口名作為引用變量的類型。通常的動態聯編將生效。引用可以轉換到接口類型或從接口類型轉換,instanceof 運算符可以用來決定某對象的類是否實現了接口。
28 abstract的method是否可同時是static,是否可同時是native,是否可同時是synchronized?
都不能
29 接口是否可繼承接口? 抽象類是否可實現(implements)接口? 抽象類是否可繼承實體類(concrete class)?
接口可以繼承接口。抽象類可以實現(implements)接口,抽象類可繼承實體類,但前提是實體類必須有明確的構造函數。
30 啟動一個線程是用run()還是start()?
啟動一個線程是調用start()方法,使線程所代表的虛擬處理機處于可運行狀態,這意味著它可以由JVM調度并執行。這并不意味著線程就會立即運行。run()方法可以產生必須退出的標志來停止一個線程。
11 &和&&的區別。
&是位運算符。&&是布爾邏輯運算符。
12 HashMap和Hashtable的區別。
都屬于Map接口的類,實現了將惟一鍵映射到特定的值上。
HashMap 類沒有分類或者排序。它允許一個 null 鍵和多個 null 值。
Hashtable 類似于 HashMap,但是不允許 null 鍵和 null 值。它也比 HashMap 慢,因為它是同步的。
13 Collection 和 Collections的區別。
Collection是個java.util下的接口,它是各種集合結構的父接口。
Collections是個java.util下的類,它包含有各種有關集合操作的靜態方法。
14 什么時候用assert。
斷言是一個包含布爾表達式的語句,在執行這個語句時假定該表達式為 true。
如果表達式計算為 false,那么系統會報告一個 Assertionerror。它用于調試目的:
assert(a > 0); // throws an Assertionerror if a <= 0
斷言可以有兩種形式:
assert Expression1 ;
assert Expression1 : Expression2 ;
Expression1 應該總是產生一個布爾值。
Expression2 可以是得出一個值的任意表達式。這個值用于生成顯示更多調試
信息的 String 消息。
斷言在默認情況下是禁用的。要在編譯時啟用斷言,需要使用 source 1.4 標記:
javac -source 1.4 Test.java
要在運行時啟用斷言,可使用 -enableassertions 或者 -ea 標記。
要在運行時選擇禁用斷言,可使用 -da 或者 -disableassertions 標記。
要系統類中啟用斷言,可使用 -esa 或者 -dsa 標記。還可以在包的基礎上啟用或者禁用斷言。
可以在預計正常情況下不會到達的任何位置上放置斷言。斷言可以用于驗證傳遞給私有方法的參數。不過,斷言不應該用于驗證傳遞給公有方法的參數,因為不管是否啟用了斷言,公有方法都必須檢查其參數。不過,既可以在公有方法中,也可以在非公有方法中利用斷言測試后置條件。另外,斷言不應該以任何方式改變程序的狀態。
15 GC是什么? 為什么要有GC? (基礎)。
GC是垃圾收集器。Java 程序員不用擔心內存管理,因為垃圾收集器會自動進行管理。要請求垃圾收集,可以調用下面的方法之一:
System.gc()
Runtime.getRuntime().gc()
16 String s = new String("xyz");創建了幾個String Object?
兩個對象,一個是“xyz”,一個是指向“xyz”的引用對象s。
17 Math.round(11.5)等於多少? Math.round(-11.5)等於多少?
Math.round(11.5)返回(long)12,Math.round(-11.5)返回(long)-11;
18 short s1 = 1; s1 = s1 + 1;有什么錯? short s1 = 1; s1 += 1;有什么錯?
short s1 = 1; s1 = s1 + 1;有錯,s1是short型,s1+1是int型,不能顯式轉化為short型。可修改為s1 =(short)(s1 + 1) 。short s1 = 1; s1 += 1正確。
19 sleep() 和 wait() 有什么區別? 搞線程的最愛
sleep()方法是使線程停止一段時間的方法。在sleep 時間間隔期滿后,線程不一定立即恢復執行。這是因為在那個時刻,其它線程可能正在運行而且沒有被調度為放棄執行,除非(a)“醒來”的線程具有更高的優先級 (b)正在運行的線程因為其它原因而阻塞。
wait()是線程交互時,如果線程對一個同步對象x 發出一個wait()調用,該線程會暫停執行,被調對象進入等待狀態,直到被喚醒或等待時間到。
20 Java有沒有goto?
Goto—java中的保留字,現在沒有在java中使用。
1. Java中的異常處理機制的簡單原理和應用。
當Java程序違反了Java的語義規則時,Java虛擬機就會將發生的錯誤表示為一個異常。違反語義規則包括2種情況。一種是Java類庫內置的語義檢查。例如數組下標越界,會引發IndexOutOfBoundsException;訪問null的對象時會引發NullPointerException。另一種情況就是Java允許程序員擴展這種語義檢查,程序員可以創建自己的異常,并自由選擇在何時用throw關鍵字引發異常。所有的異常都是java.lang.Thowable的子類。
2. Java的接口和C++的虛類的相同和不同處。
由于Java不支持多繼承,而有可能某個類或對象要使用分別在幾個類或對象里面的方法或屬性,現有的單繼承機制就不能滿足要求。與繼承相比,接口有更高的靈活性,因為接口中沒有任何實現代碼。當一個類實現了接口以后,該類要實現接口里面所有的方法和屬性,并且接口里面的屬性在默認狀態下面都是public static,所有方法默認情況下是public.一個類可以實現多個接口。
3. 垃圾回收的優點和原理。并考慮2種回收機制。
Java語言中一個顯著的特點就是引入了垃圾回收機制,使c++程序員最頭疼的內存管理的問題迎刃而解,它使得Java程序員在編寫程序的時候不再需要考慮內存管理。由于有個垃圾回收機制,Java中的對象不再有“作用域”的概念,只有對象的引用才有“作用域”。垃圾回收可以有效的防止內存泄露,有效的使用可以使用的內存。垃圾回收器通常是作為一個單獨的低級別的線程運行,不可預知的情況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,程序員不能實時的調用垃圾回收器對某個對象或所有對象進行垃圾回收?;厥諜C制有分代復制垃圾回收和標記垃圾回收,增量垃圾回收。
4.線程同步的方法。
wait():使一個線程處于等待狀態,并且釋放所持有的對象的lock。
sleep():使一個正在運行的線程處于睡眠狀態,是一個靜態方法,調用此方法要捕捉InterruptedException異常。
notify():喚醒一個處于等待狀態的線程,注意的是在調用此方法的時候,并不能確切地喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且不是按優先級。
Allnotity():喚醒所有處入等待狀態的線程,注意并不是給所有喚醒線程一個對象的鎖,而是讓它們競爭。
5. Error與Exception有什么區別?
Error表示系統級的錯誤和程序不必處理的異常,
Exception表示需要捕捉或者需要程序進行處理的異常。
6. 在java中一個類被聲明為final類型,表示了什么意思?
表示該類不能被繼承,是頂級類。
7 heap和stack有什么區別。
棧是一種線形集合,其添加和刪除元素的操作應在同一段完成。棧按照后進先出的方式進行處理。堆是棧的一個組成元素。
8談談final, finally, finalize的區別。
final—修飾符(關鍵字)如果一個類被聲明為final,意味著它不能再派生出新的子類,不能作為父類被繼承。因此一個類不能既被聲明為 abstract的,又被聲明為final的。將變量或方法聲明為final,可以保證它們在使用中不被改變。被聲明為final的變量必須在聲明時給定初值,而在以后的引用中只能讀取,不可修改。被聲明為final的方法也同樣只能使用,不能重載。
finally—異常處理時提供 finally 塊來執行任何清除操作。如果拋出一個異常,那么相匹配的 catch 子句就會執行,然后控制就會進入 finally 塊(如果有的話)。
finalize—方法名。Java 技術允許使用 finalize() 方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。這個方法是由垃圾收集器在確定這個對象沒有被引用時對這個對象調用的。它是在 Object 類中定義的,因此所有的類都繼承了它。子類覆蓋 finalize() 方法以整理系統資源或者執行其他清理工作。finalize() 方法是在垃圾收集器刪除對象之前對這個對象調用的。
9 Anonymous Inner Class (匿名內部類) 是否可以extends(繼承)其它類,是否可以implements(實現)interface(接口)?
匿名的內部類是沒有名字的內部類。不能extends(繼承) 其它類,但一個內部類可以作為一個接口,由另一個內部類實現。
10 Static Nested Class 和 Inner Class的不同
Nested Class (一般是C++的說法),Inner Class (一般是JAVA的說法)。Java內部類與C++嵌套類最大的不同就在于是否有指向外部的引用上。
注: 靜態內部類(Inner Class)意味著1創建一個static內部類的對象,不需要一個外部類對象,2不能從一個static內部類的一個對象訪問一個外部類對象 .
漢字編碼轉換
相關:
UNICODE 是為了處理包括中文,日文等字符而提出的一種通用的字符集。最初的UNICODE為雙字節字符集,即16位編碼,能夠包括65,536個字符。但這樣的容量并不能滿足所有需要,因此,現在的UNICODE已經擴展到4個字節,能夠容納1,112,064 個字符,而這些在16位之后的擴展背稱為增補字符。
UTF-32 、 UTF-16 和 UTF-8 是 Unicode 標準的編碼字符集的字符編碼方案。
UTF-8 使用一至四個字節的序列對編碼 Unicode 代碼點進行編碼
UTF-8 使用一至四個字節的序列對編碼 Unicode 代碼點進行編碼。U+0000 至 U+007F 使用一個字節編碼,U+0080 至 U+07FF 使用兩個字節,U+0800 至 U+FFFF 使用三個字節,而 U+10000 至 U+10FFFF 使用四個字節。UTF-8 設計原理為:字節值 0x00 至 0x7F 始終表示代碼點 U+0000 至 U+007F(Basic Latin 字符子集,它對應 ASCII 字符集)。這些字節值永遠不會表示其他代碼點,這一特性使 UTF-8 可以很方便地在軟件中將特殊的含義賦予某些 ASCII 字符。
GB2312(1980 年 ) 一共收錄了 7445 個字符,包括 6763 個漢字和 682 個其它符號。漢字區的內碼范圍高字節從 B0-F7 ,低字節從 A1-FE ,占用的碼位是 72*94=6768 。其中有 5 個空位是 D7FA-D7FE 。當然也可以表示數字和字符(一個字節,與 ASCII 表示相同)。
要讀取一個以 GB2312 編碼的包含漢字、數字、字母的二進制文件。
String strName =Encoding.GetEncoding("gb2312").GetString(name,0,i) ;
// name 是讀取的二進制數組。
這樣就能將二進制數組轉換為 漢字、數字或字母
同樣:也可以將包含漢字、數字、字母的字符串轉換為 二進制數組保存到 二進制文件。
String unicodeString = " 備用43E";
Byte[] encodedBytes = Encoding.GetEncoding("gb2312").GetBytes(unicodeString);
當然也可以進行二進制數組與UNICODE,UTF-8等編碼方式的轉換
Byte[] encodedBytes = utf8.GetBytes(unicodeString);
String decodedString = utf8.GetString(encodedBytes);
UnicodeEncoding unicode = new UnicodeEncoding();
Byte[] encodedBytes = unicode.GetBytes(unicodeString);
String decodedString = unicode.GetString(encodedBytes);
F1 Help
F3 查找下一個
shift + F3 反向查找下一個
ctrl + F 查找
ctrl + p 路徑查找
ctrl + F4 運行到當前位置
ctrl+F6在jbuilder中,切換不同的文件
Ctrl+F4關閉正在編輯的文件,
Ctrl + F5切換工程
F5 設置斷點
F7 跟入
F8 單步
F9 運行
ctrl + F9 編譯工程
shift + F9 調試模式運行
ctrl + shift + F9 編譯當前類
ctrl + H 顯示本類成員
ctrl + J 顯示模板
ctrl + shift + J 模板編輯
ctrl + shift + c 自動完成錯誤捕捉代碼
ctrl + Enter /mouseClick 當前關鍵字追蹤
ctrl + shit + 數字(0-9) 設置/去除標簽
ctrl + 數字(0-9) 返回標簽位置
ctrl + alt + -> / <- 返回最近訪問點
ctrl + -> / <- 光標跳過當前單位詞
ctrl + shift + -> / <- 選擇單位詞
ctrl + 上下方向鍵 滾動屏幕
ctrl + home/end/pageup/pagedown
shift +盤方向鍵/home/end/pageup/pagedown 選擇
ctrl + e 增量式查找
ctrl + w 捕捉離光標最近的單詞
ctrl + shift + h 參數查找
ctrl + alt + space class insight
ctrl + F4 關閉當前類
ctrl + shift + F4 關閉(顯示選擇)
ctrl + B 切換窗體
ctrl + F6 切換窗體
ctrl + alt + p/c/m/z/s 視圖開關
ctrl + o/n/c/v/x/p/a
ctrl + g 到指定行
Tab 格式化宿進
ctrl + / 注釋/去除注釋選擇行
ctrl + shift + I 宿進
ctrl + shift + U 反向宿進
常用到的Eclipse快捷鍵
Ctrl+s 存盤
Ctrl+/ 注釋(取消)代碼
Ctrl+shift+/ 注釋代碼塊
Ctrl+shift+\ 取消代碼塊
Alt+/ 代碼輔助/調出IF語句等程序模板:使用方法:打出if,按ALT+/
Ctrl+D 刪除一行
Ctrl+Shift+D 在 debug 模式里顯示變量值
Ctrl+1 快速修復
Ctrl+Shift+f 代碼格式化
Ctrl+Shift+o 整理導入
Ctrl+f6 切換窗口
ctrl+shift+M 導入未引用的包
ctrl+w 關閉單個窗口
F3 跳轉到類、變量的聲明
F11 運行上次程序
Ctrl + F11 調試上次程序
Alt + 回下一個編輯點
ctrl+shift+T 查找工程中的類
Alt-left arrow : 在導航歷史記錄(Navigation History)中后退。就像Web瀏覽器的后退按鈕一樣,在利用F3跳轉之后,特別有用。(用來返回原先編譯的地方)
Alt+right arrow : 導航歷史記錄中向前。
Control+Q : 回到最后依次編輯的地方。這個快捷鍵也是當你在代碼中跳轉后用的。特別是當你鉆的過深,忘記你最初在做什么的時候。
ctrl+Alt+down 復制鼠標所在行到下一行
Alt+down arrow : 將一行或多行向下移動。
Alt+up arrow 將一行或多行 向上移動。
迭代這個名詞對于熟悉Java的人來說絕對不陌生。我們常常使用JDK提供的迭代接口進行java collection的遍歷:
Iterator it = list.iterator();
while(it.hasNext()){
//using “it.next();”do some businesss logic
} |
而這就是關于迭代器模式
應用很好的例子。
二、 定義與結構
迭代器(Iterator)模式,又叫做游標(Cursor)模式。GOF給出的定義為:提供一種方法訪問一個容器(container)對象中各個元素,而又不需暴露該對象的內部細節。
從定義可見,迭代器模式是為容器而生。很明顯,對容器對象的訪問必然涉及到遍歷算法。你可以一股腦的將遍歷方法塞到容器對象中去;或者根本不去提供什么遍歷算法,讓使用容器的人自己去實現去吧。這兩種情況好像都能夠解決問題。
然而在前一種情況,容器承受了過多的功能,它不僅要負責自己“容器”內的元素維護(添加、刪除等等),而且還要提供遍歷自身的接口;而且由于遍歷狀態保存的問題,不能對同一個容器對象同時進行多個遍歷。第二種方式倒是省事,卻又將容器的內部細節暴露無遺。
而迭代器模式的出現,很好的解決了上面兩種情況的弊端。先來看下迭代器模式的真面目吧。
迭代器模式由以下角色組成:
1) 迭代器角色(Iterator):迭代器角色負責定義訪問和遍歷元素的接口。
2) 具體迭代器角色(Concrete Iterator):具體迭代器角色要實現迭代器接口,并要記錄遍歷中的當前位置。
3) 容器角色(Container):容器角色負責提供創建具體迭代器角色的接口。
4) 具體容器角色(Concrete Container):具體容器角色實現創建具體迭代器角色的接口——這個具體迭代器角色于該容器的結構相關。
迭代器模式的類圖如下:
從結構上可以看出,迭代器模式在客戶與容器之間加入了迭代器角色。迭代器角色的加入,就可以很好的避免容器內部細節的暴露,而且也使得設計符號“單一職責原則”。
注意,在迭代器模式中,具體迭代器角色和具體容器角色是耦合在一起的——遍歷算法是與容器的內部細節緊密相關的。為了使客戶程序從與具體迭代器角色耦合的困境中脫離出來,避免具體迭代器角色的更換給客戶程序帶來的修改,迭代器模式抽象了具體迭代器角色,使得客戶程序更具一般性和重用性。這被稱為多態迭代。
三、 舉例
由于迭代器模式本身的規定比較松散,所以具體實現也就五花八門。我們在此僅舉一例,根本不能將實現方式一一呈現。因此在舉例前,我們先來列舉下迭代器模式的實現方式。
1.迭代器角色定義了遍歷的接口,但是沒有規定由誰來控制迭代。在Java collection的應用中,是由客戶程序來控制遍歷的進程,被稱為外部迭代器;還有一種實現方式便是由迭代器自身來控制迭代,被稱為內部迭代器。外部迭代器要比內部迭代器靈活、強大,而且內部迭代器在java語言環境中,可用性很弱。
2.在迭代器模式中沒有規定誰來實現遍歷算法。好像理所當然的要在迭代器角色中實現。因為既便于一個容器上使用不同的遍歷算法,也便于將一種遍歷算法應用于不同的容器。但是這樣就破壞掉了容器的封裝——容器角色就要公開自己的私有屬性,在java中便意味著向其他類公開了自己的私有屬性。
那我們把它放到容器角色里來實現好了。這樣迭代器角色就被架空為僅僅存放一個遍歷當前位置的功能。但是遍歷算法便和特定的容器緊緊綁在一起了。
而在Java Collection的應用中,提供的具體迭代器角色是定義在容器角色中的內部類。這樣便保護了容器的封裝。但是同時容器也提供了遍歷算法接口,你可以擴展自己的迭代器。
好了,我們來看下Java Collection中的迭代器是怎么實現的吧。
//迭代器角色,僅僅定義了遍歷接口
public interface Iterator {
boolean hasNext();
Object next();
void remove();
}
//容器角色,這里以List為例。它也僅僅是一個接口,就不羅列出來了
//具體容器角色,便是實現了List接口的ArrayList等類。為了突出重點這里指羅列和迭代器相關的內容
//具體迭代器角色,它是以內部類的形式出來的。AbstractList是為了將各個具體容器角色的公共部分提取出來而存在的。
public abstract class AbstractList extends AbstractCollection implements List {
……
//這個便是負責創建具體迭代器角色的工廠方法
public Iterator iterator() {
return new Itr();
}
//作為內部類的具體迭代器角色
private class Itr implements Iterator {
int cursor = 0;
int lastRet = -1;
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public Object next() {
checkForComodification();
try {
Object next = get(cursor);
lastRet = cursor++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch(IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
} |
至于迭代器模式的使用。正如引言中所列那樣,客戶程序要先得到具體容器角色,然后再通過具體容器角色得到具體迭代器角色。這樣便可以使用具體迭代器角色來遍歷容器了……
四、 實現自己的迭代器
在實現自己的迭代器的時候,一般要操作的容器有支持的接口才可以。而且我們還要注意以下問題:
在迭代器遍歷的過程中,通過該迭代器進行容器元素的增減操作是否安全呢?
在容器中存在復合對象的情況,迭代器怎樣才能支持深層遍歷和多種遍歷呢?
以上兩個問題對于不同結構的容器角色,各不相同,值得考慮。
五、 適用情況
由上面的講述,我們可以看出迭代器模式給容器的應用帶來以下好處:
1) 支持以不同的方式遍歷一個容器角色。根據實現方式的不同,效果上會有差別。
2) 簡化了容器的接口。但是在java Collection中為了提高可擴展性,容器還是提供了遍歷的接口。
3) 對同一個容器對象,可以同時進行多個遍歷。因為遍歷狀態是保存在每一個迭代器對象中的。
由此也能得出迭代器模式的適用范圍:
1) 訪問一個容器對象的內容而無需暴露它的內部表示。
2) 支持對容器對象的多種遍歷。
3) 為遍歷不同的容器結構提供一個統一的接口(多態迭代)。
六、 總結
迭代器模式在我們的應用中很廣泛,希望本文能幫助你理解它。如有不對之處,還請不吝指正。
非常感謝最近發布的Hibernate 3中的XML持久性特性,Java開發者現在擁有了一個框架組件,它為易于實現的對象關系(OR)和XML持久性提供了高效的和一致的方法。
Hibernate的易用性、高性能和對象關系持久性等高級特性給IT界帶來了很大的驚喜。 Hibernate的最新版本(版本3,3月29日發布的)給產品API帶來了一個重要的新特性:XML持久性。有了Hibernate 3之后,Java應用程序開發者可以輕易地把XML文檔合并到關系型數據庫中。
這個新特性應該明確地告訴已有的Hibernate開發者,因為它也遵循POJO(純的舊Java對象)相同的一致性方法,需要學習的知識最少。XML持久性的優點也應該介紹給新用戶。本文講解的是Hibernate 3持久性方法。
XML持久性為什么重要
大多數大型商業數據庫都支持某種形式的本地XML持久性。由于XML持久性是一個相對較新的機制--即使對大型廠商也是如此,這個領域中的標準還在不斷地浮現。其結果是,為了把無處不在的關系型持久性機制與日益增長的XML解決方案集成在一起,架構師必須依賴廠商特定的特性或者實現定制的XML持久性框架組件。這兩個選擇都沒有太大的吸引力。廠商特定的特性不是普及的,因為可能產生廠商封鎖(lock-in),而定制的框架組件實現可能耗費大量的時間和財力,導致代碼難于維護。
在OR(對象關系)持久性方面,Hibernate XML持久性是一個自然而然的解決方案。它可以跨越Hibernate支持的所有關系型平臺(如虛擬的或真實的關系型平臺)移動,允許自由的遷移對象、基于XML的應用程序和集成解決方案而不用擔心下層的關系型實現方法。
體系結構的細節信息
Hibernate是一個良好架構的框架組件,它無縫地利用了本地的環境,不需要用戶進行任何特殊的干涉或安裝操作。從一個數據庫切換到另外一個數據庫通常只需要改變驅動程序,并配置Hibernate(在線配置設置信息)來使用另外一種數據庫語言。
Hibernate利用dom4j框架組件進行XML的分析和維護。如果需要完全利用Hibernate的XML特性,你就必須對dom4j非常熟悉。一般來說,你會發現dom4j比Java提供的JAXP或與JAXP兼容的XML分析器要容易使用一些。它要求我們學習的相關知識較少,并且利用最少的dom4j知識你就能夠高效率地使用Hibernate XML持久性。
實際例子:價格目錄同步
通用的電子商務案例可以演示XML關系持久性機制的作用。我們來考慮一個示例,在這個例子中XML集成了在線零售商和供應商之間的產品標價目錄。
該電子目錄包含了已標價的產品列表。在線商店銷售產品,通過自己的存貨清單來管理(類似于Amazon與Toys-R-Us和運動產品商店之間的關系)。為了精確地和有效地反映價格的變化,在線零售商必須頻繁地接收產品價格信息。它把這些信息存放為XML文檔,如下所示:
<products>
<product prod_id="3" sku="100101">
<description>Athlete mode body fat scale</description>
<list_price>100.00</list_price>
<drop_price>60.00</drop_price>
</product>
<product prod_id="4" sku="100102">
<description>Thermometer</description>
<list_price>20.00</list_price>
<drop_price>11.00</drop_price>
</product>
</products> |
全面的主要的產品價格列表存儲在數據庫中,如下所示:
CREATE TABLE PRODUCT
(
id INT UNIQUE NOT NULL,
description VARCHAR(45) NOT NULL,
sku VARCHAR(45) UNIQUE NOT NULL,
list_price FLOAT,
base_price FLOAT,
order_price FLOAT,
CONSTRAINT PK_PRODUCT PRIMARY KEY (id )
) |
在線零售商通過已有的OR映射提供定價目錄的Web表現形式,定價產品都表現為demo.Product Java對象:
/** Product對象表現了定價目錄項*/
public class Product {
int id;
String sku;
String description;
Double listPrice;
Double basePrice;
Double orderPrice; |
這些對象按照下面的方式映射(為了清楚,我們列出了列名,盡管在屬性和列名相匹配的時候Hibernate可以自動地把屬性映射為列名):
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="demo">
<class name="Product"
table="product"
node="product">
<id name="id"
type="int"
node="@prod_id"
column="id">
</id>
<property name="sku" node="@sku" column="sku" not-null="true"/>
<property name="description" node="description" column="description" not-null="true"/>
<property name="listPrice" node="list_price" column="list_price" />
<property name="basePrice" node="drop_price" column="base_price"/>
<property name="orderPrice" column="order_price"/>
</class>
</hibernate-mapping> |
在這種情況下,Hibernate的XML關系持久性就非常方便了。由于該電子商務應用程序接收了包含產品價格更新的XML,它就利用Hibernate的XML持久性機制把這些XML寫入到產品數據庫中。Hibernate提供了幾種XML持久性選擇,包括Hibernate的saveOrUpdate方法:
document = saxReader.read(inputXML);
List users = document.selectNodes("http://product");
try {
Session session = ibernateUtil.sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Session dom4jSession = session.openSession(EntityMode.DOM4J);
Iterator iter = users.iterator();
while (iter.hasNext()) {
Object next = iter.next();
dom4jSession.saveOrUpdate("demo.Product", next );
}// end while
transaction.commit();
session.close(); |
XML映射語法
上面的例子中使用的映射文件不用于Hibernate 2的映射文件。Hibernate 3引入了幾種專門用于XML持久性的新映射類型。
主要的新映射屬性是節點(node),它與被映射的XML文檔中的一個元素或文檔中的屬性相關聯。
一個"節點"可能表現為下面的某種映射:
· "element-name(元素名)":在例子中,<product></product>元素會被表示為node="product"。
· "@attribute-name(屬性名)":在例子中,node="@sku"會被映射為XML屬性<product sku="1001001">。
· ".(句點)":映射為元素的父元素(例如<products>就<product>是的父元素)。
· "element-name/@attribute-name(元素名/屬性名)":映射為命名元素的屬性(product/@sku)。
XML持久性并非Hibernate的主要任務
Hibernate 3框架組件高效率地實現了目前最通用的一些方法(除了LDAP之外)。Java社團現在擁有了一套框架組件,它為易于實現的OR和XML持久性提供了高效率的和一致性的方法。
在我們知道上面一些內容之后,了解Hibernate項目的任務是很重要的。盡管Hibernate 3的XML特性非常有用、有吸引力,但是它們并不是用來代替最流行的XML編組(marshalling)和轉換(transformation)框架組件的。不管它的OR映射解決方案多么完善,我們都不應該期待Hibernate成為主流的XML維護框架組件(根據Hibernate的作者Gavin King在TheServerSide Java Symposium 2005上的發言)。
由于這個原因,你應該把XML持久性特性看作是已有的強大的Hibernate框架組件的有用的擴展,它允許你輕易地把現在流行的其它的數據表現機制合并到自己的應用程序中。但是,如果你必須處理復雜的集成和轉換情況,最好去尋找其它的XML專用的框架組件。
一、首先學習hibernate.cfg.xml配置文件的具體配置
<?xml version="1.0" encoding="UTF-8"?>
<!--指定該文件的官方dtd-->
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd" >
<hibernate-configuration>
<session-factory>
<!-- 顯示sql語言 -->
<property name="show_sql">true</property>
<!-- sql語言 -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- jdbc驅動程式 -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<!-- jdbc url -->
<property name="connection.url">jdbc:mysql://localhost:3306/test</property>
<!-- 數據庫用戶名 -->
<property name="connection.username">root</property>
<!-- 數據庫密碼 -->
<property name="connection.password">wyq</property>
<!-- C3P0連接池設定 -->
<!--最小連接數-->
<property name="c3p0.min_size">5</property>
<!--最大連接數-->
<property name="c3p0.max_size">20</property>
<!--延遲所允許的時間-->
<property name="c3p0.timeout">1800</property>
<!--緩存所允許的最大連接數-->
<property name="c3p0.max_statements">50</property>
<!-- 每隔100筆資料送入資料庫,清除緩存(定期清除緩存,減小壓力) -->
<property name="hibernate.jdbc.batch_size">100</property>
<!-- 設定事務管理的工廠類 -->
<property name="hibernate.transaction.factiory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
<mapping resource="com/wyq/hibernate/pojo/User.hbm.xml"/>
<mapping resource="com/wyq/hibernate/pojo/TUser.hbm.xml"/>
<mapping resource="com/wyq/hibernate/pojo/Room.hbm.xml"/>
</session-factory>
</hibernate-configuration>
需要的jar包有c3p0.jar,hibernate3.jar,數據庫.jar,log4j.jar
------------------------------------------------------------------------------------------------------------------------
1、讀取配置文件獲得連接
讀取hibernate.cfg.xml配置文件,hibernate.cfg.xml文件放在Classpath下,使用下面的方式讀入該文件
//Configuration 負責管理hibernate配置信息
Configuration config=new Configuration().configure();
//根據config建立SessionFactory
//SessionFactory用于建立Session
SessionFactory sessionFactory=config.buildSessionFactory();
//開啟session,相當于jdbc的Connection
session = sessionFactory.openSession();
2、Criteria 基本資料查詢
(1)標準查詢:
//創建查詢標準
Criteria criteria=session.creteCriteria(User.class);
//查詢條件
criteria.add(Expression.eq("name","caterpillar"));
************************************************************************************
Expression.eq(String s1,String s2)---------->相等s1=s2
Expression.allEq(Map map) --------------->多個屬性-值對應關系,多個Expression.eq疊加
Expression.gt(String s1,String s2)----------->大于s1>s2
Expression.ge(String s1,String s2)----------->大于等于s1>=s2
Expression.lt(String s1,String s2)------------>小于s1<s2
Expression.le(String s1,String s2)------------>小于等于s1<=s2
Expression.between(String s1,int s2,int s3)--->s2<s1<s3
Expression.like(String s1,String s2)------------>s1 like s2
比較2個屬性
Expression.eqProperty(String s1,String s2)--->s1=s2
Expression.gtProperty(String s1,String s2)---->s1>s2
Expression.geProperty(String s1,String s2)---->s1>=s2
Expression.ltProperty(String s1,String s2)----->s1<s2
Expression.leProperty(String s1,String s2)----->s1<=s2
Expression.and()----->Expression.and(Expression.eq("String s1,String s2"),Expression.eq(String s3,String s4))
Expression.or()
************************************************************************************
(2)高級查詢
一、可以使用Criteria進行查詢,并用order對結果進行排序。
//設置從第幾條開始取的記錄
criteria.setFirstResult(100);
//最多取的幾條記錄
criteria.setMaxResults(20);
//對結果進行排序
criteria.addOrder(Order.asc(String s1));
criteria.addOrder(Order.desc(String s2));
二、可以對查詢結果進行統計操作,使用Projections的rowCount(),count(),max(),min(),countDistinct()等方法:
例如:criteria.setProjection(Projections.max("age"));
三、還可以用Projections的groupProperty()來對結果進行分組
例如:criteria.setProjection(Projections.groupProperty("age"));
(***)四、結合統計與分組的功能,可以用ProjectionList
例如:ProjectionList projectionList =Projections.projectionList();
projectionList.add(Projections.groupProperty("age"));
projectionList.add(Projections.rowCount());
criteria.setProjection(projectionList);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//查詢所有記錄
List users=criteria.list();
Iterator iterator=users.iterator();
while(iterator.hasNext()){
User user=(User)iterator.next();
System.out.println(user.getId()+"\t"+user.getName()+"/"+user.getAge());
}
3、criteria的增、刪、改(還不完善)
在用到增、刪、改時,必須先聲明事務
增加:
Transaction tx = session.beginTransaction();//Transaction表示一組會話操作
session.save(user);//將事物映射到數據庫進行存儲
tx.commit();
session.close();
刪除:
Session session=this.getSession();
User user=(User)session.get(User.class, new Integer(1));
Transaction tx = session.beginTransaction();//Transaction表示一組會話操作
session.delete(user);//將事物映射到數據庫進行存儲
tx.commit();
session.close();
修改:
Session session=this.getSession();
User user =(User)session.get(User.class,new Integer(2));//創建持久化的事物
user.setName("wyqqqqqqqqqq");
user.setAge(new Integer(30));
Transaction tx = session.beginTransaction();//Transaction表示一組會話操作
session.update(user);//將事物映射到數據庫進行存儲
tx.commit();
session.close();
----------------------------------------------------------------------------------------------------------------------
一、Query查詢可以先設定查詢參數,之后通過set等方法,將指定的參數值添入.還可以使用命名參數
Session session = sessionFactory.openSession();
Query query = session.createQuery("select user.name from User as user where user.age>?( :minAge )");
query.setInteger(0,25);
query.setInteger("minAge",25);
List names=query.list();
Iterator iterator = names.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
session.close();
二、如果查詢整個表直接使用from User如果針對某個屬性使用select user.name from User as user
使用hql可以更接近我們平時的jdbc編程,和把sql語句寫在程序中差不多,另外,也可以將sql語句寫在配置文件中。
--------------------------------------------------------------------------------------------------------------------------
多表關聯
一、多對一進行關聯(多個學生對應同一間宿舍)---學生是主體,宿舍是附體,關聯關系<many-to-one>在主體學生中設置,在學生類中設置宿舍類,由于宿舍類只有一個可以直接用類來設置,在映射學生類(User)中包含宿舍這個類(Room),在映射配置文件(User.hbm.xml)中定義
<many-to-one name="room" column="room_id" cascade="save-update" class="com.wyq.hibernate2.Room"></many-to-one>
哪個是主體類就在哪個配置文件定義關聯關系.
cascade屬性:表示關聯對象的持久化,該屬性也要設置在主體中,作用就是當主控方執行操作時,關聯對象(被動方)是否同步執行同一操作.
cascade的值:all:表示所有情況下都進行級聯操作.
none:所有情況下都不進行級聯操作
save-update:在執行save-update時進行級聯操作.
delete:在執行delete時進行級聯操作.
注意:使用cascade自動持久化時,會先檢查被關聯物件的id屬性,未被持久化的物件之id屬性是由unsaved-value決定,預設是null,如果您使用long這樣的原生型態(primitive type)時,則必須自行指定預設值.
例如:<id name="id" column="ROOM_ID" unsaved-value="0">
<generator class="increment"/>
</id>
如果您不想額外設定unsaved-value資訊,則可以將long改為Long,這可以符合預設的unsaved-value為null的設定 .
二、一對多進行關聯(一間宿舍對應多個學生)---宿舍是主體,學生是附體,關聯關系<one-to-many>在主體宿舍中設置,由于要在宿舍類中設置學生類,一個宿舍包含多個學生,所以在宿舍類中要用Set類來進行設置,用set類(private Set users = new HashSet();)來存儲多個學生類,在映射宿舍類(Room)中要包含<set>這個節點,用來與user相關聯
例如:<set name="users" table="USER">
<key column="ROOM_ID"/>
<one-to-many class="onlyfun.caterpillar.User"/>
</set>
name:表示屬性,table:表示關聯的表名,key:表示通過什么字段進行關聯,<one-to-many>:表示關聯類。這里也可以使用cascade屬性。
三、在表關聯的設計中,不論是一對多還是多對一,都要將關聯字段設置在多的那一方。
例如:user表格和room表格,要將關聯字段room_id設置在user表格中。
四、一對一進行關聯(一個人只有一個房間,一個房間也只有一個人)。
可以通過2中方式進行關聯:
(1)、通過外鍵進行關聯:在多對一的例子中就是通過外鍵進行關聯的.
在user-room的設置中(user.hbm.xml):
<many-to-one name="room"
column="ROOM_ID"
class="onlyfun.caterpillar.Room"
cascade="all"
unique="true"/>
其中unique表示限制一個User有一獨有的 Room,這只是單向的,說明一個user只有一個room.
在room-user的設置中(room.hbm.xml):
<one-to-one name="user"
class="onlyfun.caterpillar.User"
property-ref="room"/>
這樣就完成了雙向的一對一關聯,property-ref告訴hibernate,查詢出user并將其參考至room。
(2)、通過主鍵進行關聯:限制兩個資料表的主鍵使用相同的值,如此一個User與Room就是一對一關係
user.hbm.xml:
<one-to-one name="room"
class="onlyfun.caterpillar.Room"
cascade="all"/>
room.hbm.xml:
<one-to-one name="user"
class="onlyfun.caterpillar.User"
constrained="true"/>
使用constrained="true"告訴Hibernate參考至User的主鍵
五、雙向關聯,就是將一和二結合起來,如果將關聯的維護交給User的話會比較容易,因為每個User都對應至一個Room,在儲存時並用像Room一樣必須對Set中的每個物件作檢查,為了將關聯的維護交給User,我們可以在Room.hbm.xml中的<set>修改,加上inverse="true",表示將關聯的維護「反過來」交給User作
例如:<set name="users" table="users" iinverse="true" cascade="all">
<key column="room_id"/>
<one-to-many class="onlyfun.caterpillar.User"/>
在設立雙向關聯時,關聯由多對一中「多」的哪一方維護,會比由「一」的哪一方維護來的方便,在Hibernate可以藉由inverse來設定,不設定inverse基本上也可以運行,但是效能會較差。
------------------------------------------------------------------------------------------------------------------------
在Hibernate中,集合類的映射可以延遲初始(Lazy Initialization),在多對一或者一對多中,都可以使用延遲初始,例如:一個用戶(user對應user表)有多個email地址(address對應address表),也就是在真正索取該物件的資料時,才向資料庫查詢,就上次例子來說,就是我們在讀取User時,先不取得其中的 addrs屬性中之物件資料,由於只需要讀取User的name屬性,此時我們只要執行一次select即可,真正需要addrs的資料時,才向資料庫要求。在含有集合類的user.hbm.xml中要如下設置:
<set name="addrs" table="ADDRS" lazy="true">
<key column="USER_ID"/>
<element type="string" column="ADDRESS" not-null="true"/>
</set>
--------------------------------------------------------------------------------------------------------------------------
session是hibernate運做的核心,是有SessionFactory所創建,sessionFactory是線程安全的,你可以讓多個線程同時存取SessionFactory,而不會有資源共用的問題,然而session不是設計為線程安全的,所以讓多個線程共用一個session,將發生資料共用而發生混亂的問題.下面是一個標準類.
import java.io.Serializable;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Transaction;
public class HibernateSessionUtil implements Serializable
{
//創建線程局部變量 tLocalsess
public static final ThreadLocal tLocalsess = new ThreadLocal();
//創建線程局部變量 tLocaltx
public static final ThreadLocal tLocaltx = new ThreadLocal();
//取得session
public static Session currentSession(){
//從線程變量tLocalsess中,取得當前session
Session session = (Session) tLocalsess.get();
//判斷session是否為空,如果為空,將創建一個session,并付給線程變量tLocalsess
try{
if (session == null){
session = openSession();
tLocalsess.set(session);
}
}catch (HibernateException e){
throw new InfrastructureException(e);
}
return session;
}
//關閉當前session
public static void closeSession(){
//從線程變量tLocalsess中,取得當前session
Session session = (Session) tLocalsess.get();
//設置線程變量tLocalsess為空
tLocalsess.set(null);
try{
//關閉session
if (session != null && session.isOpen()){
session.close();
}
}catch (HibernateException e){
throw new InfrastructureException(e);
}
}
//事物處理
public static void beginTransaction(){
//從線程變量tLocaltx中取得事物管理對象Transaction
Transaction tx = (Transaction) tLocaltx.get();
try{
//如果為空就從session中創建一個tx
if (tx == null){
tx = currentSession().beginTransaction();
tLocaltx.set(tx);
}
}catch (HibernateException e){
throw new InfrastructureException(e);
}
}
//提交事物
public static void commitTransaction(){
//取得事物
Transaction tx = (Transaction) tLocaltx.get();
try{
//如果不為空就提交
if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack())
tx.commit();
tLocaltx.set(null);
}catch (HibernateException e){
throw new InfrastructureException(e);
}
}
//事物回滾
public static void rollbackTransaction(){
//取得tx事物
Transaction tx = (Transaction) tLocaltx.get();
try{
//將變量清空
tLocaltx.set(null);
if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack()){
//事物回滾
tx.rollback();
}
}catch (HibernateException e){
throw new InfrastructureException(e);
}
}
//取得session
private static Session openSession() throws HibernateException{
return getSessionFactory().openSession();
}
//取得sessionFactory
private static SessionFactory getSessionFactory() throws HibernateException{
return SingletonSessionFactory.getInstance();
}
}
filter的代碼:
public class HibernateSessionCloser implements Filter{
protected FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig)throws ServletException{
this.filterConfig = filterConfig;
}
public void destroy(){
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
try{
chain.doFilter(request, response);
}
finally{
try{
HibernateSessionUtil.commitTransaction();
}catch (InfrastructureException e){
HibernateSessionUtil.rollbackTransaction();
}finally{
HibernateSessionUtil.closeSession();
}
}
}
}
---------------------------------------------------------------------------------------
(1)、悲觀鎖定(Pessimistic Locking)一如其名稱所示,悲觀的認定每次資料存取時,其它的客戶端也會存取同一筆資料,因此對該筆資料進行鎖定,直到自己操作完成後解除鎖定。
悲觀鎖定通常透過系統或資料庫本身的功能來實現,依賴系統或資料庫本身提供的鎖定機制,Hibernate即是如此,可以利用Query或 Criteria的setLockMode()方法來設定要鎖定的表或列(Row)及其鎖定模式,可設定的鎖定模式有以下的幾個:
LockMode.WRITE:在insert或update時進行鎖定,Hibernate會在save()方法時自動獲得鎖定。
LockMode.UPGRADE:利用SELECT ... FOR UPDATE進行鎖定。
LockMode.UPGRADE_NOWAIT:利用SELECT ... FOR UPDATE NOWAIT進行鎖定,在Oracle環境下使用。
LockMode.READ:在讀取記錄時Hibernate會自動獲得鎖定。
LockMode.NONE:沒有鎖定。
(2)、樂觀鎖定(Optimistic locking)則樂觀的認為資料的存取很少發生同時存取的問題,因而不作資料庫層次上的鎖定,為了維護正確的資料,樂觀鎖定使用應用程式上的邏輯實現版本控制的解決。
在不實行悲觀鎖定策略的情況下,資料不一致的情況一但發生,有幾個解決的方法,一種是先更新為主,一種是後更新的為主,比較複雜的就是檢查發生變動的資料來實現,或是檢查所有屬性來實現樂觀鎖定。
要注意的是,由於樂觀鎖定是使用系統中的程式來控制,而不是使用資料庫中的鎖定機制,因而如果有人特意自行更新版本訊息來越過檢查,則鎖定機制就會無效,例如在上例中自行更改userV2的version屬性,使之與資料庫中的版本號相同的話就不會有錯誤,像這樣版本號被更改,或是由於資料是由外部系統而來,因而版本資訊不受控制時,鎖定機制將會有問題,設計時必須注意。
持久層的組成
這一節的名字應該換成“基于Hibernate的持久層的組成”更合適一點,可是它太長了。既然Hibernate是用來開發持久層,那么我先介紹一下這個持久層中的各個元素。
1. POJO:Plain Old Java Object,你可以把它看作是簡單的JavaBean。一般說來,一張數據庫表對應一個POJO,也就是對象/關系的一一映射。
2. DAO:對于每一個POJO,一般都有一個DAO與之對應,承擔所有關于該POJO的訪問控制。實際上也就是控制了對數據庫中一張表的訪問控制。
3. *.hbm.xml文件:這個文件定義了POJO和數據庫中的表是如何映射的,比如POJO中的字段對應數據庫表中的哪個字段等等。一般每個映射都用單獨的文件來描述,也就是有一個POJO就有一個*.hbm.xml文件。
4. *.cfg.xml文件:這個文件定義了Hibernate的基本信息,比如數據庫驅動,用戶名,密碼等等連接信息,也包括了所有要用的*.hbm.xml文件,在初始化的時候,Hibernate會讀取這個文件來找相應的映射文件完成對象/關系。
我們還是以上文的例子來詳細描述一下這里提到的各個元素的內容。
1. Student.java:
代碼片段4:
-
- public class Student implements java.io.Serializable
- {
- private String id;
- private String name;
- private Set courseSelections = new HashSet(0);
-
- public Student()
- {
- }
-
- public String getId()
- {
- return this.id;
- }
-
- public void setId(String id)
- {
- this.id = id;
- }
-
- public String getName()
- {
- return this.name;
- }
-
- public void setName(String name)
- {
- this.name = name;
- }
-
- public Set getCourseSelections()
- {
- return this.courseSelections;
- }
-
- public void setCourseSelections(Set courseSelections)
- {
- this.courseSelections = courseSelections;
- }
- }
這個類就是一個POJO,你可以很明顯的看出來它就是一個JavaBean。我想解釋它的courseSelection字段。很顯然,在數據庫表student中,沒有這個字段。這里的這個字段是因為一個外鍵引用,course_selection的student_id是一個外鍵,引用了student表中的id字段。那么在Student類中courseSelection來記錄這樣的外鍵關系,也就是說,當我們獲取了Student對象以后,就可以直接獲取他的選課記錄,這樣就為上層的調用提供了很大的方便。這里有點模糊沒關系,我在介紹映射定義文件(*.hbm.xml)的時候還會提到這個問題。
2. StudentDAO.java
代碼片段5:
-
- public class StudentDAO
- {
- Session session;
- public StudentDAO()
- {
- Configuration cfg = new Configuration();
- cfg.configure("/hibernate.cfg.xml");
- SessionFactory sessionFactory = cfg.buildSessionFactory();
- session = sessionFactory.openSession();
- }
-
- public void save(Student transientInstance)
- {
- session.save(transientInstance);
- }
-
- public void delete(Student persistentInstance)
- {
- session.delete(persistentInstance);
- }
-
- public Student findById(java.lang.String id)
- {
- List list = session.createCriteria(Student.class).add(
- Expression.eq("id", id)).list();
- if (list.size() > 0)
- {
- return (Student)list.get(0);
- }
- return null;
- }
- }
這里的構造函數是用來啟動Hibernate,并獲取session。打開一個session就相當于打開了一個數據庫連接,然后我們就可以對這個session進行操作,完成數據庫操作,完全不用寫SQL語句。我這里Hibernate的啟動方式寫的很不規范,系統應該只需要完成一次Hibernate啟動就可以在不同的DAO中使用,我把它寫在構造函數里面純粹是為了簡化演示代碼。
你可以看到save和delete方法都很簡單直接對對象操作,而findById就有些麻煩,因為這里有一個查詢過程在里面。Hibernate里面查詢可以用Criteria這個類來完成,我們也常用Hibernate獨有的HQL(Hibernate Query Language)來完成查詢。當然Hibernate也是支持原生SQL的。關于查詢的詳細信息請參考其他文章或書籍,我只是演示一個流程,介紹一些概念。
3. Student.hbm.xml
代碼片段6:
-
- <hibernate-mapping>
- <class name="Student" table="STUDENT">
- <id name="id" type="string">
- <column name="ID" length="10" />
- <generator class="assigned" />
- </id>
- <property name="name" type="string">
- <column name="NAME" not-null="true" />
- </property>
- <set name="courseSelections" inverse="true">
- <key>
- <column name="STUDENT_ID" length="10"
- not-null="true" />
- </key>
- <one-to-many class="CourseSelection" />
- </set>
- </class>
- </hibernate-mapping>
這個文件定義了Student類和Student表是如何映射的。class元素定義了Sudent類和STUDENT表映射,然后就定義了各個屬性是如何映射的。如果一個屬性是數據庫的key,那么會用id標簽來定義,column定義了當前類的屬性和數據庫中的哪個字段對應,generator是id特有的。一般來說id是自增的,由于我的數據庫是用的Oracle,它沒有自增字段,要實現自增必須用Sequence,這超出了本文的范圍,所以我就用assigned來簡化示例代碼。assigned表示id是用戶給定的。
有一個比較特別的標簽是set,它對應著數據庫中的外鍵關系,上文我提到的通過Student對象可以獲得所有相關的選課記錄就是通過這里的定義實現的。name屬性對應了Student類中的字段名,key表示哪個字段是外鍵,one-to-many表示Student和CourseSelection是一對多關系,這和事實相符。類似的還有many-to-one,many-to-many,不過這些都不常用,我不介紹了。Hibernate根據這個映射定義文件,在實例化一個POJO(比如Student)的時候,會自動的把定義過映射的屬性用數據庫中的數據填充,set也包括在內。
4. hibernate.cfg.xml
代碼片段7:
-
- <hibernate-configuration>
- <session-factory>
- <property name="connection.username">test</property>
- <property name="connection.url">
- jdbc:oracle:thin:@10.85.33.199:1521:glee</property>
- <property name="dialect">
- org.hibernate.dialect.Oracle9Dialect</property>
- <property name="connection.password">test</property>
- <property name="connection.driver_class">
- oracle.jdbc.OracleDriver</property>
- <mapping resource="Student.hbm.xml"></mapping>
- <mapping resource="CourseSelection.hbm.xml"></mapping>
- <mapping resource="Course.hbm.xml"></mapping>
- </session-factory>
- </hibernate-configuration>
這個文件我不解釋了,自己看吧。結合上文StudentDAO的例子,我想你應該能看明白。
看了這么多,或許你會有點頭皮發麻,POJO,DAO,配置文件...好像要寫的東西還是很多。值得慶幸的是現在Hibernate已經發展的比較成熟了,有很多工具來幫助我們完成這些工作,比如MiddleGen,Hibernate Synchronizer等等。我使用的開發工具是Eclipse+MyEclipse,我所要做的只是把數據庫表建好,然后MyEclipse提供的工具會自動根據數據庫表生成POJO,DAO,*.hbm.xml,甚至hibernate.cfg.xml都是自動完成的(前提是MyEclipse知道你的數據庫連接信息)。我并不打算介紹如何用IDE來開發Hibernate,你可以參考IDE的幫助文檔。
到這里為止,使用Hibernate進行開發的基本組成元素我都介紹好了,強烈建議你馬上實踐一遍,即使有些不理解,也先依葫蘆畫瓢一個。對了,別忘了把Hibernate的包down下來放到classpath里面。
三、Session與SessionFactory
Session可以說是Hibernate的核心,Hibernate對外暴露的接口就是Session。所以我這里講一下有關Session的常用函數和特性。
在講Session之前,我想先提一下SessionFactory,這個東西不復雜,只要配置好就行了。顧名思義,SessionFactory就是用來創建Session的。SessionFactory是線程安全的,也就是說對于同一個數據庫的所有操作共享一個SessionFactory就行了?;仡^看代碼片段5,我們可以看到SessionFactory的常用配置方式。
代碼片段8:
-
- Configuration cfg = new Configuration();
- cfg.configure("/hibernate.cfg.xml");
- SessionFactory sessionFactory = cfg.buildSessionFactory();
我們通過Configuration來讀取配置文件,然后就可以創建SessionFactory,這段代碼在 所有系統中都大同小異,一般就是xml配置文件的名字不一樣,所以也沒什么好說的。
當我們有了SessionFactory以后就可以獲取Session了。調用SessionFactory.openSession()就會返回一個Session實例,然后我們操作這個Session來訪問數據庫。值得一提的是Session并不是線程安全的,也就是每一個線程都必須有自己的Session。所以我們一般通過以下方法來獲取和關閉Session:
代碼片段9:
-
- public static Session currentSession() throws HibernateException
- {
- Session session = (Session) threadLocal.get();
- if (session == null || !session.isOpen())
- {
- if (sessionFactory == null)
- {
- try
- {
- cfg.configure(CONFIG_FILE_LOCATION);
- sessionFactory = cfg.buildSessionFactory();
- }
- catch (Exception e)
- {
- e.printStackTrace();
- }
- }
- session = (sessionFactory != null) ?
- sessionFactory.openSession(): null;
- threadLocal.set(session);
- }
- return session;
- }
-
- public static void closeSession() throws HibernateException
- {
- Session session = (Session) threadLocal.get();
- threadLocal.set(null);
-
- if (session != null)
- {
- session.close();
- }
- }
可以看到,我們通過threadLocal來保存每個線程的session,這樣就保證了各個線程之 間的互不干擾,也保證了系統只有一個SessionFactory實例(對于大多數應用來說已經 足夠了)。如果你使用MyEclipse進行開發的話,它會自動生成一個 HibernateSessionFactory.java,其中就包含了以上代碼。
好了,現在我們已經獲得了Session,下面我來介紹以下Session的常用函數,這些函數都有很多重載函數,我只介紹以下大概是干嘛的,不一一解釋,詳細信息你可以查看Hibernate的API。
1.Session.get(),獲取某個類的實例,一般都是通過id來獲取比如
Session.get(Student.class, "0361095");
這句話的意思就是獲取id(primary key)為“0361095”的Student對象。這里要注 意的是第二個參數必須是Object,也就是說,如果是long類型的1,那么必須轉換 成new Long(1)再傳入。
2.Session.load(),用法和意義都和get一樣,不過它們還是有點區別,我稍后解釋。
3.Session.save(),將某個實例保存到數據庫中去(往往在數據庫中形成一條新的記錄)。
4.Session.update(),更新某個實例,這個實例必須和數據庫中有對應,否則會報錯。
5.Session.delete(),刪除某個實例,也就是刪除這個實例對應的數據表中的數據。
6.Session.saveOrUpdate(),保存或者更新某個實例,調用這個方法你就不用去關心到 底是save還是update了,它會自己判斷,然后調用相應的函數。其實save和update 涉及到實體對象生命周期中的三種狀態,這個比較重要,我在后面會單獨講的。
對于get和load的區別,很難講清楚,這里涉及到Hibernate的緩存機制,是一個非常 復雜的話題,我不打算深入討論這個內容。簡單的來講,Session實現了Hibernate的一 級緩存,SessionFactory實現了Hibernate的二級緩存。load方法會先查找一級緩存再查 找二級緩存,最后再去數據庫中找,而get只會查找一級緩存,然后就去數據庫中找了。 這只是是get和load的一個區別,另外的區別如果你有興趣的話自己去google吧。關于Hibernate的緩存機制,如果你只是一般用用Hibernate的話沒有必要深入研究,就當它不存在好了,get和load到底用哪個也不用非常講究,如果你用工具生成DAO的話, 生成的代碼用什么就用什么吧。
四、關鍵概念的理解
在使用Hibernate進行開發的時候有幾個概念在我看來是必須理解的,否則在開發的時候會遇到很多問題而摸不著頭腦。
1.Lazy loading,懶加載(延遲加載)
這個技術在很多地方被應用,比如Eclipse的插件管理也是用的延遲加載。在Hibernate中,所謂延遲加載就是返回一個POJO但是某些數據(往往是實體類型或者Set類型)并沒有被真正的被填充,直到POJO的某個字段真正被引用的時候才從數據庫中讀取相應的數據來填充POJO中的字段。這樣就能有效的避免很多不必要的數據庫操作,因為POJO的有些數據我們并不需要,而且數據庫操作是很費時間的。在Hibernate2中,默認是非延遲加載的,而在Hibernate3中,默認就是延遲加載了。
如果使用了延遲加載,那么在讀取數據的時候有一個問題必須注意,那就是在數據真正被加載之前,Session不能被關閉。你可以回頭看一下代碼片段5,我在構造函數里面open了session以后就沒有關閉這個session,所以我在使用的時候沒有什么問題,但這樣總占著數據庫連接也不好,用好了應該及時關閉給別人用。我上文給的例子中沒有關閉Session的代碼,要加的話給DAO加一個方法調用Session.close(),然后在代碼片段1中的return之前調用這個方法就行了。
Hibernate的延遲加載機制遠不是這么簡單,但是普通的應用沒有必要去深究這些東西,了解這么多就夠了。
2. Object lifecycle,對象生命周期
在Hibernate中,對象分為三種狀態,Transient(自由狀態)、Persistent(持久狀態),Detached(游離狀態),下面我分別解釋一下。
1、自由狀態:所謂自由狀態就是說這個對象是自由的,與Hibernate無關,比 如:
Student student = new Student();
student.setId("0361095");
這里的student就是一個普通的對象與hibernate無關,稱為自由狀態。
2、持久狀態:所謂持久狀態就是指對象和數據庫中的數據(持久狀態的數據) 有關聯,也就是說對象被Hibernate所管理了,比如:
session.save(student);
這樣student對象就從自由狀態變為持久狀態了。持久狀態的對象在Session 與數據庫中的數據進行同步時(比如commit)會把數據更新到數據庫。而 其他狀態的則不會。我覺得可以這樣來理解持久狀態,可以看成Hibernate 也擁有一份對象的引用,那么如果你對持久狀態對象的屬性進行更改的話, Hibernate看到的對象的狀態也更改了,而Hibernate所看到的對象和數據 庫中的數據是等價的。也正是這樣,Hibernate才實現了Object/Relation 的映射。類似的,load和get方法也一樣會獲取Persistent狀態的對象。
3、游離狀態:每次調用Session.close以后,所有跟這個session有關的處于
Persistant的對象就變成了游離狀態。也許你要問Detached和Transient有什么區別。其實從我給的例子來說看不出什么區別,因為我這里ID是給定的,而真正開發的時候ID往往是自增的,那么Transient的對象是沒有ID 的,當save了以后就有了,顯而易見Detached的對象也是有ID,只不過這個對象已經和Hibernate脫離了關系。但是游離狀態的對象仍然和數據庫 中的記錄有一定聯系,至少游離狀態的對象知道數據庫中有條記錄的ID為xxx。從這一點上來講,游離狀態是可以自己創造出來的,只要你知道數據庫中的主鍵信息。
在使用Hibernate開發的時候要分清楚這三種狀態,否則很容易出錯。比如不能去save一個游離狀態的對象,不能去update一個自由狀態的對象等等。
五、結束語
我要講的就這么多,這篇文章不是詳細介紹Hibernate,我只是總結了自己學習和使用Hibernate的一些感受和經驗,希望能給沒有用過Hibernate的開發者一個上手的指引。如果你看完了這篇文章仍然滿頭霧水,無從下手的話,我只能向你表示歉意,浪費你的時間了。不管怎樣,我強烈推薦一本書《深入淺出Hibernate》,這本書既能作為學習的教程,也能作為日后的查詢用書,相當實用。
基本類型有以下四種:
int長度數據類型有:byte(8bits)、short(16bits)、int(32bits)、long(64bits)、
float長度數據類型有:單精度(32bits float)、雙精度(64bits double)
boolean類型變量的取值有:ture、false
char數據類型有:unicode字符,16位
對應的類類型:
Integer、Float、Boolean、Character、Double、Short、Byte、Long
轉換原則:
從低精度向高精度轉換
byte 、short、int、long、float、double、char
注:兩個char型運算時,自動轉換為int型;當char與別的類型運算時,也會先自動轉換為int型的,再做其它類型的自動轉換
基本類型向類類型轉換
正向轉換:
通過類包裝器來new出一個新的類類型的變量
Integer a= new Integer(2);
反向轉換:
通過類包裝器來轉換
int b=a.intValue();
類類型向字符串轉換
正向轉換:
因為每個類都是object類的子類,而所有的object類都有一個toString()函數,所以通過toString()函數來轉換即可
反向轉換:
通過類包裝器new出一個新的類類型的變量
eg1: int i=Integer.valueOf(“123”).intValue()
說明:上例是將一個字符串轉化成一個Integer對象,然后再調用這個對象的intValue()方法返回其對應的int數值。
eg2: float f=Float.valueOf(“123”).floatValue()
說明:上例是將一個字符串轉化成一個Float對象,然后再調用這個對象的floatValue()方法返回其對應的float數值。
eg3: boolean b=Boolean.valueOf(“123”).booleanValue()
說明:上例是將一個字符串轉化成一個Boolean對象,然后再調用這個對象的booleanValue()方法返回其對應的boolean數值。
eg4:Double d=Double.valueOf(“123”).doubleValue()
說明:上例是將一個字符串轉化成一個Double對象,然后再調用這個對象的doubleValue()方法返回其對應的double數值。
eg5: long l=Long.valueOf(“123”).longValue()
說明:上例是將一個字符串轉化成一個Long對象,然后再調用這個對象的longValue()方法返回其對應的long數值。
eg6: char=Character.valueOf(“123”).charValue()
說明:上例是將一個字符串轉化成一個Character對象,然后再調用這個對象的charValue()方法返回其對應的char數值。
基本類型向字符串的轉換
正向轉換:
如:int a=12;
String b;
b=a+””;
反向轉換:
通過類包裝器
eg1:
int i=Integer.parseInt(“123”)
說明:此方法只能適用于字符串轉化成整型變量
eg2: float f=Float.valueOf(“123”).floatValue()
說明:上例是將一個字符串轉化成一個Float對象,然后再調用這個對象的floatValue()方法返回其對應的float數值。
eg3: boolean b=Boolean.valueOf(“123”).booleanValue()
說明:上例是將一個字符串轉化成一個Boolean對象,然后再調用這個對象的booleanValue()方法返回其對應的boolean數值。
eg4:Double d=Double.valueOf(“123”).doubleValue()
說明:上例是將一個字符串轉化成一個Double對象,然后再調用這個對象的doubleValue()方法返回其對應的double數值。
eg5: long l=Long.valueOf(“123”).longValue()
說明:上例是將一個字符串轉化成一個Long對象,然后再調用這個對象的longValue()方法返回其對應的long數值。
eg6: char=Character.valueOf(“123”).charValue()
說明:上例是將一個字符串轉化成一個Character對象,然后再調用這個對象的charValue()方法返回其對應的char數值。
java
數據庫基本操作
1、java數據庫操作基本流程
2、幾個常用的重要技巧:
·可滾動、更新的記錄集
·批量更新
·事務處理
java數據庫操作基本流程:取得數據庫連接 - 執行sql語句 - 處理執行結果 - 釋放數據庫連接
1、取得數據庫連接
1)用DriverManager取數據庫連接
例子
String className,url,uid,pwd;
className = "oracle.jdbc.driver.OracleDriver";
url = "jdbc:oracle:thin:@127.0.0.1:1521:orasvr;
uid = "system";
pwd = "manager";
Class.forName(className);
Connection cn = DriverManager.getConnection(url,uid,pwd);
2)用jndi(java的命名和目錄服務)方式
例子
String jndi = "jdbc/db";
Context ctx = (Context) new InitialContext().lookup("java:comp/env");
DataSource ds = (DataSource) ctx.lookup(jndi);
Connection cn = ds.getConnection();
多用于jsp中
2、執行sql語句
1)用Statement來執行sql語句
String sql;
Statement sm = cn.createStatement();
sm.executeQuery(sql); // 執行數據查詢語句(select)
sm.executeUpdate(sql); // 執行數據更新語句(delete、update、insert、drop等)statement.close();
2)用PreparedStatement來執行sql語句
String sql;
sql = "insert into user (id,name) values (?,?)";
PreparedStatement ps = cn.prepareStatement(sql);
ps.setInt(1,xxx);
ps.setString(2,xxx);
...
ResultSet rs = ps.executeQuery(); // 查詢
int c = ps.executeUpdate(); // 更新
3、處理執行結果
查詢語句,返回記錄集ResultSet
更新語句,返回數字,表示該更新影響的記錄數
ResultSet的方法
1、next(),將游標往后移動一行,如果成功返回true;否則返回false
2、getInt("id")或getSting("name"),返回當前游標下某個字段的值
4、釋放連接
cn.close();
一般,先關閉ResultSet,然后關閉Statement(或者PreparedStatement);最后關閉Connection
可滾動、更新的記錄集
1、創建可滾動、更新的Statement
Statement sm = cn.createStatement(ResultSet.TYPE_SCROLL_ENSITIVE,ResultSet.CONCUR_READ_ONLY);
該Statement取得的ResultSet就是可滾動的
2、創建PreparedStatement時指定參數
PreparedStatemet ps = cn.prepareStatement(sql,ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
ResultSet.absolute(9000);
·批量更新
1、Statement
Statement sm = cn.createStatement();
sm.addBatch(sql1);
sm.addBatch(sql2);
...
sm.executeBatch()
一個Statement對象,可以執行多個sql語句以后,批量更新。這多個語句可以是delete、update、insert等或兼有
2、PreparedStatement
PreparedStatement ps = cn.preparedStatement(sql);
{
ps.setXXX(1,xxx);
...
ps.addBatch();
}
ps.executeBatch();
一個PreparedStatement,可以把一個sql語句,變換參數多次執行,一次更新。
·事務的處理
1、關閉Connection的自動提交
cn.setAutoCommit(false);
2、執行一系列sql語句
要點:執行每一個新的sql語句前,上一次執行sql語句的Statement(或者PreparedStatemet)必須先close
Statement sm ;
sm = cn.createStatement(insert into user...);
sm.executeUpdate();
sm.close();
sm = cn.createStatement("insert into corp...);
sm.executeUpdate();
sm.close();
3、提交
cn.commit();
4、如果發生異常,那么回滾
cn.rollback();