摘要:

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

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


動機(jī)

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

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

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

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

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

介紹Amber

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

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

映射

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

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

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

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

一步一步來

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

我們先定義一個叫Jedi的簡單類。
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é)果集中的一個字段連接起來,這樣從數(shù)據(jù)庫中得到的表列數(shù)據(jù)就能夠被Amber 寫入bean的屬性里。注解@ColumnAssociation只適用于setter方法,因為Amber使用這些注解以及從數(shù)據(jù)庫中讀取的相應(yīng)值來尋找和調(diào)用那些方法。

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

必須提供一個無參數(shù)的構(gòu)造函數(shù),因為這個類會被自動構(gòu)造(通過反射)。在上面的類中,沒有無參數(shù)構(gòu)造函數(shù)是允許的,因為我們沒有提供其他的構(gòu)造函數(shù),但是當(dāng)我們增加了額外的構(gòu)造函數(shù)時,就必須提供一個明確的給Amber。

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

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

Amber的連接器和JDBC

在我們開始讀寫數(shù)據(jù)庫之前,我們需要與數(shù)據(jù)庫建立連接。Amber使用一個Connector來訪問數(shù)據(jù)庫。簡單說來,這就是把數(shù)據(jù)庫驅(qū)動和連接字符串結(jié)合使用而已。在應(yīng)用中,我們使用一個ConnectorFactory來管理可用連接。像下面的代碼那樣,我們使用一個本地的type-4驅(qū)動來初始化一個到SQL server的連接。我們假設(shè)服務(wù)器的名字是localhost,數(shù)據(jù)庫的名字是jedi,用戶名是use,密碼是theforce,為了簡單一點,我在下面的代碼中省略了全部的異常處理。
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使用一個String作為別名來從ConnectorFactory獲取連接,接下來,我們將使用別名starwars。
ConnectorFactory.getInstance().add(
?? "starwars", driverClassName, url
);


因為Connector是對JDBC連接的輕量級封裝,所以我們可以像以前一樣操作這個連接。

讀取

封裝在Connector外面的是一個BeanReader對象,它需要一個Connector和一個Class來告訴reader從數(shù)據(jù)庫讀出的bean是什么類型的。現(xiàn)在讀取一列Jedi對象就只需要下面幾行了。
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類型的對象組成。編譯器在這里會發(fā)出警告,reader只有在運(yùn)行時刻才知道會產(chǎn)生什么類型的對象。因為在J2SE 5.0中,泛型在執(zhí)行的時候會把類型信息抹掉,所以可能導(dǎo)致不安全的類型轉(zhuǎn)換。非常遺憾的,因為同一原因,我們不能把BeanReader寫成BeanReader<Jedi>。簡單說,就是Java的反射和泛型不能混合使用。

那么復(fù)合結(jié)構(gòu)會如何呢?好吧,我們有幾種方法可以處理這個問題。比如,我們在Jedi和Fighter (例如,每個Jedi有好幾艘太空戰(zhàn)斗機(jī))之間有一個一對多的關(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域中有幾種方法可以模型化。我們只挑選最簡單的那種。所以我們需要Jedi類可以擁有一組Fighter對象作為成員。下面的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類。它新增加了一個List<Fighter>類型的成員。下面的J2SE 5.0代碼表明鏈表只包含F(xiàn)ighter類型的對象。新增加的代碼用粗體表示。
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ī)了。請注意,我們并沒有敲出把Fighter讀進(jìn)Jedi的代碼。因為Jedi和Fighter會嚴(yán)格的匹配。你會說上面的代碼在依賴注入模式中只是一些部件。也許我是在說大話,我只想說:把互相依賴的東西分開,并且使分布在各處的代碼共同工作。如果你想在這方面知道得更多,請看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



如果你想建立一個新的Jedi,我們只需要簡單的構(gòu)造一個新的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" );


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


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

這里需要注意一下。因為jediId是由數(shù)據(jù)庫提供的,所以這個數(shù)據(jù)一定會通過存儲過程InsertJedi傳回數(shù)據(jù)庫。隨后,方法executeStringInsert返回一個JDBC的ResultSet,用來返回ID或者剛插入的數(shù)據(jù)行。這個信息可以手動處理,不過Amber提供了輔助函數(shù)來把新的ID注入到新對象中。

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

如果Jedi的戰(zhàn)斗機(jī)列表改變了,還是手動更新數(shù)據(jù)庫比較好。取決于Fighter列表發(fā)生的變化,比較粗魯?shù)淖龇ㄊ莿h除這個Jedi的全部戰(zhàn)斗機(jī)列表,然后把新的列表寫回數(shù)據(jù)庫中。假設(shè)我們手里有一個jedi對象和一列新的Fighter對象,我們接下來需要把新列表寫進(jìn)fighters中。更進(jìn)一步,我們假設(shè)通過存儲過程InsertFighter把一個新的Fighter對象寫進(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í)行字符串,每個字符串中的name分別對應(yīng)著fighters表中的fighter:
InsertFighter @jediId=..., @name="...";


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

局限和缺點

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

結(jié)論

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

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

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

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


版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時請務(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