分類
發燒車訊

鋰電池原料暴漲,電動車產業受影響

鋰電池關鍵材料鈷、鋰價格暴漲,令電池乃至電動車製造商成本控制面臨極大壓力。

鈷的國際報價四月中旬來到每磅27.5美元,寫下八年新高記錄。從2017年初以來,鈷價累計漲幅達90%,對照一年之前,更是跳增2.5倍。根據主要交易機構官員表示,鈷的身價節節升高,主因需求看漲,市場預先囤貨所致。(日經新聞)

另外,鋰價近兩年翻升約三倍,電池製造商的成本上漲壓力可以想見,這將進一步轉嫁至電動車製造商身上。據估計,鈷約佔電池製造成本的兩成。

特斯拉大眾化電動車Model 3預計在今年稍晚量產,目前特斯拉已收到40萬輛預購訂單。除了特斯拉之外,日產新一代Leaf電動車也即將上市,這些均為鈷與鋰等電池材料提供炒作題材。

(本文內容由授權使用。圖片出處:wikipedia)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

分類
發燒車訊

英威騰:高效電驅動專家 攜多款新品亮相振威新能源汽車展

中國已經成為全球新能源汽車發展最快,保有量最大的國家。但是隨著國家補貼的逐漸退坡,新能源汽車企業的壓力也越來越大。那麼隨著中國新能源汽車行業由政策導向變為市場導向,如何能快速、有效的降低生產成本,成為了新能源車企亟待解決的問題。

目前來說,動力電池方面價格在逐步的下降,除此之外電機、電控等環節也應該成為降低成本的重要方向。市場的規模效應一直都是降低成本的最好辦法,也就是說,除了動力電池之外,其餘的環節也需要這樣的規模效應。

英威騰作為電機控制器領域的龍頭企業,在新能源汽車電控產品的創新和研發上取得了重大的進展。據英威騰負責人介紹,後補貼時代的到來,英威騰將通過創新設計和規模效應來降低成本,通過提升產品品質降低費用,讓更多的客戶認可其產品品質和產品性能,從而推動大批量應用。這樣才會使企業減少對補貼的依賴,使產業更健康發展。

目前,英威騰電機控制器系列產品主要有純電動客車主電機控制器、混合動力客車主電機控制器、環衛車主電機控制器、物流車主電機控制器、輔助控制器及各種集成方案。其主電機控制器主要用於新能源汽車發電機與電動機的控制,可用來控制新能源汽車的非同步電機和永磁同步電機,實現高效、節能,控制器效率可達98%。輔助控制器主要用於新能源汽車轉向、刹車、上裝等的電機控制、蓄電池或輔助電源的供電等。

為了應對新能源汽車行業新的發展態勢,英威騰於去年8月成立了深圳市英威騰電動汽車充電技術有限公司,專門從事與電動汽車相關的電機控制器、電機、輔助控制器、 DC-DC轉換器、車載充電機、地面充電樁/充電機及電池組件產品和整體解決方案的開發及銷售。至此,英威騰基本形成了以電機電控、電池和充電設施等完善的新能源汽車配套產業解決方案產品體系。

據悉,英威騰將攜最新研發的電機控制器及其自主研發的最新充電設備產品首度亮相2017中國國際新能源汽車產業博覽會(以下簡稱振威新能源汽車展)。

本次英威騰將展出四款充電設備最新的相關產品。涵蓋了掛壁式充電樁,充電模組、充電機、DCDC。此四款產品的特點在於對電流的保護,特別是在過壓、欠壓、短路等方面表現出色。

振威新能源汽車展是由廣東省充電設施協會、廣東省新能源汽車產業協會、充電設施線上網及振威展覽股份聯合舉辦。將於2017年6月16-18日、2017年8月23-25日分別在深圳會展中心及上海新國際博覽中心舉行。超過800家新能源汽車產業相關的企業將參與本次展會,累積展示面積超過6萬平方米。

本次展會振威展覽股份醞釀了多年,從最初的鋰電展到充電設備展,經過多年的籌備,在2017年整合了新能源汽車產業的整車製造、核心三電(電池、電機、電控)、充電設備、BMS、零部件等全產業鏈環節,致力於打造中國新能源汽車產業第一展。

組委會聯繫方式:+86-20-83953286
連絡人:黃俊鵬
官方微信公眾號:nevechina

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

分類
發燒車訊

工研院高速充電鋁電池獲愛迪生獎,未來望應用電動車

台灣研發於國際發光!擁有「創新界奧斯卡獎」美譽的愛迪生獎(Edison Awards)於21日美國紐約公布得獎名單,工研院在經濟部能源局的支持下,以「可高速充放電鋁電池」贏得評審團青睞,勇奪「能源與永續」類的銀牌,這次獲獎讓台灣的創意與研發能力持續在國際舞台上大放光芒,也突顯工研院的技術創新領先國際。

愛迪生獎於1987 年設立,每年表彰全球創新產品與服務,紀念發明家愛迪生追求創意與卓越的精神,強調研發過程中的合作、試作、設計到產品應用開發,著重技術創新、差異化、商品化及影響力。歷屆獲獎者有iPad、3M 分子檢測系統、波音787 Dreamliner 等,今年入圍決選的國際大廠與研發機構包括:陶氏化學、MIT Media Lab、3M、美國國防部高等研究計劃署(DARPA)、Nokia、HP、默克藥廠等。

愛迪生獎資深遴選委員Steve Herring 在頒獎現場表示:「我們很榮幸2017 年愛迪生獎有工研院參賽,他們努力追求創新的精神正是我們想要表揚的。」愛迪生獎執行總監Frank Bonafilia 亦讚許工研院在電池技術上提供了前所未有的實質發展。

工研院綠能與環境研究所所長胡耀祖表示:「根據工研院產經中心(IEK)分析,2015 年全球商業電池市場約有791 億美元的規模。鋁電池突破過去30 年的技術瓶頸,不但可快速充放電,材料安全更不會引起爆炸,還可結合現有製程,大幅提升效率及降低成本,短期可望取代鉛酸電池,未來能將此應用擴大至電動機車、輕型電動車等交通載具,以及做為備用電力與儲能裝置。」

胡耀祖進一步指出:「鋁電池未來還可搭配再生能源,成為我國儲能的重要生力軍,開啟一個能源產業與應用的新時代。目前電池材料、組裝和終端應用等產業,都能切入未來鋁電池的廣大市場,可望帶動我國電池產業升級,迎接新能源產業商機。」

「可高速充放電鋁電池」為工研院與美國史丹佛大學共同合作,以地球上蘊藏豐富的石墨和鋁為原料,成功開發出全球第一個可以穩定充放電的鋁電池,只要一分鐘便可完成充電,且重複充放電逾萬次,仍可維持高蓄電量。此外,鋁電池所使用的離子液體在室溫下是液態的鹽類,若遇到高溫短路或是受到外力破壞,也不會爆炸燃燒。自2015 年登上英國《Nature》雜誌開始,「可高速充放電鋁電池」接連拿下2016 年百大研發獎(R&D 100 Awards)及2017 年愛迪生獎銀牌,在國際上備受肯定。工研院目前已布局包括核心電極材料、電解質材料、電芯與充電器等台灣及多國專利,且陸續獲證中,建立我國發展鋁電池產業的堅實根基。

(合作媒體:。圖片出處:TechNews)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

地球發燒了研究:2080年前熱死人數激增20倍

摘錄自2018年8月1日蘋果日報台北報導

天氣讓人熱得受不了,而這種情況若不加以控制,地球上熱死人的數目將增加20倍。最新研究指出,在各國政府完全不做任何事的最糟情況,部分國家在2080年前,因為高溫導致的死亡數字將增加2000%。

這項刊登在「公共科學圖書館醫學期刊」(PLOS Medicine)的報告指出,歐洲、部分亞洲與北美洲地區的氣溫逐年升高,熱浪造成數以千計的死亡案例。該報告首席研究員、澳洲蒙納士大學(Monash University)副教授郭玉明(Yuming Guo,音譯)說:「未來的熱浪會發生得更頻繁、更強烈,維持時間也更長」、「如果我們找不到減緩氣候變遷的方法,並幫助人民適應熱浪氣候,和熱浪有關的死亡案例將會急遽增加。」

報告中指出,最糟的情況下,哥倫比亞在2031至2080年間被高溫熱死的人數,會比1971年至2010年增加2000%,即增加20倍;菲律賓在2031到2080因熱浪而額外死亡的人數,是1971到2020的12倍;澳洲、美國是5倍,英國是4倍。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

分類
發燒車訊

效率思維模式與Zombie Scrum

Scrum是由Ken Schwaber和Jeff Sutherland在20世紀90年代提出的概念,並在1995年首次正式確定。起初Scrum是為了解決產品和軟件開發固有的複雜性,然而現在Scrum被成功地應用於市場營銷、組織變革和科學研究等多個領域的複雜問題。

Scrum主要建立在以下三個原則的基礎上:

  • 透明度:你需要收集數據(比如一些指標、團隊成員的反饋或其他團隊的經驗之談),從而找到你的目標。
  • 檢查:你需要和大家一起監督迭代的進度,並決定迭代完成的標準是什麼。
  • 適應:你需要做出改變,希望能更好更快地完成你的目標。

在實施Scrum之前首先要用一段時間來定義和調整這些規則,以發現工作中的問題,找到可以改善的方向,這裏說的問題不是那種一年一次或項目完成時才發生的問題,而是每天、每周或每月都在持續發生的問題。我們不是將我們的決策建立在對可能永遠不會發生的潛在風險的假設上,而是根據我們收集到的數據來做決策,這就是所謂的經驗主義。

Scrum的價值?

當你需要接受你並不了解和無法控制一切的時候,Scrum提供的經驗方法就會變得非常有用。也正因如此,你會改變之前的想法,雖然可能會犯錯,但也會有新的、有價值的想法出現,而這些是你從未考慮過的。與其在前期制定一個精確的計劃,然後無論如何都要堅持下去,不如把你的想法當作假設或假說,用Scrum的方式來驗證。

