分類
發燒車訊

macOS 使用Miniconda配置本地數據運算環境

目前,做數據分析工作,基本人手Numpy,pandas,scikit-learn。而這些計算程序包都是基於python平台的,所以搞數據的都得先裝個python環境。。。(當然,你用R或Julia請忽略本文)

在macOS上,默認安裝有python 2.7,鑒於python2即將停止更新,如果沒有大量的python2代碼需要維護,就直接安裝python3吧。

版本選擇

做數據運算,流行的方式是直接下載Anaconda安裝包,大概500M左右,各種依賴包(綁定了四五百個科學計算程序包),開發工具(jupyter notebook,spyder)一股腦兒都包含了,按照步驟安裝完成,開箱即用,不過裝完後會佔用幾個G的硬盤空間。

我這邊由於硬盤空間有限,採用Miniconda這個發行版本,最新的基於python3.7版本的不到50M。而Miniconda一樣使用conda作為包管理器,可以輕鬆的安裝自己需要的包,例如Numpy,pandas, matplotlib等等。

當然,也可以從安裝包或homebrew開始裝,然後再使用pip來安裝相關的程序包。總體上來說,python自身的版本和執行路徑是相當混亂的,可參考下圖。

安裝步驟

  • 下載
    先從官網下載適合自己操作系統的版本,Miniconda
    支持Windows/Linux/macOS這三種主流操作系統。如果遇到官網下載慢的問題,可以考慮國內的鏡像站點,如。

下載完成后,可以先核對下hash值,與官網的值(5cf91dde8f6024061c8b9239a1b4c34380238297adbdb9ef2061eb9d1a7f69bc)是否一致保證安裝文件未被篡改。

$ shasum -a 256 Miniconda3-latest-MacOSX-x86_64.sh 
5cf91dde8f6024061c8b9239a1b4c34380238297adbdb9ef2061eb9d1a7f69bc  Miniconda3-latest-MacOSX-x86_64.sh
  • 執行安裝
$ bash ./Miniconda3-latest-MacOSX-x86_64.sh 

Welcome to Miniconda3 4.7.12

In order to continue the installation process, please review the license
agreement.
Please, press ENTER to continue


Do you accept the license terms? [yes|no]
[no] >>> yes

Miniconda3 will now be installed into this location:
/Users/shenfeng/miniconda3

  - Press ENTER to confirm the location
  - Press CTRL-C to abort the installation
  - Or specify a different location below

[/Users/shenfeng/miniconda3] >>> 

>>> 

按照提示,敲擊回車。中間需要同意使用條款,需要輸入yes,按照路徑點回車默認即可。

Do you wish the installer to initialize Miniconda3
by running conda init? [yes|no]
[yes] >>> yes

==> For changes to take effect, close and re-open your current shell. <==

If you'd prefer that conda's base environment not be activated on startup, 
   set the auto_activate_base parameter to false: 

conda config --set auto_activate_base false

Thank you for installing Miniconda3!

最後的提示是,可以用conda config --set auto_activate_base false命令取消python3環境在啟動時自行加載。

  • 重新開一個新的終端
    可以發現,python3的env已經生效了。
(base) my:~ shenfeng$ python
Python 3.7.4 (default, Aug 13 2019, 15:17:50) 
[Clang 4.0.1 (tags/RELEASE_401/final)] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 
  • 查看env配置
$ conda env list
# conda environments:
#
base                  *  /Users/shenfeng/miniconda3

使用conda deactivate可以python3的執行環境,使用conda activate base可以激活默認的python3環境。

  • 添加國內鏡像源
    由於conda的包服務器都在海外,直接連接安裝可能出現連接超時無法完成的時候,所以可以通過修改用戶目錄下的.condarc 文件。
channels:
  - defaults
show_channel_urls: true
default_channels:
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
custom_channels:
  conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  msys2: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  bioconda: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  menpo: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  simpleitk: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  • 使用conda安裝相應的程序包
    先使用conda list檢查已經安裝的包,使用conda install需要的程序包
$ $ conda list numpy
# packages in environment at /Users/shenfeng/miniconda3:
#
# Name                    Version                   Build  Channel

$ conda install numpy

$ conda list numpy
# packages in environment at /Users/shenfeng/miniconda3:
#
# Name                    Version                   Build  Channel
numpy                     1.17.3           py37h4174a10_0    defaults
numpy-base                1.17.3           py37h6575580_0    defaults

相同的方式,我們可以安裝scipy,pandas等包,不再贅述。

交互式工具安裝

大家耳熟能詳的交互式工具肯定就是Jupyter notebook,但我在本機同樣由於磁盤空間問題只安裝ipython。實際上,Jupyter是基於ipython notebook的瀏覽器版本。

  • 安裝
$ conda install ipython
  • 執行ipython交互
$ ipython
Python 3.7.4 (default, Aug 13 2019, 15:17:50) 
Type 'copyright', 'credits' or 'license' for more information
IPython 7.9.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import numpy as np                                               
In [2]: dataset= [2,6,8,12,18,24,28,32]                                   
In [3]: sd= np.std(dataset,ddof=1)                                       
In [4]: print(sd)                                                        
10.977249200050075

樣例數據處理

先從網上下載一個樣例數據,為excel文件,另存為成csv進行處理。

以下結合上周文章中的,計算這組數據的概括性度量。

  • 讀取數據
import numpy as np
from scipy import stats

dataset = np.genfromtxt('/Users/shenfeng/Downloads/test1.csv',delimiter=',', skip_header=1)
print('Shape of numpy array: ', dataset.shape)
Shape of numpy array:  (699,)

集中趨勢的度量

  • 眾數
mode = stats.mode(dataset)                                
print('該組數據的眾數為: ', mode)         
該組數據的眾數為:  ModeResult(mode=array([1.]), count=array([145]))
# 結果說明眾數為1,出現了145次
  • 中位數
print('該組數據的中位數為: ', np.median(dataset))
該組數據的中位數為:  4.0
  • 四分位數
# 不需要提前排序
print("1/4分位數: ", np.percentile(dataset, 25, interpolation='linear')) 
1/4分位數:  2.0

print("1/2分位數: ", np.percentile(dataset, 50, interpolation='linear')) 
1/2分位數:  4.0

print("3/4分位數: ", np.percentile(dataset, 75, interpolation='linear')) 
3/4分位數:  6.0
  • 平均數
print('該組數據的平均數為: ', np.mean(dataset))
該組數據的平均數為:  4.417739628040057

離散程度的度量

  • 標準差
print('該組數據的總體標準差為: ', np.std(dataset,ddof=0))
該組數據的總體標準差為:  2.8137258170785375
  • 標準分數
# 變量值與其平均數的離差除以標準差后的稱為標準分數(standard score)
print('該組數據的標準分數為: ', stats.zscore(dataset))
該組數據的標準分數為:  [ 0.20693572  0.20693572 -0.50386559  0.56233637 -0.14846494  1.27313768
 -1.2146669  -0.85926625 -0.85926625 -0.14846494 -1.2146669  -0.85926625 ...省略 ]
  • 離散係數
# 離散係數是測度數據離散程度的統計量,主要用於比較不同樣本數據的離散程度。
print('該組數據的離散係數為: ', stats.variation(dataset))
該組數據的離散係數為:  0.6369152675317026

