摘要:

本文推薦的一個(gè)叫Amber的框架提供了一種相反的輕量級(jí)實(shí)現(xiàn)。這種實(shí)現(xiàn)利用Java注解(annotations)來管理JavaBeans的CRUD周期(Create Read Update Delete)。事務(wù)處理被交還給數(shù)據(jù)庫,而XML映射描述符則被注解代替。本文所面向的讀者是那些對(duì)不使用XML描述符來有效操縱數(shù)據(jù)庫感興趣的Java中級(jí)開發(fā)者。

計(jì)算機(jī)業(yè)中有一條不成文的說法:面向?qū)ο筌浖完P(guān)系數(shù)據(jù)庫之間的數(shù)據(jù)共享,最好通過對(duì)象/關(guān)系(O/R)映射框架來進(jìn)行,而這種框架是實(shí)體關(guān)系(ER)模型依賴于面向?qū)ο竽P偷摹1疚耐扑]的一個(gè)叫Amber的框架提供了一種相反的輕量級(jí)實(shí)現(xiàn)。這種實(shí)現(xiàn)利用Java注解(annotations)來管理JavaBeans的CRUD周期(Create Read Update Delete)。事務(wù)處理被交還給數(shù)據(jù)庫,而XML映射描述符則被注解代替。本文所面向的讀者是那些對(duì)不使用XML描述符來有效操縱數(shù)據(jù)庫感興趣的Java中級(jí)開發(fā)者。


動(dòng)機(jī)

普通O/R映射框架是非常強(qiáng)大的;但是在介紹如何設(shè)計(jì)和部署時(shí),一些問題卻很少被提及。我們將列出這些缺點(diǎn),并針對(duì)這些問題來演示一個(gè)叫Amber的框架。
1.????????OO驅(qū)動(dòng)的數(shù)據(jù)模型導(dǎo)致了過于簡(jiǎn)單的ER模型。
2.????????XML描述符使得維護(hù)困難。
3.????????在O/R層進(jìn)行事務(wù)處理非常困難。
4.????????現(xiàn)有框架的學(xué)習(xí)曲線相對(duì)陡峭。

在兩種模型之間交換數(shù)據(jù),比如ER模型和OO模型,必須克服所謂的阻抗不匹配。對(duì)于大多數(shù)O/R模型工具來說,對(duì)象模型處于支配地位。大體上,這意味著Java持久層負(fù)責(zé)從現(xiàn)有的對(duì)象模型生成ER模型。這個(gè)主意非常引人注目,因?yàn)楫?dāng)商務(wù)模型確定以后,開發(fā)團(tuán)隊(duì)就再也不需要擔(dān)心持久化的問題了。

對(duì)于常規(guī)的O/R工具而言,ER模型是一個(gè)結(jié)果,一個(gè)產(chǎn)物,頂多是一個(gè)容器。而商務(wù)過程實(shí)際上是按ER模型設(shè)計(jì)的,這就導(dǎo)致了兩者之間的不協(xié)調(diào)。這樣的話,ER模型的調(diào)整就非常困難,甚至是不可能的,因?yàn)镺/R框架可能會(huì)在任何時(shí)候重構(gòu)ER模型。同樣,當(dāng)商務(wù)過程發(fā)生改變時(shí),O/R域的調(diào)整會(huì)自動(dòng)重構(gòu)ER域,于是ER模型變得令人費(fèi)解,并且有時(shí)性能會(huì)下降到臨界點(diǎn)。

還有另一個(gè)問題。會(huì)被持久化的類需要在外部XML描述(映射)文件中部署。初看不錯(cuò),但是當(dāng)我們處理現(xiàn)存的系統(tǒng)時(shí),這很快就成了煩人的事。只要發(fā)生了一點(diǎn)變動(dòng),就得有不只一個(gè)地方需要修改,也就是源代碼和映射文件。

最后,現(xiàn)有的O/R框架是為了處理事務(wù)而設(shè)計(jì)的。綜合來看,這不是必須的,因?yàn)榇鎯?chǔ)容器(例如關(guān)系數(shù)據(jù)庫)是非常傻的容器。盡管我們不得不進(jìn)行事務(wù)處理,但那并不是我們想要的。這些應(yīng)該是數(shù)據(jù)庫的事。

介紹Amber

