James Gosling : Java之父 作為Java之父,James Gosling的名字可謂是耳熟能詳。當人們評論一種編程語言時,總喜歡捎帶著把下蛋的母雞一起帶上。Java做為中國的編程語言學習者餐桌上有限的那么幾樣餐點中的流行款式,自然是讓James Gosling風光不已。雖然James Gosling現在已經不是領導Java發展潮流的領軍人物了,做為Sun的開發者產品組的CTO,怎么算來也是身居高位了,俗事纏身吧,但是這并不妨礙其對于Java一如既往的愛護,表達著各式各樣鮮明的觀點,引發一場又一場的爭論。 Bill Joy : 軟件業的愛迪生 Joy生于1954年,1982年與Vinod Khosla, Scott McNealy和Andy Bechtolsheim一起創建了Sun Microsystems,并從那時起擔任首席科學家,直到2003年離開。他是一位令人崇敬的軟件天才,他在軟件和硬件的歷史上留下了無數令人仰止的傳奇。 Joshua Bloch : Java 2 元勛 早在1996年,適逢Java剛剛嶄露頭角,年內好事連連。先是1月份發布JDK 1.0,然后是5月底在舊金山召開首屆JavaOne大會,年末又是JDK 1.1緊跟其后。正是在Java技術如火如荼、大展拳腳的背景之下,Joshua Bloch來到了Sun,開始了他帶領Java社區步入“迦南美地”的漫長歷程。
Bruce Eckel原本是一位普通的匯編程序員。不知道是什么因緣際會,他轉行去寫計算機技術圖書,卻在此大紅大紫。他成功的秘訣不外乎兩點:超人的表達能力和捕捉機會的能力。他最早的一本書是1990年代初期的《C++ Inside & Out》,隨后,在1995年他寫出了改變自己命運的《Thinking in C++》。如果說這本書充分表現了他作為優秀技術作家的一面,那么隨后他寫作《Thinking in Java》并因此步入頂級技術作家行列,則體現了他作為優秀的機會主義分子善于捕捉機會的另一面。寫作中擅長舉淺顯直接的小例子來說明問題,語言生動,娓娓道來,特別適合于缺乏實踐經驗的初學者。因此《Thinking in Java》儼然成為天字第一號的Java教科書,對Java的普及與發展發揮著不可忽略的作用。不過公允地說,Bruce Eckel的書欠深刻。比如在“Thinking in…”系列中對設計模式的解說就有失大師水準。這一方面是因為書的定位非常清晰,另一方面也是因為Bruce太過分心趕潮流,未能深入之故。TIJ之后,他預言Python將火,就匆匆跑去寫了半本《Thinking in Python》。后來Python并未如期而旺,于是他也就把書稿撂在那里不過問了,機會主義的一面暴露無遺。我們也可以善意的猜測一下,他的下一個投機對象會是什么呢?Ruby?.NET?MDA?總之,是什么我都不奇怪。
Oberg的作品很多,流行的代碼生成工具XDoclet和MVC框架WebWork都出自他的手筆。這兩個框架有一個共同的特點,即它們的功能雖然簡單,但設計都非常優雅靈活,能夠很方便地擴展新功能甚至移植到新環境下使用。優雅的設計源自Oberg的過人才華,簡單的功能則折射出他玩世不恭的人生態度。正是這兩種特質的融合,才造就了這個不世出的奇才。 Doug Lea : 世界上對Java影響力最大的個人 如果IT的歷史,是以人為主體串接起來的話,那么肯定少不了Doug Lea。這個鼻梁掛著眼鏡,留著德王威廉二世的胡子,臉上永遠掛著謙遜靦腆笑容,服務于紐約州立大學Oswego分校計算器科學系的老大爺。 Scott McNealy :SUN十年來的掌舵者 McNealy,Sun的CEO、總裁兼董事長。他曾經狂傲的說:“摧毀微軟是我們每個人的任務。”這位英勇的硅谷英雄,似乎帶頭起義,試圖組織一個反微軟陣線聯盟,以對抗微軟這股龐大的托拉斯惡勢力。他時常口出驚人之語,在公開場合大肆的批評微軟,并曾經說微軟的.NET是.NOT。
Rod在悉尼大學不僅獲得了計算機學位,同時還獲得了音樂學位。更令人吃驚的是在回到軟件開發領域之前,他還獲得了音樂學的博士學位。有著相當豐富的C/C++技術背景的Rod早在1996年就開始了對Java服務器端技術的研究。他是一個在保險、電子商務和金融行業有著豐富經驗的技術顧問,同時也是JSR-154(Servlet 2.4)和JDO 2.0的規范專家、JCP的積極成員。
Alan Kay :Java的精神先鋒 Sun的官方Java教材中有一句話,說Java是“C++的語法與Smalltalk語義的結合”。而Smalltalk的創造者就是Alan Kay。
Beck全家似乎都彌漫著技術的味道。生長在硅谷, 有著一個對無線電癡迷的祖父,以及一個電器工程師父親。從小就引導Kent Beck成為了業余無線電愛好者。 |
功能:在線拍照
簡介:用flex與java結合實現在線拍照
需求:為了滿足希望通過攝像頭拍照的圖片,然后通過服務器來展示需要
效果:
后臺:
前臺:
實現代碼:
flex:
java:
源碼:/Files/obpm/onlinetakephoto.rar
原創人員:Denny
LDAP快速入門
LDAP(輕量級目錄訪問協議,Lightweight Directory Access Protocol)是實現提供被稱為目錄服務的信息服務。目錄服務是一種特殊的數據庫系統,其專門針對讀取,瀏覽和搜索操作進行了特定的優化。目錄一般用來包含描述性的,基于屬性的信息并支持精細復雜的過濾能力。目錄一般不支持通用數據庫針對大量更新操作操作需要的復雜的事務管理或回卷策略。而目錄服務的更新則一般都非常簡單。這種目錄可以存儲包括個人信息、web鏈結、jpeg圖像等各種信息。為了訪問存儲在目錄中的信息,就需要使用運行在TCP/IP 之上的訪問協議—LDAP。
LDAP目錄中的信息是是按照樹型結構組織,具體信息存儲在條目(entry)的數據結構中。條目相當于關系數據庫中表的記錄;條目是具有區別名DN (Distinguished Name)的屬性(Attribute),DN是用來引用條目的,DN相當于關系數據庫表中的關鍵字(Primary Key)。屬性由類型(Type)和一個或多個值(Values)組成,相當于關系數據庫中的字段(Field)由字段名和數據類型組成,只是為了方便檢索的需要,LDAP中的Type可以有多個Value,而不是關系數據庫中為降低數據的冗余性要求實現的各個域必須是不相關的。LDAP中條目的組織一般按照地理位置和組織關系進行組織,非常的直觀。LDAP把數據存放在文件中,為提高效率可以使用基于索引的文件數據庫,而不是關系數據庫。類型的一個例子就是mail,其值將是一個電子郵件地址。
LDAP的信息是以樹型結構存儲的,在樹根一般定義國家(c=CN)或域名(dc=com),在其下則往往定義一個或多個組織 (organization)(o=Acme)或組織單元(organizational units) (ou=People)。一個組織單元可能包含諸如所有雇員、大樓內的所有打印機等信息。此外,LDAP支持對條目能夠和必須支持哪些屬性進行控制,這是有一個特殊的稱為對象類別(objectClass)的屬性來實現的。該屬性的值決定了該條目必須遵循的一些規則,其規定了該條目能夠及至少應該包含哪些屬性。例如:inetorgPerson對象類需要支持sn(surname)和cn(common name)屬性,但也可以包含可選的如郵件,電話號碼等屬性。
設計目錄結構是LDAP最重要的方面之一。下面我們將通過一個簡單的例子來說明如何設計合理的目錄結構。該例子將通過Netscape地址薄來訪文。假設有一個位于美國US(c=US)而且跨越多個州的名為Acme(o=Acme)的公司。Acme希望為所有的雇員實現一個小型的地址薄服務器。
我們從一個簡單的組織DN開始:
dn: o=Acme, c=US
Acme所有的組織分類和屬性將存儲在該DN之下,這個DN在該存儲在該服務器的目錄是唯一的。Acme希望將其雇員的信息分為兩類:管理者(ou= Managers)和普通雇員(ou=Employees),這種分類產生的相對區別名(RDN,relative distinguished names。表示相對于頂點DN)就shi :
dn: ou=Managers, o=Acme, c=US
dn: ou=Employees, o=Acme, c=US
在下面我們將會看到分層結構的組成:頂點是US的Acme,下面是管理者組織單元和雇員組織單元。因此包括Managers和Employees的DN組成為:
dn: cn=Jason H. Smith, ou=Managers, o=Acme, c=US
dn: cn=Ray D. Jones, ou=Employees, o=Acme, c=US
dn: cn=Eric S. Woods, ou=Employees, o=Acme, c=US
為了引用Jason H. Smith的通用名(common name )條目,LDAP將采用cn=Jason H. Smith的RDN。然后將前面的父條目結合在一起就形成如下的樹型結構:
cn=Jason H. Smith
+ ou=Managers
+ o=Acme
+ c=US
-> dn: cn=Jason H. Smith,ou=Managers,o=Acme,c=US
現在已經定義好了目錄結構,下一步就需要導入目錄信息數據。目錄信息數據將被存放在LDIF文件中,其是導入目錄信息數據的默認存放文件。用戶可以方便的編寫Perl腳本來從例如/etc/passwd、NIS等系統文件中自動創建LDIF文件。
下面的實例保存目錄信息數據為testdate.ldif文件,該文件的格式說明將可以在man ldif中得到。
在添加任何組織單元以前,必須首先定義Acme DN:
dn: o=Acme, c=US
objectClass: organization
這里o屬性是必須的
o: Acme
下面是管理組單元的DN,在添加任何管理者信息以前,必須先定義該條目。
dn: ou=Managers, o=Acme, c=US
objectClass: organizationalUnit
這里ou屬性是必須的。
ou: Managers
第一個管理者DN:
dn: cn=Jason H. Smith, ou=Managers, o=Acme, c=US
objectClass: inetOrgPerson
cn和sn都是必須的屬性:
cn: Jason H. Smith
sn: Smith
但是還可以定義一些可選的屬性:
telephoneNumber: 111-222-9999
mail: headhauncho@acme.com
localityName:
可以定義另外一個組織單元:
dn: ou=Employees, o=Acme, c=US
objectClass: organizationalUnit
ou: Employees
并添加雇員信息如下:
dn: cn=Ray D. Jones, ou=Employees, o=Acme, c=US
objectClass: inetOrgPerson
cn: Ray D. Jones
sn: Jones
telephoneNumber: 444-555-6767
mail: jonesrd@acme.com
localityName:
dn: cn=Eric S. Woods, ou=Employees, o=Acme, c=US
objectClass: inetOrgPerson
cn: Eric S. Woods
sn: Woods
telephoneNumber: 444-555-6768
mail: woodses@acme.com
localityName:
本文實踐了在 Windows 下安裝配 openldap,并添加一個條目,LdapBrowser 瀏覽,及 Java 程序連接 openldap 的全過程。
1. 下載安裝 openldap for windows,當前版本
相關鏈接:http://lucas.bergmans.us/hacks/openldap/
安裝很簡單,一路 next 即可,假設我們安裝在 c:\openldap
2. 配置 openldap,編輯 sldap.conf 文件
1) 打開 c:\openldap\sldap.conf,找到
include C:/openldap/etc/schema/core.schema,在它后面添加
include C:/openldap/etc/schema/cosine.schema
include C:/openldap/etc/schema/inetorgperson.schema
接下來的例子只需要用到以上三個 schema,當然,如果你覺得需要的話,你可以把其他的 schema 全部添加進來
include C:/openldap/etc/schema/corba.schema
include C:/openldap/etc/schema/dyngroup.schema
include C:/openldap/etc/schema/java.schema
include C:/openldap/etc/schema/misc.schema
include C:/openldap/etc/schema/nis.schema
include C:/openldap/etc/schema/openldap.schema
2) 還是在 sldap.conf 文件中,找到
suffix "dc=my-domain,dc=com"
rootdn "cn=Manager,dc=my-domain,dc=com"
把這兩行改為
suffix "o=teemlink,c=cn"
rootdn "cn=Manager,o=teemlink,dc=cn"
suffix 就是看自己如何定義了,后面步驟的 ldif 文件就必須與它定義了。還要注意到這個配置文件中有一個 rootpw secret,這個 secret 是 cn=Manager 的密碼,以后會用到,不過這里是明文密碼,你可以用命令: slappasswd -h {MD5} -s secret 算出加密的密碼 {MD5}Xr4ilOzQ4PCOq3aQ0qbuaQ== 取代配置中的 secret。
3. 啟動 openldap
CMD 進入到 c:\openldap 下,運行命令 sldapd -d 1
用可以看到控制臺下打印一片信息,openldap 默認是用的 Berkeley DB 數據庫存儲目錄數據的。
4. 建立條目,編輯導入 ldif 文件
1) 新建一個 ldif(LDAP Data Interchanged Format) 文件(純文本格式),例如 test.ldif,文件內容如下:
dn: o=teemlink
objectclass: top
objectclass: organization
o: develop
2) 執行命令:ldapadd -l test.ldif
5. 使用LDAP Browser進行訪問
5.1安裝LDAP Browser2.6軟件,進行如下操作:
5.2顯示效果
package cn.myapps.test;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
public class LdapTest {
public void JNDILookup() {
String root = "o=teemlink,c=cn";
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://192.168.0.30/" + root);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "cn=Nicholas,ou=develop,o=teemlink,c=cn");
env.put(Context.SECURITY_CREDENTIALS, "123456");
DirContext ctx = null;
try {
ctx = new InitialDirContext(env);
Attributes attrs = ctx.getAttributes("cn=Nicholas,ou=develop");
System.out.println("Last Name: " + attrs.get("sn").get());
System.out.println("認證成功");
} catch (javax.naming.AuthenticationException e) {
e.printStackTrace();
System.out.println("認證失敗");
} catch (Exception e) {
System.out.println("認證出錯:");
e.printStackTrace();
}
if (ctx != null) {
try {
ctx.close();
} catch (NamingException e) {
// ignore
}
}
}
public static void main(String[] args) {
LdapTest LDAPTest = new LdapTest();
LDAPTest.JNDILookup();
}
}
訪問地址:http://www.openldap.org/jldap/ 并下載相關lib
import com.novell.ldap.*;
import java.io.UnsupportedEncodingException;
public class List
{
public static void main(String[] args)
{
int ldapPort = LDAPConnection.DEFAULT_PORT;
int searchScope = LDAPConnection.SCOPE_ONE;
int ldapVersion = LDAPConnection.LDAP_V3;
boolean attributeOnly = false;
String attrs[] = null;
String ldapHost = "192.168.0.30";
String loginDN = "cn=Manager,o=teemlink,c=cn";
String password = "secret";
String searchBase = "ou=develop,o=teemlink,c=cn";
String searchFilter = "objectClass=*";
LDAPConnection lc = new LDAPConnection();
try {
// connect to the server
lc.connect(ldapHost, ldapPort);
// bind to the server
lc.bind(ldapVersion, loginDN, password.getBytes("UTF8"));
LDAPSearchResults searchResults =
lc.search(searchBase, // container to search
searchScope, // search scope
searchFilter, // search filter
attrs, // "1.1" returns entry name only
attributeOnly); // no attributes are returned
// print out all the objects
while (searchResults.hasMore()) {
LDAPEntry nextEntry = null;
try {
nextEntry = searchResults.next();
System.out.println("\n" + nextEntry.getDN());
System.out.println(nextEntry.getAttributeSet());
} catch (LDAPException e) {
System.out.println("Error: " + e.toString());
// Exception is thrown, go for next entry
continue;
}
}
// disconnect with the server
lc.disconnect();
} catch (LDAPException e) {
System.out.println("Error: " + e.toString());
} catch (UnsupportedEncodingException e) {
System.out.println("Error: " + e.toString());
}
System.exit(0);
}
}
訪問地址:http://www.openldap.org/jdbcldap/ 并下載相關lib
package jdbcldap;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class JdbcLdap {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Class.forName("com.octetstring.jdbcLdap.sql.JdbcLdapDriver");
String ldapConnectString = "jdbc:ldap://192.168.0.30/o=teemlink,c=cn?SEARCH_SCOPE:=subTreeScope";
Connection con = DriverManager.getConnection(ldapConnectString, "cn=Manager,o=teemlink,c=cn", "secret");
String sql = "SELECT * FROM ou=develop,o=teemlink,c=cn";
Statement sat = con.createStatement();
ResultSet rs = sta.executeQuery(sql);
while (rs.next()) {
System.out.println(rs.getString(1));
}
if (con != null)
con.close();
}
}
原創人員:Nicholas
以子類取代類型編碼
Replace Type Code with Subclasses
對軟件內部結構的一種調整,目的是在不改變「軟件之可察行為」前提下,提高其可理解性,降低其修改成本。
使用一系列重構準則(手法),在不改變「軟件之可察行為」前提下,調整其結構。
同樣完成一件事,設計不良的程序往往需要更多代碼,這常常是因為代碼在不同的地方使用完全相同的語句做同樣的事。因此改進設計的一個重要方向就是消除重復代碼(Duplicate Code)
你的源碼還有其它讀者:數個月之后可能會有另一位程序員嘗試讀懂你的代碼并做一些修改。我們很容易忘記這第二位讀者,但他才是最重要的。計算器是否多花了數個鐘頭進行編譯,又有什么關系呢?如果一個程序員花費一周時間來修改某段代碼,那才關系重大— 如果他理解你的代碼,這個修改原本只需一小時
Kent Beck 經常形容自己的一句話:『我不是個偉大的程序員;我只是個有著一些優秀習慣的好程序員而已。』重構能夠幫助我更有效地寫出強固穩健(robust)的代碼。
終于,前面的一切都歸結到了這最后一點:重構幫助你更快速地開發程序。聽起來有違反直覺。當我談到重構,人們很容易看出它能夠提高質量。改善設計、提升可讀性、減少錯誤,這些都是提高質量。但這難道不會降低開發速度嗎?我強烈相信:良好設計是快速軟件開發的根本。事實上擁有良好設計才可能達成快速的開發。如果沒有良好設計,或許某一段時間內你的進展迅速,但惡劣的設計很快就讓你的速度慢下來。你會把時間花在調試上面,無法添加新功能。修改時間愈來愈長,因為你必須花愈來愈多的時間去理解系統、尋找重復代碼。隨著你給最初程序打上一個又一個的補丁(patch),新特性需要更多代碼才能實現。真是個惡性循環。
重構本來就不是一件「特別撥出時間做」的事情,重構應該隨時隨地進行。你不應該為重構而重構,你之所以重構,是因為你想做別的什么事,而重構可以幫助你把那些事做好
Don Roberts 給了我一條準則:第一次做某件事時只管去做;第二次做類似的事會產生反感,但無論如何還是做了;第三次再做類似的事,你就應該重構。
☆ 事不過三,三則重構。(Three strikes and you refactor.)
添加功能時一并重構
修補錯誤時一并重構
復審代碼時一并重構
-以上章節摘抄自《重構-改善既有代碼的設計》
1. 實例模塊為View,重構元素為ViewAction、ViewProcessBean、View,以下為關系圖。
2. 由于View存在多種editMode(編輯模式),而每個調用的地方都需要進行type code(類型碼)判斷,然后再進行相應的業務邏輯處理,最終在每個調用的地方都形成了大量的if-else代碼,大大減弱了代碼的可讀性,和邏輯清晰度。
3. 調用的地方:
如上圖所示,將每種type code重構成subclass,加強了每種類型處理業務邏輯的能力。
View-版本1566代碼片段:
public EditMode getEditModeType() {
if (EDIT_MODE_CODE_DQL.equals(getEditMode())) {
return new DQLEditMode(this);
} else if (EDIT_MODE_CODE_SQL.equals(getEditMode())) {
return new SQLEditMode(this);
} else if (EDIT_MODE_DESIGN.equals(getEditMode())) {
return new DesignEditMode(this);
}
return new NullEditMode(this);
}
說明:調用者無需了解具體的類型,由View自身作判斷,返回EditMode接口,從而實現多態調用。
ViewProcessBean代碼片段:
重構前-版本1503:
public String expDocToExcel(String viewid, WebUser user, ParamsTable params) throws Exception {
if (view.getEditMode().equals(View.EDIT_MODE_DESIGN)) {
datas = dp.queryBySQLPage(sql, params, tempPage, LINES, user.getDomainid());
} else if (view.getEditMode().equals(View.EDIT_MODE_CODE_DQL)) {
datas = dp.queryByDQLPage(dql, params, tempPage, LINES, user.getDomainid());
} else if (view.getEditMode().endsWith(View.EDIT_MODE_CODE_SQL)) {
datas = dp.queryBySQLPage(sql, params, tempPage, LINES, user.getDomainid());
}
}
重構后-版本1566:
public String expDocToExcel(String viewid, WebUser user, ParamsTable params) throws Exception {
datas = view.getEditModeType().getDataPackage(params, tempPage, LINES, user, currdoc);
//其他業務邏輯
}
由上述案例可看到,引入subclass代替type code可以大大減少if-else判斷,而且可以把責任內聚到每種type中,使代碼結構更清晰易懂。
在本案例中引入了空類型概念,即NullEditMode,代碼如下:
/**
*
* @author nicholas zhen
*
*/
public class NullEditMode extends AbstractEditMode implements EditMode {
public NullEditMode(View view) {
super(view);
}
public String getQueryString(ParamsTable params, WebUser user, Document sDoc) {
return "";
}
public DataPackage getDataPackage(ParamsTable params, WebUser user, Document doc) throws Exception {
return new DataPackage();
}
public DataPackage getDataPackage(ParamsTable params, int page, int lines, WebUser user, Document doc) throws Exception {
return new DataPackage();
}
public long count(ParamsTable params, WebUser user, Document doc) throws Exception {
return 0;
}
}
說明:空類型保證了調用每個方法都有默認值返回,而不需要進行非空判斷,當View沒有類型時,即返回默認的空類型
原創人員:Nicholas
不會畫流程圖的,看了這個就懂了,絕對經典!
轉載人員:Nicholas
在obpm系統后臺表單右上角有一個“一鍵生成視圖”功能。實現它的真正目的是為了后臺管理人員方便從實現好的表單中快速生成所有帶值的列的視圖。這樣管理人員就不需要手工新建視圖,然后再添加視圖中的帶值的列。
實現原理圖:
在實現原理圖中,我們發現沒有視圖中并沒有不帶值Field4相應的Column4在視圖中,這是因為在視圖中是要根據不同Column顯示不同的值的。如果Column是不帶值的話,那么視圖中就不應該要這個Column,即使是要了,在視圖中沒有意義了。
實現原理代碼:
其中代碼路徑是:src-java-cn-myapps-core-dynaform-form-ejb-FormProcessBean.java
/**
* 根據表單編號來生成視圖
* @param formid 表單編號
* @throws Exception
*/
public Form oneKeyCreateView(String formid) throws Exception {
FormProcess formPross = (FormProcess) ProcessFactory.createProcess(FormProcess.class);
ViewProcess viewPross = (ViewProcess) ProcessFactory.createProcess(ViewProcess.class);
Form form = (Form) formPross.doView(formid);//獲得form
Collection formfield=form.getValueStoreFields();//獲得form存儲值的field
//新建視圖
View view = new View();
if (view.getId() == null || view.getId().trim().length() <= 0) {
view.setId(Sequence.getSequence());//設置視圖的ID
view.setSortId(Sequence.getTimeSequence());//設置視圖的排序ID }
view.setName(form.getName());//把表單的名字賦給視圖
view.setOpenType(view.OPEN_TYPE_NORMAL); //設置視圖打開類型-普通類型
view.setLastmodifytime(new Date());//最后修改日期
view.setApplicationid(form.getApplicationid());//把表單應用程序Id賦給視圖的應用程序Id
view.setModule(form.getModule());//把表單模塊Id賦給視圖的模塊ID
view.setPagelines("10");//設置視圖的分頁每頁顯示10條數據
view.setShowTotalRow(true); //是否顯示總共條數數據
view.setPagination(true); //是否分頁顯示
view.setRelatedForm(form.getId());//把表單ID賦給視圖的映射表單,從而映射了該表單
//將表單中對應有值的列轉換為視圖的列
int i=0;
for(Iterator iterator=formfield.iterator();iterator.hasNext();){
FormField field=(FormField)iterator.next();
Column column = new Column();
if (column.getId() == null || column.getId().trim().length() <= 0) {
column.setId(Sequence.getSequence());
column.setOrderno(i);
}
if(field.getDiscript()!=null && !field.getDiscript().equals("")){//如果該表單中帶值Field有描述的話,就作為視圖Column,否則的用Field名稱
column.setName(field.getDiscript());
}else{
column.setName(field.getName());
}
column.setFormid(form.getId());//把表單中的ID賦給Column的表單ID
column.setApplicationid(form.getApplicationid());//把表單中應用程序的ID賦給Column的表單應用程序ID
column.setFieldName(field.getName()); //把表單中的名稱賦給Column的表單名稱
column.setParentView(view.getId());//將視圖ID賦給Column的父視圖
view.getColumns().add(column); //將視圖和Column關聯
i++;
}
//分別創建兩個按鈕 新建,刪除
Activity activityCreate = new Activity();
if (activityCreate.getId() == null || activityCreate.getId().trim().length() <= 0) {
activityCreate.setId(Sequence.getSequence());
activityCreate.setOrderno(0);
}
activityCreate.setApplicationid(form.getApplicationid());
activityCreate.setName("新建");
activityCreate.setParentView(view.getId());
activityCreate.setType(ActivityType.DOCUMENT_CREATE);
activityCreate.setOnActionForm(form.getId());
view.getActivitys().add(activityCreate); //將視圖和新建按鈕關聯
Activity activityDelete = new Activity();
if (activityDelete.getId() == null || activityDelete.getId().trim().length() <= 0) {
activityDelete.setId(Sequence.getSequence());
activityDelete.setOrderno(1);
}
activityDelete.setApplicationid(form.getApplicationid());
activityDelete.setName("刪除");
activityDelete.setParentView(view.getId());
activityDelete.setType(ActivityType.DOCUMENT_DELETE);
view.getActivitys().add(activityDelete); //將視圖和刪除按鈕關聯
viewPross.doCreate(view); //創建視圖
return form;
}
后臺效果圖:
表單:
視圖:
視圖列:
視圖按鈕:
前臺效果:
視圖:
表單:
原創人員:Denny
鎖( locking )
業務邏輯的實現過程中,往往需要保證數據訪問的排他性。如在金融系統的日終結算處理中,我們希望針對某個 cut-off 時間點的數據進行處理,而不希望在結算進行過程中(可能是幾秒種,也可能是幾個小時),數據再發生變化。此時,我們就需要通過一些機制來保證這些數據在某個操作過程中不會被外界修改,這樣的機制,在這里,也就是所謂的 “鎖” ,即給我們選定的目標數據上鎖,使其無法被其他程序修改。Hibernate 支持兩種鎖機制:即通常所說的 “悲觀鎖( Pessimistic Locking )”和 “樂觀鎖( Optimistic Locking )” 。
悲觀鎖( Pessimistic Locking )
悲觀鎖,正如其名,它指的是對數據被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個數據處理過程中,將數據處于鎖定狀態。悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據)。
一個典型的倚賴數據庫的悲觀鎖調用:
select * from account where name=”Erica” for update
這條 sql 語句鎖定了 account 表中所有符合檢索條件(name=”Erica”)的記錄。本次事務提交之前(事務提交時會釋放事務過程中的鎖),外界無法修改這些記錄。Hibernate 的悲觀鎖,也是基于數據庫的鎖機制實現。
下面的代碼實現了對查詢記錄的加鎖:
String hqlStr ="from TUser as user where user.name='Erica'";
Query query = session.createQuery(hqlStr);
query.setLockMode("user",LockMode.UPGRADE); // 加鎖
List userList = query.list();// 執行查詢,獲取數據
query.setLockMode 對查詢語句中,特定別名所對應的記錄進行加鎖(我們為TUser 類指定了一個別名 “user” ),這里也就是對返回的所有 user 記錄進行加鎖。
觀察運行期 Hibernate 生成的 SQL 語句:
select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id
as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex
from t_user tuser0_ where (tuser0_.name='Erica' ) for update
這里 Hibernate 通過使用數據庫的 for update 子句實現了悲觀鎖機制。
Hibernate 的加鎖模式有:
LockMode.NONE : 無鎖機制。
LockMode.WRITE : Hibernate 在 Insert 和 Update 記錄的時候會自動獲取。
LockMode.READ : Hibernate 在讀取記錄的時候會自動獲取。
以上這三種鎖機制一般由 Hibernate 內部使用,如 Hibernate 為了保證 Update過程中對象不會被外界修改,會在 save 方法實現中自動為目標對象加上 WRITE 鎖。
LockMode.UPGRADE :利用數據庫的 for update 子句加鎖。
LockMode. UPGRADE_NOWAIT : Oracle 的特定實現,利用 Oracle 的 for update nowait 子句實現加鎖。
上面這兩種鎖機制是我們在應用層較為常用的,加鎖一般通過以下方法實現:
Criteria.setLockMode
Query.setLockMode
Session.lock
注意,只有在查詢開始之前(也就是 Hiberate 生成 SQL 之前)設定加鎖,才會真正通過數據庫的鎖機制進行加鎖處理,否則,數據已經通過不包含 for update 子句的 Select SQL 加載進來,所謂數據庫加鎖也就無從談起。
樂觀鎖( Optimistic Locking )
相對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制。悲觀鎖大多數情況下依靠數據庫的鎖機制實現,以保證操作最大程度的獨占性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。
如一個金融系統,當某個操作員讀取用戶的數據,并在讀出的用戶數據的基礎上進行修改時如更改用戶帳戶余額),如果采用悲觀鎖機制,也就意味著整個操作過程中(從操作員讀出數、開始修改直至提交修改結果的全過程,甚至還包括操作員中途去煮咖啡的時間),數據庫記錄始終處于加鎖狀態,可以想見,如果面對幾百上千個并發,這樣的情況將導致怎樣的后果。樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基于數據版本( Version )記錄機制實現。何謂數據版本?即為數據增加一個版本標識,在基于數據庫表的版本解決方案中,一般是通過為數據庫表增加一個 “version” 字段來實現。
讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號大于數據庫表當前版本號,則予以更新,否則認為是過期數據。
對于上面修改用戶帳戶信息的例子而言,假設數據庫中帳戶信息表中有一個version 字段,當前值為 1 ;而當前帳戶余額字段(balance)為 $100 。
1 操作員 A 此時將其讀出(version=1),并從其帳戶余額中扣除 $50($100-$50)。
2 在操作員 A 操作的過程中,操作員 B 也讀入此用戶信息(version=1),并從其帳戶余額中扣除 $20 ($100-$20)。
3 操作員 A 完成了修改工作,將數據版本號加一(version=2),連同帳戶扣除后余額(balance=$50),提交至數據庫更新,此時由于提交數據版本大于數據庫記錄當前版本,數據被更新,數據庫記錄 version 更新為 2 。
4 操作員 B 完成了操作,也將版本號加一(version=2)試圖向數據庫提交數據(balance=$80),但此時比對數據庫記錄版本時發現,操作員 B 提交的數據版本號為 2 ,數據庫記錄當前版本也為 2 ,不滿足“ 提交版本必須大于記錄當前版本才能執行更新“ 的樂觀鎖策略,因此,操作員 B 的提交被駁回。這樣,就避免了操作員 B 用基于 version=1 的舊數據修改的結果覆蓋操作員 A 的操作結果的可能。
從上面的例子可以看出,樂觀鎖機制避免了長事務中的數據庫加鎖開銷(操作員 A 和操作員 B 操作過程中,都沒有對數據庫數據加鎖),大大提升了大并發量下的系統整體性能表現。需要注意的是,樂觀鎖機制往往基于系統中的數據存儲邏輯,因此也具備一定的局限性,如在上例中,由于樂觀鎖機制是在我們的系統中實現,來自外部系統的用戶余額更新操作不受我們系統的控制,因此可能會造成臟數據被更新到數據庫中。在系統設計階段,我們應該充分考慮到這些情況出現的可能性,并進行相應調整(如將樂觀鎖策略在數據庫存儲過程中實現,對外只開放基于此存儲過程的數據更新途徑,而不是將數據庫表直接對外公開)。
Hibernate 在其數據訪問引擎中內置了樂觀鎖實現。如果不用考慮外部系統對數據庫的更新操作,利用 Hibernate 提供的透明化樂觀鎖實現,將大大提升我們的生產力。
Hibernate 中可以通過 class 描述符的 optimistic-lock 屬性結合 version描述符指定。
現在,我們為之前示例中的 TUser 加上樂觀鎖機制。
1 . 首先為 TUser 的 class 描述符添加 optimistic-lock 屬性:
<hibernate-mapping>
<class name="org.hibernate.sample.TUser" table="t_user" dynamic-update="true"
dynamic-insert="true" optimistic-lock="version">
……
</class>
</hibernate-mapping>
optimistic-lock 屬性有如下可選取值:
none:無樂觀鎖
version:通過版本機制實現樂觀鎖
dirty:通過檢查發生變動過的屬性實現樂觀鎖
all:通過檢查所有屬性實現樂觀鎖
其中通過 version 實現的樂觀鎖機制是 Hibernate 官方推薦的樂觀鎖實現,同時也是 Hibernate 中,目前唯一在數據對象脫離 Session 發生修改的情況下依然有效的鎖機制。因此,一般情況下,我們都選擇 version 方式作為 Hibernate 樂觀鎖實現機制。
2 . 添加一個 Version 屬性描述符
<hibernate-mapping>
<class name="org.hibernate.sample.TUser" table="t_user" dynamic-update="true" dynamic-insert="true"
optimistic-lock="version">
<id name="id" column="id" type="java.lang.Integer">
<generator class="native">
</generator>
</id>
<version column="version" name="version" type="java.lang.Integer"/>
……
</class>
</hibernate-mapping>
注意 version 節點必須出現在 ID 節點之后。這里我們聲明了一個 version 屬性,用于存放用戶的版本信息,保存在 TUser 表的version 字段中。
此時如果我們嘗試編寫一段代碼,更新 TUser 表中記錄數據,如:
Criteria criteria = session.createCriteria(TUser.class);
criteria.add(Expression.eq("name","Erica"));
List userList = criteria.list();
TUser user =(TUser)userList.get(0);
Transaction tx = session.beginTransaction();
user.setUserType(1); // 更新 UserType 字段
tx.commit();
每次對 TUser 進行更新的時候,我們可以發現,數據庫中的 version 都在遞增。而如果我們嘗試在 tx.commit 之前,啟動另外一個 Session ,對名為 Erica 的用戶進行操作,以模擬并發更新時的情形:
Session session= getSession();
Criteria criteria = session.createCriteria(TUser.class);
criteria.add(Expression.eq("name","Erica"));
Session session2 = getSession();
Criteria criteria2 = session2.createCriteria(TUser.class);
criteria2.add(Expression.eq("name","Erica"));
List userList = criteria.list();
List userList2 = criteria2.list();TUser user =(TUser)userList.get(0);
TUser user2 =(TUser)userList2.get(0);
Transaction tx = session.beginTransaction();
Transaction tx2 = session2.beginTransaction();
user2.setUserType(99);
tx2.commit();
user.setUserType(1);
tx.commit();
執行以上代碼,代碼將在 tx.commit() 處拋出 StaleObjectStateException 異常,并指出版本檢查失敗,當前事務正在試圖提交一個過期數據。通過捕捉這個異常,我們就可以在樂觀鎖校驗失敗時進行相應處理。
轉載人員:Nicholas