偏態與峰態的度量

  • 數據分布圖
import matplotlib.pyplot as plt 
plt.style.use('ggplot') 
plt.hist(dataset, bins=30) 

獲得以下分布圖

  • 偏態
print('該組數據的偏態係數為: ', stats.skew(dataset))
該組數據的偏態係數為:  0.5915855449527385
# 偏態係數在0.5~1或-1~-0.5之間,則認為是中等偏態分佈
  • 峰態係數
print('該組數據的峰態係數為: ', stats.kurtosis(dataset))
該組數據的峰態係數為:  -0.6278342838815454
# 當K<0時為扁平分佈,數據的分佈更分散

總結

本文使用Miniconda發行版配置本地數據運算環境,並對樣例做數據的概括性度量。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

讀《MySQL必知必會》我學到了什麼?

前言

最近在寫項目的時候發現自己的SQL基本功有些薄弱,遂上知乎查詢MYSQL關鍵字,期望得到某些高贊答案的指點,於是乎發現了

https://www.zhihu.com/question/34840297/answer/272185020 這位老兄的建議的書單,根據他的建議首先拜讀了《MYSQL必知必會》這本書,整體講的很基礎,頁數也不多一共 253 頁,適合基礎比較薄弱的同學進行食用。然後循序漸進,閱讀更深層次的書籍進行自我提升。這裏記載了自己在閱讀的過程中記錄的一些關鍵內容,分享給大家。書本 PDF 可以在上面的知乎鏈接獲取,或者點擊 http://www.notedeep.com/note/38/page/282 前往老哥的深度筆記進行下載。

閱讀心得

SQL語句和大小寫

SQL語句不區分大小寫,並且在 Windows 環境下,4.1.1版本之後(現在常用的都是 5.6/5.7/8.0+),MYSQL表名,字段名也是不區分大小寫的,因此我們在命名的時候建議使用單個單詞_單個單詞的形式命名,如:mysql_crash_course user_role

這裏附上阿里代碼規範的一條強制要求:

【強制】表名、字段名必須使用小寫字母或数字,禁止出現数字開頭,禁止兩個下劃線中間只出現数字。數據庫字段名的修改代價很大,因為無法進行預發布,所以字段名稱需要慎重考慮。
說明: MySQL 在 Windows 下不區分大小寫,但在 Linux 下默認是區分大小寫。因此,數據庫名、表名、字段名,都不允許出現任何大寫字母,避免節外生枝。

不能部分使用DISTINCT

  • DISTINCT 關鍵字應用於所有列而不僅是前置它的列。如果給出 SELECT DISTINCT vend_id,prod_price ,除非指定的兩個列都不同,否則所有行都將被檢索出來。
  • DISTINCT 不會返回其後面跟的所有字段都相同的列。即兩行中SELECT查詢的任何一個字段都相同才會被去重。不能通過DISTINCT加括號 () 等方式來對單個字段進行去重。

比如項目中有這麼一個需求:

需要分頁查詢綁了某收費標準的房屋,因為是房屋列表查詢,我們默認相同ID的房屋只出現一次,錯誤的SQL如下:

房屋表

收費標準表

SELECT DISTINCT h.id, h.num, s.name 
FROM house h 
LEFT JOIN standard s 
ON h.id = s.room_id 
WHERE s.name = '物業費' OR s.name = '暖氣費';

這條語句並不能返回想要的結果,即每套房屋只出現一次,因為不同的收費標準名稱不一樣,DISTINCT 不能對部分查詢條件去重。可以看到房號為1-001的記錄出現了兩次。

不過其實按照需求的描述我這裏僅查詢房屋的信息,對於查詢結果來說同一條記錄的房屋信息肯定完全相同,因此 DISTINCT 在我的業務中滿足要求。而有其他業務需要此關鍵字的時候,請大家慎重使用,切記不能部分使用該關鍵字

區分大小寫和排序順序

在對文本性的數據進行排序時,A與a相同嗎?a位於B之前還是位於Z之後?

在創建字段時可以指定字符集,一般使用 utf8mb4, 此時可以選擇相應的排序規則。

  1. utf8mb4_general_ci ci即大小寫不敏感,排序時忽略大小寫,A a 視作相同
  2. utf8mb4_bin / 帶 cs 的即大小寫敏感,相應的升序排列的話, A~Z 在前,小a~z在後
    相應的,在設置大小寫敏感后,查詢條件 where cs = ‘a’ 只能查找到表中該字段為小寫 a 的行。而不敏感,即ci時,A,a都可以被查詢出來。

BETWEEN關鍵字的注意事項

在區間查詢時,我們最關注的不應該是區間內的能否被匹配到,因為這是肯定的。而區間的邊界能否被匹配才是我們應該注意的知識點,BETWEEN AND 關鍵字匹配區間時,包含左右邊界條件。如下面的 SQL 執行結果如下:

SELECT prod_name, prod_price 
FROM products p
WHERE p.`prod_price` BETWEEN 5.99 AND 10

NULL 與不匹配

在通過過濾選擇出不具有特定值的行時,你可能希望返回具有 NULL 值的行。但是,不行。常見的錯誤會發生在is_xxx 字段上,我經常有這個毛病,

對於 is_delete 字段,我認為為 null 或者 0 都是未刪除的房屋,所以當我使用

SELECT * FROM house WHERE is_delete = '0';

查詢未刪除的房屋時,我只能查到 id 為 3 的房屋,這顯然與我的預期是不符的,解決辦法是where後面加 or is_delete is null ,或者給 is_delete 列默認值 0;建議使用後者。

這裏附上阿里代碼手冊的一條強制項目:

【強制】表達是與否概念的字段,必須使用 is_xxx 的方式命名,數據類型是 unsigned tinyint(1 表示是,0 表示否)。說明:任何字段如果為非負數,必須是 unsigned 。

解釋:tinyint 相當於 Java 中的 byte,取值範圍 -128 ~ 127 ,用來表達是否長度已經足夠,也可以用來表示人的年齡。而 unsigned 表示無符號的,對於確定為非負數的字段,使用 unsigned 可以將取值範圍擴大一倍。

AND 和 OR 的計算次序

舉個例子:假如需要列出價格為10美元(含)以上且由 1002 或 1003 製造的所有產品。下面的 SELECT 語句使用 AND 和 OR 操作符的組合建立了一個WHERE 子句:

SELECT *
FROM products
WHERE vend_id = 1002 OR vend_id = 1003 AND prod_price >= 10;

查詢結果如下:

而被我用紅框標註的行很顯然不是我們需要的行,為什麼會這樣呢?原因在於計算的次序。SQL(像多數語言一樣)在處理 OR 操作符前,優先處理 AND 操作符。當SQL看到上述 WHERE 子句時,它理解為由供應商 1003 製造的任何價格為10美元(含)以上的產品,或者由供應商 1002 製造的任何產品,而不管其價格如何。換句話說,由於 AND 在計算次序中優先級更高,操作符被錯誤地組合了。解決方法就是加 ();正確的 SQL 如下:

SELECT *
FROM products
WHERE (vend_id = 1002 OR vend_id = 1003) AND prod_price >= 10;

建議在任何時候使用具有 AND 和 OR 操作符的 WHERE 子句,都應該使用圓括號明確地分組操作符。不要過分依賴默認計算次序,即使它確實是你想要的東西也是如此。使用圓括號沒有什麼壞處,它能消除歧義。