Scrum可以讓你快速了解自己是否偏離了軌道,是否需要做出調整,而不是簡單地按照計劃行事,你可以先解決你目前面臨的最大風險。當你在一個不確定的、不斷變化的環境中工作時,這一點尤為重要。你一開始的假設在當時可能是絕對正確的,但是當你在開發產品的時候,環境可能會發生很大的變化,以至於你的整個方案都失敗。在一個漫長的項目結束的時候,經驗主義的方法並不是災難性的失敗,而是將其降低為一個小的減速帶,需要你修正一下方向。

所以,實際上Scrum是降低了複雜的、適應性問題、固有的不可預測性和不確定性的風險。它允許你不斷地驗證你仍然在做正確的事情,並朝着解決你設定的目標前進。更好的是,你現在有了一個积極發現更好想法的過程,並將其納入到下一步的塑造中。現在,不確定性反而變成了一件好事,因為其中蘊含着所有的可能性。

“Scrum降低了複雜的、適應性問題固有的不可預測性和不確定性的風險。”

Zombie Scrum和效率思維模式

那麼,Zombie Scrum與這一切有什麼聯繫呢?我們發現一個現象:人們使用Scrum的起因很多都是錯誤的。當你問一個Zombie Scrum組織中的人,他們希望從Scrum中得到什麼時,你會聽到諸如 “更快”、”更多的大腦”、”更多的產出 “和 “更高的效率”。這與 “敏捷 “這個詞的實際含義是非常不同的。這與Scrum的設計目的也大相徑庭。這種矛盾從何而來?

傳統的組織管理和產品開發方式是為了實現與敏捷性相反的目標,這種心理模式通常被稱為 “效率思維模式”。它的目的是盡可能地減少不確定性,提高可預測性,推動效率的提高。這通常表現為會制定詳細的計劃,通過協議和程序使工作標準化,高度的任務專業化,以及衡量效率(如每天的工作量、出現的問題) 。這種思維模式當然可以在工作相當重複和簡單的環境中發揮作用,比如流水線化的工作或某些行政工作,但在人們處理複雜的、適應性強的問題的環境中肯定行不通,因為這些問題本身就具有不可預測性和不確定性。

“效率思維模式的目的是盡可能地減少不確定性,提高可預測性,推動效率的提高。”

Zombie Scrum與領導強烈關注績效和工作量是有很大關係的,但最終客戶是否滿意?是否交付了有價值的東西?卻無人問津。而且,這種思維模式在很多企業中是根深蒂固的,它已經成為一個我們不需要討論的 “真相”。這樣的企業是想試圖用Scrum來影響效率、速度和產出的角度來理解它是有道理的,只不過當發現Scrum似乎並沒有做到這一點時,人們就會感到失望。

從非常廣泛的意義上來說,Scrum關注的更多是效率,而不是高效。效率是為了盡可能多的完成工作(產出),而高效則是為了工作的價值和有用性(結果)。雖然完全有可能通過Scrum提高效率,但這既不是承諾也不是目標。

在充斥着 “Zombie Scrum”的環境中,大家是很看重“效率思維”的,以至於人們只看到Scrum的結構性元素:角色、事件和工件。他們沒有看到也沒有體會到這個過程的價值。這就是為什麼Zombie Scrum只是看起來像Scrum,但沒有其精髓。

“Scrum更關注的是有效(結果),而不是高效(產出)。”

在這篇文章中,我們提到了Scrum的三個原則,如何在必要的時候重複進行,以捕捉工作中出現的偏差、意外發現和潛在機會。Scrum中的所有內容都是圍繞這三個支柱設計的。這也是經驗主義發揮作用的原因。採用Zombie Scrum的組織,往往有一種效率思維,目標是盡可能減少不確定性,提高可預測性,推動效率。這與在複雜工作中學習和發現的經驗主義過程相矛盾。

原文作者:Barry Overeem

翻譯整理:Worktile

Worktile 官網:worktile.com

文章首發於「Worktile官方博客」,轉載請註明出處。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

分類
發燒車訊

圖像處理中的valid卷積與same卷積

valid卷積

在full卷積的卷積過程中,會遇到\(K_{flip}\)靠近I的邊界(K矩陣與I矩陣),就會有部分延申到I之外,這時候忽略邊界,只考慮I完全覆蓋\(K_{flip}\)內的值情況,這個的過程就是valid卷積。一個高為H1,寬為W1的矩陣I與高為H2,寬為W2的矩陣K,在H1大於等於H2,W1大於等於W2的情況下,valid卷積的結果就是一個(H1-H2+1)*(W-W+1)的矩陣\(C_{valid}\)

\[C_{valid}與C_{full}的對應關係為: C_{valid} = C_{full}( Rect (W_{2}-1,H_{2}-1,W_{1}-W_{2}+1,H_{1}-H_{2}+1) ) \]

same卷積

無論是full卷積還是valid卷積都不會得到正好的尺寸,要麼比原尺寸大要麼比原尺寸小,這時就需要same卷積來解決這個問題。若想得到寬和高都正好的矩陣我們首先需要給\(K_{flip}\)一個錨點,將錨點放在(循環)圖像矩陣的(r,c)處,((r,c)在矩陣之內),將對應位置的元素逐個相乘,最終將所有的積進行求和作為輸出圖像矩陣在(r,c)處的輸出值。這個過程稱為same卷積。
OpenCv函數copyMakeBorder的參數表

參數 解釋
src 輸入矩陣
dst 輸出矩陣
top 上側擴充的行數
bottom 下側擴充的行數
left 左側擴充的行數
right 右側擴充的行數
borderType 邊界擴充的類型
value border Type= BORDER_CONSTANT事的常數

其中borderType有多種類型,比如:BORDER_REPLICATE(邊界複製)、BORDER_CONSTANT(常數擴充)、BORDER_REFLECT(反射擴充)等。
在使用Python進行卷積操作時用到包Scipy,其中有關的操作函數為convolve2d(in1,in2,mode=’full’,boundary=’fill’,fillvalue=0)

參數 解釋
in1 輸入數組
in2 輸入數組,代表K(卷積算子)
mode 卷積類型,也就是以上提到的三種類型:full,valid,same
boundary 邊界填充:fill\wrap\symm
fillvalue 當boundary=’fill’時,設置邊界填充的值,默認為0

在這裏需要注意的是當model為same時卷積算子的錨點位置由不同尺寸而不同,假設K(卷積算子)的寬和高分別為W、H。

W和H的值 錨點位置
均為奇數 默認為中心點
H為偶數、W為奇數 (H-1,(W-1)/2)
H為奇數,W為偶數 ((H-1)/2,W-1)
均為偶數 (H-1,W-1)

代碼實現:

import numpy as np
from scipy import signal

if __name__ == "__main__":

    I = np.array([[1,2],[3,4],np.float32])
    #I的高和寬
    H1,W1 = I.shape[:2]
    #卷積算子
    k = np.array([[-1,-2],[2,1],np.float32])
    #K的寬和高
    H2,W2 = k.shape[:2]
    #計算full卷積
    c_full = signal.convolve2d(I,k,mode='full')
    #設定錨點
    r,c = 0,0
    #根據錨點來從full卷積中截取same卷積
    c_same= c_full[H2-r-1:H1-r-1,W2-c-1:W1+W2-c-1]

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

分類
發燒車訊

編碼的道與禪

該文章發布在github中,如果您覺得寫的還不錯的話,可以 star 一下進行支持,傳送門:TechShare。

Bob 大叔在《代碼整潔之道》一書的前言打趣着說,當你寫的代碼在經受代碼審查時,如果審查者憤怒的吼道“What the fuck is this shit?”等言辭激烈的詞語時,那說明你寫的是 Bad Code;如果審查者只是漫不經心的吐出幾個“What the fuck?”,那說明你寫的是 Good Code。這就是衡量代碼質量的唯一標準——每分鐘罵出“What the fuck?”的頻率。

想寫出整潔的代碼很難,有一部分原因在於糟糕的代碼太容易編寫。想快點完成任務時,考慮不周全時,忽略安全時,隨意命名時,參數過多時,嵌套太深時,未及時更改註釋時,違反法則時,重複你自己時等等情形,我們有太多的機會來製造糟糕的代碼。只有嚴肅對待自己的代碼,了解哪些事情會使我們的代碼變味,才有可能寫出整潔的代碼。

寫代碼和寫文章在某種程度上有相似之處,好的文章一定有好的可讀性,寫代碼也一樣,只有優美乾淨的代碼才能具有良好的可讀性。編寫具有可讀性的代碼不光是保持有意義的命名就行,如果你想成為一名更好的程序員,寫代碼時你需要注意的有很多,比如:

  1. 規範本地變量的位置
  2. 使函數盡量短小
  3. 調用者盡可能放在被調用者上面
  4. 保持代碼擁有良好的格式
  5. 編寫只做一件事的函數
  6. 函數參數不要超過三個
  7. 暴露時序耦合
  8. 使用異常代替返回錯誤碼

除此之外,你還須牢記眾多設計原則,如:

  1. 開放封閉原則(OCP)
  2. 迪米特法則
  3. 依賴倒置原則(DIP)
  4. 單一職責原則(SRP)
  5. 里氏替換原則(LSP)
  6. 不要重複(DRY)
  7. 你不會需要它(YAGNI)

當然僅有這些是不夠的,這不是騎自行車,學寫整潔代碼得花許多功夫,必須不斷實踐,從失敗中提取代碼的壞味道並從中得到啟發。

編寫整潔代碼,你需要牢記並遵守很多東西,但這並不是循規蹈矩和刻板,而是對簡單之美、代碼之美的追求。代碼整潔之道,是編寫優秀代碼的一種方法,其核心是儘力使代碼保持簡單——Keep It Simple, Stupid。判斷一個人寫的代碼的好壞,不是看它的代碼寫的有多複雜,而是看他有沒有把複雜的事物抽象出來並用簡單的方式去描述它,此外這個人對代碼的態度也至關重要,大多數時候我們並不能從一開始就把代碼寫的很完美,當我們需要快速做出一個原型,或者一開始代碼看起來不錯,但新的需求使現有的設計無法滿足,如果不對設計進行改動的話,那麼代碼就會變的醜陋,如果你熱愛自己正在做的事情,崇尚代碼之美,那麼你就會有足夠的動力去重構它、完善它,而不是破壞結構使代碼腐爛。