Amber從相反的角度來解決數(shù)據(jù)交換的問題。它采用ER模型為參考來確立OO結(jié)構(gòu)。它還采用存儲(chǔ)過程作為數(shù)據(jù)庫訪問主要方式,存儲(chǔ)過程提供了訪問數(shù)據(jù)庫的唯一途徑,并且完全的建立起事務(wù)處理機(jī)制。最后中間層會(huì)被實(shí)現(xiàn)為一系列存儲(chǔ)過程的集合。這意味著ER模型的專家,數(shù)據(jù)庫管理員要負(fù)責(zé)設(shè)計(jì)和優(yōu)化包括存儲(chǔ)過程在內(nèi)的一系列問題,于是比起自動(dòng)創(chuàng)建的系統(tǒng),新的系統(tǒng)能夠擁有更好的結(jié)構(gòu),更快的速度和更安全的訪問。因此,許多難題迎刃而解。
•????????事務(wù)能夠(或者說應(yīng)該)被封裝進(jìn)存儲(chǔ)過程。
•????????讀操作僅返回結(jié)果集合。
•????????寫操作只需要調(diào)用存儲(chǔ)過程,而不是在Java代碼中嵌入SQL。
•????????使用存儲(chǔ)過程,就不會(huì)因?yàn)镾QL注入而導(dǎo)致安全漏洞。

當(dāng)然,這意味著通常在Java代碼中處理的事被轉(zhuǎn)移到存儲(chǔ)過程中了。這樣不會(huì)有人犯錯(cuò)了。這對(duì)Java開發(fā)者來說有莫大的好處。

映射

Amber的核心在于,不管被提交到數(shù)據(jù)庫的查詢是什么,查詢結(jié)果都是一列Java對(duì)象。當(dāng)然,這是從Java開發(fā)者的角度來看的。那么剩下的問題只是把字段映射到對(duì)象的屬性。以及在把數(shù)據(jù)寫入數(shù)據(jù)庫時(shí),把Java對(duì)象的屬性映射到存儲(chǔ)過程的參數(shù)。

Amber把結(jié)果集映射到JavaBean,并用相同的機(jī)制在增刪改時(shí)把bean的內(nèi)容映射到參數(shù)。對(duì)于JavaBean的相關(guān)信息和定義,請(qǐng)查看資源那一段。

這種做法用到了Java語言的新特性,這個(gè)叫做注解的特性是從J2SE 5.0開始使用的。

注解,在JSR 175中也叫做“元數(shù)據(jù)”,是一種輔助代碼,可以用來提供類,方法,屬性的詳細(xì)信息。在Javadoc API中,元數(shù)據(jù)本來是為了用來內(nèi)聯(lián)文檔的。所以,在不干擾正常代碼的前提下,注解可以用來描述代碼的具體作用。如果你想知道關(guān)于注解更多的信息以及作用,請(qǐng)參考Tiger: A Developer's Notebook,或者看看我寫的一篇更有趣的文章" Annotations to the Rescue"。

一步一步來

我們來解決一個(gè)小的持久化問題。從數(shù)據(jù)庫讀取一列Jedi對(duì)象,我們假設(shè)返回的結(jié)果集看起來像下面的表格。請(qǐng)注意,我們接下來的討論并不依賴于這些表格,盡管這些例子實(shí)際上都是基于這些表格的。一般來說,我們得到的表列數(shù)據(jù)是通過使用少量的SQL連接多個(gè)表或者視圖來得到的。(當(dāng)然,先向星球大戰(zhàn)的愛好者們告罪。)
image

我們先定義一個(gè)叫Jedi的簡(jiǎn)單類。
public class Jedi {

?? private Integer _id;
?? private String _name;
?? private Double _forceRating;
?? private Integer _age;
?? private Boolean _alive;

?? @ColumnAssociation(name="jedi_id")
?? public void setId( Integer id ) {
??????_id = id;
?? }
?? @ColumnAssociation(name="name")
?? public void setName( String name ) {
??????_name = name;
?? }
?? @ColumnAssociation(name="force_rating")
?? public void setForceRating( Double fr ) {
??????_forceRating = fr;
?? }
?? @ColumnAssociation(name="age")
?? public void setAge( Integer age ) {
??????_age = age;
?? }
?? @ColumnAssociation(name="alive")
?? public void setAlive( Boolean alive ) {
??????_alive = alive;
?? }

?? @ParameterAssociation(name="@jedi_id",
?? index=0, isAutoIdentity=true)
?? public Integer getId() {
??????return _id;
?? }
?? @ParameterAssociation(name="@name", index=1)
?? public String getName() {
??????return _name;
?? }
?? @ParameterAssociation(name="@force_rating",
?? index=2)
?? public Double getForceRating() {
??????return _forceRating;
?? }
?? @ParameterAssociation(name="@age", index=3)
?? public Integer getAge() {
??????return _age;
?? }
?? @ParameterAssociation(name="@alive", index=4)
?? public Boolean getAlive() {
??????return _alive;
?? }
}