UNION 組合查詢

  • 利用 UNION ,可給出多條SELECT 語句,將它們的結果組合成單個結果集。
  • UNION 中的每個查詢必須包含相同的列、表達式或聚集函數。
  • 在使用UNION 時,重複的行被自動取消。這是 UNION 的默認行為,但是如果需要,可以改變它。事實上,如果想返回所有匹配行,可使用 UNION ALL 而不是 UNION 。
  • 在用 UNION 組合查詢時,只能使用一條 ORDER BY 子句,它必須出現在最後一條 SELECT 語句之後

INSERT語句總是使用列的列表

一般不要使用沒有明確給出列的列表的 INSERT 語句。使用列的列表能使SQL代碼繼續發揮作用,即使表結構發生了變化。實際開發中有可能由於業務的需要,對錶結構進行修改,添加/刪除某一列。這時如果代碼中使用的SQL語句是沒有明確列表的插入語句就會報錯。當然一般我們使用逆向工程生成的 insertSelective(POJO) 並不存在這個問題,因為它對應生成的 SQL 會為我們生成列的列表。

小心使用更新和刪除語句

MySQL 沒有撤銷按鈕,因此在使用 UPDATE / DELETE 時一定要加上 WHERE 條件,並且在執行更新/刪除操作之前先進行 SELECT 操作,開啟事務。在執行結束后核對影響的行數和 SELECT 查詢出來的行數一致后再 COMMIT;

另外,使用 ALTER TABLE 要極為小心,應該在進行改動前做一個完整的備份(模式和數據的備份)。數據庫表的更改不能撤銷,如果增加了不需要的列,可能不能刪除它們。類似地,如果刪除了不應該刪除的列,可能會丟失該列中的所有數據。

視圖的規則和限制

  • 與表一樣,視圖必須唯一命名(不能給視圖取與別的視圖或表相同的名字)。
  • 對於可以創建的視圖數目沒有限制。
  • 為了創建視圖,必須具有足夠的訪問權限。這些限制通常由數據庫管理人員授予。
  • 視圖可以嵌套,即可以利用從其他視圖中檢索數據的查詢來構造一個視圖。
  • ORDER BY 可以用在視圖中,但如果從該視圖檢索數據 SELECT 中也含有 ORDER BY ,那麼該視圖中的 ORDER BY 將被覆蓋。
  • 視圖不能索引,也不能有關聯的觸發器或默認值。
  • 視圖可以和表一起使用。例如,編寫一條聯結表和視圖的 SELECT語句。

其中視圖不能索引這一點要格外注意,在我開發過程中遇到過這樣一個視圖:使用 UNION 連結了好幾張表進行查詢,對查詢的結果使用 WHERE 條件再過濾,這裏雖然對於被連接的表對於 WHERE 條件后的字段都建立了索引,但是使用 UNION 連結生成視圖的臨時表並不能擁有索引。因此查詢的效率會很慢!所以建議在 UNION 查詢中將 WHERE 條件放在每個 SELECT 語句中。

改善性能的一些建議

  • 首先,MySQL(與所有DBMS一樣)具有特定的硬件建議。在學習和研究MySQL時,使用任何舊的計算機作為服務器都可以。但
    對用於生產的服務器來說,應該堅持遵循這些硬件建議。

  • 一般來說,關鍵的生產DBMS應該運行在自己的專用服務器上。

  • MySQL是用一系列的默認設置預先配置的,從這些設置開始通常是很好的。但過一段時間后你可能需要調整內存分配、緩衝區大小等。(為查看當前設置,可使用 SHOW VARIABLES; 和 SHOWSTATUS; )

  • MySQL一個多用戶多線程的DBMS,換言之,它經常同時執行多個任務。如果這些任務中的某一個執行緩慢,則所有請求都會執
    行緩慢。如果你遇到顯著的性能不良,可使用 SHOW PROCESSLIST显示所有活動進程(以及它們的線程ID和執行時間)。你還可以用KILL 命令終結某個特定的進程(使用這個命令需要作為管理員登錄)。

  • 總是有不止一種方法編寫同一條 SELECT 語句。應該試驗聯結、並、子查詢等,找出最佳的方法。

  • 使用 EXPLAIN 語句讓MySQL解釋它將如何執行一條 SELECT 語句。

  • 一般來說,存儲過程執行得比一條一條地執行其中的各條MySQL語句快。但存儲過程一般難以調試和擴展,並且沒有移植性,因此阿里代碼規約裏面強制禁止使用存儲過程

  • 應該總是使用正確的數據類型。

  • 決不要檢索比需求還要多的數據。換言之,不要用 SELECT * (除非你真正需要每個列)。

  • 有的操作(包括 INSERT )支持一個可選的 DELAYED 關鍵字,如果使用它,將把控制立即返回給調用程序,並且一旦有可能就實際執行該操作。

    ​ 延遲插入,當插入和查詢併發執行時,插入被放入等待隊列中。直至所有查詢執行完畢后執行插入。並且MYSQL會在收到插入請求后直接返回給客戶端狀態信息,既是INSERT語句還在隊列中

  • 在導入數據時,應該關閉自動提交。你可能還想刪除索引(包括FULLTEXT 索引),然後在導入完成后再重建它們。

  • 必須索引數據庫表以改善數據檢索的性能。確定索引什麼不是一件微不足道的任務,需要分析使用的 SELECT 語句以找出重複的WHERE 和 ORDER BY 子句。如果一個簡單的 WHERE 子句返回結果所花的時間太長,則可以斷定其中使用的列(或幾個列)就是需要索引的對象。

  • 你的 SELECT 語句中有一系列複雜的 OR 條件嗎?通過使用多條SELECT 語句和連接它們的 UNION 語句,你能看到極大的性能改進。

  • 索引改善數據檢索的性能,但損害數據插入、刪除和更新的性能。如果你有一些表,它們收集數據且不經常被搜索,則在有必要之前不要索引它們。(索引可根據需要添加和刪除。)

  • LIKE 很慢。一般來說,最好是使用 FULLTEXT 而不是 LIKE 。但是 MYSQL FULLTEXT 對漢字並不友好,如果需要使用全文索引,建議使用搜索引擎 ES,可以參考我的另一篇博客: https://www.cnblogs.com/keatsCoder/p/11341835.html

  • 數據庫是不斷變化的實體。一組優化良好的表一會兒后可能就面目全非了。由於表的使用和內容的更改,理想的優化和配置也會改變。

  • 最重要的規則就是,每條規則在某些條件下都會被打破。

實際開發過程中,我們需要根據業務的需要,開啟慢查詢日誌,然後針對慢SQL,不斷地進行 EXPLAIN 與修改SQL和索引,以求達到 ref 級別,至少達到 range 級別。這就需要強大的內功支持而不是每次都通過百度來解決,希望閱讀此篇文章的你和我一起不斷修鍊。加油!

常用的函數

文本處理函數

日期和時間處理函數

數值處理函數

聚合函數

  • 雖然 MAX() 一般用來找出最大的數值或日期值,但MySQL允許將它用來返回任意列中的最大值,包括返迴文本列中的最大值。在用於文本數據時,如果數據按相應的列排序,則 MAX() 返回最後一行。MIN() 函數類似
  • MAX() 函數忽略列值為 NULL 的行。MIN() 函數類似,SUM() 也是