保持簡單、追求簡單,我想這就是編碼之中的禪意,一種追求本真的境界。這種禪在 Python 的設計哲學中體現的淋漓盡致,讓我們在 Python 解釋器中輸入“import this”,來看看經典的 Python 之禪。

  • Beautiful is better than ugly.
    優美勝於醜陋。
  • Explicit is better than implicit.
    顯式勝於隱式。
  • Simple is better than complex.
    簡單勝於複雜。
  • Complex is better than complicated.
    複雜勝於難懂。
  • Flat is better than nested.
    扁平勝於嵌套。
  • Sparse is better than dense.
    分散勝於密集。
  • Readability counts.
    可讀性應當被重視。
  • Special cases aren’t special enough to break the rules. Although practicality beats purity.
    儘管實用性會打敗純粹性,特例也不能凌駕於規則之上。
  • Errors should never pass silently. Unless explicitly silenced.
    除非明確地使其沉默,錯誤永遠不應該默默地溜走。
  • In the face of ambiguity, refuse the temptation to guess.
    面對不明確的定義,拒絕猜測的誘惑。
  • There should be one– and preferably only one –obvious way to do it.
    用一種方法,最好只有一種方法來做一件事。
  • Although that way way not be obvious at first unless you’re Dutch.
    雖然一開始這種方法並不是顯而易見的,但誰叫你不是Python之父呢。
  • Now is better than never. Although never is often better than right now.
    做比不做好,但立馬去做有時還不如不做。
  • If the implementation is hard to explain, it’s a bad idea.
    如果實現很難說明,那它是個壞想法。
  • If the implementation is easy to explain, it may be a good idea.
    如果實現容易解釋,那它有可能是個好想法。
  • Namespaces are one honking great idea – let’s do more of those!
    命名空間是個絕妙的想法,讓我們多多地使用它們吧!

道着重於方法,禪着重於態度,讓我們把這兩者相結合,做一個有追求的程序員,為成為軟件匠人而奮鬥吧。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

分類
發燒車訊

面試必問系列之JDK動態代理

掃描文末二維碼或者微信搜索公眾號小李不禿,即可關注微信公眾號,獲取到更多 Java 相關內容。

1. 帶着問題去學習

面試中經常會問到關於 Spring 的代理方式有哪兩種?大家異口同聲的回答:JDK 動態代理和 CGLIB 動態代理。

這兩種代理有什麼區別呢?JDK 動態代理的類通過接口實現,CGLIB 動態代理是通過子類來實現的。

那 JDK 動態代理你了到底了解多少呢?有去看過代理對象的 class 文件么?下面兩個關於 JDK 動態代理的問題你能回答上來么?

  • 問題1:為什麼 JDK 動態代理要基於接口實現?而不是基於繼承來實現?
  • 問題2:JDK 動態代理中,目標對象調用自己的另一個方法,會經過代理對象么

小李帶着大家更深入的了解一下 JDK 的動態代理。

2. JDK 動態代理的寫法

  • JDK 動態代理需要這幾部分內容:接口、實現類、代理對象。
  • 代理對象需要繼承 InvocationHandler,代理類調用方法時會調用 InvocationHandlerinvoke 方法。
  • Proxy 是所有代理類的父類,它提供了一個靜態方法 newProxyInstance 動態創建代理對象。
public interface IBuyService {
     void buyItem(int userId);
     void refund(int nums);
}

 

@Service
public class BuyServiceImpl implements IBuyService {
    @Override
    public void buyItem(int userId) {
        System.out.println("小李不禿要買東西!小李不禿的id是: " + userId);
    }
    @Override
    public void refund(int nums) {
        System.out.println("商品過保質期了,需要退款,退款數量 :" + nums);
    }
}

 

public class JdkProxy implements InvocationHandler {

    private Object target;
    public JdkProxy(Object target) {
        this.target = target;
    }
    // 方法增強
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before(args);
        Object result = method.invoke(target,args);
        after(args);
        return result;
    }
    private void after(Object result) { System.out.println("調用方法后執行!!!!" ); }
    private void before(Object[] args) { System.out.println("調用方法前執行!!!!" ); }

    // 獲取代理對象
    public <T> getProxy(){
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }
}

 

public class JdkProxyMain {
    public static void main(String[] args) {
        // 標明目標 target 是 BuyServiceImpl
        JdkProxy proxy = new JdkProxy(new BuyServiceImpl());
        // 獲取代理對象實例
        IBuyService buyItem = proxy.getProxy();
        // 調用方法
        buyItem.buyItem(12345);
    }
}

查看運行結果

調用方法前執行!!!!
小李不禿要買東西!小李不禿的id是: 12345
調用方法后執行!!!!

我們完成了對目標方法的增強,開始對代理對象進行一個更全面的分析。

3. 剖析代理對象並解答問題

剖析代理對象的前提得是有代理對象,動態代理的對象是在運行時期創建的,我們就沒辦法通過打斷點的方式進行分析了。但是我們可以通過反編譯 .class 文件進行分析。如何獲取到 .class 文件呢?

通過在代碼中添加:System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true") ,就能夠實現將動態代理對象的 class 文件寫入到磁盤中。代碼如下:

public class JdkProxyMain {
    public static void main(String[] args) {
        // 代理對象的 class 文件寫入到磁盤中
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles""true");
        // 標明目標 target 是 BuyServiceImpl
        JdkProxy proxy = new JdkProxy(new BuyServiceImpl());
        // 獲取代理對象實例
        IBuyService buyItem = proxy.getProxy();
        // 調用方法
        buyItem.buyItem(12345);
    }
}

在項目的根目錄下多了一個 $Proxy0.class 文件

看一下這個文件的內容

public final class $Proxy0 extends Proxy implements IBuyService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void buyItem(int var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void refund(int var1) throws  {
        try {
            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.example.springtest.service.IBuyService").getMethod("buyItem", Integer.TYPE);
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.example.springtest.service.IBuyService").getMethod("refund", Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

動態代理對象 $Proxy0 繼承了 Proxy 類並且實現了 IBuyService 接口。那問題 1 的答案就出來了:動態代理對象默認繼承了 Proxy 對象,而且 Java 不支持多繼承,所以 JDK 動態代理要基於接口來實現。

$Proxy0 重寫了 IBuyService 接口的方法,還有 Object 的方法。在重寫的方法中,統一調用 super.h.invoke 方法。super 指的是 Proxyh 代表 InvocationHandler,這裏就是 JdkProxy。所以這裏調用的是 JdkProxyinvoke 方法。

所以每次調用 buyItem 方法的時候,會先打印出 調用方法前執行!!!!

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before(args);
// 通過反射調用方法
Object result = method.invoke(target,args);
after(args);
return result;
}
private void after(Object result) { System.out.println("調用方法后執行!!!!" ); }
private void before(Object[] args) { System.out.println("調用方法前執行!!!!" ); }

問題 2 還沒解決呢,接着往下看

@Service
public class BuyServiceImpl implements IBuyService {
    @Override
    public void buyItem(int userId) {
        System.out.println("小李不禿要買東西!小李不禿的id是: " + userId);
        refund(100);
    }
    @Override
    public void refund(int nums) {
        System.out.println("商品過保質期了,需要退款,退款數量 :" + nums);
    }
}

上面這段代碼中,在 buyItem 調用內部的 refund 方法,那這個內部調用方法是否走代理對象呢?看一下執行結果:

調用方法前執行!!!!
小李不禿要買東西!小李不禿的id是: 12345
商品過保質期了,需要退款,退款數量 :100
調用方法后執行!!!!

確實是沒有走代理對象,其實我們期待的結果是下面這樣的

調用方法前執行!!!!
小李不禿要買東西!小李不禿的id是: 12345
調用方法前執行!!!!
商品過保質期了,需要退款,退款數量 :100
調用方法后執行!!!!
調用方法后執行!!!!

那為什麼會造成這種差異呢?

因為內部調用 refund 方法的調用,相當於 this.refund(100),而這個 this 指的是 BuyServiceImpl 對象,而不是代理對象,所以refund 方法沒有得到增強

4. 總結和延伸

  • 本篇文章了解了 JDK 動態代理的使用,通過分析 JDK 動態代理生成對象的 class 文件,解決了兩個問題:

    • 問題1:為什麼 JDK 動態代理要基於接口實現?而不是基於繼承來實現?
    • 解答:因為 JDK 動態代理生成的對象默認是繼承 Proxy ,Java 不支持多繼承,所以 JDK 動態代理要基於接口來實現。
    • 問題2:JDK 動態代理中,目標對象調用自己的另一個方法,會經過代理對象么
    • 解答:內部調用方法使用的對象是目標對象本身,被調用的方法不會經過代理對象。
  • 我們知道了 JDK 動態代理內部調用是不走代理對象的。那對於 @Transactional 和 @Async 等註解不起作用是不是就搞清楚為啥了?

  • 因為 @Transactional@Async 等註解是通過 Spring AOP 來進行實現的,如果動態代理使用的是 JDK 動態代理,那麼在方法的內部調用該方法中其它帶有該註解的方法,由於此時調用的不是動態代理對象,所以註解失效

  • 上面這些問題就是 JDK 動態代理的缺點,那 Spring 如何避免這個問題呢?就是另個一個動態代理:CGLIB 動態代理,我會在下篇文章進行分析。

5. 參考

  • https://juejin.im/post/5d8a0799f265da5b7a752e7c#heading-6
  • https://blog.csdn.net/varyall/article/details/102952365

6. 猜你喜歡

  • JSON的學習和使用

  • 學習反射看這一篇就夠了

  • 併發編程學習(一)Java 內存模型

掃描下方二維碼即可關注微信公眾號小李不禿,一起高效學習 Java。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

分類
發燒車訊

看了Java的Class的源碼,我自閉了

java源碼之Class

​ 源碼的重要性不言而喻,雖然枯燥,但是也有拍案叫絕。這是我的源碼系列第二彈,後續還會一直更新,歡迎交流。String源碼可以看我的Java源碼之String,如有不足,希望指正。

1.class這個類是什麼

Class的本質也是一個類,只不過它是將我們定義類的共同的部分進行抽象,比如我們常定義的類都含有構造方法,類變量,函數,而Class這個類就是來操作這些屬性和方法的。當然我們常定義的類包含的類型都可以通過Class間接的來操作。而類的類型包含一般的類,接口,枚舉類型,註解類型等等。這麼說可能有點太理論,我們看下面這個例子:

我們將生活中的一類事物抽象為一個類的時候,往往是因為他們具有相同的共性和不同的個性。定義一個類的作用就是將相同的共性抽離出來。一般的類都包含屬性和方法(行為),下面我們定義水果和汽車這兩個大類:

代碼如下:

汽車類:

class Car{

    // 定義屬性
    private String name;
    private String color;

    /**
     * 定義兩個構造方法
     */
    public Car(){

    }

    public Car(String name,String color){
        this.name = name;
        this.color = color;
    }

    /**
     * 定義兩個普通方法(行為)
     */
    public void use(){
        
    }
    
    public void run(){
        
    }

    /**
     * 屬性的get和set方法
     * @return
     */
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
         this.color = color;
    }
}


水果類:

class Fruit{

    // 定義屬性
    private String name;
    private int size;

    /**
     * 定義兩個構造方法
     */
    public Fruit(){

    }

    public Fruit(String name,int size){
        this.name = name;
        this.size =size;
    }

    /**
     * 定義兩個方法(行為)
     */
    public void use(){
        
    }
    
    public void doFruit(){
        
    }

    /**
     * 屬性的get和set方法
     * @return
     */
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }
}