這里發(fā)生了什么?你在類中看到getter和setter方法上面有兩種注解。

注解@ColumnAssociation用來把JavaBean中的setter方法和結(jié)果集中的一個(gè)字段連接起來,這樣從數(shù)據(jù)庫中得到的表列數(shù)據(jù)就能夠被Amber 寫入bean的屬性里。注解@ColumnAssociation只適用于setter方法,因?yàn)锳mber使用這些注解以及從數(shù)據(jù)庫中讀取的相應(yīng)值來尋找和調(diào)用那些方法。

同樣,getter方法需要@ParameterAssociation注解來把JavaBean的屬性和調(diào)用增刪改操作時(shí)的參數(shù)連接起來。這個(gè)注解只適用于getter方法,因?yàn)锳mber使用getter方法來把值填到參數(shù)里。因?yàn)镴DBC的緣故,需要提供參數(shù)的索引。這個(gè)也許的多余的,取決于數(shù)據(jù)庫以及存儲(chǔ)過程是否需要,不過為了完整性,以及遵循JDBC API規(guī)范,最好還是提供一下。

必須提供一個(gè)無參數(shù)的構(gòu)造函數(shù),因?yàn)檫@個(gè)類會(huì)被自動(dòng)構(gòu)造(通過反射)。在上面的類中,沒有無參數(shù)構(gòu)造函數(shù)是允許的,因?yàn)槲覀儧]有提供其他的構(gòu)造函數(shù),但是當(dāng)我們?cè)黾恿祟~外的構(gòu)造函數(shù)時(shí),就必須提供一個(gè)明確的給Amber。

這個(gè)JavaBean的作用是從數(shù)據(jù)庫里讀出數(shù)據(jù)以及寫回?cái)?shù)據(jù)庫。完全不需要外部的描述文件。注意,我們也可以用這種方式建立任何類,不只是JavaBean。

你也許會(huì)奇怪:為什么用注解?為什么不是像JavaBean那樣通過屬性名來隱式關(guān)聯(lián)?我們這么做,是為了讓我們的設(shè)計(jì)保持一定的自由度。換句話說,我們的Java代碼不需要依賴于ER模型設(shè)計(jì)的字段名。如果你已經(jīng)習(xí)慣于操作表,你也許不同意這點(diǎn),但是當(dāng)你使用的存儲(chǔ)過程里需要連接表和視圖時(shí),你就不得不使用別名。

Amber的連接器和JDBC

在我們開始讀寫數(shù)據(jù)庫之前,我們需要與數(shù)據(jù)庫建立連接。Amber使用一個(gè)Connector來訪問數(shù)據(jù)庫。簡(jiǎn)單說來,這就是把數(shù)據(jù)庫驅(qū)動(dòng)和連接字符串結(jié)合使用而已。在應(yīng)用中,我們使用一個(gè)ConnectorFactory來管理可用連接。像下面的代碼那樣,我們使用一個(gè)本地的type-4驅(qū)動(dòng)來初始化一個(gè)到SQL server的連接。我們假設(shè)服務(wù)器的名字是localhost,數(shù)據(jù)庫的名字是jedi,用戶名是use,密碼是theforce,為了簡(jiǎn)單一點(diǎn),我在下面的代碼中省略了全部的異常處理。
String driverClassName = 
?? "com.microsoft.jdbc.sqlserver.SQLServerDriver";
String url =
?? "jdbc:microsoft:sqlserver://" +
?? "localhost;databasename=jedi;" +
?? "user=use;pwd=theforce";
Amber's Connector is associated with a String, alias under which it remains accessible from the ConnectorFactory. Here, we're going to use the alias starwars.
Amber的Connector使用一個(gè)String作為別名來從ConnectorFactory獲取連接,接下來,我們將使用別名starwars。
ConnectorFactory.getInstance().add(
?? "starwars", driverClassName, url
);