附錄

由於數中所附的附件內容(建表語句即數據插入語句)需要外網才能訪問,為了方便大家使用。這裏我已經下載下來附在了這篇博客裏面。如果不需要這些語句,可以直接通過網頁右邊的目錄跳躍到下一章進行瀏覽

建表語句

########################################
# MySQL Crash Course MYSQL必知必會建表語句
# http://www.forta.com/books/0672327120/
# 提供者博客園:后青春期的Keats 複製請註明出處
########################################


########################
# Create customers table
########################
CREATE TABLE customers
(
  cust_id      int       NOT NULL AUTO_INCREMENT,
  cust_name    char(50)  NOT NULL ,
  cust_address char(50)  NULL ,
  cust_city    char(50)  NULL ,
  cust_state   char(5)   NULL ,
  cust_zip     char(10)  NULL ,
  cust_country char(50)  NULL ,
  cust_contact char(50)  NULL ,
  cust_email   char(255) NULL ,
  PRIMARY KEY (cust_id)
) ENGINE=InnoDB;

#########################
# Create orderitems table
#########################
CREATE TABLE orderitems
(
  order_num  int          NOT NULL ,
  order_item int          NOT NULL ,
  prod_id    char(10)     NOT NULL ,
  quantity   int          NOT NULL ,
  item_price decimal(8,2) NOT NULL ,
  PRIMARY KEY (order_num, order_item)
) ENGINE=InnoDB;


#####################
# Create orders table
#####################
CREATE TABLE orders
(
  order_num  int      NOT NULL AUTO_INCREMENT,
  order_date datetime NOT NULL ,
  cust_id    int      NOT NULL ,
  PRIMARY KEY (order_num)
) ENGINE=InnoDB;

#######################
# Create products table
#######################
CREATE TABLE products
(
  prod_id    char(10)      NOT NULL,
  vend_id    int           NOT NULL ,
  prod_name  char(255)     NOT NULL ,
  prod_price decimal(8,2)  NOT NULL ,
  prod_desc  text          NULL ,
  PRIMARY KEY(prod_id)
) ENGINE=InnoDB;

######################
# Create vendors table
######################
CREATE TABLE vendors
(
  vend_id      int      NOT NULL AUTO_INCREMENT,
  vend_name    char(50) NOT NULL ,
  vend_address char(50) NULL ,
  vend_city    char(50) NULL ,
  vend_state   char(5)  NULL ,
  vend_zip     char(10) NULL ,
  vend_country char(50) NULL ,
  PRIMARY KEY (vend_id)
) ENGINE=InnoDB;

###########################
# Create productnotes table
###########################
CREATE TABLE productnotes
(
  note_id    int           NOT NULL AUTO_INCREMENT,
  prod_id    char(10)      NOT NULL,
  note_date datetime       NOT NULL,
  note_text  text          NULL ,
  PRIMARY KEY(note_id),
  FULLTEXT(note_text)
) ENGINE=MyISAM;


#####################
# Define foreign keys
#####################
ALTER TABLE orderitems ADD CONSTRAINT fk_orderitems_orders FOREIGN KEY (order_num) REFERENCES orders (order_num);
ALTER TABLE orderitems ADD CONSTRAINT fk_orderitems_products FOREIGN KEY (prod_id) REFERENCES products (prod_id);
ALTER TABLE orders ADD CONSTRAINT fk_orders_customers FOREIGN KEY (cust_id) REFERENCES customers (cust_id);
ALTER TABLE products ADD CONSTRAINT fk_products_vendors FOREIGN KEY (vend_id) REFERENCES vendors (vend_id);

數據語句

########################################
# MySQL Crash Course MYSQL必知必會數據語句
# http://www.forta.com/books/0672327120/
# 提供者博客園:后青春期的Keats 複製請註明出處
########################################


##########################
# Populate customers table
##########################
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES(10001, 'Coyote Inc.', '200 Maple Lane', 'Detroit', 'MI', '44444', 'USA', 'Y Lee', 'ylee@coyote.com');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
VALUES(10002, 'Mouse House', '333 Fromage Lane', 'Columbus', 'OH', '43333', 'USA', 'Jerry Mouse');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES(10003, 'Wascals', '1 Sunny Place', 'Muncie', 'IN', '42222', 'USA', 'Jim Jones', 'rabbit@wascally.com');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES(10004, 'Yosemite Place', '829 Riverside Drive', 'Phoenix', 'AZ', '88888', 'USA', 'Y Sam', 'sam@yosemite.com');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
VALUES(10005, 'E Fudd', '4545 53rd Street', 'Chicago', 'IL', '54545', 'USA', 'E Fudd');


########################
# Populate vendors table
########################
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1001,'Anvils R Us','123 Main Street','Southfield','MI','48075', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1002,'LT Supplies','500 Park Street','Anytown','OH','44333', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1003,'ACME','555 High Street','Los Angeles','CA','90046', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1004,'Furball Inc.','1000 5th Avenue','New York','NY','11111', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1005,'Jet Set','42 Galaxy Road','London', NULL,'N16 6PS', 'England');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1006,'Jouets Et Ours','1 Rue Amusement','Paris', NULL,'45678', 'France');


#########################
# Populate products table
#########################
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('ANV01', 1001, '.5 ton anvil', 5.99, '.5 ton anvil, black, complete with handy hook');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('ANV02', 1001, '1 ton anvil', 9.99, '1 ton anvil, black, complete with handy hook and carrying case');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('ANV03', 1001, '2 ton anvil', 14.99, '2 ton anvil, black, complete with handy hook and carrying case');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('OL1', 1002, 'Oil can', 8.99, 'Oil can, red');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('FU1', 1002, 'Fuses', 3.42, '1 dozen, extra long');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('SLING', 1003, 'Sling', 4.49, 'Sling, one size fits all');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('TNT1', 1003, 'TNT (1 stick)', 2.50, 'TNT, red, single stick');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('TNT2', 1003, 'TNT (5 sticks)', 10, 'TNT, red, pack of 10 sticks');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('FB', 1003, 'Bird seed', 10, 'Large bag (suitable for road runners)');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('FC', 1003, 'Carrots', 2.50, 'Carrots (rabbit hunting season only)');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('SAFE', 1003, 'Safe', 50, 'Safe with combination lock');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('DTNTR', 1003, 'Detonator', 13, 'Detonator (plunger powered), fuses not included');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('JP1000', 1005, 'JetPack 1000', 35, 'JetPack 1000, intended for single use');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('JP2000', 1005, 'JetPack 2000', 55, 'JetPack 2000, multi-use');



#######################
# Populate orders table
#######################
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20005, '2005-09-01', 10001);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20006, '2005-09-12', 10003);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20007, '2005-09-30', 10004);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20008, '2005-10-03', 10005);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20009, '2005-10-08', 10001);


###########################
# Populate orderitems table
###########################
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 1, 'ANV01', 10, 5.99);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 2, 'ANV02', 3, 9.99);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 3, 'TNT2', 5, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 4, 'FB', 1, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20006, 1, 'JP2000', 1, 55);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20007, 1, 'TNT2', 100, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20008, 1, 'FC', 50, 2.50);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 1, 'FB', 1, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 2, 'OL1', 1, 8.99);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 3, 'SLING', 1, 4.49);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 4, 'ANV03', 1, 14.99);