可以看到水果和汽車這兩個類都有共同的部分,也就是一個類共同的部分,那就是屬性和方法,而Class就是來操作我們定義類的屬性和方法。

​小試牛刀:通過Class這個類來獲取Fruit這個類中定義的方法;

public static void main(String[] args) {

        Fruit fruit = new Fruit();
        Class fruitClass = fruit.getClass();

        Method[] fruitMethods = fruitClass.getMethods();
        System.out.println("方法個數:" + fruitMethods.length);

        for (Method method : fruitMethods) {
            //得到返回類型
            System.out.print("方法名稱和參數:" + method.getName() + "(");
            //取得某個方法對應的參數類型數組
            Class[] paramsType = method.getParameterTypes();
            for (Class paramType : paramsType) {
                System.out.print(paramType.getTypeName() + " ");
            }
            System.out.print(")");

            Class returnType = method.getReturnType();
            System.out.println("返回類型:" + returnType.getTypeName());
        }
    }

運行結果:

方法個數:15
方法名稱和參數:getName()返回類型:java.lang.String
方法名稱和參數:setName(java.lang.String )返回類型:void
方法名稱和參數:getSize()返回類型:int
方法名稱和參數:setSize(int )返回類型:void
方法名稱和參數:use()返回類型:void
方法名稱和參數:doFruit()返回類型:void
方法名稱和參數:wait()返回類型:void
方法名稱和參數:wait(long int )返回類型:void
方法名稱和參數:wait(long )返回類型:void
方法名稱和參數:equals(java.lang.Object )返回類型:boolean
方法名稱和參數:toString()返回類型:java.lang.String
方法名稱和參數:hashCode()返回類型:int
方法名稱和參數:getClass()返回類型:java.lang.Class
方法名稱和參數:notify()返回類型:void
方法名稱和參數:notifyAll()返回類型:void

這裏可能有人疑惑了,Fruit類並沒有定義的方法為什麼會出現,如wait(),equals()方法等。這裏就有必要說一下java的繼承和反射機制。在繼承時,java規定每個類默認繼承Object這個類,上述這些並沒有在Fruit中定義的方法,都是Object中的方法,我們看一下Object這個類的源碼就會一清二楚:

 public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

 public final native void wait(long timeout) throws InterruptedException;

 public final void wait() throws InterruptedException {
        wait(0);
    }

而Class類中的getMethods()方法默認會獲取父類中的公有方法,也就是public修飾的方法。所以Object中的公共方法也出現了。

注: 要想獲得父類的所有方法(public、protected、default、private),可以使用apache commons包下的FieldUtils.getAllFields()可以獲取類和父類的所有(public、protected、default、private)屬性。

是不是感覺非常的強大 ,當然,使用Class來獲取一些類的方法和屬性的核心思想就是利用了Java反射特性。萬物皆反射,可見反射的強大之處,至於反射的原理,期待我的下一個博客。

2.常用方法的使用以及源碼分析

2.1構造方法

源碼如下:

 private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }

可以看到Class類只有一個構造函數,並且是私有的。也就是說不能通過new來創建這個類的實例。官方文檔的解釋:私有構造函數,僅Java虛擬機創建Class對象。我想可能就是為了安全,具體原因不是很了解。如果有了解的話,可以在評論區內共同的交流。

Class是怎麼獲取一個實例的。

那麼既然這個class構造器私有化,那我們該如何去構造一個class實例呢,一般採用下面三種方式:

1.運用.class的方式來獲取Class實例。對於基本數據類型的封裝類,還可以採用.TYPE來獲取相對應的基本數據類型的Class實例,如下的示例。

 // 普通類獲取Class的實例。接口,枚舉,註解,都可以通過這樣的方式進行獲得Class實例
Class fruitClass = Fruit.class;

// 基本類型和封裝類型獲得Class實例的方式,兩者等效的
Class intClass = int.class;
Class intClass1 = Integer.TYPE;

下面的表格兩邊等價:

boolean.class Boolean.TYPE
char.class Character.TYPE
byte.class Byte.TYPE
short.class Short.TYPE
int.class Integer.TYPE
long.class Long.TYPE
float.class Float.TYPE
double.class Double.TYPE
void.class Void.TYPE

但是這種方式有一個不足就是對於未知的類,或者說不可見的類是不能獲取到其Class對象的。

2.利用對象.getClass()方法獲取該對象的Class實例;

這是利用了Object提供的一個方法getClass() 來獲取當著實例的Class對象,這種方式是開發中用的最多的方式,同樣,它也不能獲取到未知的類,比如說某個接口的實現類的Class對象。

Object類中的getClass()的源碼如下:

public final native Class<?> getClass();

源碼說明:

可以看到,這是一個native方法(一個Native Method就是一個java調用非java代碼的接口),並且不允許子類重寫,所以理論上所有類型的實例都具有同一個 getClass 方法。

使用:

 Fruit fruit = new Fruit();
 Class fruitClass = fruit.getClass();

3.使用Class類的靜態方法forName(),用類的名字獲取一個Class實例(static Class forName(String className) ),這種方式靈活性最高,根據類的字符串全名即可獲取Class實例,可以動態加載類,框架設計經常用到;

源碼如下:

    /*
     由於方法區 Class 類型信息由類加載器和類全限定名唯一確定,所以參數name必須是全限定名,
     參數說明   name:class名,initialize是否加載static塊,loader 類加載器
     */
    public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        Class<?> caller = null;
        
        // 1.進行安全檢查
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
           ....
            }
        }
        // 2.調用本地的方法
        return forName0(name, initialize, loader, caller);
    }
   
    // 3.核心的方法
    private static native Class<?> forName0(String name, boolean initialize,
                                            ClassLoader loader,
                                            Class<?> caller)
      throws ClassNotFoundException;

   /* 
    這個 forName是上述方法的重載,平時一般都使用這個 方法默認使用調用者的類加載器,將類的.class文件加載     到 jvm中
    這裏傳入的initialize為true,會去執行類中的static塊
    */
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

源碼說明已在註釋中說明,有些人會疑惑, static native Class<?> forName0()這個方法的實現。

這就要說到java的不完美的地方了,Java的不足除了體現在運行速度上要比傳統的C++慢許多之外,Java無法直接訪問到操作系統底層(如系統硬件等),為此Java使用native方法來擴展Java程序的功能。有關native的方法請移步這裏。

基本使用:

 Class fruitClass = Class.forName("cn.chen.test.util.lang.Fruit");

: 這種方式必須使用類的全限定名,,這是因為由於方法區 Class 類型信息由類加載器和類全限定名唯一確定,否則會拋出ClassNotFoundException的異常。

2.2一般方法以及源碼分析:

Class類的一般的方法總共有六十多種,其實看到這麼多方法咱也不要慫,這裏面還有很多重載的方法,根據二八原則,我們平時用的也就那麼幾個方法,所以這裏只對以下幾個方法的使用和實現進行交流,其他的方法可以移步Java官方文檔:

2.2.1 獲得類的構造方法

這個方法主要是用來了解一個類的構造方法有哪些,包含那些參數,特別是在單例的模式下。一般包含的方法如下:

  • public Constructor[] getConstructors() :獲取類對象的所有可見的構造函數

  • public Constructor[] getDeclaredConstructors():獲取類對象的所有的構造函數

  • public Constructor getConstructor(Class… parameterTypes): 獲取指定的可見的構造函數,參數為:指定構造函數的參數類型數組,如果該構造函數不可見或不存在,會拋出 NoSuchMethodException 異常

  • public Constructor getDeclaredConstructor(Class… parameterTypes) :獲取指定的構造函數,參數為:指定構造函數的參數類型數組,無論構造函數可見性如何,均可獲取

基本使用:

Constructor[] constructors = fruitClass.getConstructors();
 for (Constructor constructor : constructors) {
            System.out.println("獲得共有的構造方法:"+constructor);
        }

輸出結果:

獲得共有的構造方法:public cn.chen.test.util.lang.Fruit()
獲得共有的構造方法:public cn.chen.test.util.lang.Fruit(java.lang.String,int)

可以看到我們前面定義的來個構造方法,都被打印出來了。注意getConstructors()只能獲得被public修飾的構造方法,如果要獲得被(protected,default,private)修飾的構造方法,就要使用的getDeclaredConstructors()這個方法了。接下來,修改Fruit中的一個構造方法為private:

 private  Fruit(String name,int size){
        this.name = name;
        this.size =size;
    }

使用getConstructors()和getDeclaredConstructors()着兩個方法進行測試:

       Class fruitClass = Fruit.class;       
       Constructor[] constructors = fruitClass.getConstructors();
       Constructor[] constructors1 = fruitClass.getDeclaredConstructors();

        for (Constructor constructor : constructors) {
            System.out.println("獲得共有的構造方法:"+constructor);
        }

        System.out.println("=================================================");
        for (Constructor constructor : constructors1) {
            System.out.println("獲得所有的構造方法:"+constructor);
        }

輸出結果:

獲得共有的構造方法:public cn.chen.test.util.lang.Fruit()
===================分隔線=============================
獲得所有的構造方法:public cn.chen.test.util.lang.Fruit()
獲得所有的構造方法:private cn.chen.test.util.lang.Fruit(java.lang.String,int)

