 2008年1月11日
	2008年1月11日		  
		
			
			
			程序代码自动排版: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.blogjava.net/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.blogjava.net/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("http://www.jscud.com");
 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 、JOTM 和JBoss Transaction 和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"];
 int userAge = (int)this.Session["UserAge"];
    我们知道,Session中存放的是键值对,键是string类型的,如果我们一不小心把上面的语句写成这样:
 int userAge = (int)this.Session["UseAge"];
  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中的图片
  处理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。