因?yàn)镃onnector是對(duì)JDBC連接的輕量級(jí)封裝,所以我們可以像以前一樣操作這個(gè)連接。

讀取

封裝在Connector外面的是一個(gè)BeanReader對(duì)象,它需要一個(gè)Connector和一個(gè)Class來告訴reader從數(shù)據(jù)庫讀出的bean是什么類型的。現(xiàn)在讀取一列Jedi對(duì)象就只需要下面幾行了。
Connector connector = 
?? ConnectorFactory.createConnector( "starwars" );
BeanReader reader =
?? new BeanReader( Jedi.class, connector );
Collection<Jedi> jediList =
?? reader.executeCreateBeanList(
??????"select * from jedi"
?? );


這段代碼使用了一種叫泛型的新特性,這種特性是從J2SE 5.0開始使用的。Collection聲明的那行代碼表明jediList一律由Jedi類型的對(duì)象組成。編譯器在這里會(huì)發(fā)出警告,reader只有在運(yùn)行時(shí)刻才知道會(huì)產(chǎn)生什么類型的對(duì)象。因?yàn)樵贘2SE 5.0中,泛型在執(zhí)行的時(shí)候會(huì)把類型信息抹掉,所以可能導(dǎo)致不安全的類型轉(zhuǎn)換。非常遺憾的,因?yàn)橥辉颍覀儾荒馨袯eanReader寫成BeanReader<Jedi>。簡(jiǎn)單說,就是Java的反射和泛型不能混合使用。

那么復(fù)合結(jié)構(gòu)會(huì)如何呢?好吧,我們有幾種方法可以處理這個(gè)問題。比如,我們?cè)贘edi和Fighter (例如,每個(gè)Jedi有好幾艘太空戰(zhàn)斗機(jī))之間有一個(gè)一對(duì)多的關(guān)系。在數(shù)據(jù)庫中,F(xiàn)ighter的數(shù)據(jù)看起來像下面那樣。
image

換句話說,Luke有兩艘戰(zhàn)斗機(jī)(X-和B-Wing),Yoda則擁有一艘Star Destroyer,而Obi Wan已經(jīng)死掉了。

數(shù)據(jù)之間的關(guān)系在OO域中有幾種方法可以模型化。我們只挑選最簡(jiǎn)單的那種。所以我們需要Jedi類可以擁有一組Fighter對(duì)象作為成員。下面的Fighter類是為了讓Amber使用而建立的。
public class Fighter {

?? private Integer _id;
?? private Integer _jediId;
?? private String _name;
?? private Double _firepowerRating;
?? private Boolean _turboLaserEquipped;

?? @ColumnAssociation(name="fighter_id")
?? public void setId( Integer id ) {
??????_id = id;
?? }
?? @ColumnAssociation(name="jedi_id")
?? public void setJediId( Integer jediId ) {
??????_jediId = jediId;
?? }
?? @ColumnAssociation(name="name")
?? public void setName( String name ) {
??????_name = name;
?? }
?? @ColumnAssociation(name="firepower_rating")
?? public void setFirepowerRating( Double firepowerRating ) {
??????_firepowerRating = firepowerRating;
?? }
?? @ColumnAssociation(name="turbo_laser_equipped")
?? public void setTurboLaserEquipped(
??????Boolean turboLaserEquipped ) {
??????_turboLaserEquipped = turboLaserEquipped;
?? }
?? @ParameterAssociation(name="@fighter_id",
??????index=0,isAutoIdentity=true)
?? public Integer getId() {
??????return _id;
?? }
?? @ParameterAssociation(name="@jedi_id",index=1)
?? public Integer getJediId() {
??????return _jediId;
?? }
?? @ParameterAssociation(name="@name",index=2)
?? public String getName() {
??????return _name;
?? }
?? @ParameterAssociation(name="@firepower_rating",
??????index=3)
?? public Double getFirepowerRating() {
??????return _firepowerRating;
?? }
?? @ParameterAssociation(name="@turbo_laser_equipped",
??????index=4)
?? public Boolean getTurboLaserEquipped() {
??????return _turboLaserEquipped;
?? }
}