可以看到兩者的區別。所以,反射在一定程度上破壞了java的封裝特性。畢竟人無完人,語言亦是一樣。

getConstructors()的源碼分析:

public Constructor<?>[] getConstructors() throws SecurityException {
          
        // 1.檢查是否允許訪問。如果訪問被拒絕,則拋出SecurityException。
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return copyConstructors(privateGetDeclaredConstructors(true));
    }
    
 private static <U> Constructor<U>[] copyConstructors(Constructor<U>[] arg) {
      // 2.使用克隆,得到當前類的所有構造函數   
      Constructor<U>[] out = arg.clone();
     // 3.使用ReflectionFactory構造一個對象,也是不使用構造方法構造對象的一種方式。
        ReflectionFactory fact = getReflectionFactory();
     // 4.遍歷,將構造函數進行拷貝返回,注意在調用fact.copyConstructor(out[i])這個方法的時候,還會進行安全檢查,用的就是下面的LangReflectAccess() 這個方法。
        for (int i = 0; i < out.length; i++) {
            out[i] = fact.copyConstructor(out[i]);
        }
        return out;
    }



 private static LangReflectAccess langReflectAccess() {
        if (langReflectAccess == null) {
            Modifier.isPublic(1);
        }

        return langReflectAccess;
    } 

通過打斷點調試,可以看到下面的信息:

代碼的調用邏輯在註釋里已進行說明。

2.2.2 獲得屬性

主要獲取類的屬性字段,了解這個類聲明了那些字段。

一般有四個方法:

  • public Field[] getFields():獲取所有可見的字段信息,Field數組為類中聲明的每一個字段保存一個Field 實例
  • public Field[] getDeclaredFields():獲取所有的字段信息
  • public Field getField(String name) :通過字段名稱獲取字符信息,該字段必須可見,否則拋出異常
  • public Field getDeclaredField(String name) :通過字段名稱獲取可見的字符信息

基本使用:

首先我們在Fruit的類中加入一個public修飾的屬性:

    public double weight;
Class fruitClass = Fruit.class; 
Field[] field2 = fruitClass.getFields();
        for (Field field : field2) {
            System.out.println("定義的公有屬性:"+field);
        }

        Field[] fields = fruitClass.getDeclaredFields();
        for (Field field : fields) {
            System.out.println("定義的所有屬性:"+field);
        }

輸出結果:

定義的公有屬性:public double cn.chen.test.util.lang.Fruit.weight
========================分隔線============================
定義的所有屬性:private java.lang.String cn.chen.test.util.lang.Fruit.name
定義的所有屬性:private int cn.chen.test.util.lang.Fruit.size
定義的所有屬性:public double cn.chen.test.util.lang.Fruit.weight

源碼分析,就以getFileds()這個方法為例,涉及以下幾個方法:

public Field[] getFields() throws SecurityException {
        // 1.檢查是否允許訪問。如果訪問被拒絕,則拋出SecurityException。
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return copyFields(privateGetPublicFields(null));
    }
 
 private static Field[] copyFields(Field[] arg) {
         // 2. 聲明一個Filed的數組,用來存儲類的字段 
        Field[] out = new Field[arg.length];
        //  3.使用ReflectionFactory構造一個對象,也是不使用構造方法構造對象的一種方式。
        ReflectionFactory fact = getReflectionFactory();
       // 4.遍歷,將字段複製后返回。
        for (int i = 0; i < arg.length; i++) {
            out[i] = fact.copyField(arg[i]);
        }
        return out;
    }
    
 public Field copyField(Field var1) {
        return langReflectAccess().copyField(var1);
    }
 
// 再次檢查屬性的訪問權限
  private static LangReflectAccess langReflectAccess() {
        if (langReflectAccess == null) {
            Modifier.isPublic(1);
        }

        return langReflectAccess;
    }

2.2.3 獲得一般方法

就是獲取一個類中的方法,一般有以下幾個方法:

  • public Method[] getMethods(): 獲取所有可見的方法

  • public Method[] getDeclaredMethods() :獲取所有的方法,無論是否可見

  • public Method getMethod(String name, Class… parameterTypes)

    參數說明:

  1. 通過方法名稱、參數類型獲取方法
  2. 如果你想訪問的方法不可見,會拋出異常
  3. 如果你想訪問的方法沒有參數,傳遞 null作為參數類型數組,或者不傳值)
  • public Method getDeclaredMethod(String name, Class… parameterTypes)
  1. 通過方法名稱、參數類型獲取方法
  2. 如果你想訪問的方法沒有參數,傳遞 null作為參數類型數組,或者不傳值)

基本使用:

//在fruit中定義一個這樣的方法
 private  void eat(String describe){
        System.out.println("通過getMethod()方法調用了eat()方法:  "+describe);
    }

調用這個方法:

        Class fruitClass = Fruit.class;
        Method method = fruitClass.getDeclaredMethod("eat",String.class);
        method.setAccessible(true);
        method.invoke(fruitClass.newInstance(),"我是該方法的參數值");

輸出結果:

  通過getMethod()方法調用了eat()方法:我是該方法的參數值

分析getDeclaredMethod()涉及的源碼:

public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        // 1.檢查方法的修飾符
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        // 2.searchMethods()方法的第一個參數確定這個方法是不是私有方法,第二個參數我們定義的方法名,第三個參數就是傳入的方法的參數類型
        Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
        }
        return method;
    }

// 這個方法就是通過傳入的方法名找到我們定義的方法,然後使用了Method的copy()方法返回一個Method的實例,我們通過操作mehtod這個實例就可以操作我們定義的方法。
 private static Method searchMethods(Method[] methods,
                                        String name,
                                        Class<?>[] parameterTypes)
    {
        Method res = null;
        String internedName = name.intern();
        for (int i = 0; i < methods.length; i++) {
            Method m = methods[i];
            if (m.getName() == internedName
                && arrayContentsEq(parameterTypes, m.getParameterTypes())
                && (res == null
                    || res.getReturnType().isAssignableFrom(m.getReturnType())))
                res = m;
        }

        return (res == null ? res : getReflectionFactory().copyMethod(res));
    }

 public Method copyMethod(Method var1) {
        return langReflectAccess().copyMethod(var1);
    }

 
// 檢查屬性的訪問權限
  private static LangReflectAccess langReflectAccess() {
        if (langReflectAccess == null) {
            Modifier.isPublic(1);
        }

        return langReflectAccess;
    }

2.2.4 判斷類的類型的方法

這類型的方法顧名思義,就是來判斷這個類是什麼類型,是接口,註解,枚舉,還是一般的類等等。部分方法如下錶

boolean isAnnotation()判斷是不是註解
boolean isArray() 判斷是否為數組
boolean isEnum()判斷是否為枚舉類型
boolean isInterface() 是否為接口類型
boolean isMemberClass()當且僅當基礎類是成員類時,返回“true”
boolean isPrimitive()確定指定的“類”對象是否表示原始類型。
boolean isSynthetic()如果這個類是合成類,則返回’ true ‘;否則返回“false”。

基本用法:

// 定義一個接口:
interface  Animal{
    public void run();
}

判斷是不是一個接口:

Class AnimalClass = Animal.class;
 boolean flag = AnimalClass.isInterface();
 System.out.println(flag);

輸出結果:

true

源碼分析isInterface():

 public native boolean isInterface();

這是一個native方法,大家都知道native方法是非Java語言實現的代碼,供Java程序調用的,因為Java程序是運行在JVM虛擬機上面的,要想訪問到比較底層的與操作系統相關的就沒辦法了,只能由靠近操作系統的語言來實現。

2.2.5 toString()方法

將對象轉換為字符串。字符串表示形式是字符串“類”或“接口”,後跟一個空格,然後是該類的全限定名。

基本使用:

// 這是前面定義的兩個類Fruit和Car,Car是一個接口
 Class fruitClass = Fruit.class;
 Class AnimalClass = Animal.class;
 System.out.println(AnimalClass.toString());
 System.out.println(fruitClass.toString());

輸出結果:

// 格式  字符串“類”或“接口”,後跟一個空格,然後是該類的全限定名
interface cn.chen.test.util.lang.Animal
class cn.chen.test.util.lang.Fruit

源碼如下:

 public String toString() {
       // 先是判斷是接口或者類,然後調用getName輸出類的全限定名
        return (isInterface() ? "interface " : (isPrimitive() ? "" : "class "))
            + getName();
    }

  public native boolean isInterface();
  public native boolean isPrimitive();

追本溯源,方能闊步前行。

參考資料

​ https://blog.csdn.net/x_panda/article/details/17120479

​ https://juejin.im/post/5d4450fbe51d4561ce5a1be1

​ JavaSE的官方文檔

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

Spring Data JPA

一、JPA概述:

  JPA的全稱是Java Persistence API, 即Java 持久化API,是SUN公司推出的一套基於ORM的規範,內部是由一系列的接口和抽象類構成。JPA通過JDK 5.0註解描述對象-關係表的映射關係,並將運行期的實體對象持久化到數據庫中。

  JPA的優勢:標準化、容器級特性的支持、簡單方便、查詢能力、高級特性

二、JPA與Hibernate的關係:

  JPA規範本質上就是一種ORM規範,注意不是ORM框架——因為JPA並未提供ORM實現,它只是制訂了一些規範,提供了一些編程的API接口,但具體實現則由服務廠商來提供實現。JPAHibernate的關係就像JDBCJDBC驅動的關係,JPA是規範,Hibernate除了作為ORM框架之外,它也是一種JPA實現。

 

 

 

三、JPA環境搭建:

1、創建一個maven工程,在pom.xml中導入對應的坐標

 1    <properties>
 2         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 3         <project.hibernate.version>5.0.7.Final</project.hibernate.version>
 4     </properties>
 5 
 6     <dependencies>
 7         <!-- junit -->
 8         <dependency>
 9             <groupId>junit</groupId>
