orm提取指定列_使用ORM提取数据很容易! 是吗?
orm提取指定列
介紹
幾乎所有系統(tǒng)都以某種方式與外部數(shù)據(jù)存儲一起運(yùn)行。 在大多數(shù)情況下,它是一個關(guān)系數(shù)據(jù)庫,并且通常將數(shù)據(jù)獲取委托給某些ORM實(shí)現(xiàn)。 ORM涵蓋了很多例程,并且?guī)砹艘恍┬碌某橄笞鳛榛貓蟆?
Martin Fowler寫了一篇有關(guān)ORM的有趣文章 ,其中的主要思想之一是:“ ORM幫助我們處理大多數(shù)企業(yè)應(yīng)用程序中非常實(shí)際的問題。 …他們不是很好的工具,但是他們解決的問題也不是很可愛。 我認(rèn)為他們應(yīng)該得到更多的尊重和更多的理解。”
在CUBA框架中,由于我們在全球范圍內(nèi)有各種各樣的項(xiàng)目,因此我們非常頻繁地使用ORM,并且對它的局限性了解很多。 有很多事情可以討論,但我們將集中討論其中之一:懶惰與急切的數(shù)據(jù)獲取。 我們將討論數(shù)據(jù)獲取的不同方法(主要是在JPA API和Spring中),我們?nèi)绾卧贑UBA中處理數(shù)據(jù)以及我們?yōu)樘岣逤UBA中的ORM層所做的RnD工作。 我們將研究一些基本要素,這些要素可能會幫助開發(fā)人員避免使用ORM帶來糟糕的性能問題。
獲取數(shù)據(jù):懶惰還是渴望?
如果您的數(shù)據(jù)模型僅包含一個實(shí)體,那么使用ORM不會有任何問題。 讓我們看一個例子。 我們有一個具有ID和名稱的用戶:
public class User { @Id @GeneratedValue private int id; private String name; //Getters and Setters here }要獲取它,我們只需要很好地詢問EntityManager即可:
EntityManager em = entityManagerFactory.createEntityManager(); User user = em.find(User. class , id);當(dāng)實(shí)體之間存在一對多關(guān)系時,事情就會變得有趣起來:
public class User { @Id @GeneratedValue private int id; private String name; @OneToMany private List<Address> addresses; //Getters and Setters here }如果要從數(shù)據(jù)庫中獲取用戶記錄,則會出現(xiàn)一個問題:“我們也應(yīng)該獲取地址嗎?”。 正確的答案將是:“取決于”。 在某些用例中,我們可能需要其中一些地址-不需要。 通常,ORM提供兩個用于獲取數(shù)據(jù)的選項(xiàng):惰性和渴望。 它們中的大多數(shù)默認(rèn)情況下都設(shè)置了惰性獲取模式。 而當(dāng)我們編寫以下代碼時:
EntityManager em = entityManagerFactory.createEntityManager(); User user = em.find(User. class , 1 ); em.close(); System.out.println(user.getAddresses().get( 0 ));我們得到了所謂的“LazyInitException” ,這使ORM新手非常困惑。 在這里,我們需要解釋“附加”和“分離”對象的概念,并講述數(shù)據(jù)庫會話和事務(wù)。
好的,然后,應(yīng)將實(shí)體實(shí)例附加到會話,以便我們能夠獲取詳細(xì)信息屬性。 在這種情況下,我們遇到了另一個問題–交易時間越來越長,因此陷入僵局的風(fēng)險增加了。 而且,由于短查詢的數(shù)量增加,將我們的代碼拆分為一系列短事務(wù)可能會導(dǎo)致數(shù)據(jù)庫“百萬蚊子死亡”。
如前所述,您可能需要也可能不需要獲取Addresses屬性,因此僅在某些用例中需要“觸摸”集合,從而添加更多條件。 嗯... 看起來越來越復(fù)雜。
好的,另一種提取類型會有所幫助嗎?
public class User { @Id @GeneratedValue private int id; private String name; @OneToMany (fetch = FetchType.EAGER) private List<Address> addresses; //Getters and Setters here }好吧,不完全是。 我們將擺脫煩人的懶惰init異常,并且不應(yīng)該檢查實(shí)例是已附加還是已分離。 但是我們遇到了性能問題,因?yàn)橥瑯?#xff0c;我們并不需要所有情況的地址,而是始終選擇它們。 還有其他想法嗎?
Spring JDBC
一些開發(fā)人員對ORM感到非常惱火,以至于他們使用Spring JDBC切換到“半自動”映射。 在這種情況下,我們將為唯一的用例創(chuàng)建唯一的查詢,并返回包含僅對特定用例有效的屬性的對象。
它給了我們極大的靈活性。 我們只能得到一個屬性:
String name = this .jdbcTemplate.queryForObject( "select name from t_user where id = ?" , new Object[]{1L}, String. class );或整個對象:
User user = this .jdbcTemplate.queryForObject( "select id, name from t_user where id = ?" , new Object[]{1L}, new RowMapper<User>() { public User mapRow(ResultSet rs, int rowNum) throws SQLException { User user = new User(); user.setName(rs.getString( "name" )); user.setId(rs.getInt( "id" )); return user; } });您也可以使用ResultSetExtractor來獲取地址,但是它涉及編寫一些額外的代碼,并且您應(yīng)該知道如何編寫SQL聯(lián)接以避免n + 1 select問題 。
好吧,它又變得越來越復(fù)雜。 您可以控制所有查詢,也可以控制映射,但是您必須編寫更多代碼,學(xué)習(xí)SQL并知道如何執(zhí)行數(shù)據(jù)庫查詢。 盡管我認(rèn)為了解SQL基礎(chǔ)知識對于幾乎每個開發(fā)人員都是必不可少的技能,但其中一些人并不這么認(rèn)為,因此我不會與他們爭論。 如今,了解x86匯編器也不是每個人都必不可少的技能。 讓我們考慮一下如何簡化開發(fā)。
JPA實(shí)體圖
讓我們退后一步,嘗試了解我們將要實(shí)現(xiàn)什么? 似乎我們需要做的就是準(zhǔn)確告訴我們在不同用例中要獲取哪些屬性。 那我們做吧! JPA 2.1引入了新的API –實(shí)體圖。 該API背后的想法很簡單–您只需編寫一些注釋來描述應(yīng)獲取的內(nèi)容。 讓我們看一個例子:
@Entity @NamedEntityGraphs ({ @NamedEntityGraph (name = "user-only-entity-graph" ), @NamedEntityGraph (name = "user-addresses-entity-graph" , attributeNodes = { @NamedAttributeNode ( "addresses" )}) }) public class User { @Id @GeneratedValue private int id; private String name; @OneToMany (fetch = FetchType.LAZY) private Set<Address> addresses; //Getters and Setters here }對于這個實(shí)體,我們描述了兩個實(shí)體圖– user-only-entity-graph不獲取Addresses屬性(標(biāo)記為惰性),而第二個圖則指示ORM選擇地址。 如果我們將屬性標(biāo)記為渴望,則實(shí)體圖設(shè)置將被忽略,并且將獲取該屬性。
因此,從JPA 2.1開始,您可以通過以下方式選擇實(shí)體:
EntityManager em = entityManagerFactory.createEntityManager(); EntityGraph graph = em.getEntityGraph( "user-addresses-entity-graph" ); Map<String, Object> properties = Map.of( "javax.persistence.fetchgraph" , graph); User user = em.find(User. class , 1 , properties); em.close();這種方法極大地簡化了開發(fā)人員的工作,無需“接觸”惰性屬性并創(chuàng)建長事務(wù)。 很棒的事情是,實(shí)體圖可以應(yīng)用于SQL生成級別,因此不會從數(shù)據(jù)庫中將多余的數(shù)據(jù)提取到Java應(yīng)用程序。 但是仍然有問題。 我們不能說獲取了哪些屬性,哪些沒有。 為此有一個API,您可以使用PersistenceUnit類檢查屬性:
PersistenceUtil pu = entityManagerFactory.getPersistenceUnitUtil(); System.out.println( "User.addresses loaded: " + pu.isLoaded(user, "addresses" "User.addresses loaded: " + pu.isLoaded(user, "addresses" ));但這很無聊。 我們可以簡化一下,只是不顯示未提取的屬性嗎?
Spring預(yù)測
Spring Framework提供了一個很棒的工具,稱為Projections (它與Hibernate的Projections不同)。 如果我們只想獲取實(shí)體的某些屬性,則可以指定一個接口,Spring將從數(shù)據(jù)庫中選擇接口“實(shí)例”。 讓我們看一個例子。 如果我們定義以下接口:
interface NamesOnly { String getName(); }然后定義一個Spring JPA存儲庫以獲取我們的User實(shí)體:
interface UserRepository extends CrudRepository<User, Integer> { Collection<NamesOnly> findByName(String lastname); }在這種情況下,調(diào)用findByName方法后,我們將無法訪問未提取的屬性! 同樣的原則也適用于詳細(xì)實(shí)體類。 因此,您可以通過這種方式獲取主記錄和明細(xì)記錄。 此外,在大多數(shù)情況下,Spring會生成“適當(dāng)?shù)摹?SQL并僅獲取投影中指定的屬性,即,投影的工作方式類似于實(shí)體圖描述。
這是一個非常強(qiáng)大的概念,您可以使用SpEL表達(dá)式,使用類而不是接口等。如果您有興趣,可以在文檔中查看更多信息。
投影的唯一問題是在幕后將它們實(shí)現(xiàn)為地圖,因此是只讀的。 因此,考慮到您可以為投影定義setter方法,則既不能使用CRUD存儲庫也不能使用EntityManager保存更改。 您可以將投影視為DTO,并且必須編寫自己的DTO到實(shí)體的轉(zhuǎn)換代碼。
CUBA實(shí)施
從CUBA框架開發(fā)的開始,我們就嘗試優(yōu)化與數(shù)據(jù)庫一起使用的代碼。 在框架中,我們使用EclipseLink來實(shí)現(xiàn)數(shù)據(jù)訪問層API。 關(guān)于EclipseLink的好處-它從一開始就支持部分實(shí)體加載,這就是為什么我們首先選擇它而不是Hibernate的原因。 在此ORM中,您可以指定在JPA 2.1成為標(biāo)準(zhǔn)之前應(yīng)確切加載哪些屬性。 因此,我們將類似內(nèi)部“實(shí)體圖”的概念添加到我們的框架CUBA Views中 。 視圖非常強(qiáng)大-您可以擴(kuò)展它們,合并等等。CUBA視圖創(chuàng)建背后的第二個原因-我們想要使用短事務(wù),并專注于主要處理分離對象,否則,我們不能使富Web UI快速響應(yīng)。
在CUBA視圖中,描述存儲在XML文件中,如下所示:
<view class = "com.sample.User" extends = "_local" name= "user-minimal-view" > <property name= "name" /> <property name= "addresses" view= "address-street-only-view" /> </property> </view>該視圖指示CUBA DataManager提取具有其本地名稱屬性的User實(shí)體,并在查詢級獲取地址(重要!)的同時應(yīng)用地址僅街道視圖獲取地址。 定義視圖后,可以使用DataManager類將其應(yīng)用于獲取實(shí)體:
List<User> users = dataManager.load(User. class ).view( "user-edit-view" ).list();它的工作原理很像,并且由于不加載未使用的屬性而節(jié)省了大量網(wǎng)絡(luò)流量,但是像在JPA Entity Graph中一樣,存在一個小問題:我們無法說出User實(shí)體的哪些屬性已加載。 在CUBA中,我們“IllegalStateException: Cannot get unfetched attribute [...] from detached object”令人討厭的“IllegalStateException: Cannot get unfetched attribute [...] from detached object” 。 像在JPA中一樣,您可以檢查是否未提取屬性,但是為要提取的每個實(shí)體編寫這些檢查是一項(xiàng)無聊的工作,開發(fā)人員對此并不滿意。
CUBA View Interfaces PoC
如果我們能充分利用兩個世界的優(yōu)勢,該怎么辦? 我們決定使用Spring的方法來實(shí)現(xiàn)所謂的實(shí)體接口,但是這些接口在應(yīng)用程序啟動期間會轉(zhuǎn)換為CUBA視圖,然后可以在DataManager中使用。 這個想法非常簡單:您定義一個指定實(shí)體圖的接口(或一組接口)。 它看起來像Spring Projections,并且像Entity Graph一樣工作:
interface UserMinimalView extends BaseEntityView<User, Integer> { String getName(); void setName(String val); List<AddressStreetOnly> getAddresses(); interface AddressStreetOnly extends BaseEntityView<Address, Integer> { String getStreet(); void setStreet(String street); } }請注意,如果僅在一種情況下使用,則AddressStreetOnly接口可以嵌套。
在CUBA應(yīng)用程序啟動期間(實(shí)際上,大多數(shù)情況是Spring Context初始化),我們?yōu)镃UBA視圖創(chuàng)建了程序化表示并將其存儲在Spring上下文中的內(nèi)部存儲庫bean中。
之后,我們需要調(diào)整DataManager,以便它除了可以接受CUBA View字符串名稱之外,還可以接受類名稱,然后我們只需傳遞接口類即可:
List<User> users = dataManager.loadWithView(UserMinimalView. class ).list();我們?yōu)槊總€從數(shù)據(jù)庫獲取的實(shí)例生成代理來實(shí)現(xiàn)實(shí)體視圖,就像冬眠一樣。 并且,當(dāng)您嘗試獲取屬性的值時,代理會將調(diào)用轉(zhuǎn)發(fā)給真實(shí)實(shí)體。
通過這種實(shí)現(xiàn),我們試圖用一塊石頭殺死兩只鳥:
- 接口中未說明的數(shù)據(jù)不會加載到Java應(yīng)用程序代碼中,從而節(jié)省了服務(wù)器資源
- 開發(fā)人員僅使用獲取的屬性,因此不再使用“ UnfetchedAttribute”錯誤(在Hibernate中也稱為LazyInitException )。
與Spring Projections相比,實(shí)體視圖包裝實(shí)體并實(shí)現(xiàn)CUBA的實(shí)體接口,因此可以將它們視為實(shí)體:您可以更新屬性并將更改保存到數(shù)據(jù)庫。
這里的“第三只鳥” –您可以定義一個僅包含吸氣劑的“只讀”接口,從而完全防止實(shí)體在API級別進(jìn)行修改。
另外,我們可以對分離的實(shí)體執(zhí)行一些操作,例如將該用戶的名稱轉(zhuǎn)換為小寫:
@MetaProperty default String getNameLowercase() { return getName().toLowerCase(); }在這種情況下,所有計算出的屬性都可以從實(shí)體模型中移出,因此您不必將數(shù)據(jù)獲取邏輯與用例特定的業(yè)務(wù)邏輯混合在一起。
另一個有趣的機(jī)會–您可以繼承接口。 這使您可以準(zhǔn)備具有不同屬性集的多個視圖,然后根據(jù)需要將它們混合。 例如,您可以有一個包含用戶名和電子郵件的界面,以及另一個包含名和地址的界面。 而且,如果您需要第三個視圖接口,其中應(yīng)該包含名稱,電子郵件和地址,則可以通過將二者結(jié)合在一起來實(shí)現(xiàn)–這要?dú)w功于Java中接口的多重繼承。 請注意,您可以將此第三個接口傳遞給使用第一個或第二個接口的方法,OOP原理照常在這里工作。
我們還實(shí)現(xiàn)了視圖之間的實(shí)體轉(zhuǎn)換–每個實(shí)體視圖都有reload()方法,該方法接受另一個視圖類作為參數(shù):
UserFullView userFull = userMinimal.reload(UserFullView. class );UserFullView可能包含其他屬性,因此將從數(shù)據(jù)庫重新加載該實(shí)體。 實(shí)體重新加載是一個懶惰的過程,僅當(dāng)您嘗試獲取實(shí)體屬性值時才執(zhí)行。 我們之所以這樣做是因?yàn)樵贑UBA中,我們有一個“網(wǎng)絡(luò)”模塊,可呈現(xiàn)豐富的UI,并可能包含自定義的REST控制器。 在此模塊中,我們使用相同的實(shí)體,并且可以將其部署在單獨(dú)的服務(wù)器上。 因此,每個實(shí)體重新加載都會通過核心模塊(aka中間件)引起對數(shù)據(jù)庫的附加請求。 因此,通過引入惰性實(shí)體重新加載,我們節(jié)省了一些網(wǎng)絡(luò)流量和數(shù)據(jù)庫查詢。
PoC可以從GitHub下載-隨時使用。
結(jié)論
ORM將在不久的將來在企業(yè)應(yīng)用程序中大量使用。 我們只需要提供一些將數(shù)據(jù)庫行轉(zhuǎn)換為Java對象的工具即可。 當(dāng)然,在復(fù)雜的高負(fù)載應(yīng)用程序中,我們將繼續(xù)看到獨(dú)特的解決方案,但是ORM的生存時間將與RD??BMSes一樣長。
在CUBA框架中,我們試圖簡化ORM的使用,以使開發(fā)人員盡可能地輕松。 在下一版本中,我們將引入更多更改。 我不確定這些接口是視圖接口還是其他接口,但是我可以肯定的是,使用CUBA在下一版本中使用ORM將得到簡化。
翻譯自: https://www.javacodegeeks.com/2019/09/fetching-data-with-orm-easy.html
orm提取指定列
總結(jié)
以上是生活随笔為你收集整理的orm提取指定列_使用ORM提取数据很容易! 是吗?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么建css文件(怎样建css文件)
- 下一篇: solid设计原则_SOLID设计原则