下面的是改進(jìn)后的Jedi類。它新增加了一個(gè)List<Fighter>類型的成員。下面的J2SE 5.0代碼表明鏈表只包含F(xiàn)ighter類型的對(duì)象。新增加的代碼用粗體表示。
public class Jedi {

?? private Integer _id;
?? private String _name;
?? private Double _forceRating;
?? private Integer _age;
?? private Boolean _alive;
??
?? private ArrayList<Fighter> _fighterList =
??????new ArrayList<Fighter>();
??
?? @ColumnAssociation(name="jedi_id")
?? public void setId( Integer id ) {
??????_id = id;
?? }
?? @ColumnAssociation(name="name")
?? public void setName( String name ) {
??????_name = name;
?? }
?? @ColumnAssociation(name="force_rating")
?? public void setForceRating( Double forceRating ) {
??????_forceRating = forceRating;
?? }
?? @ColumnAssociation(name="age")
?? public void setAge( Integer age ) {
??????_age = age;
?? }
?? @ColumnAssociation(name="alive")
?? public void setAlive( Boolean alive ) {
??????_alive = alive;
?? }

?? @ParameterAssociation(name="@jedi_id",
??????index=0, isAutoIdentity=true)
?? public Integer getId() {
??????return _id;
?? }
?? @ParameterAssociation(name="@name", index=1)
?? public String getName() {
??????return _name;
?? }
?? @ParameterAssociation(name="@force_rating",
??????index=2)
?? public Double getForceRating() {
??????return _forceRating;
?? }
?? @ParameterAssociation(name="@age", index=3)
?? public Integer getAge() {
??????return _age;
?? }
?? @ParameterAssociation(name="@alive", index=4)
?? public Boolean getAlive() {
??????return _alive;
?? }
?? public ArrayList<Fighter> getFighterList() {
??????return _fighterList;
?? }
?? public void setFighterList( ArrayList<Fighter> fighterList ) {
??????_fighterList = fighterList;
?? }
}


從數(shù)據(jù)庫讀取Jedis的代碼看起來像下面這樣:
Connector connector = 
?? ConnectorFactory.getInstance().createConnector( "starwars" );
BeanReader jediReader =
?? new BeanReader( Jedi.class, connector );
BeanReader fighterReader =
?? new BeanReader( Fighter.class, connector );
Collection<Jedi> jediList =
?? reader.executeCreateBeanList( "select * from jedi" );
for( Jedi jedi : jediList ) {
?? String query =
??????"select * from fighter where jedi_id = " + jedi.getId();
?? Collection<Fighter> fighters =
??????fighterReader.executeCreateBeanList( query );
?? jedi.setFighterList(
??????new ArrayList<Fighter>( fighters ) );
}


瞧,這就是Jedi們擁有的戰(zhàn)斗機(jī)了。請(qǐng)注意,我們并沒有敲出把Fighter讀進(jìn)Jedi的代碼。因?yàn)镴edi和Fighter會(huì)嚴(yán)格的匹配。你會(huì)說上面的代碼在依賴注入模式中只是一些部件。也許我是在說大話,我只想說:把互相依賴的東西分開,并且使分布在各處的代碼共同工作。如果你想在這方面知道得更多,請(qǐng)看Martin Fowler的"Inversion of Control Containers and the Dependency Injection pattern"。

寫入

現(xiàn)在,該寫入了。把改變了的Jedi寫入數(shù)據(jù)庫只需要下面幾行代碼。
Connector connector = 
?? ConnectorFactory.getInstance().createConnector( "starwars" );
BeanWriter writer =
?? new BeanWriter( Jedi.class, connector );
writer.executeStringUpdate(
?? sampleBean, "UpdateJedi" );


這里,數(shù)據(jù)庫訪問通過生成SQL查詢字符串。最下面一行代碼生成執(zhí)行字符串并發(fā)送到數(shù)據(jù)庫,修改了使用1000作為id的Jedi(就是Obi Wan)的狀態(tài)(假設(shè)我們把屬性alive改為true,把forceRating改為6.0)。
UpdateJedi 
?? @name='Obi Wan Kenobi', @jedi_id=1000,
?? @alive=1, @force_rating=6.0, @age=30