10             <artifactId>junit</artifactId>
11             <version>4.12</version>
12             <scope>test</scope>
13         </dependency>
14 
15         <!-- hibernate對jpa的支持包 -->
16         <dependency>
17             <groupId>org.hibernate</groupId>
18             <artifactId>hibernate-entitymanager</artifactId>
19             <version>${project.hibernate.version}</version>
20         </dependency>
21 
22         <!-- c3p0 -->
23         <dependency>
24             <groupId>org.hibernate</groupId>
25             <artifactId>hibernate-c3p0</artifactId>
26             <version>${project.hibernate.version}</version>
27         </dependency>
28 
29         <!-- log日誌 -->
30         <dependency>
31             <groupId>log4j</groupId>
32             <artifactId>log4j</artifactId>
33             <version>1.2.17</version>
34         </dependency>
35 
36         <!-- Mysql and MariaDB -->
37         <dependency>
38             <groupId>mysql</groupId>
39             <artifactId>mysql-connector-java</artifactId>
40             <version>5.1.6</version>
41         </dependency>
42     </dependencies>

2、編寫實體類和數據表的映射配置,創建實體類以後,使用對應的註釋配置映射關係

     @Entity
            作用:指定當前類是實體類。
        @Table
            作用:指定實體類和表之間的對應關係。
            屬性:
                name:指定數據庫表的名稱
        @Id
            作用:指定當前字段是主鍵。
        @GeneratedValue
            作用:指定主鍵的生成方式。。
            屬性:
                strategy :指定主鍵生成策略。
        @Column
            作用:指定實體類屬性和數據庫表之間的對應關係
            屬性:
                name:指定數據庫表的列名稱。
                unique:是否唯一  
                nullable:是否可以為空  
                inserttable:是否可以插入  
                updateable:是否可以更新  
                columnDefinition: 定義建表時創建此列的DDL  
                secondaryTable: 從表名。如果此列不建在主表上(默認建在主表),該屬性定義該列所在從表的名字搭建開發環境[重點]

3、配置JPA的核心配置文件

在java工程的src路徑下創建一個名為META-INF的文件夾,在此文件夾下創建一個名為persistence.xml的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence  
    http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
    version="2.0">
    <!--配置持久化單元 
        name:持久化單元名稱 
        transaction-type:事務類型
             RESOURCE_LOCAL:本地事務管理 
             JTA:分佈式事務管理 -->
    <persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
        <!--配置JPA規範的服務提供商 -->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <properties>
            <!-- 數據庫驅動 -->
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
            <!-- 數據庫地址 -->
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa" />
            <!-- 數據庫用戶名 -->
            <property name="javax.persistence.jdbc.user" value="root" />
            <!-- 數據庫密碼 -->
            <property name="javax.persistence.jdbc.password" value="111111" />

            <!--jpa提供者的可選配置:我們的JPA規範的提供者為hibernate,所以jpa的核心配置中兼容hibernate的配 -->
<property name="hibernate.show_sql" value="true" /> <property name="hibernate.format_sql" value="true" />
        
       <!--自動創建數據庫表:create(運行時創建表),update(如過有表則不創建表),none(不創建表) <property name="hibernate.hbm2ddl.auto" value="create" /> </properties> </persistence-unit> </persistence>

4、測試數據庫操作

通過調用EntityManager的方法完成獲取事務,以及持久化數據庫的操作

方法說明:    
    getTransaction : 獲取事務對象
    persist : 保存操作
    merge : 更新操作
    remove : 刪除操作
    find/getReference : 根據id查詢
 1 @Test
 2     public void test() {
 3         /**
 4          * 創建實體管理類工廠,藉助Persistence的靜態方法獲取
 5          *         其中傳遞的參數為持久化單元名稱,需要jpa配置文件中指定
 6          */
 7         EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
 8         //創建實體管理類
 9         EntityManager em = factory.createEntityManager();
10         //獲取事務對象
11         EntityTransaction tx = em.getTransaction();
12         //開啟事務
13         tx.begin();
14         Customer c = new Customer();
15         c.setCustName("天地壹號");
16         //保存操作
17         em.persist(c);
18         //提交事務
19         tx.commit();
20         //釋放資源
21         em.close();
22         factory.close();
23     }

6、抽取JPAUtil工具類,通過工具類生成實體類管理器對象

package top.biyenanhai.dao;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public final class JPAUtil {
    // JPA的實體管理器工廠:相當於Hibernate的SessionFactory
    private static EntityManagerFactory em;
    // 使用靜態代碼塊賦值
    static {
        // 注意:該方法參數必須和persistence.xml中persistence-unit標籤name屬性取值一致
        em = Persistence.createEntityManagerFactory("myPersistUnit");
    }

    /**
     * 使用管理器工廠生產一個管理器對象
     * 
     * @return
     */
    public static EntityManager getEntityManager() {
        return em.createEntityManager();
    }
}

 

三、Spring Data JPA概述:

  Spring Data JPA 是 Spring 基於 ORM 框架、JPA 規範的基礎上封裝的一套JPA應用框架,可使開發者用極簡的代碼即可實現對數據庫的訪問和操作。它提供了包括增刪改查等在內的常用功能,且易於擴展!學習並使用 Spring Data JPA 可以極大提高開發效率!Spring Data JPA 讓我們解脫了DAO層的操作,基本上所有CRUD都可以依賴於它來實現,在實際的工作工程中,推薦使用Spring Data JPA + ORM(如:hibernate)完成操作,這樣在切換不同的ORM框架時提供了極大的方便,同時也使數據庫層操作更加簡單,方便解耦。

四、Spring Data JPA 與 JPA和hibernate之間的關係:

  JPA是一套規範,內部是有接口和抽象類組成的。hibernate是一套成熟的ORM框架,而且Hibernate實現了JPA規範,所以也可以稱hibernate為JPA的一種實現方式,我們使用JPA的API編程,意味着站在更高的角度上看待問題(面向接口編程)。Spring Data JPA是Spring提供的一套對JPA操作更加高級的封裝,是在JPA規範下的專門用來進行數據持久化的解決方案。

五、Spring Data JPA快速搭建開發環境:

1、創建maven工程,導入Spring Data JPA的坐標

  1    <properties>
  2         <spring.version>4.2.4.RELEASE</spring.version>
  3         <hibernate.version>5.0.7.Final</hibernate.version>
  4         <slf4j.version>1.6.6</slf4j.version>
  5         <log4j.version>1.2.12</log4j.version>
  6         <c3p0.version>0.9.1.2</c3p0.version>
  7         <mysql.version>5.1.6</mysql.version>
  8     </properties>
  9 
 10     <dependencies>
 11         <!-- junit單元測試 -->
 12         <dependency>
 13             <groupId>junit</groupId>
 14             <artifactId>junit</artifactId>
 15             <version>4.12</version>
 16             <scope>test</scope>
 17         </dependency>
 18         
 19         <!-- spring beg -->
 20         <dependency>
 21             <groupId>org.aspectj</groupId>
 22             <artifactId>aspectjweaver</artifactId>
 23             <version>1.6.8</version>
 24         </dependency>
 25 
 26         <dependency>
 27             <groupId>org.springframework</groupId>
 28             <artifactId>spring-aop</artifactId>
 29             <version>${spring.version}</version>
 30         </dependency>
 31 
 32         <dependency>
 33             <groupId>org.springframework</groupId>
 34             <artifactId>spring-context</artifactId>
 35             <version>${spring.version}</version>
 36         </dependency>
 37 
 38         <dependency>
 39             <groupId>org.springframework</groupId>
 40             <artifactId>spring-context-support</artifactId>
 41             <version>${spring.version}</version>
 42         </dependency>
 43 
 44         <dependency>
 45             <groupId>org.springframework</groupId>
 46             <artifactId>spring-orm</artifactId>
 47             <version>${spring.version}</version>
 48         </dependency>
 49 
 50         <dependency>
 51             <groupId>org.springframework</groupId>
 52             <artifactId>spring-beans</artifactId>
 53             <version>${spring.version}</version>
 54         </dependency>
 55 
 56         <dependency>
 57             <groupId>org.springframework</groupId>
 58             <artifactId>spring-core</artifactId>
 59             <version>${spring.version}</version>
 60         </dependency>
 61         
 62         <!-- spring end -->
 63 
 64         <!-- hibernate beg -->
 65         <dependency>
 66             <groupId>org.hibernate</groupId>
 67             <artifactId>hibernate-core</artifactId>
 68             <version>${hibernate.version}</version>
 69         </dependency>
 70         <dependency>
 71             <groupId>org.hibernate</groupId>
 72             <artifactId>hibernate-entitymanager</artifactId>
 73             <version>${hibernate.version}</version>
 74         </dependency>
 75         <dependency>
 76             <groupId>org.hibernate</groupId>
 77             <artifactId>hibernate-validator</artifactId>
 78             <version>5.2.1.Final</version>
 79         </dependency>
 80         <!-- hibernate end -->
 81 
 82         <!-- c3p0 beg -->
 83         <dependency>
 84             <groupId>c3p0</groupId>
 85             <artifactId>c3p0</artifactId>
 86             <version>${c3p0.version}</version>
 87         </dependency>
 88         <!-- c3p0 end -->
 89 
 90         <!-- log end -->
 91         <dependency>
 92             <groupId>log4j</groupId>
 93             <artifactId>log4j</artifactId>
 94             <version>${log4j.version}</version>
 95         </dependency>
 96 
 97         <dependency>
 98             <groupId>org.slf4j</groupId>
 99             <artifactId>slf4j-api</artifactId>
100             <version>${slf4j.version}</version>
101         </dependency>
102 
103         <dependency>
104             <groupId>org.slf4j</groupId>
105             <artifactId>slf4j-log4j12</artifactId>
106             <version>${slf4j.version}</version>
107         </dependency>
108         <!-- log end -->
109 
110         
111         <dependency>
112             <groupId>mysql</groupId>
113             <artifactId>mysql-connector-java</artifactId>
114             <version>${mysql.version}</version>
115         </dependency>
116 
117         <dependency>
118             <groupId>org.springframework.data</groupId>
119             <artifactId>spring-data-jpa</artifactId>
120             <version>1.9.0.RELEASE</version>
121         </dependency>
122 
123         <dependency>
124             <groupId>org.springframework</groupId>
125             <artifactId>spring-test</artifactId>
126             <version>4.2.4.RELEASE</version>
127         </dependency>
128         
129         <!-- el beg 使用spring data jpa 必須引入 -->
130         <dependency>  
131             <groupId>javax.el</groupId>  
132             <artifactId>javax.el-api</artifactId>  
133             <version>2.2.4</version>  
134         </dependency>  
135           
136         <dependency>  
137             <groupId>org.glassfish.web</groupId>  
138             <artifactId>javax.el</artifactId>  
139             <version>2.2.4</version>  
140         </dependency> 
141         <!-- el end -->
142 
143         <dependency>
144             <groupId>javax.xml.bind</groupId>
145             <artifactId>jaxb-api</artifactId>
146             <version>2.3.0</version>
147         </dependency>
148         <dependency>
149             <groupId>com.sun.xml.bind</groupId>
150             <artifactId>jaxb-impl</artifactId>
151             <version>2.3.0</version>
152         </dependency>
153         <dependency>
154             <groupId>com.sun.xml.bind</groupId>
155             <artifactId>jaxb-core</artifactId>
156             <version>2.3.0</version>
157         </dependency>
158         <dependency>
159             <groupId>javax.activation</groupId>
160             <artifactId>activation</artifactId>
161             <version>1.1.1</version>
162         </dependency>
163     </dependencies>
164         