#############################
# Populate productnotes table
#############################
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(101, 'TNT2', '2005-08-17',
'Customer complaint:
Sticks not individually wrapped, too easy to mistakenly detonate all at once.
Recommend individual wrapping.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(102, 'OL1', '2005-08-18',
'Can shipped full, refills not available.
Need to order new can if refill needed.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(103, 'SAFE', '2005-08-18',
'Safe is combination locked, combination not provided with safe.
This is rarely a problem as safes are typically blown up or dropped by customers.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(104, 'FC', '2005-08-19',
'Quantity varies, sold by the sack load.
All guaranteed to be bright and orange, and suitable for use as rabbit bait.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(105, 'TNT2', '2005-08-20',
'Included fuses are short and have been known to detonate too quickly for some customers.
Longer fuses are available (item FU1) and should be recommended.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(106, 'TNT2', '2005-08-22',
'Matches not included, recommend purchase of matches or detonator (item DTNTR).'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(107, 'SAFE', '2005-08-23',
'Please note that no returns will be accepted if safe opened using explosives.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(108, 'ANV01', '2005-08-25',
'Multiple customer returns, anvils failing to drop fast enough or falling backwards on purchaser. Recommend that customer considers using heavier anvils.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(109, 'ANV03', '2005-09-01',
'Item is extremely heavy. Designed for dropping, not recommended for use with slings, ropes, pulleys, or tightropes.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(110, 'FC', '2005-09-01',
'Customer complaint: rabbit has been able to detect trap, food apparently less effective now.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(111, 'SLING', '2005-09-02',
'Shipped unassembled, requires common tools (including oversized hammer).'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(112, 'SAFE', '2005-09-02',
'Customer complaint:
Circular hole in safe floor can apparently be easily cut with handsaw.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(113, 'ANV01', '2005-09-05',
'Customer complaint:
Not heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(114, 'SAFE', '2005-09-07',
'Call from individual trapped in safe plummeting to the ground, suggests an escape hatch be added.
Comment forwarded to vendor.'
);

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

Springboot 自動配置淺析

Introduction

我們知道,SpringBoot之所以強大,就是因為他提供了各種默認的配置,可以讓我們在集成各個組件的時候從各種各樣的配置文件中解放出來。

拿一個最普通的 web 項目舉例。我們需要使用 servlet 容器,SpringBoot 就提供了嵌入式的 Tomcat 作為默認容器,不需要一行配置就能直接以最普通的 Java 程序的方式啟動:java -jar;接收請求需要一個網絡端口,默認配置好8080;處理請求需要 servlet 的多線程特性,默認配置好了最大線程數為200;處理好的請求以Restful 風格返回給調用方,SpringBoot 默認配置好了jackson進行 json 序列化,業務代碼需要做的只是返回一個 POJO 對象;連接池直接就默認配置了性能最好的的 Hikari,以及連接池的默認尺寸為10……在一個簡單的web應用中,這些配置我們甚至都可能不了解或者沒有意識到它們的存在,就可以讓程序正常運行。

這就是自動配置的魔力——潤物細無聲。

那麼自動配置是怎麼實現的呢?本文就從POM文件和 @SpringBootApplication 註解來簡單分析一下自動配置原理。

真的只是簡單地,分析一下。

POM文件

環境

  • SpringBoot 2.0.6

父項目

在每一個 SpringBoot 項目一開始的 POM 文件中,就有一個父依賴

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.0.6.RELEASE</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>

點進artifactId之後來到 spring-boot-starter-parent-2.0.6.RELEASE.pom 文件,這個文件還有一個父依賴:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.0.6.RELEASE</version>
  <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

再繼續點進去來到 spring-boot-dependencies-2.0.6.RELEASE.pom 文件中。在該文件的 <properties> 標籤中可以看到各種各樣的組件的版本信息, <dependencyManagement> 標籤中聲明了各個組件的maven坐標信息。其中,我數了一下,在 SpringBoot 2.0.6 中一個有177個帶有版本信息的組件名,包括了大家熟悉的 elasticsearch、activemq、redis、kafka等等。

這裏(spring-boot-dependencies-2.0.6.RELEASE.pom文件)稱為 SpringBoot 的“版本仲裁中心”,它是用來管理 SpringBoot 應用裏面的所有依賴版本的地方。 它包含了大部分開發中會用到的一些組件的版本,所以我們導入依賴默認是不需要寫版本號的,SpringBoot 會自動幫我們配置需要的版本。這樣在引入某個組件的時候不用考慮這個組件和系統中已有的組件兼不兼容之類的問題,避免了煩人的版本衝突問題。(當然,沒有在 dependencies裏面管理的依賴自然需要聲明版本號)

啟動器(Starters)

父項目做版本仲裁,那麼真正的 jar 包是從哪裡導入進來的呢?這就要說到我們的starters了。點開web-starter可以看到它幫我們導入了web模塊正常運行所依賴的組件,就不用我們一個一個手動導入了。

所以,所謂的Starters就是一系列依賴描述的組合,我們可以通過導入這些starters就會有相應的依賴了並可以基於他們進行相應的開發

Springboot把開發中中會遇到的場景都抽象成一個個的starter,比如(),只需要在項目裏面引入這些starter 相關場景的所有依賴都會導入進來。要用什麼功能就導入什麼場景的啟動器

@SpringBootApplication

一個典型的 springboot 項目的入口類如下所示:

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

只要運行main方法就能啟動該應用。main 中運行 run 方法時需要傳入一個類,這個類正是使用@SpringBootApplication註解標註的類,如果沒有這個註解程序是跑不起來的。

這個註解標註在某個類上就說明這個類是 springboot 的主配置類,springboot 就應該運行這個類的 main 方法來啟動springboot應用

點開這個註解的源碼,可以看到這是一個組合註解,最主要的是這兩個註解:

  • @SpringBootConfiguration

  • @EnableAutoConfiguration

@SpringBootConfiguration標註在某個類上,表示這是一個 SpringBoot 的配置類,它的底層是@Configuration,屬於spring的底層註解,標註了這個註解的類表名這個類是一個配置類,取代以前開發中的xml文件配置,同時也表明這個類是 spring 容器中的組件,受容器管理。

接下來是@EnableAutoConfiguration註解,它的用處是開啟自動配置,使用 spring 開發需要手動配置的東西,現在由 springboot 幫我們配置了,具體的實現就是通過這個註解來實現的。該組合註解有兩個。

1. @AutoConfigurationPackage

它的 註解中的核心代碼是 @Import({Registrar.class})

@Import 的作用就是為容器中導入一個它指定的組件。

Registrar 這個類中有一個方法叫registerBeanDefinitions,用於註冊一些 bean 定義信息:

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
     AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
}

打個斷點在這一行代碼上,運行

(new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()

得到的結果是主配置類所在的包名,目的就是將主配置類所在包及下面所有子包裏面的所有組件掃描到Spring容器中。同時也說明了,如果在主配置所在包的上層包添加組件的話是不會被掃描到的、不起作用的。

2. @Import({AutoConfigurationImportSelector.class})

該註解導入了一個自動配置的選擇器,真正地給容器中導入 springboot 自動幫我們配置好的配置類。在 AutoConfigurationImportSelector 這個選擇器中的 getAutoConfigurationEntry 方法中,以全限定類名的方式把所有需要的配置類導入 springboot 容器中。這些全限定類型所在文件路徑為:

org/springframework/boot/spring-boot-autoconfigure/2.1.8.RELEASE/spring-boot-autoconfigure-2.1.8.RELEASE.jar!/META-INF/spring.factories

上述兩個註解一個負責掃描我們將要加容器中的類,一個加入 springboot 為我們自動配置好的類,springboot 通過這兩種方式省略了我們大量的配置工作。

總結

本文從用於maven項目管理的pom.xml文件和標註在啟動類上的@SpringBootApplication註解,簡單分析了springboot 自動配置的實現。前者通過版本仲裁中心為我們維護項目組件的版本,防止依賴衝突;後者通過在加載程序的時候導入數以百計的自動配置類實現自動配置。

原文發表於:https://pengcheng.site/2019/10/28/springboot-zi-dong-pei-zhi-qian-xi/

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

【其他文章推薦】

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

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

分類
發燒車訊

比亞迪計畫在廣州增城設廠 組裝K9電動大巴

日前,有媒體報導稱,比亞迪高層與廣州市長洽談在廣州增城投資建廠事宜,屆時增城將雲集廣汽本田、北汽和比亞迪三大汽車品牌,成為廣州汽車產業最為集中的區域。

比亞迪向媒體確認,其總裁王傳福及高級副總裁吳經勝此前一段時間的確曾赴廣州,與廣州市市長陳建華洽談在廣州投資建廠事宜。此項目與比亞迪在陝西、雲南等地的模式類似,依舊是為其公交電動化做準備。目前比亞迪仍在積極籌備還有天津、昆明、武漢等地的建廠項目,也是其在新能源推廣的地方保護背景下的無奈之舉。

比亞迪的電動大巴在國內具有較強的優勢,目前已經出口到荷蘭、巴西、美國等地,目前在國內由於地方保護,只有在當地合資設廠,是打入當地市場的一個有效市場。而電動大巴的組裝線投資不大,也是比亞迪較能接受的方式。

為了推動電動車的發佈,比亞迪率先推出了“零元購車、零成本、零排放”城市公交電動化解決方案,為加速公交電動化進程開闢一條現實可行的道路。在深圳、西安、寶雞、韶關、荷蘭、新加坡、美國、丹麥、德國、英國倫敦等地成功實現電動車的規模化、商業化運營。

比亞迪總部所在廣州運行的電動公交大巴超過千輛,成為電動大巴運行最多的城市。而百公里之外的廣州,目前擁有超過1萬輛的公交大巴,其中新能源車為2000輛,少數為電動大巴,主要廣汽客車品牌;計程車約有3萬輛,電動車計程車幾乎為零,這給比亞迪很大的期望值。

比亞迪也在做廣州的工作。在2012廣州國際馬拉松賽上,作為本屆馬拉松比賽獨家汽車贊助商,比亞迪旗下的城市多功能SUV車型S6、T動力智慧新典範G6以及純電動車e6成為賽事的指定用車。

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

【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

廣西建成國首條無線供電車道 電動汽車可邊行駛邊充電

1月19日,廣西電網公司“面向智慧電網的無線電能傳輸關鍵技術研究”項目通過驗收,宣告國內首條為電動汽車無線供電的車道建成,車輛在其上行駛,可即時、無線獲得電能,目前已投入使用。

據瞭解,該專案是南方電網於2012年下達的科技項目,由廣西電網有限責任公司電力科學研究院負責實施,項目成功建設了國內首條電動汽車無線供電小型試驗車道,研製出國內首套輸配電線路監測終端無線供電系統。相關專家介紹,該專案攻克了電動汽車無線充電技術、無線供電技術和電能轉換3個技術難點。基於無線電能傳輸技術,電動汽車在行駛過程中,可通過車道直接給電動汽車電機供電,不需要車載電池;也可以同時為車載電池充電。目前,該項目已申請發明專利9件(均已實審,其中授權7件),授權實用新型專利20件。

目前,該車道提供的最大功率為30千瓦,以總電池容量為24千瓦時的電動汽車為例,如果以30千瓦的功率進行充電,充滿電所需時間不足1小時,續航里程100公里。推廣使用後,該車道可以根據需求安裝任意長度,甚至可以取代車載電池,有效解決目前電動汽車電池重、電池續航力低等難題,尤其適用於固定線路電動車輛如電動公交、景區迴圈車等。

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

【其他文章推薦】

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

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

分類
發燒車訊

Jetpack Compse 實戰 —— 全新的開發體驗

公眾號回復 Compose 獲取安裝包

項目地址:

經過前段時間的 Android Dev Summit ,相信你已經大概了解了 Jetpack Compose 。如果你還沒有聽說過,可以閱讀這篇文章 。總而言之,Compose 是一個 顛覆性聲明式 UI 框架 ,它的口號就是 消滅 xml 文件 !

儘管 Jetpack Compose 還只是預覽版,API 可能發生變化,缺乏足夠的控件支持,甚至不是那麼穩定,但這阻止不了我這顆好奇的心。我在第一時間就上手擼了一款 Compose 版本 Wanandroid 應用,功能也比較簡單,僅僅包括首頁,廣告和最新項目,類似於 Android 原生頁面的 Viewpager + TabLayout 。下面的 gif 展示了應用的基本頁面:

可以看出來頁面並不是那麼流暢,View 的復用應該是個問題,甚至我也沒發現應該怎麼做下拉刷新。那麼,Compose 給我們帶來了什麼呢?在解答這個問題之前,我想先來說說 Android 應用架構問題。

荒蕪年代 —— MVC

在我剛入行的時候,可以說是 Android 開發的黃金時代,也可以說是開發者的荒蕪時代。一方面,毫不誇張的說,基本會寫 xml 都能謀得一份工作。另一方面,對於開發者來說,遠遠沒有現在的規範的開發架構。沒記錯的話,我當年的主力開發框架是 xUtils 2 ,一個類庫大包干,從 布局和 ID 綁定,網絡請求,到圖片展示,ORM 操作,一應俱全。當時的 布局和 ID 綁定還是運行時反射,而不是編譯期註解。很長一段時間以來,Android 連一個官方的網絡庫都沒有。

在架構方面,很多人都是一個 Activity 擼到死,我真的見過上千行zouguolai的 MainActivity 。並且覺得這就是 MVC 架構,實體類 Entity 就是 Model 層,Activity/Fragment 就是 Controller 層,布局文件就是 View 層。

但這當真是 MVC 嗎?其實並不是。不管是 MVCMVP,還是 MVVM,都應該遵循一個最起碼的原則,表現層和業務層分離 ,也就是 Android 官網給出的 中強調的 分離關注點Activity/Fragment 既要承擔視圖層的任務,展示和更新 UI,又要處理業務層邏輯,獲取數據等。這並不符合架構設計的基本原則。

正確的 MVC 模式中,Model 層不僅包含實體類 Entity,更重要的作用是處理業務邏輯。View 層負責處理視圖邏輯。而 Controller 就是 Model 和 View 之間的橋樑。橋怎麼建,其實並沒有標準,根據你自己的需求就可以了。

引用一張阮一峰老師的圖,大致是這麼個意思,但是也不一定就完全都是單向依賴。

從荒蕪時代走過來,MVC 總算有點分層的味道在裏面了,分離了視圖層和業務層。但是 View 層和 Model 層的依賴關係,造成代碼耦合,終將導致 Activity 日益臃腫。那麼有沒有辦法將 View 層和 Model 層徹底分離,做到視圖層和模型層完全分離呢? MVP 就應運而生了。

青銅年代 —— MVP

依舊是阮一峰老師的圖片:

相較於 MVC ,MVP 用 Presenter 層 代替了 Controller 層 ,且 View 層Model 層 完全分離,依靠 Presenter 進行通信 。

想象一個獲取用戶信息的場景。IView 接口中定義了一系列視圖層接口 ,View 層(Activity)實現 IView 接口中相應視圖邏輯。 View 層通過持有的 Presenter 處理業務邏輯,即請求用戶信息。一般情況下,Presenter 也不直接處理業務邏輯,而是通過 Model 層,例如數據倉庫 Repository, 來獲取數據,避免 Presenter 重蹈覆轍,日漸臃腫。同時,Presenter 層也是持有 VIew 的,獲取用戶信息之後再轉發給 View 。

總結一下,MVP 中 View 和 Model 完全解耦,通過 Presenter 通信。View 和 Presenter 共同處理視圖層邏輯,Model 層負責業務邏輯。

在 Github 上 Android 官方的架構示例 中 MVP 作為主分支堅挺了很久。我最初也是根據這個官方示例改造了自己的 MVP 架構,並且使用了很長時間。但是 MVP 作為一款面向接口編程的架構,隨着業務的複雜程度不斷加大,有種遍地都是接口的既視感,實在顯得有點繁瑣。

另外一點,Presenter 的職責邊界不夠清晰,它除了承擔調用 Model 層獲取業務邏輯之外,還要控制 View 層處理 UI。用下面一段代碼錶示一下:

class LoginPresenter(private val mView: LoginContract.View) : LoginContract.Presenter {

    ......

    override fun login(userName: String, passWord: String) {
        CoroutineScope(Dispatchers.Main).launch {
            val result = WanRetrofitClient.service.login(userName, passWord).await()
            with(result) {
                if (errorCode == -1)  mView.loginError(errorMsg) else mView.login(data)
            }
        }
    }
}

一旦 View 層發生任何變化,Presenter 層也要做出相應改動。雖然 View 和 Model 之間解耦了,但是 View 和 Presenter 卻耦合了。理想情況下,Presenter 層應該僅負責數據的獲取,View 層自動觀察數據的變化。於是,MVVM 來了。

黃金時代 —— MVVM

Google 官圖鎮樓 。

MVP 風光早已不在, Android 官方的架構示例 的主分支已經切換到 MVVM 。在 Android 的 MVVM 架構中,ViewModel 是重中之重,它一方面通過數據倉庫 Repository 獲取數據,另一方面根據獲取的數據更新 View 層的 Activity/Fragment。等等,這句話怎麼聽着這麼耳熟,Presenter 不也是幹了這些事嗎?的確,它們乾的事情都差不多,但是實現上完全不一樣。

以我的開源項目 中的 LoginViewModel 為例:

class LoginViewModel(val repository: LoginRepository) : BaseViewModel() {

    private val _uiState = MutableLiveData<LoginUiModel>()
    val uiState: LiveData<LoginUiModel>
        get() = _uiState


    fun loginDataChanged(userName: String, passWord: String) {
        emitUiState(enableLoginButton = isInputValid(userName, passWord))
    }

    // ViewModel 只處理視圖邏輯,數據倉庫 Repository 負責業務邏輯
    fun login(userName: String, passWord: String) {
        viewModelScope.launch(Dispatchers.Default) {
            if (userName.isBlank() || passWord.isBlank()) return@launch

            withContext(Dispatchers.Main) { showLoading() }

            val result = repository.login(userName, passWord)

            withContext(Dispatchers.Main) {
                if (result is Result.Success) {
                    emitUiState(showSuccess = result.data,enableLoginButton = true)
                } else if (result is Result.Error) {
                    emitUiState(showError = result.exception.message,enableLoginButton = true)
                }
            }
        }
    }

    private fun showLoading() {
        emitUiState(true)
    }

    private fun emitUiState(
            showProgress: Boolean = false,
            showError: String? = null,
            showSuccess: User? = null,
            enableLoginButton: Boolean = false,
            needLogin: Boolean = false
    ) {
        val uiModel = LoginUiModel(showProgress, showError, showSuccess, enableLoginButton,needLogin)
        _uiState.value = uiModel
    }

    data class LoginUiModel(
            val showProgress: Boolean,
            val showError: String?,
            val showSuccess: User?,
            val enableLoginButton: Boolean,
            val needLogin:Boolean
    )
}

可以看到,ViewModel 中是沒有 View 的引用的,View 通過可觀察的 LIveData 來觀察數據變化,基於觀察者模式做到和 ViewModel 完全解耦。

數據驅動視圖 ,這是 Jetpack MVVM 推崇的一個重要原則。其基本數據流如下所示 :

  • 數據層 Repository 負責從不同數據源獲取和整合數據,基本負責所有的業務邏輯
  • ViewModel 持有 Repository,獲取數據並驅動 View 層更新
  • View 持有 ViewModel,觀察 LiveData 攜帶的數據,數據驅動 UI

曾經和一些開發者討論過這樣一個問題,** 不使用 DataBinding 還算是 MVVM 嗎 ?** 我認為 MVVM 的核心從來不在於 DataBinding 。DataBinding 只是可以幫助我們將 數據驅動視圖 做到極致,順便還可以雙向綁定。

要說到對 Jetpack MVVM 中最不滿意的一塊,那非 DataBinding 莫屬了。在我狹隘的認為 DataBinding 就是一個在 xml 裏面寫邏輯代碼的反人類的庫時,我是堅決反對在任何項目中引入它的。固執己見的時候就容易走進誤區,在閱讀 KunminX 的 之後,正如這篇文章名字一樣,真香。

香的確是香,一切能讓我早下班的都是好東西。在我的某次提交日誌上,我寫下了 消滅 Adapter 幾個字,那時我剛用 DataBinding 消滅了大部分 RecyclerView 的 Adapter 。可是在提交之後,我的良心惴惴不安,我追究還是在 xml 文件里寫邏輯代碼了,難道這真的不反人類嗎?

未來可期 —— Jetpack Compose

現在你應該可以理解我對 Jetpack Compose 的執念了。拋去其他特性,在我看來,它完美的解決了 數據驅動視圖 的問題,我再也不需要使用 DataBinding 了。

簡單代碼展示一下 Compose 的用法。下面的代碼描繪的是首頁 Tab 下的文章列表。

@Composable
fun MainTab(articleUiModel: ArticleViewModel.ArticleUiModel?) {

    VerticalScroller {
        FlexColumn {
            inflexible {
                HeightSpacer(height = 16.dp)
            }
            flexible(1f) {
                articleUiModel?.showSuccess?.datas?.forEach {
                    ArticleItem(article = it)
                }

                articleUiModel?.showError?.let { toast(App.CONTEXT, it) }
wenjian
                articleUiModel?.showLoading?.let { Progress() }
            }
        }
    }
}

這種寫法叫做 聲明式編程 ,會用 Flutter 的同學應該很熟悉。方法參數 ArticleUiModel 就是數據實體類,直接根據數據 ArticleUiModel 構建 UI 。說的大白話一點,就是給你長方形的長和寬了,讓你畫個長方形出來。最後加上 @Compose 註解,就是一個可用的 UI 組件了。仔細看代碼,裏面還用了兩個 UI 組件 ,ArticleItemProgress ,代碼就不貼出來了。分別是文章列表的 item 項目 和加載進度條。

那麼,數據如何更新呢?最簡單的方式是使用 @Model 註解。

@Model
data class ArticleUiModel(){
  ......
}

對,就是這麼簡單。@Model 註解會自動把你的數據類變成可觀察對象,只要 ArticleUIModel 發生變化,UI 就會自動更新。

但是我在實際開發中結合 LiveData 使用時,好像表現的不是那麼正常。後來在 Medium 上無意中看到了解決方案,針對 LiveData 做了特殊處理 :

// general purpose observe effect. this will likely be provided by LiveData. effect API for
// compose will also simplify soon.
fun <T> observe(data: LiveData<T>) = effectOf<T?> {
    val result = +state<T?> { data.value }
    val observer = +memo { Observer<T> { result.value = it } }

    +onCommit(data) {
        data.observeForever(observer)
        onDispose { data.removeObserver(observer) }
    }

    result.value
}wenjian

在 Activity/Fragment 中觀測 LiveData 即可:

class MainActivity : BaseVMActivity<ArticleViewModel>() {

    override fun initView() {
        setContent {
           +observe(mViewModel.uiState)
            WanandroidApp(mViewModel)
        }
    }

    override fun initData() {
       mViewModel.getHomeArticleList()
    }
}

這樣 View 層就可以自動觀察 LiveData 所包含的值了。

沒有 xml,沒有 DataBinding,一切看起來稱心如意多了。但就是 UI 體驗有那麼一點糟心,你可以在公眾號後台回復 Compose 安裝體驗一下。由於還是早期的預覽版,這也是可以理解的。我相信,等到發布 Release 版本的時候,一定足以完全代替原聲的 View 體系。

本文並沒有詳細介紹 Jetpack Compose 的詳細使用過程和其他特性,更多信息我推薦下面兩篇文章:

最後

正如 Android 官網 Jetpack 介紹頁所說,Jetpack 可以幫助開發者更輕鬆的編寫優質應用。的確,隨着應用架構的規範,我們只需要把精力放在需要的代碼上,加速開發,消除樣板代碼,減少崩潰和內存泄露,構建高質量的強大應用。我想不出來有任何理由不使用 Jetpack 來構建你的應用。而 Compose 必將稱為 Jetpack 中極其重要的一塊拼圖。

Jetpack Compse ,未來可期 !

添加我的微信,加入技術交流群。

公眾號後台回復 “compose”, 獲取最新安裝包。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

養猛獸當寵物歪風 巴政客名流炫耀引效尤

摘錄自2020年2月10日公視報導

在巴基斯坦,擁有野生猛獸被視為身分地位的象徵。政客名流習慣炫耀自己豢養的獅子老虎來凸顯優越感。近年來一般百姓也掀起這股把大貓當寵物的風潮,通常把牠們養在屋頂鐵籠子裡,不但對野生動物是種折磨,也違反法規。

在當地購買幼獅的價格,一隻折合台幣超過13萬,漢姆斯跟朋友砸下所有積蓄合買,就養在頂樓,當成貓狗一樣的逗弄寵愛。平常房屋管理員負責照顧這些獅子,雖然看起來很鎮定,面對猛獸還是提防三分。

追根究底,野生動物根本就不該非法進口拿來當寵物,但相關法規約束力薄弱,公權力對亂象也都視而不見。這些野生獅子老虎,多數來自非洲或西伯利亞,都是持有原產國的證明許口而進口。顯示不肖業者早有門路管道規避灰暗不明的法規限制,還大剌剌宣稱只要48小時,就可以把動物送到全國各地的買家手上。

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

【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

破紀錄降雨釀洪災 巴西交通癱瘓車輛淹沒

摘錄自2020年2月11日公視報導

巴西聖保羅豪雨成災,創下37年來二月份雨量最高紀錄。大雨造成交通大癱瘓,有車輛被淹沒,導致駕駛受困車頂。

10號這一天,聖保羅的直升機、橡皮艇、竹筏等的救援任務,極為辛苦繁忙。有人見到空中吊籃離去,也只能繼續撐傘,坐車頂待救。官方宣布43所學校停課,公車僅有部分繼續營運,市區地鐵亦取消部分班次。

聖保羅全市交通幾乎癱瘓,3小時內降下100毫米的雨量,兩條主要河流「泰特河」跟「平赫洛河」的河水溢出堤外。泰特河的水位創下15年來新高,強大水勢往低窪地區流動,街道全被淹沒,連噸位較大的大卡車都成了泡水車。國家氣象研究所指出,這場降雨已經寫下37年來二月份最多雨量。

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

【其他文章推薦】

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

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

分類
發燒車訊

武漢肺炎源頭 WHO:與菊頭蝠有關聯

摘錄自2020年2月13日公視報導

世界衛生組織WHO發表的疫情報告,指出越來越多證據顯示,武漢肺炎的病毒跟蝙蝠亞種菊頭蝠有關。另外,中國專家近來表示疫情可能在4月結束,WHO也反駁現在預測還太早。

WHO新報告指出,武漢肺炎病毒來自蝙蝠的可能性越來越高。WHO食安與人畜共同傳染病部門彼得表示:「過去我們在蝙蝠族群中發現非常相似的病毒,所以我們認為病毒一開始也是來自蝙蝠。」

WHO表示,武漢肺炎的新型冠狀病毒,跟蝙蝠亞種菊頭蝠有關,牠們常見於中國南部,廣泛分布在亞洲、非洲、歐洲。不過病毒的傳播途徑仍不清楚,可能是某種中間宿主動物,傳染給人類。

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

【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

美國全市5大區 免費發放環保袋

摘錄自2020年02月15日世界新聞網美國報導

因應3月1日將生效的禁塑令,代表曼哈頓華埠、多年來一直積極推動禁塑令的市議員陳倩雯,15日在曼哈頓集市公園(Washington Market Park),向民眾免費發放可重複使用的環保袋;此外,民眾還可通過填寫網路表格或參加社區活動,免費領取市清潔局(DSNY)的環保袋。

當日發放的環保袋包含90%的可回收材料,袋面為鮮豔橙色,印有「零垃圾填埋」(Zero Waste to Landfills)的大字標語,環保袋可多次摺疊,並由附贈的登山扣扣起,更易隨身攜帶出行;民眾可通過參加市清潔局之後將在全是五區舉辦的各類社區活動,或填寫「零浪費承諾」(Take the Zero Waste Pledge)表格,免費獲得可重複使用的購物袋。

對於華裔耆老習慣使用塑料袋並將塑料袋作為垃圾袋,陳倩雯則表示,耆老們仍可攜帶塑料袋前往超市購物,超市散裝水果的連卷塑料袋,未在禁塑範圍,耆老也可二次使用這些塑袋,裝載輕型垃圾。

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

【其他文章推薦】

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

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!