如果你想建立一個(gè)新的Jedi,我們只需要簡(jiǎn)單的構(gòu)造一個(gè)新的Jedi并用下面的代碼寫入數(shù)據(jù)庫。
Jedi newJedi = new Jedi();
newJedi.setName( "Mace Windu");
newJedi.setAge( 40 );
newJedi.setAlive( false );
newJedi.setForceRating( 9.7 );
Connector connector =
????ConnectorFactory.getInstance().createConnector( "starwars" );
BeanWriter writer =
????new BeanWriter( Jedi.class, connector );
writer.executeStringInsert(
????newJedi, "InsertJedi" );


你會(huì)注意到,我們使用了不同的方法和存儲(chǔ)過程來寫入數(shù)據(jù)。最后字符串會(huì)是這樣。
InsertJedi 
?? @name='Mace Windu', @alive=0,
?? @force_rating=9.7, @age=40


發(fā)生了什么?我們假設(shè)屬性jediId是由數(shù)據(jù)庫自動(dòng)生成的。實(shí)際上,在上面定義的Jedi類中,我們指定@ParameterAssociation的屬性isAutoIdentity=true來達(dá)成這一點(diǎn)。因?yàn)閿?shù)據(jù)庫會(huì)給bean提供主鍵,所以參數(shù)@jedi_id就省略了。

這里需要注意一下。因?yàn)閖ediId是由數(shù)據(jù)庫提供的,所以這個(gè)數(shù)據(jù)一定會(huì)通過存儲(chǔ)過程InsertJedi傳回?cái)?shù)據(jù)庫。隨后,方法executeStringInsert返回一個(gè)JDBC的ResultSet,用來返回ID或者剛插入的數(shù)據(jù)行。這個(gè)信息可以手動(dòng)處理,不過Amber提供了輔助函數(shù)來把新的ID注入到新對(duì)象中。

比起操作的透明度,讀寫時(shí)使用字符串來處理的類型安全問題更容易讓人擔(dān)心。因?yàn)榘褏?shù)轉(zhuǎn)化成字符串后,類型信息就丟失了。然而,這種技術(shù)有一個(gè)很大的優(yōu)勢(shì):任何查詢字符串都會(huì)被記錄下來,數(shù)據(jù)庫管理員可以通過分析來找出錯(cuò)誤原因,并且準(zhǔn)確知道應(yīng)用調(diào)用了什么或者從數(shù)據(jù)庫查詢了什么。這種類型的透明使得調(diào)試更加容易。

如果Jedi的戰(zhàn)斗機(jī)列表改變了,還是手動(dòng)更新數(shù)據(jù)庫比較好。取決于Fighter列表發(fā)生的變化,比較粗魯?shù)淖龇ㄊ莿h除這個(gè)Jedi的全部戰(zhàn)斗機(jī)列表,然后把新的列表寫回?cái)?shù)據(jù)庫中。假設(shè)我們手里有一個(gè)jedi對(duì)象和一列新的Fighter對(duì)象,我們接下來需要把新列表寫進(jìn)fighters中。更進(jìn)一步,我們假設(shè)通過存儲(chǔ)過程InsertFighter把一個(gè)新的Fighter對(duì)象寫進(jìn)數(shù)據(jù)庫。
Connector connector = 
?? ConnectorFactory.createConnector( "starwars" );
BeanWriter writer =
?? new BeanWriter( Fighter.class, connector );
connector.execute(
?? "delete from fighters where jedi_id = " + jedi.getId() );
for( Fighter fighter : fighters ) {
?? fighter.setJediId( jedi.getId() );
?? connector.executeStringInsert(
??????fighter, "InsertFighter" )
}


這段代碼處理一整套的執(zhí)行字符串,每個(gè)字符串中的name分別對(duì)應(yīng)著fighters表中的fighter:
InsertFighter @jediId=..., @name="...";


你也許注意到了,這個(gè)方法并沒動(dòng)用事務(wù)。像上面說的那樣,這里并沒使用異常處理,如果delete操作失敗了,會(huì)產(chǎn)生一個(gè)SQLException,而后面的循環(huán)根本不會(huì)被執(zhí)行。可是如果是其他情況呢?比如接下來的InsertFighter調(diào)用出錯(cuò)了呢?這時(shí)事務(wù)是必須的,最好把操作放在存儲(chǔ)過程里面。如果我想在事務(wù)中從Fighter對(duì)象獲取全部參數(shù)以及Jedi ID并處理“新”戰(zhàn)斗機(jī)呢?這個(gè)話題值得在另一篇文章中討論。