2、整合Spring Data JPA與Spring

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
 4     xmlns:context="http://www.springframework.org/schema/context"
 5     xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
 6     xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
 7     xsi:schemaLocation="
 8         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
 9         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
10         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
11         http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
12         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
13         http://www.springframework.org/schema/data/jpa 
14         http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
15     
16     <!-- 1.dataSource 配置數據庫連接池-->
17     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
18         <property name="driverClass" value="com.mysql.jdbc.Driver" />
19         <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa" />
20         <property name="user" value="root" />
21         <property name="password" value="111111" />
22     </bean>
23     
24     <!-- 2.配置entityManagerFactory -->
25     <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
26         <property name="dataSource" ref="dataSource" />
27         <property name="packagesToScan" value="cn.itcast.entity" />
28         <property name="persistenceProvider">
29             <bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
30         </property>
31         <!--JPA的供應商適配器-->
32         <property name="jpaVendorAdapter">
33             <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
34                 <property name="generateDdl" value="false" />
35                 <property name="database" value="MYSQL" />
36                 <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
37                 <property name="showSql" value="true" />
38             </bean>
39         </property>
40         <property name="jpaDialect">
41             <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
42         </property>
43     </bean>
44     
45     
46     <!-- 3.事務管理器-->
47     <!-- JPA事務管理器  -->
48     <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
49         <property name="entityManagerFactory" ref="entityManagerFactory" />
50     </bean>
51     
52     <!-- 整合spring data jpa-->
53     <jpa:repositories base-package="top.biyenanhai.mapper"
54         transaction-manager-ref="transactionManager"
55         entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
56         
57     <!-- 4.txAdvice-->
58     <tx:advice id="txAdvice" transaction-manager="transactionManager">
59         <tx:attributes>
60             <tx:method name="*" propagation="REQUIRED"/>
61             <tx:method name="get*" read-only="true"/>
62             <tx:method name="find*" read-only="true"/>
63         </tx:attributes>
64     </tx:advice>
65     
66     <!-- 5.aop-->
67     <aop:config>
68         <aop:pointcut id="pointcut" expression="execution(* top.biyenanhai.service.*.*(..))" />
69         <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
70     </aop:config>
71     
72     <context:component-scan base-package="cn.itcast"></context:component-scan>
73     <!--6、配置包掃描-->
74     <context:component-scan base-package="top.biyenanhai"/>
75     <!--組裝其它 配置文件-->
76     
77 </beans>

3、使用JPA註解配置映射關係

package top.biyenanhai.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * 
 *      * 所有的註解都是使用JPA的規範提供的註解,
 *      * 所以在導入註解包的時候,一定要導入javax.persistence下的
 */
@Entity //聲明實體類
@Table(name="cst_customer") //建立實體類和表的映射關係
public class Customer {
    
    @Id//聲明當前私有屬性為主鍵
    @GeneratedValue(strategy=GenerationType.IDENTITY) //配置主鍵的生成策略
    @Column(name="cust_id") //指定和表中cust_id字段的映射關係
    private Long custId;
    
    @Column(name="cust_name") //指定和表中cust_name字段的映射關係
    private String custName;
    
    @Column(name="cust_source")//指定和表中cust_source字段的映射關係
    private String custSource;
    
    @Column(name="cust_industry")//指定和表中cust_industry字段的映射關係
    private String custIndustry;
    
    @Column(name="cust_level")//指定和表中cust_level字段的映射關係
    private String custLevel;
    
    @Column(name="cust_address")//指定和表中cust_address字段的映射關係
    private String custAddress;
    
    @Column(name="cust_phone")//指定和表中cust_phone字段的映射關係
    private String custPhone;
    
    //添加get,set方法,toString方法
}

3、編寫符合Spring Data JPA規範的Dao層接口,繼承JpaRepository<T,ID>和JpaSpecificationExecutor<T>

 *     JpaRepository<操作的實體類類型,實體類中主鍵屬性的類型>
 *              *封裝了基本CURD操作
 *     JpaSpecificationExecutor<操作的實體類類型>
 *              *封裝了複雜查詢(分頁)
package top.biyenanhai.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import top.biyenanhai.domain.Customer;

import java.util.List;

/**
 * Created with IntelliJ IDEA.
 *
 * @Auther: 畢業男孩
 *
 * 符合SpringDataJpa的dao接口規範
 *      JpaRepository<操作的實體類類型,實體類中主鍵屬性的類型>
 *              *封裝了基本CURD操作
 *      JpaSpecificationExecutor<操作的實體類類型>
 *              *封裝了複雜查詢(分頁)
 *
 */
public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {

    /**
     * 案例:根據客戶名稱查詢客戶
     *      使用jpql的形式查詢
     *
     *  jpql:from Customer where custName = ?
     *
     *  配置jpql語句,使用@Query註解
     */
    @Query(value="from Customer where custName = ? ")
    Customer findJpql(String custName);

    /**
     * 案例:根據客戶名稱和客戶id查詢客戶
     *      jqpl:from Customer where cutName = ? and custId = ?
     *
     *   對於多個佔位符參數
     *          賦值的時候,默認的情況下,佔位符的位置需要和方法參數中的位置保持一致
     *   可以指定佔位符參數的位置
     *          ?索引的方式,指定此佔位的取值來源
     */
    @Query(value = "from Customer where custName=?2 and custId=?1")
    Customer findCustNameAndCustId(Long id,String name);


    /**
     * 使用jpql完成更新操作
     *      案例:根據id更新,客戶的名稱
     *          更新4號客戶的名稱,將名稱改為“老男孩”
     *
     *
     * sql:update cst_customer set cust_name = ?where cust_id=?
     * jpql:update Customer set custName=? where custId=?
     *
     * @Query:代表的是進行查詢
     *      聲明此方法是用來更新操作
     * @Modifying:當前執行的是一個更新操作
     */
    @Query(value = "update Customer set custName=?2 where custId=?1")
    @Modifying
    void updateCustomer(long id, String custName);


    /**
     * 使用sql的形式查詢:
     *      查詢全部的客戶
     *      sql:select * from cst_custimer;
     *      Query:配置sql查詢
     *          value: sql語句
     *          nativeQuery: 查詢方式
     *              true:sql查詢
     *              false:jpql查詢
     */
//    @Query(value = "select * from cst_customer", nativeQuery = true)  //查詢全部

    @Query(value = "select * from cst_customer where cust_name like ?1",nativeQuery = true) //條件查詢
    List<Object[]> findSql(String name);


    /**
     * 方法名的約定:
     *      findBy:查詢
     *          對象中的屬性名(首字母大寫):查詢的條件
     *          CustName
     *          *默認情況:使用 等於的方式查詢
     *              特殊的查詢方式
     * findByCustName --  根據客戶名稱查詢
     *
     * 在springdataJpa的運行階段
     *      會根據方法名稱進行解析  findBy  from xxx(實體類)
     *                                  屬性名稱    where custName =
     *
     *
     *      1、findBy + 屬性名稱(根據屬性名稱進行完成匹配的查詢=)
     *      2、findBy + 屬性名稱 + “查詢方式(Like|isnull)”
     *      3、多條件查詢
     *          findBy + 屬性名 + "查詢條件" + "多條件的連接符(and|or)" + 屬性名 + “查詢方式”
     *
     */
    Customer findByCustName(String custName);

    List<Customer> findByCustNameLike(String name);

    List<Customer> findByCustNameLikeAndCustIndustry(String name, String industry);

}

4、測試基本CRUD操作

@RunWith(SpringJUnit4ClassRunner.class) //聲明spring提供的單元測試環境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class CustomerDaoTest {

    @Autowired
    private CustomerDao customerDao;

    /**
     * 根據id查詢
     */
    @Test
    public void testFindOne(){
        Customer customer = customerDao.findOne(1l);
        System.out.println(customer);
    }
......
    
    /**
     * 測試jqpl的更新操作
     * 更新和刪除操作要加上事務的註解
     *
     * springDataJpa中使用jpql完成  更新/刪除操作
     *          需要手動添加事務的支持
     *          默認回執行結束之後,回滾事務
     *    @Rollback:設置是否自動回滾
     */
    @Test
    @Transactional//添加事務的支持
    @Rollback(false)
    public void testUpdate(){
        customerDao.updateCustomer(1l, "航空航天科技有限公司");
    }
......

5、具體關鍵字,使用方法和生產成SQL如下錶所示

Keyword

Sample

JPQL

And

findByLastnameAndFirstname

… where x.lastname = ?1 and x.firstname = ?2

Or

findByLastnameOrFirstname

… where x.lastname = ?1 or x.firstname = ?2

Is,Equals

findByFirstnameIs,

findByFirstnameEquals

… where x.firstname = ?1

Between

findByStartDateBetween

… where x.startDate between ?1 and ?2

LessThan

findByAgeLessThan

… where x.age < ?1

LessThanEqual

findByAgeLessThanEqual

… where x.age ?1

GreaterThan

findByAgeGreaterThan

… where x.age > ?1

GreaterThanEqual

findByAgeGreaterThanEqual

… where x.age >= ?1

After

findByStartDateAfter

… where x.startDate > ?1

Before

findByStartDateBefore

… where x.startDate < ?1

IsNull

findByAgeIsNull

… where x.age is null

IsNotNull,NotNull

findByAge(Is)NotNull

… where x.age not null

Like

findByFirstnameLike

… where x.firstname like ?1

NotLike

findByFirstnameNotLike

… where x.firstname not like ?1

StartingWith

findByFirstnameStartingWith

… where x.firstname like ?1 (parameter bound with appended %)

EndingWith

findByFirstnameEndingWith

… where x.firstname like ?1 (parameter bound with prepended %)

Containing

findByFirstnameContaining

… where x.firstname like ?1 (parameter bound wrapped in %)

OrderBy

findByAgeOrderByLastnameDesc

… where x.age = ?1 order by x.lastname desc

Not

findByLastnameNot

… where x.lastname <> ?1

In

findByAgeIn(Collection ages)

… where x.age in ?1

NotIn

findByAgeNotIn(Collection age)

… where x.age not in ?1

TRUE

findByActiveTrue()

… where x.active = true

FALSE

findByActiveFalse()

… where x.active = false

IgnoreCase

findByFirstnameIgnoreCase

… where UPPER(x.firstame) = UPPER(?1)

 

六、Specifications動態查詢:

  有時我們在查詢某個實體的時候,給定的條件是不固定的,這時就需要動態構建相應的查詢語句,在Spring Data JPA中可以通過JpaSpecificationExecutor接口查詢。相比JPQL,其優勢是類型安全,更加的面向對象。對於JpaSpecificationExecutor,這個接口基本是圍繞着Specification接口來定義的。我們可以簡單的理解為,Specification構造的就是查詢條件。

/**
    *    root    :Root接口,代表查詢的根對象,可以通過root獲取實體中的屬性
    *    query    :代表一個頂層查詢對象,用來自定義查詢
    *    cb        :用來構建查詢,此對象里有很多條件方法
    **/
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);

案例:使用Specifications完成條件查詢

//依賴注入customerDao
    @Autowired
    private CustomerDao customerDao;    
    @Test
    public void testSpecifications() {
          //使用匿名內部類的方式,創建一個Specification的實現類,並實現toPredicate方法
        Specification <Customer> spec = new Specification<Customer>() {
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                //cb:構建查詢,添加查詢方式   like:模糊匹配
                //root:從實體Customer對象中按照custName屬性進行查詢
                return cb.like(root.get("custName").as(String.class), "航天航空%");
            }
        };
        Customer customer = customerDao.findOne(spec);
        System.out.println(customer);
    }

案例:基於Specifications的分頁查詢

@Test
    public void testPage() {
        //構造查詢條件
        Specification<Customer> spec = new Specification<Customer>() {
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                return cb.like(root.get("custName").as(String.class), "航天%");
            }
        };
        
        /**
         * 構造分頁參數
         *         Pageable : 接口
         *             PageRequest實現了Pageable接口,調用構造方法的形式構造
         *                 第一個參數:頁碼(從0開始)
         *                 第二個參數:每頁查詢條數
         */
        Pageable pageable = new PageRequest(0, 5);
        
        /**
         * 分頁查詢,封裝為Spring Data Jpa 內部的page bean
         *         此重載的findAll方法為分頁方法需要兩個參數
         *             第一個參數:查詢條件Specification
         *             第二個參數:分頁參數
         */
        Page<Customer> page = customerDao.findAll(spec,pageable);
        
    }

對於Spring Data JPA中的分頁查詢,是其內部自動實現的封裝過程,返回的是一個Spring Data JPA提供的pageBean對象。其中的方法說明如下:

//獲取總頁數
int getTotalPages();
//獲取總記錄數    
long getTotalElements();
//獲取列表數據
List<T> getContent();

方法對應關係:

方法名稱

Sql對應關係

equal

filed = value

gt(greaterThan )

filed > value

lt(lessThan )

filed < value

ge(greaterThanOrEqualTo )

filed >= value

le( lessThanOrEqualTo)

filed <= value

notEqual

filed != value

like

filed like value

notLike

filed not like value

七、JPA中的一對多:

實體類(一對多,一的實體類)

/**
 * 客戶的實體類
 * 明確使用的註解都是JPA規範的
 * 所以導包都要導入javax.persistence包下的
 */
@Entity//表示當前類是一個實體類
@Table(name="cst_customer")//建立當前實體類和表之間的對應關係
public class Customer implements Serializable {
    
    @Id//表明當前私有屬性是主鍵
    @GeneratedValue(strategy=GenerationType.IDENTITY)//指定主鍵的生成策略
    @Column(name="cust_id")//指定和數據庫表中的cust_id列對應
    private Long custId;
    @Column(name="cust_name")//指定和數據庫表中的cust_name列對應
    private String custName;
    
    //配置客戶和聯繫人的一對多關係
    @OneToMany(targetEntity=LinkMan.class)
    @JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
    private Set<LinkMan> linkmans = new HashSet<LinkMan>();

...get,set方法

實體類(一對多,多的實體類)

/**
 * 聯繫人的實體類(數據模型)
 */
@Entity
@Table(name="cst_linkman")
public class LinkMan implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="lkm_id")
    private Long lkmId;
    @Column(name="lkm_name")
    private String lkmName;
    @Column(name="lkm_gender")
    private String lkmGender;
    @Column(name="lkm_phone")
    private String lkmPhone;

    //多對一關係映射:多個聯繫人對應客戶
    @ManyToOne(targetEntity=Customer.class)
    @JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
    private Customer customer;//用它的主鍵,對應聯繫人表中的外鍵

...get,set方法

八、JPA中的多對多:

/**
 * 用戶的數據模型
 */
@Entity
@Table(name="sys_user")
public class SysUser implements Serializable {
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="user_id")
    private Long userId;
    @Column(name="user_name")
    private String userName;

    
    //多對多關係映射
    @ManyToMany(mappedBy="users")
    private Set<SysRole> roles = new HashSet<SysRole>();
/**
 * 角色的數據模型
 */
@Entity
@Table(name="sys_role")
public class SysRole implements Serializable {
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="role_id")
    private Long roleId;
    @Column(name="role_name")
    private String roleName;
    
    //多對多關係映射
    @ManyToMany
    @JoinTable(name="user_role_rel",//中間表的名稱
              //中間表user_role_rel字段關聯sys_role表的主鍵字段role_id
              joinColumns={@JoinColumn(name="role_id",referencedColumnName="role_id")},
              //中間表user_role_rel的字段關聯sys_user表的主鍵user_id
              inverseJoinColumns={@JoinColumn(name="user_id",referencedColumnName="user_id")}
    )
    private Set<SysUser> users = new HashSet<SysUser>();

 

映射的註解說明

@OneToMany:
       作用:建立一對多的關係映射
    屬性:
        targetEntityClass:指定多的多方的類的字節碼
        mappedBy:指定從表實體類中引用主表對象的名稱。
        cascade:指定要使用的級聯操作
        fetch:指定是否採用延遲加載

@ManyToOne
    作用:建立多對一的關係
    屬性:
        targetEntityClass:指定一的一方實體類字節碼
        cascade:指定要使用的級聯操作
        fetch:指定是否採用延遲加載
        optional:關聯是否可選。如果設置為false,則必須始終存在非空關係。

@JoinColumn
     作用:用於定義主鍵字段和外鍵字段的對應關係。
     屬性:
        name:指定外鍵字段的名稱
        referencedColumnName:指定引用主表的主鍵字段名稱
        unique:是否唯一。默認值不唯一
        nullable:是否允許為空。默認值允許。
        insertable:是否允許插入。默認值允許。
        updatable:是否允許更新。默認值允許。
        columnDefinition:列的定義信息。

 

九、Spring Data JPA中的多表查詢:

  對象導航查詢:對象導航檢索方式是根據已經加載的對象,導航到他的關聯對象。它利用類與類之間的關係來檢索對象。例如:我們通過ID查詢方式查出一個客戶,可以調用Customer類中的getLinkMans()方法來獲取該客戶的所有聯繫人。對象導航查詢的使用要求是:兩個對象之間必須存在關聯關係。

//查詢一個客戶,獲取該客戶下的所有聯繫人
@Autowired
private CustomerDao customerDao;
    
@Test
//由於是在java代碼中測試,為了解決no session問題,將操作配置到同一個事務中
@Transactional 
public void testFind() {
    Customer customer = customerDao.findOne(5l);
    Set<LinkMan> linkMans = customer.getLinkMans();//對象導航查詢
    for(LinkMan linkMan : linkMans) {
          System.out.println(linkMan);
    }
}
//查詢一個聯繫人,獲取該聯繫人的所有客戶
@Autowired
private LinkManDao linkManDao;
    
@Test
public void testFind() {
    LinkMan linkMan = linkManDao.findOne(4l);
    Customer customer = linkMan.getCustomer(); //對象導航查詢
    System.out.println(customer);
}

採用延遲加載的思想。通過配置的方式來設定當我們在需要使用時,發起真正的查詢。

配置方式:

/**
 * 在客戶對象的@OneToMany註解中添加fetch屬性
 *     FetchType.EAGER    :立即加載
 *     FetchType.LAZY            :延遲加載
 */
 @OneToMany(mappedBy="customer",fetch=FetchType.EAGER)
 private Set<LinkMan> linkMans = new HashSet<>();
    
/**
 * 在聯繫人對象的@ManyToOne註解中添加fetch屬性
 *         FetchType.EAGER    :立即加載
 *         FetchType.LAZY    :延遲加載
 */
 @ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER)
 @JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id")
 private Customer customer;

使用Specification查詢

/**
 * Specification的多表查詢
 */
@Test
public void testFind() {
    Specification<LinkMan> spec = new Specification<LinkMan>() {
        public Predicate toPredicate(Root<LinkMan> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            //Join代錶鏈接查詢,通過root對象獲取
            //創建的過程中,第一個參數為關聯對象的屬性名稱,第二個參數為連接查詢的方式(left,inner,right)
            //JoinType.LEFT : 左外連接,JoinType.INNER:內連接,JoinType.RIGHT:右外連接
            Join<LinkMan, Customer> join = root.join("customer",JoinType.INNER);
            return cb.like(join.get("custName").as(String.class),"航空航天");
        }
    };
    List<LinkMan> list = linkManDao.findAll(spec);
    for (LinkMan linkMan : list) {
        System.out.println(linkMan);
    }
}

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?