局限和缺點(diǎn)

像任何工具或者技術(shù)一樣,我們討論的方法具有一定的局限性。
&#8226;????????因?yàn)椴皇褂肵ML描述符,所以當(dāng)數(shù)據(jù)庫和對(duì)象域之間的接口發(fā)生改變時(shí),就會(huì)出問題。實(shí)際上,當(dāng)改變只發(fā)生在名字而不是類型時(shí),或者沒有屬性/字段增減時(shí),使用XML描述符比Amber好一點(diǎn)。如果不是上述情況,兩種系統(tǒng)都需要重新編譯和部署。
&#8226;????????復(fù)合管理不是自動(dòng)化的。事實(shí)上,當(dāng)你比較Amber和大的O/R框架時(shí),你會(huì)發(fā)現(xiàn)有很多東西都不是自動(dòng)化的。在把數(shù)據(jù)庫作為啞存儲(chǔ)設(shè)備或者用表連接中間層的商業(yè)設(shè)定中,Amber并沒有太大的用處。另一方面,你可以說Amber適合依賴注入風(fēng)格的設(shè)計(jì),以及數(shù)據(jù)之間的松耦合,這通常認(rèn)為比隱式依賴要好。
&#8226;????????最后,注解分析以及自省機(jī)制的運(yùn)行開銷比較大。在一個(gè)與數(shù)據(jù)庫有大量交互(比如,一個(gè)用于并發(fā)用戶交互的中間件)而不是單用戶或者少量用戶偶爾交互的系統(tǒng)中,Amber會(huì)導(dǎo)致性能問題。

結(jié)論

這篇文章示范了一種相對(duì)于傳統(tǒng)的O/R映射相反的R/O映射。所謂的面向?qū)ο蠛完P(guān)系系統(tǒng)之間的阻抗不匹配,在把關(guān)系數(shù)據(jù)模型定義成對(duì)象域的引用模型,以及使用存儲(chǔ)過程這一工具來操作數(shù)據(jù)庫(尤其是寫操作)之后,這個(gè)復(fù)雜的映射任務(wù)被簡(jiǎn)化了。這種映射是通過注解這種Java 1.5的新語言特性來實(shí)現(xiàn)的。我們通過Amber框架來支持和演示了這種方法。

Amber是一個(gè)小型框架,易于學(xué)習(xí)和使用。只需要處理幾個(gè)非常接近JDBC的類。數(shù)據(jù)庫和JavaBean之間的連接通過注解來實(shí)現(xiàn),不需要任何XML描述符,因?yàn)閄ML對(duì)人來說可讀性不高。而數(shù)據(jù)庫和應(yīng)用之間的映射也都在bean類之中。Amber也提供了一種強(qiáng)制檢測(cè)機(jī)制來驗(yàn)證內(nèi)容,不過為了節(jié)約篇幅,就不在這篇文章中討論了。

Amber只做了一件事并且做得很好:把數(shù)據(jù)庫的列以及查詢參數(shù)映射到JavaBean的屬性。不多,也不少。Amber不是銀彈,也沒有解決那些龐大的工業(yè)O/R框架才能處理的問題。

Amber已經(jīng)在一個(gè)商業(yè)環(huán)境中證明了它的價(jià)值。在Impetus,我們?yōu)橐患业聡畲蟮泥]購公司提供了銷售人員解決方案,系統(tǒng)基于Java,使用了MS SQL Server,而我們使用Amber處理了全部的數(shù)據(jù)庫交互。自從今年春天(自從J2SE 5.0的到來)以來,我們沒有改變一點(diǎn)API,而且使用中也沒出現(xiàn)什么大問題。


版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時(shí)請(qǐng)務(wù)必保留以下作者信息和鏈接
作者:Norbert Ehrekedeafwolf(作者的blog:http://blog.matrix.org.cn/page/deafwolf)
原文:http://www.matrix.org.cn/resource/article/44/44381_Amber+Object+Mapping.html
關(guān)鍵字:relational;mapping;Amber