台北萬豪酒店首設Tesla充電站,即日啟用

美商特斯拉(Tesla)於今年9月正式進軍台灣,在台引發電動車熱。台北萬豪酒店宣布與Tesla合作設置充電站,同時推出限時活動,鼓勵民眾響應環保行動。

繼在台灣科技大學校區內設置充電站後,Tesla正式宣布與台北萬豪酒店合作,於飯店內設置6組充電站,成為全台首家提供Tesla電動車充電服務的飯店。車主只要於飯店內消費,就可免費享有充電服務。

台北萬豪酒店主管表示,萬豪國際的核心理念是「擁抱改變」,不但要跟著時代進步,還要勇於創新,與Tesla的形象相輔相成。藉由與Tesla合作設置電動車充電站,希望能提供客戶更便捷的服務,並推動現代科技。

萬豪酒店同時也推出相關活動,凡是11月30日前佩戴紅色配件到萬豪酒店消費的顧客,就可享有9折優惠。

Tesla主管表示,Tesla進入台灣市場後,將提供銷售、服務、充電等全方位服務,未來將繼續加速布建「目的地充電站」的腳步,以為台灣的Tesla車主提供更綿密的充電網絡。

除了萬豪酒店之外,台北晶華酒店也與BMW合作設置了i公共充電站,可供i3、i8等車款使用。

(照片來源:)

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

聚甘新

BMW 2017年目標售出10萬輛電動車

德國媒體報導,汽車廠商BMW CEO Harald Krueger 在接受採訪時表示,電動車的時代即將到來,這一市場還有巨大的潛力,BMW 將在2017 年力爭把電動車的銷量提升到10 萬台,目前該公司2015 年電動車和混合動力車的銷量大約為6 萬台。

BMW CEO Harald Krueger 表示,該公司希望能夠在2017 年大幅提升電動車的銷量,至少提升六成,達到年銷量10 萬台。BMW 2016 年電動車和混合動力車的銷量合計大約為6 萬台,自2013 年發售電動車以來,BMW 電動車的雷軍銷量已經超過了10 萬台。

Harald Krueger 認為電動車的時代即將到來,這一市場還有很大的潛力,需求還沒有完全被發掘。

BMW 旗下唯一的純電動車車款i3 上市以來市場反響不如預期,2015 年i3 僅售出了2.5 萬台,為了提升這款產品的競爭力,BMW 將在2016 年的升級中把i3 系列電動車的續航里程提升50%。

在高階汽車市場中,BMW 的產品銷量已經落後於Benz,該公司希望在2025 年前將電動車和混合動力車的銷量提升到15%-25%。

(本文由《》授權提供。照片來源:)

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

聚甘新

電動車有害道路安全?美國規定汽車不能太安靜!

根據外電報導,美國政府正式通過一則汽車行駛新規定,也就是未來一些「安靜的汽車」,如電動汽車或混合動力汽車等,在行駛速度超過時速18.6 英里 (約30 公里)的情況下,必須增加噪音警報裝置,以避免傷及行人、盲人和騎車的路人。

根據美國國家公路交通安全管理局(NHTSA)發布的這則新規定表示,該規定將在2019 年9 月1 日起生效。屆時,包括特斯拉 (TESLA)、日產汽車 (Nisson) 和豐田汽車 (TOYOTA) 等廠商都需要為所有汽車增加噪音裝置。美國政府希望通過此規定,能夠防止每年多達2,400 件的行人受傷事故,並為53 萬輛汽車增設噪音專制。

NHTSA 指出,這項新規每年約會給汽車廠商增加3,900 萬美元的花費。因為,他們要為汽車增設一個外設防水喇叭。但是,能夠透過降低交通事故而每年節省約2.5 億到3.2 億美元的醫療費用。

該規定未來將適用於全美各地區,並且涵蓋路上行駛的電動汽車和混合動力汽車等。另外,包括帶有4 個輪子、總重量低於10,000 磅的車輛,也都必須在向後或向前行駛時,在速度達到每小時30 公里之際發出聲響,以警告周遭的路人與騎車的民眾。不過,NHTSA 也認為,規定汽車喇叭可有可無,因為輪胎和風也可以製造出足夠的噪音聲響,只要該聲響能達到警告的標準即可。

該項新的規定雖然受到全美盲人主組織的歡迎,但是卻讓汽車廠商表示擔憂。因為噪音也許會太大,並且實施起來將可能相當複雜。因為,新規只規定了最低的聲音要求,卻沒有制定必須發出什麼聲音。

(本文由《》授權提供。照片來源:特斯拉)

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

FB行銷專家,教你從零開始的技巧

聚甘新

優秀源碼帶給我們的一些啟示

首先是個人的一些閱讀源碼的小技巧,不一定適用每個人,可以直接跳過。

 

閱讀源碼的一些個人技巧

 

博客+總結

個人覺得大多數情況下跟着一篇優秀的博客配合著看就足夠了,之後再自己寫博客總結一遍加深印象,畫一下流程圖基本都能理順。(圖為學AQS時本人畫的獲取獨佔鎖流程圖)

 

類關係

配合idea看類之間的關係(ctrl+alt+shift+u)的功能也能更好的理解整個項目的整體架構。因為很多源碼其實並不是真的複雜,只是為了擴展性優雅簡潔等原因建立了大量的接口和抽象類以及各種設計模式,使得項目看起來很龐大很複雜,藉助這個功能有利於你排除掉一些你暫時不想去關心的設計邏輯。知道那個部分才是最核心的邏輯,專註於去看核心代碼。 

 

多看註釋

但是如果你看的博客裏面剛好缺少了一部分你想看的內容,而你又找不到資料,需要自己去看,又或者你想看的源碼一點點資料都找不到的情況下想去看源碼。

這個時候比較有作用的就是註釋,源碼中的註釋看不懂也沒關係,放到百度翻譯里基本也能理解大概的意思。仔細看完方法或類的註釋之後你就理解了接下來這個類大致是在做什麼,之後讀它的源碼會通順很多,有一些方法或類甚至在你看完註釋之後就已經能知道你想看的內容了,已經沒有需要繼續往下讀了。

不僅僅是類或方法的註釋文檔,方法中代碼的註釋也很重要,基本上稍微複雜一點點的代碼,甚至有時候加個鎖,作者都會認認真真的寫一行註釋解釋自己這麼做的原因。

 

適當囫圇吞棗

還有一點是適當忽略一些不重要的細節,這個主要看你想看什麼,一般我們看第一遍大多數只是想知道大致的流程是什麼樣的,所以可以適當忽略併發邏輯和一些方法里的內容(看一眼註釋先知道這個方法會做什麼的就夠了)。第一遍大致知道流程,第二第三遍再去深究細節和併發邏輯等。

 

善用debug

多用debug,很多時候源碼走的路線會和你想象中的有很大不同,你以為會進入這個if,其實他偷偷進了else。

  

短路機制

經常看到利用短路機制的代碼,這裏以 AbstractQueuedSynchronizer 的 acquire 方法為例子,只有 tryAcquire 獲取鎖失敗, !tryAcquire 返回 true 時才會執行後面進入阻塞隊列並掛起的方法,如果獲取鎖成功了,根據短路機制自然不會執行入隊方法。

// AbstractQueuedSynchronizer.acquire(int arg)
if (!tryAcquire(arg) &&
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
    selfInterrupt();
}

 

拆高低位

ReentrantReadWriteLock的這段代碼里將AQS的state一分為二給共享鎖和獨佔鎖使用,個人覺得這種設計非常巧妙:

// ReentrantReadWriteLock
abstract static class Sync extends AbstractQueuedSynchronizer {
    // 下面這塊說的就是將 state 一分為二,高 16 位用於共享模式,低16位用於獨佔模式
    static final int SHARED_SHIFT   = 16;
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
    // 取 c 的高 16 位值,代表讀鎖的獲取次數(包括重入)
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    // 取 c 的低 16 位值,代表寫鎖的重入次數,因為寫鎖是獨佔模式
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

 

 While(n– > 0)和while (–n >= 0)

忘記在哪裡看到的了,翻了一下瀏覽記錄應該是在Java AIO部分的源碼里,這種寫法感覺很簡潔就記下來了,不過可讀性似乎不太高,特別是第一種乍一看還以為是lambda表達式

意思等同於 for (int i = 0; i < n; i++)  ,但是 while(n– > 0) 和 while (–n >= 0) 這種寫法會直接改變n的值

 

xx = null

在很多jdk的源碼中我們都可以看到 xx = null // help GC 這樣的代碼,用來置空引用,幫助jvm完成gc。具體可以了解可達性算法。

這裏我們以LinkList為例子:

// LinkList 的方法
private E unlinkFirst(Node<E> f) {
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}

 

位移運算符

在很多地方都會使用位移來進行運算,平時寫算法題也一樣很多人都這麼使用,下面以 ArrayList 的 grow 方法為例子,這裏通過右移1位使 oldCapacity 變為原來的0.5倍,之後加上它本身得到  newCapacity 

// ArrayList.grow(int minCapacity)
private void grow(int minCapacity) {
    // . . . . . .
    int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity就是1.5倍的oldCapacity
    // . . . . . . 
}

 

以上是我目前的水平所能總結出來的,後續學到其他的會繼續更新,如果大家有什麼補充的請告訴我

 

最後慣例附一圖:(根本不存在想雇傭我的地方( ´_ゝ`).jpg

 

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

【其他文章推薦】

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

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

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

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

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

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

聚甘新

.Net Core微服務入門全紀錄(五)——Ocelot-API網關(下)

前言

上一篇【.Net Core微服務入門全紀錄(四)——Ocelot-API網關(上)】已經完成了Ocelot網關的基本搭建,實現了服務入口的統一。當然,這隻是API網關的一個最基本功能,它的進階功能還有很多很多。

服務發現

首先需要解決的就是服務發現的問題,服務發現的優點之前講過,就不說了。
上一篇中我們的服務地址都是寫在ocelot.json配置文件里,現在我們需要結合之前的Consul來實現服務發現。

  • 改造代碼:

首先NuGet安裝Ocelot.Provider.Consul

修改Startup.cs:

        public void ConfigureServices(IServiceCollection services)
        {
            //添加ocelot服務
            services.AddOcelot()
                .AddConsul();//添加consul支持
        }

修改ocelot.json配置:

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/products",
      "DownstreamScheme": "http",
      "UpstreamPathTemplate": "/products",
      "UpstreamHttpMethod": [ "Get" ],
      "ServiceName": "ProductService",
      "LoadBalancerOptions": {
        "Type": "RoundRobin"
      }
    },
    {
      "DownstreamPathTemplate": "/orders",
      "DownstreamScheme": "http",
      "UpstreamPathTemplate": "/orders",
      "UpstreamHttpMethod": [ "Get" ],
      "ServiceName": "OrderService",
      "LoadBalancerOptions": {
        "Type": "RoundRobin"
      }
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "http://localhost:9070",
    "ServiceDiscoveryProvider": {
      "Scheme": "http",
      "Host": "localhost",
      "Port": 8500,
      "Type": "Consul"
    }
  }
}

這個配置應該很好理解,就是把我們上次的DownstreamHostAndPorts節點去掉了,然後增加了ServiceDiscoveryProvider服務發現相關配置。
注意,Ocelot除了支持Consul服務發現以外,還有Eureka也可以,Eureka也是一個類似的註冊中心。

好了,代碼修改就差不多了,下面運行程序測試一下:

客戶端正常運行。

至此我們就實現了服務註冊與發現和api網關的基本功能。接下來就要提到:服務治理

服務治理

其實服務治理也沒有一個非常明確的定義。它的作用簡單來說,就是幫助我們更好的管理服務,提升服務的可用性。——緩存,限流,熔斷,鏈路追蹤 等等。。。都屬於常用的服務治理手段。
之前講的負載均衡,服務發現也可以算是服務治理。

  • 緩存:

在Ocelot中啟用緩存,需要NuGet安裝一下Ocelot.Cache.CacheManager

修改Startup.cs中的ConfigureServices()方法:

//添加ocelot服務
services.AddOcelot()
    //添加consul支持
    .AddConsul()
    //添加緩存
    .AddCacheManager(x =>
    {
        x.WithDictionaryHandle();
    });

修改ocelot.json配置文件:

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/products",
      "DownstreamScheme": "http",
      "UpstreamPathTemplate": "/products",
      "UpstreamHttpMethod": [ "Get" ],
      "ServiceName": "ProductService",
      "LoadBalancerOptions": {
        "Type": "RoundRobin"
      },
      "FileCacheOptions": {
        "TtlSeconds": 5,
        "Region": "regionname"
      }
    },
    {
      "DownstreamPathTemplate": "/orders",
      "DownstreamScheme": "http",
      "UpstreamPathTemplate": "/orders",
      "UpstreamHttpMethod": [ "Get" ],
      "ServiceName": "OrderService",
      "LoadBalancerOptions": {
        "Type": "RoundRobin"
      },
      "FileCacheOptions": {
        "TtlSeconds": 5,
        "Region": "regionname"
      }
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "http://localhost:9070",
    "ServiceDiscoveryProvider": {
      "Scheme": "http",
      "Host": "localhost",
      "Port": 8500,
      "Type": "Consul"
    }
  }
}

在Routes路由配置中增加了FileCacheOptions。TtlSeconds代表緩存的過期時間,Region代表緩衝區名稱,這個我們目前用不到。

好了,代碼修改完需要編譯重啟一下網關項目,然後打開客戶端網站測試一下:

可以看到,5秒之內的請求都是同樣的緩存數據。Ocelot也支持自定義緩存。

  • 限流:

限流就是限制客戶端一定時間內的請求次數。
繼續修改配置:

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/products",
      "DownstreamScheme": "http",
      "UpstreamPathTemplate": "/products",
      "UpstreamHttpMethod": [ "Get" ],
      "ServiceName": "ProductService",
      "LoadBalancerOptions": {
        "Type": "RoundRobin"
      },
      "FileCacheOptions": {
        "TtlSeconds": 5,
        "Region": "regionname"
      },
      "RateLimitOptions": {
        "ClientWhitelist": [ "SuperClient" ],
        "EnableRateLimiting": true,
        "Period": "5s",
        "PeriodTimespan": 2,
        "Limit": 1
      }
    },
    {
      "DownstreamPathTemplate": "/orders",
      "DownstreamScheme": "http",
      "UpstreamPathTemplate": "/orders",
      "UpstreamHttpMethod": [ "Get" ],
      "ServiceName": "OrderService",
      "LoadBalancerOptions": {
        "Type": "RoundRobin"
      },
      "FileCacheOptions": {
        "TtlSeconds": 5,
        "Region": "regionname"
      },
      "RateLimitOptions": {
        "ClientWhitelist": [ "SuperClient" ],
        "EnableRateLimiting": true,
        "Period": "5s",
        "PeriodTimespan": 2,
        "Limit": 2
      }
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "http://localhost:9070",
    "ServiceDiscoveryProvider": {
      "Scheme": "http",
      "Host": "localhost",
      "Port": 8500,
      "Type": "Consul"
    },
    "RateLimitOptions": {
      "DisableRateLimitHeaders": false,
      "QuotaExceededMessage": "too many requests...",
      "HttpStatusCode": 999,
      "ClientIdHeader": "Test"
    }
  }
}

在Routes路由配置中增加了RateLimitOptions。ClientWhitelist代表客戶端白名單,在白名單中的客戶端可以不受限流的影響;EnableRateLimiting代表是否限流;Period代表限流的單位時間,例如1s,5m,1h,1d等;PeriodTimespan代表客戶端達到請求上限多少秒后可以重試;Limit代表客戶端在定義的時間內可以發出的最大請求數。
在GlobalConfiguration配置中也增加了RateLimitOptions。DisableRateLimitHeaders代表是否禁用X-Rate-Limit和Retry-After標頭(請求達到上限時response header中的限制數和多少秒后能重試);QuotaExceededMessage:代表請求達到上限時返回給客戶端的消息;HttpStatusCode:代表請求達到上限時返回給客戶端的HTTP狀態代碼。ClientIdHeader可以允許自定義用於標識客戶端的標頭。默認情況下為“ ClientId”。
最重要的就是Period,PeriodTimespan,Limit這幾個配置。

重新編譯啟動看一下效果:

  • 超時/熔斷

超時很好理解,就是網關請求服務時可容忍的最長響應時間。熔斷的意思就是當請求某個服務的異常次數達到一定量時,那麼網關在一定時間內就不再對這個服務發起請求了,直接熔斷。
Ocelot中啟用 超時/熔斷 需要NuGet安裝一下Ocelot.Provider.Polly

修改Startup.cs中的ConfigureServices()方法:

//添加ocelot服務
services.AddOcelot()
    //添加consul支持
    .AddConsul()
    //添加緩存
    .AddCacheManager(x =>
    {
        x.WithDictionaryHandle();
    })
    //添加Polly
    .AddPolly();

同樣的在ocelot.json路由配置中增加QoSOptions:

"QoSOptions": {
        "ExceptionsAllowedBeforeBreaking": 3,
        "DurationOfBreak": 10000,
        "TimeoutValue": 5000
      }

ExceptionsAllowedBeforeBreaking代表發生錯誤的次數,DurationOfBreak代表熔斷時間,TimeoutValue代表超時時間。
以上的配置意思就是當服務發生3次錯誤時,那麼就熔斷10秒,期間客戶端的請求直接返回錯誤,10秒之後恢復。
這個不太好模擬,就不演示了,應該也挺好理解的。

。。。。。。

關於服務治理的學問還有很多,不繼續了。。。就到此為止吧。
想要更深入了解Ocelot的,請看官網:https://ocelot.readthedocs.io/en/latest/
或者看源碼:https://github.com/ThreeMammals/Ocelot

下一篇準備說一下:事件總線。

代碼放在:https://github.com/xiajingren/NetCoreMicroserviceDemo

未完待續…

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

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

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

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

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

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

※超省錢租車方案

聚甘新

深入理解 EF Core:EF Core 寫入數據時發生了什麼?

閱讀本文大概需要 14 分鐘。

原文:https://bit.ly/2C67m1C
作者:Jon P Smith
翻譯:王亮
聲明:我翻譯技術文章不是逐句翻譯的,而是根據我自己的理解來表述的。其中可能會去除一些本人實在不知道如何組織但又不影響理解的句子。

這是深入理解 EF Core 系列的第二篇文章。第一篇是關於 EF Core 如何從數據庫讀取數據的;而這一篇是關於 EF Core 如何向數據庫寫入數據的。這是四種數據庫操作 CRUD(新增、讀取、更新和刪除)中的 CUD 部分。

我假設你對 EF Core 已經有了一定的認識,但在深入學習之前,我們先來了解一下如何使用 EF Core,以確保我們已經掌握了一些基本知識。這是一個“深入研究”的課題,所以我準備大量的技術細節,希望我的描述方式你能理解。

本文是“深入理解 EF Core”系列中的第二篇。以下是本系列文章列表:

  • 深入理解 EF Core:當 EF Core 從數據庫讀取數據時發生了什麼?
  • 深入理解 EF Core:當 EF Core 寫入數據到數據庫時發生了什麼?(本文)
  • 深入理解 EF Core:使用查詢過濾器軟刪除數據(敬請期待)

概要

∮. EF Core 可以通過新的或已存在的關聯關係創建一個新的實體。為此,它必須以正確的順序來組織實體類,以便能夠建立各類之間的關聯。這使得開發人員很容易寫出具有複雜關聯關係的類。

∮. 當你調用 EF Core 的 Add 命令來添加一個新條目時,會發生很多事情:

  • EF Core 查找添加的類和其他類的所有關聯。對於每個關聯的類,它也會判斷是否需要在數據庫中創建一個新行,或者僅僅鏈接到數據庫中現有的行。
  • 它使用現有行的主鍵或偽主鍵為新添加的條目填充外鍵信息。

∮. EF Core 可以監測你從數據庫讀取的類的屬性的變化。它通過已讀入的類的隱藏副本來實現這一點。當你調用 SaveChanges 時,它會將每個讀入的屬性值與其原始值進行比較,並且會創建相應的數據更新命令。

∮. EF Core 的 Remove 方法將刪除參數提供的實體類的主鍵所指向的數據行。如果被刪除的類有外鍵關聯,那麼數據庫會自動進行相關的操作(比如級聯刪除),但你可以更改刪除的規則。

數據寫入基礎

提示:如果你已經對 EF Core 有一定的了解,那麼你可以跳過這一部分,這隻是一個簡單的 EF Core 寫入數據的例子。

在這一節的介紹中,我將描述一下本文用到的數據庫結構,然後給出一個簡單的數據庫寫入示例。下面是類/表的基本關係圖:

這些表被映射到具有類似名稱的類,例如 Book、BookAuthor、Author,這些類的屬性名稱與表的字段名稱相同。由於篇幅有限,我不打算展開來講這些類,但您可以在我的 GitHub 倉庫[1]中查看這些類。

和讀取數據一樣,EF Core 將數據寫入數據庫也是五部分:

  1. 數據庫服務器,如 SQL server, Sqlite, PostgreSQL…
  2. 映射到數據庫的一個類或多個類—我將它們稱為實體類
  3. 一個繼承 EF Core 的 DbContext 的類,該類包含 EF Core 的配置
  4. 一個創建數據庫的方法
  5. 最後,向數據庫寫入數據的命令

下面的單元測試代碼來自我的 GitHub 創庫[2],展示了一個簡單的示例,它從現有數據庫中讀取 4 個 Book 實體及其關聯的 BookAuthor 和 Authors 實體。

[Fact]
public void TestWriteTestDataSqliteInMemoryOk()
{
    //SETUP
    var options = SqliteInMemory.CreateOptions<EfCoreContext>();
    using (var context = new EfCoreContext(options))
    {
        context.Database.EnsureCreated();

        //ATTEMPT
        var book = new Book
        {
            Title = "Test",
            Reviews = new List<Review>()
        };
        book.Reviews.Add(new Review { NumStars = 5 });
        context.Add(book);
        context.SaveChanges();

        //VERIFY
        var bookWithReview = context.Books
            .Include(x => x.Reviews).Single()
        bookWithReview.Reviews.Count.ShouldEqual(1);
    }
}

現在,如果我們將單元測試代碼對應到上面的 5 部分,結果是這樣的:

  1. 數據庫服務器——第 5 行:我選擇了一個 Sqlite 數據庫服務器,在本例中是 SqliteInMemory.CreateOptions 方法,它使用我的一個 NuGet 包 EfCore.TestSupport 創建了一個內存數據庫(內存中的數據庫對於單元測試非常有用,因為你可以為這個測試建立一個新的空數據庫)。
  2. 實體類——和上一篇結構差不多,但是多了一個與 Book 關聯的 Review 實體類。
  3. 一個繼承 DbContext 的類——第 6 行:EfCoreContext 類繼承了 DbContext 類並配置了從類到數據庫的映射關係(你可以在我的 GitHub 倉庫[3] 中查看該類)。
  4. 一個創建數據庫的方法——第 8 行:第一次執行時,這句代碼會創建一個新的數據庫,包括創建正確的表、鍵、索引等。EnsureCreated 方法用於單元測試,但對於真實的應用程序,你最好手動執行 EF Core 的 Migration 命令。
  5. 向數據庫寫入數據的命令——第 17 到 18 行:
    • 第 17 行:Add 方法告訴 EF Core 需要將一個 Book 實體及其關係(在本例中,只是一個 Review 實體)寫入數據庫。
    • 第 18 行:SaveChange 方法將在數據庫中的 Books 和 Reviews 表中創建新行。

在 //VERIFY 註釋之後的最後幾行用來檢查數據是否已經被寫入數據庫。

在本例中,你向數據庫添加了新的記錄(SQL 的 INSERT INTO 命令)。EF Core 也可以處理更新和刪除數據庫的數據,下一節介紹這個新增示例,然後介紹其他新增、更新和刪除的示例。

寫入數據時數據庫端發生了什麼

我將從創建一個新的 Book 實體類和新的 Review 實體類開始。這兩個類的關係比較簡單。使用上面單元測試的例子,主要代碼如下:

var book = new Book
{
    Title = "Test",
    Reviews = new List<Review>()
};
book.Reviews.Add(new Review { NumStars = 1 });
context.Add(book);
context.SaveChanges();

為了將這兩個實體添加到數據庫,EF Core 需要這樣做:

  1. 確定它應該以什麼順序創建這些新行——在本例中,它必須在 Books 表中創建一行,這樣它就擁有 Books 的主鍵。
  2. 將主鍵複製到與其關聯的外鍵——在本例中,它將 Books 中的主鍵 BookId 複製到 Review 的外鍵。
  3. 複製數據庫中新創建的數據,以便實體類正確表示數據庫的數據——在這種情況下,它必須複製 BookId 並更新 BookId 屬性,包括 Book 和 Review 實體類以及 Review 實體類的 ReviewId。

下面我們看看上面代碼生成的 SQL 語句:

-- 第一次訪問數據庫
SET NOCOUNT ON;
-- 向數據庫的 Books 表生成一條新數據.
-- 數據庫生成 Books 的主鍵值
INSERT INTO [Books] ([Description], [Title], ...)
VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6);

-- 返回主鍵值,檢查並確認數據行是否已添加
SELECT [BookId] FROM [Books]
WHERE @@ROWCOUNT = 1 AND [BookId] = scope_identity();

-- 第二次訪問數據庫
SET NOCOUNT ON;
-- 向數據庫的 Review 表生成一條新數據.
-- 數據庫生成 Review 的主鍵值
INSERT INTO [Review] ([BookId], [Comment], ...)
VALUES (@p7, @p8, @p9, @p10);

-- 返回主鍵值,檢查並確認數據行是否已添加
SELECT [ReviewId] FROM [Review]
WHERE @@ROWCOUNT = 1 AND [ReviewId] = scope_identity();

重要的一點是,EF Core 是按正確的順序處理實體類的,這樣它就可以填充外鍵。這是簡單的例子,但我遇到一個客戶項目的例子是,我不得不建立一個非常複雜的數據組成的 15 個不同的實體類,一些實體類是新增,一些是更新和刪除,EF Core 通過一個 SaveChanges 將把所有工作有序地完成了庫。因此,EF Core 使開發者可以很容易地將複雜的數據寫入數據庫。

我之所以提到這一點,是因為我看到過在 EF Core 代碼中,開發人員多次調用 SaveChanges 方法來從第一個新增命令中獲得主鍵,並把它設置為相關實體的外鍵。例如:

var book = new Book
{
    Title = "Test"
};
context.Add(book);
context.SaveChanges();
var review = new Review { BookId = book.BookId, NumStars = 1 }
context.Add(review);
context.SaveChanges();

雖然這代碼效果是一樣的,但它有一個缺陷——如果第二 SaveChanges 失敗,那麼就會發生部分數據更新到數據庫的情況。在某種情況下,這可能不是個問題,但對於像我客戶那種需要保證數據一致的情況,就非常糟糕了。

因此,從中得到的收穫是,您不需要將主鍵複製到外鍵中,因為你可以設置導航屬性,EF Core 將為您挑選出外鍵。因此,如果你認為需要調用兩次 SaveChanges,那麼通常意味着你沒有設置正確的導航屬性來處理這種情況。

寫數據時 DbContext 做了什麼

在上一節中,你看到了 EF Core 在數據庫端做了什麼,現在你要看看在 EF Core 中發生了什麼。大多數情況,你不需要知道,但有時候知道這些是非常重要的。例如,你只能在 SaveChanges 之前捕獲數據的狀態。而對於自增主鍵,你只有在 SaveChanges 被調用之後才能拿到主鍵的值。

與上一個示例相比,這個示例稍微複雜一些。在這個示例中,我想向你展示 EF Core 通過從數據庫中讀取的已有實體類的實例來處理另一個實體類的新實例。下面的代碼創建了一個新的 Book,但 Author 已經在數據庫中了。代碼註明了階段 1、階段 2 和階段 3,然後我用圖表描述每個階段發生的事情。

// 階段 1
var author = context.Authors.First();
var bookAuthor = new BookAuthor { Author = author };
var book = new Book
{
    Title = "Test Book",
    AuthorsLink = new List<BookAuthor> { bookAuthor }
};

// 階段 2
context.Add(book);

// 階段 3
context.SaveChanges();

接下來的三個圖向你展示了實體類及其跟蹤數據在每個階段內發生的事情。每個圖显示了其階段結束時的以下數據:

  • 流程的每個階段中每個實例的狀態。
  • Book 和 BookAuthor 類是棕色的,表示它們是類的新實例,需要添加到數據庫中,而 Author 實體類是藍色的,表示從數據庫中讀取的實例。
  • 主鍵和外鍵旁邊的括號是其當前的值。如果一個鍵是 (0),那麼它還沒有被設值。
  • 箭頭連線連接的是從導航屬性到其相應實體類。
  • 每個階段之間的變化通過粗體文本或箭頭連線的粗線显示。

下圖显示了階段 1 完成后的情況。用於設置一個新的 Book 實體類(左)和一個新的 BookAuthor 實體類(中),後者將 Book 連接接到一個現有的 Author 實體類(右)。

階段 1 這是調用任何 EF Core 方法之前的起點。

下一個圖显示了執行 context.Add(book) 之後的情況。更改部分以粗體显示。

你可能會驚訝於執行 Add 方法時所發生的事情。它將作為參數提供的實體的狀態設置為 Added(在本例中為 Book 實體)。然後通過導航屬性或外鍵值查看與該實體連接的所有實體。對於每個被連接的實體,它會執行以下操作(注意:我不知道它們執行的確切順序)。

  • 如果實體未被跟蹤(即其當前狀態為 Detached),則將其狀態設置為 Added——在本例中,它是 BookAuthor 實體。
  • 它用主鍵的值填充正確的外鍵的值。如果連接的主鍵還不可用,它將為跟蹤的主鍵和外鍵數據的 CurrentValue 屬性設置一個惟一的負數。你可以在上圖中看到這一點。
  • 它填充當前未設值的導航屬性——如上圖中所示。

最後一個階段,即階段 3,是調用 SaveChanges 方法時發生的情況,如圖所示。

在“寫數據時數據庫端發生了什麼”一節中,數據庫更改的任何列都被複制回實體類中,以便實體與數據庫匹配。在本例中,數據更新到數據庫時會把主鍵值更新到 Book 的 BookId 和 BookAuthor 的 BookId。
而且,此次數據庫寫入完成后,涉及的所有實體的狀態都會被更新為 Unchanged。

對於上面這樣一個很長的解釋,很多時候你不需要知道這些細節,你只管它“工作了”就行。但是,當某些東西不能正常工作或者想做一些複雜的事情時,比如記錄實體類的更改,那麼了解這個就非常有用。

更新數據到數據庫時發生了什麼

上面的示例是關於向數據庫添加新記錄的,但是沒有進行更新。在這一節中,我將展示當你更新數據庫中已有的記錄時會發生什麼。這裏使用我上一篇文章“EF Core 讀取數據時發生了什麼?”中講到的查詢例子。

這個更新很簡單,只有三行,但是它在代碼中有三個階段:讀取、更新和保存。

var books = context.Books.ToList();
books.First().PublishedOn = new DateTime(2020, 1, 1);
context.SaveChanges();

下圖展示了這三個階段:

如你所見,你使用的查詢類型很重要——普通查詢加載數據並把返回的實體保存一份“跟蹤快照”,返回的實體類被稱為“被跟蹤的”。如果實體沒有沒跟蹤,則無法更新它。

注意:上一節中的 Author 實體類也是被“跟蹤”的。在這個例子中,Author 的跟蹤狀態告訴 EF Core Author 已經在數據庫中,因此不會再次創建。

因此,如果你更改了加載的跟蹤實體類中的任何屬性,那麼當你調用 SaveChanges 時,它會將所有跟蹤的實體類與它們的跟蹤快照進行比較。對於每個類,它遍歷映射到數據庫字段的所有屬性。這個過程稱為更改跟蹤,它將檢測被跟蹤實體中的每一個更改,包括 Title、PubishedOn 等非關係屬性。

在這個簡單的示例中,只有 4 個 Book 實體,但在實際應用程序中,您可能已經加載了許多相互連接的實體類。在這一點上,比較階段可能需要一段時間。因此,你應該嘗試只加載需要更改的實體類。

注意:EF Core 有一個名為 Update 的命令,它用於更新每個屬性/列的特定情況。EF Core 會自動跟蹤更改,默認只更新已更改的屬性/列。

每次更新都將創建一個 SQL UPDATE 命令,所有這些更新都將在一個 SQL 事務中執行。使用 SQL 事務意味着所有更新都作為一個整體,如果其中任何一部分失敗,那麼事務中的任何數據庫更改都會失效。

從數據庫刪除數據時發生了什麼

CRUD 的最後一部分是 DELETE,這在某些情況很簡單,你只需要調用 context.Remove()。在另一些情況它很複雜,例如,當你刪除另一個實體類依賴的實體類時會發生什麼?

刪除映射到數據庫的實體類的方法是 Remove。舉個例子,我加載一個特定的 Book,然後刪除它。

var book = context.Books
    .Single(p => p.Title == "Quantum Networking");
context.Remove(book);
context.SaveChanges();

它的階段如下:

  1. 加載要刪除的 Book 實體類。這會獲取它的所有屬性數據,但對於刪除,您實際上只需要實體類的主鍵。
  2. 調用 Remove 方法其實是將 Book 的狀態標記為 Deleted。這些信息會有序地存儲在跟蹤快照中。
  3. 最後,SaveChanges 創建一個 SQL DELETE 命令,該命令與任何其他數據庫更改一起發送到數據庫,並且在一個 SQL 事務中。

這看起來很簡單,但這裏發生了一些重要的事情,從代碼看並不明顯。原來書名為“Quantum Networking”的書有其他一些實體類關聯到到它——在某個特定的測試用例中,書名為“Quantum Networking”的書關聯到以下實體類:

  • 兩個 Review
  • 一個 PriceOffer
  • 一個 BookAuthor

現在,Review、PriceOffer 和 BookAuthor 實體類只與這本書相關——我們使用術語叫依賴於 Book 實體類。因此,如果這本書被刪除了,那麼這些 Review、PriceOffer 和所關聯的 BookAuthor 數據行也應該被刪除。如果不刪除,那麼數據庫的關聯關係就是不正確的,SQL 數據庫將拋出異常。那麼,為什麼做這個刪除工作?

這裏所發生的都是因為設置了級聯刪除,級聯刪除規則設置了 Books 表和三個依賴表之間的數據庫關係。
下面是 EF Core 為創建 Review 表而生成的 SQL 命令的一個示例:

CREATE TABLE [Review] (
    [ReviewId] int NOT NULL IDENTITY,
    [VoterName] nvarchar(max) NULL,
    [NumStars] int NOT NULL,
    [Comment] nvarchar(max) NULL,
    [BookId] int NOT NULL,
    CONSTRAINT [PK_Review] PRIMARY KEY ([ReviewId]),
    CONSTRAINT [FK_Review_Books_BookId] FOREIGN KEY ([BookId])
         REFERENCES [Books] ([BookId]) ON DELETE CASCADE
);

CONSTRAINT 語句部分定義了約束規則,該約束表示 Review 通過 BookId 列鏈接到 Books 表中的一行。在該約束的最後,你將看到關於 DELETE 級聯的規則。它告訴數據庫,如果它鏈接的書被刪除了,那麼這個 Review 也應該被刪除。這意味着書的刪除是允許的,因為所有相關的行也被刪除了。

這是非常有用的,但有時候想要更改刪除規則怎麼辦?比如我決定不允許刪除客戶訂單中存在的書。為了做到這一點,我在 DbContext 中添加了一些 EF Core 配置來改變刪除規則,如下:

public class EfCoreContext : DbContext
{
    private readonly Guid _userId;

    public EfCoreContext(DbContextOptions<EfCoreContext> options)
        : base(options)

    public DbSet<Book> Books { get; set; }
    //… 其它 DbSet<T>

    protected override void OnModelCreating(ModelBuilder modelBuilder
    {
        //… 其它代碼

        modelBuilder.Entity<LineItem>()
            .HasOne(p => p.ChosenBook)
            .WithMany()
            .OnDelete(DeleteBehavior.Restrict);
    }
}

一旦該配置應用到數據庫,就不會生成 SQL 語句的 DELETE CASCADE。這意味着,如果你試圖刪除客戶訂單中的一本書,那麼數據庫將返回一個錯誤,EF Core 將把這個錯誤變成一個異常。

這使你對正在發生的事情有一個更深的了解,但是還有相當多的內容我沒有介紹(但我在我的書中介紹了)。這裡有一些關於刪除我還沒有提到的事情:

  • 實體類之間可以有 required 關係(依賴關係)和 optional 關係,EF Core 為每種類型使用不同的規則。
  • EF Core 可以通過設置 DeleteBehavior 來設置級聯刪除規則,當實體類存在循環關聯關係時,可以用它避免一些錯誤——一些數據庫在發現循環刪除時會拋出錯誤。
  • 你可以在調用 Remove 方法時提供一個新的只有主鍵有值的類來刪除實體類。這在處理只返回主鍵的場景非常有用。

總結

本文我介紹了 CRUD 中的新增、更新和刪除部分,前一篇文章介紹了讀取部分。

正如您所看到的,使用 EF Core 在數據庫中創建記錄很容易,但內部很複雜。你通常不需要知道 EF Core 或數據庫中發生了什麼,但了解一些細節可以讓你更好地利用 EF Core 的優勢。

更新也很簡單——只需在你讀入的實體類中更改一個或多個屬性,當你調用 SaveChanges 時,EF Core 會找到已更改的數據,並構建 SQL 命令更新數據庫。這適用於非關係屬性(如圖 Book 的 Title 屬性)和導航屬性(你可以在他們的關係)。

最後,我們看了一個刪除案例。同樣很容易使用,但很多處理也是在背後執行的。​ 另外,敬請關注我的下一篇文章,我將討論所謂的“軟刪除”。如果你設置了一個標誌,EF Core 就不會再看到這個實體類了,它仍然在數據庫中,但它是隱藏的。

希望本文對你有用,也希望你關注本系列的更多文章。

祝你編程愉快!

[1]. https://bit.ly/2MXK3ZY
[2]. https://bit.ly/2Yza7QQ
[3]. https://bit.ly/2Y0UORO

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

【其他文章推薦】

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

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

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

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

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

聚甘新

重學 Java 設計模式:實戰責任鏈模式「模擬618電商大促期間,項目上線流程多級負責人審批場景」

作者:小傅哥
博客:https://bugstack.cn – 原創系列專題文章

沉澱、分享、成長,讓自己和他人都能有所收穫!

一、前言

場地和場景的重要性

射擊需要去靶場學習、滑雪需要去雪場體驗、開車需要能上路實踐,而編程開發除了能完成產品的功能流程,還需要保證系統的可靠性能。就像你能聽到的一些系統監控指標;QPSTPSTP99TP999可用率響應時長等等,而這些指標的總和評估就是一個系統的健康度。但如果你幾乎沒有聽到這樣的技術術語,也沒接觸過類似高併發場景,那麼就很像駕駛證的科目1考了100分,但不能上路。沒有這樣的技術場景給你訓練,讓你不斷的體會系統的脾氣秉性,即便你有再多的想法都沒法實現。所以,如果真的想學習一定要去一個有實操的場景,下水試試才能學會狗刨。

你的視覺盲區有多大

同樣一本書、同樣一條路、同樣一座城,你真的以為生活有選擇嗎?有時候很多選項都是擺設,給你多少次機會你都選的一模一樣。這不是你選不選而是你的認知範圍決定了你下一秒做的事情,另外的一個下一秒又決定了再下一個下一秒。就像管中窺豹一樣,20%的面積在你視覺里都是黑色的,甚至就總是忽略看不到,而這看不到的20%就是生命中的時運!但,人可以學習,可以成長,可以脫胎換骨,可以努力付出,通過一次次的蛻變而看到剩下的20%!

沒有設計圖紙你敢蓋樓嗎

編程開發中最好的什麼,是設計。運用架構思維、經驗心得、才華靈感,構建出最佳的系統。真正的研發會把自己寫的代碼當做作品來欣賞,你說這是一份工作,但在這樣的人眼裡這可不是一份工作,而是一份工匠精神。就像可能時而你也會為自己因為一個niubility的設計而豪邁萬丈,為能上線一個扛得住每秒200萬訪問量的系統會精神煥發。這樣的自豪感就是一次次壘磚一樣墊高腳底,不斷的把你的視野提高,讓你能看到上層設計也能知曉根基建設。可以把控全局,也可以治理細節。這一份份知識的沉澱,來幫助你繪製出一張系統架構藍圖。

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三個,可以通過關注公眾號bugstack蟲洞棧,回復源碼下載獲取(打開獲取的鏈接,找到序號18)
工程 描述
itstack-demo-design-13-00 場景模擬工程;模擬一個上線流程審批的接口。
itstack-demo-design-13-01 使用一坨代碼實現業務需求
itstack-demo-design-13-02 通過設計模式優化改造代碼,產生對比性從而學習

三、責任鏈模式介紹

擊鼓傳雷,看上圖你是否想起周星馳有一個電影,大家坐在海邊圍成一個圈,拿着一個點燃的炸彈,互相傳遞。

責任鏈模式的核心是解決一組服務中的先後執行處理關係,就有點像你沒錢花了,需要家庭財務支出審批,10塊錢以下找閨女審批,100塊錢先閨女審批在媳婦審批。你可以理解想象成當你要跳槽的時候被安排的明明白白的被各個領導簽字放行。

四、案例場景模擬

在本案例中我們模擬在618大促期間的業務系統上線審批流程場景

像是這些一線電商類的互聯網公司,阿里、京東、拼多多等,在618期間都會做一些運營活動場景以及提供的擴容備戰,就像過年期間百度的紅包一樣。但是所有開發的這些系統都需要陸續的上線,因為臨近618有時候也有一些緊急的調整的需要上線,但為了保障線上系統的穩定性是盡可能的減少上線的,也會相應的增強審批力度。就像一級響應、二級響應一樣。

而這審批的過程在隨着特定時間點會增加不同級別的負責人加入,每個人就像責任鏈模式中的每一個核心點。對於研發小夥伴並不需要關心具體的審批流程處理細節,只需要知道這個上線更嚴格,級別也更高,但對於研發人員來說同樣是點擊相同的提審按鈕,等待審核。

接下來我們就模擬這樣一個業務訴求場景,使用責任鏈的設計模式來實現此功能。

1. 場景模擬工程

itstack-demo-design-13-00
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── AuthService.java
  • 這裏的代碼結構比較簡單,只有一個模擬審核和查詢審核結果的服務類。相當於你可以調用這個類去審核工程和獲取審核結構,這部分結果信息是模擬的寫到緩存實現。

2. 場景簡述

2.1 模擬審核服務

public class AuthService {

    private static Map<String, Date> authMap = new ConcurrentHashMap<String, Date>();

    public static Date queryAuthInfo(String uId, String orderId) {
        return authMap.get(uId.concat(orderId));
    }

    public static void auth(String uId, String orderId) {
        authMap.put(uId.concat(orderId), new Date());
    }

}
  • 這裏面提供了兩個接口一個是查詢審核結果(queryAuthInfo)、另外一個是處理審核(auth)。
  • 這部分是把由誰審核的和審核的單子ID作為唯一key值記錄到內存Map結構中。

五、用一坨坨代碼實現

這裏我們先使用最直接的方式來實現功能

按照我們的需求審批流程,平常系統上線只需要三級負責人審批就可以,但是到了618大促時間點,就需要由二級負責以及一級負責人一起加入審批系統上線流程。在這裏我們使用非常直接的if判斷方式來實現這樣的需求。

1. 工程結構

itstack-demo-design-13-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── AuthController.java
  • 這部分非常簡單的只包含了一個審核的控制類,就像有些夥伴開始寫代碼一樣,一個類寫所有需求。

2. 代碼實現

public class AuthController {

    private SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 時間格式化

    public AuthInfo doAuth(String uId, String orderId, Date authDate) throws ParseException {

        // 三級審批
        Date date = AuthService.queryAuthInfo("1000013", orderId);
        if (null == date) return new AuthInfo("0001", "單號:", orderId, " 狀態:待三級審批負責人 ", "王工");

        // 二級審批
        if (authDate.after(f.parse("2020-06-01 00:00:00")) && authDate.before(f.parse("2020-06-25 23:59:59"))) {
            date = AuthService.queryAuthInfo("1000012", orderId);
            if (null == date) return new AuthInfo("0001", "單號:", orderId, " 狀態:待二級審批負責人 ", "張經理");
        }

        // 一級審批
        if (authDate.after(f.parse("2020-06-11 00:00:00")) && authDate.before(f.parse("2020-06-20 23:59:59"))) {
            date = AuthService.queryAuthInfo("1000011", orderId);
            if (null == date) return new AuthInfo("0001", "單號:", orderId, " 狀態:待一級審批負責人 ", "段總");
        }

        return new AuthInfo("0001", "單號:", orderId, " 狀態:審批完成");
    }

}
  • 這裏從上到下分別判斷了在指定時間範圍內由不同的人員進行審批,就像618上線的時候需要三個負責人都審批才能讓系統進行上線。
  • 像是這樣的功能看起來很簡單的,但是實際的業務中會有很多部門,但如果這樣實現就很難進行擴展,並且在改動擴展調整也非常麻煩。

3. 測試驗證

3.1 編寫測試類

@Test
public void test_AuthController() throws ParseException {
    AuthController authController = new AuthController();  

    // 模擬三級負責人審批
    logger.info("測試結果:{}", JSON.toJSONString(authController.doAuth("小傅哥", "1000998004813441", new Date())));
    logger.info("測試結果:{}", "模擬三級負責人審批,王工");
    AuthService.auth("1000013", "1000998004813441");  

    // 模擬二級負責人審批                                 
    logger.info("測試結果:{}", JSON.toJSONString(authController.doAuth("小傅哥", "1000998004813441", new Date())));
    logger.info("測試結果:{}", "模擬二級負責人審批,張經理");
    AuthService.auth("1000012", "1000998004813441");    

    // 模擬一級負責人審批
    logger.info("測試結果:{}", JSON.toJSONString(authController.doAuth("小傅哥", "1000998004813441", new Date())));
    logger.info("測試結果:{}", "模擬一級負責人審批,段總");
    AuthService.auth("1000011", "1000998004813441");            

    logger.info("測試結果:{}", "審批完成");
}
  • 這裏模擬每次查詢是否審批完成,隨着審批的不同節點,之後繼續由不同的負責人進行審批操作。
  • authController.doAuth,是查看審批的流程節點、AuthService.auth,是審批方法用於操作節點流程狀態。

3.2 測試結果

23:25:00.363 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:{"code":"0001","info":"單號:1000998004813441 狀態:待三級審批負責人 王工"}
23:25:00.366 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:模擬三級負責人審批,王工
23:25:00.367 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:{"code":"0001","info":"單號:1000998004813441 狀態:待二級審批負責人 張經理"}
23:25:00.367 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:模擬二級負責人審批,張經理
23:25:00.368 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:{"code":"0001","info":"單號:1000998004813441 狀態:待一級審批負責人 段總"}
23:25:00.368 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:模擬一級負責人審批,段總
23:25:00.368 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:審批完成

Process finished with exit code 0
  • 從測試結果上可以看到一層層的由不同的人員進行審批,審批完成後到下一個人進行處理。單看結果是滿足我們的訴求,只不過很難擴展和調整流程,相當於代碼寫的死死的。

六、責任鏈模式重構代碼

接下來使用裝飾器模式來進行代碼優化,也算是一次很小的重構。

責任鏈模式可以讓各個服務模塊更加清晰,而每一個模塊間可以通過next的方式進行獲取。而每一個next是由繼承的統一抽象類實現的。最終所有類的職責可以動態的進行編排使用,編排的過程可以做成可配置化。

1. 工程結構

itstack-demo-design-13-02
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── impl
                │    ├── Level1AuthLink.java
                │    ├── Level2AuthLink.java
                │    └── Level3AuthLink.java
                ├── AuthInfo.java
                └── AuthLink.java

責任鏈模式模型結構

  • 上圖是這個業務模型中責任鏈結構的核心部分,通過三個實現了統一抽象類AuthLink的不同規則,再進行責任編排模擬出一條鏈路。這個鏈路就是業務中的責任鏈。
  • 一般在使用責任鏈時候如果是場景比較固定,可以通過寫死到代碼中進行初始化。但如果業務場景經常變化可以做成xml配置的方式進行處理,也可以落到庫里進行初始化操作。

2. 代碼實現

2.1 責任鏈中返回對象定義

public class AuthInfo {

    private String code;
    private String info = "";

    public AuthInfo(String code, String ...infos) {
        this.code = code;
        for (String str:infos){
            this.info = this.info.concat(str);
        }
    }
    
    // ...get/set
}
  • 這個類的是包裝了責任鏈處理過程中返回結果的類,方面處理每個責任鏈的返回信息。

2.2 鏈路抽象類定義

public abstract class AuthLink {

    protected Logger logger = LoggerFactory.getLogger(AuthLink.class);

    protected SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 時間格式化
    protected String levelUserId;                           // 級別人員ID
    protected String levelUserName;                         // 級別人員姓名
    private AuthLink next;                                  // 責任鏈

    public AuthLink(String levelUserId, String levelUserName) {
        this.levelUserId = levelUserId;
        this.levelUserName = levelUserName;
    }

    public AuthLink next() {
        return next;
    }

    public AuthLink appendNext(AuthLink next) {
        this.next = next;
        return this;
    }

    public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);

}
  • 這部分是責任鏈,鏈接起來的核心部分。AuthLink next,重點在於可以通過next方式獲取下一個鏈路需要處理的節點。
  • levelUserIdlevelUserName,是責任鏈中的公用信息,標記每一個審核節點的人員信息。
  • 抽象類中定義了一個抽象方法,abstract AuthInfo doAuth,這是每一個實現者必須實現的類,不同的審核級別處理不同的業務。

2.3 三個審核實現類

Level1AuthLink

public class Level1AuthLink extends AuthLink {

    public Level1AuthLink(String levelUserId, String levelUserName) {
        super(levelUserId, levelUserName);
    }

    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "單號:", orderId, " 狀態:待一級審批負責人 ", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "單號:", orderId, " 狀態:一級審批完成負責人", " 時間:", f.format(date), " 審批人:", levelUserName);
        }

        return next.doAuth(uId, orderId, authDate);
    }

}

Level2AuthLink

public class Level2AuthLink extends AuthLink {

    private Date beginDate = f.parse("2020-06-11 00:00:00");
    private Date endDate = f.parse("2020-06-20 23:59:59");

    public Level2AuthLink(String levelUserId, String levelUserName) throws ParseException {
        super(levelUserId, levelUserName);
    }

    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "單號:", orderId, " 狀態:待二級審批負責人 ", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "單號:", orderId, " 狀態:二級審批完成負責人", " 時間:", f.format(date), " 審批人:", levelUserName);
        }

        if (authDate.before(beginDate) || authDate.after(endDate)) {
            return new AuthInfo("0000", "單號:", orderId, " 狀態:二級審批完成負責人", " 時間:", f.format(date), " 審批人:", levelUserName);
        }

        return next.doAuth(uId, orderId, authDate);
    }

}

Level3AuthLink

public class Level3AuthLink extends AuthLink {

    private Date beginDate = f.parse("2020-06-01 00:00:00");
    private Date endDate = f.parse("2020-06-25 23:59:59");

    public Level3AuthLink(String levelUserId, String levelUserName) throws ParseException {
        super(levelUserId, levelUserName);
    }

    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "單號:", orderId, " 狀態:待三級審批負責人 ", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "單號:", orderId, " 狀態:三級審批負責人完成", " 時間:", f.format(date), " 審批人:", levelUserName);
        }

        if (authDate.before(beginDate) || authDate.after(endDate)) {
            return new AuthInfo("0000", "單號:", orderId, " 狀態:三級審批負責人完成", " 時間:", f.format(date), " 審批人:", levelUserName);
        }

        return next.doAuth(uId, orderId, authDate);
    }

}
  • 如上三個類;Level1AuthLinkLevel2AuthLinkLevel3AuthLink,實現了不同的審核級別處理的簡單邏輯。
  • 例如第一個審核類中會先判斷是否審核通過,如果沒有審核通過則返回結果給調用方,引導去審核。(這裏簡單模擬審核後有時間信息不為空,作為判斷條件)
  • 判斷完成后獲取下一個審核節點;super.next();,如果不存在下一個節點,則直接返回結果。
  • 之後是根據不同的業務時間段進行判斷是否需要,二級和一級的審核。
  • 最後返回下一個審核結果;next.doAuth(uId, orderId, authDate);,有點像遞歸調用。

3. 測試驗證

3.1 編寫測試類

@Test
public void test_AuthLink() throws ParseException {
    AuthLink authLink = new Level3AuthLink("1000013", "王工")
            .appendNext(new Level2AuthLink("1000012", "張經理")
                    .appendNext(new Level1AuthLink("1000011", "段總")));

    logger.info("測試結果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));

    // 模擬三級負責人審批
    AuthService.auth("1000013", "1000998004813441");
    logger.info("測試結果:{}", "模擬三級負責人審批,王工");
    logger.info("測試結果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));

    // 模擬二級負責人審批
    AuthService.auth("1000012", "1000998004813441");
    logger.info("測試結果:{}", "模擬二級負責人審批,張經理");
    logger.info("測試結果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));

    // 模擬一級負責人審批
    AuthService.auth("1000011", "1000998004813441");
    logger.info("測試結果:{}", "模擬一級負責人審批,段總");
    logger.info("測試結果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));
}
  • 這裏包括最核心的責任鏈創建,實際的業務中會包裝到控制層; AuthLink authLink = new Level3AuthLink("1000013", "王工") .appendNext(new Level2AuthLink("1000012", "張經理") .appendNext(new Level1AuthLink("1000011", "段總"))); 通過把不同的責任節點進行組裝,構成一條完整業務的責任鏈。
  • 接下里不斷的執行查看審核鏈路authLink.doAuth(...),通過返回結果對數據進行3、2、1級負責人審核,直至最後審核全部完成。

3.2 測試結果

23:49:46.585 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:{"code":"0001","info":"單號:1000998004813441 狀態:待三級審批負責人 王工"}
23:49:46.590 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:模擬三級負責人審批,王工
23:49:46.590 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:{"code":"0001","info":"單號:1000998004813441 狀態:待二級審批負責人 張經理"}
23:49:46.590 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:模擬二級負責人審批,張經理
23:49:46.590 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:{"code":"0001","info":"單號:1000998004813441 狀態:待一級審批負責人 段總"}
23:49:46.590 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:模擬一級負責人審批,段總
23:49:46.590 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:{"code":"0000","info":"單號:1000998004813441 狀態:一級審批完成負責人 時間:2020-06-18 23:49:46 審批人:段總"}

Process finished with exit code 0
  • 從上述的結果可以看到我們的責任鏈已經生效,按照責任鏈的結構一層層審批,直至最後輸出審批結束到一級完成的結果。
  • 這樣責任鏈的設計方式可以方便的進行擴展和維護,也把if語句幹掉了。

七、總結

  • 從上面代碼從if語句重構到使用責任鏈模式開發可以看到,我們的代碼結構變得清晰乾淨了,也解決了大量if語句的使用。並不是if語句不好,只不過if語句並不適合做系統流程設計,但是在做判斷和行為邏輯處理中還是非常可以使用的。
  • 在我們前面學習結構性模式中講到過組合模式,它像是一顆組合樹一樣,我們搭建出一個流程決策樹。其實這樣的模式也是可以和責任鏈模型進行組合擴展使用,而這部分的重點在於如何關聯鏈路的關聯,最終的執行都是在執行在中間的關係鏈。
  • 責任鏈模式很好的處理單一職責和開閉原則,簡單了耦合也使對象關係更加清晰,而且外部的調用方並不需要關心責任鏈是如何進行處理的(以上程序中可以把責任鏈的組合進行包裝,在提供給外部使用)。但除了這些優點外也需要是適當的場景才進行使用,避免造成性能以及編排混亂調試測試疏漏問題。

八、推薦閱讀

  • 1. 重學 Java 設計模式:實戰工廠方法模式「多種類型商品不同接口,統一發獎服務搭建場景」
  • 2. 重學 Java 設計模式:實戰原型模式「上機考試多套試,每人題目和答案亂序排列場景」
  • 3. 重學 Java 設計模式:實戰橋接模式「多支付渠道(微信、支付寶)與多支付模式(刷臉、指紋)場景」
  • 4. 重學 Java 設計模式:實戰組合模式「營銷差異化人群發券,決策樹引擎搭建場景」
  • 5. 重學 Java 設計模式:實戰外觀模式「基於SpringBoot開發門面模式中間件,統一控制接口白名單場景」

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

【其他文章推薦】

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

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

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

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

聚甘新

特性速覽| Apache Hudi 0.5.3版本正式發布

1. 下載連接

  • 源代碼下載:Apache Hudi 0.5.3 Source Release (asc, sha512)
  • 0.5.3版本相關jar包地址:https://repository.apache.org/#nexus-search;quick~hudi

2. 遷移指南

  • 這是一個bugfix版本,從0.5.2升級時不需要任何特殊的遷移步驟。如果要從早期版本”X”升級,請閱讀”X”和0.5.3之間的每個後續版本的遷移指南。
  • 0.5.3是Hudi畢業后的第一個版本,因此所有hudi jar的版本名稱中不再帶有”-incubating”。在所有提及hudi版本的地方,請確保不再存在”-incubating”。

例如,hudi-spark-bundle pom依賴如下所示:

<dependency>
	<groupId>org.apache.hudi</groupId>
	<artifactId>hudi-spark-bundle_2.12</artifactId>
	<version>0.5.3</version>
</dependency>

3. 關鍵特性

  • Hudi內置支持 aliyun OSS 對象存儲。

  • 默認情況下將為delta-streamer和spark datasource寫入啟用Embedded Timeline Server。在此版本之前,此功能處於實驗模式,embeddedTimeline Server在Spark Driver中緩存文件列表,並提供Restful接口給Spark Writer任務調用來減少了每次寫入時的list文件列表的操作,此優化對雲上對象存儲非常友好。

  • 默認情況下為delta-streamer和Spark datasource寫入均啟用”增量清理(incremental cleaning)”。在此版本之前,此功能還處於實驗模式,在穩定狀態下,增量清理避免了掃描所有分區的昂貴步驟,而是使用Hudi元數據來查找要清理的文件,此優化也對雲上對象存儲非常友好。

  • 支持將Delta-Streamer配置文件放置在與實際數據不同的文件系統中。

  • Hudi Hive Sync現在支持按日期類型列分區的表。

  • Hudi Hive Sync現在支持直接通過Hive MetaStore進行同步。您只需要設置hoodie.datasource.hive_sync.use_jdbc = false。Hive Metastore Uri將從environment中隱式讀取。例如當通過Spark datasource寫入時,

     spark.write.format(“hudi”)
     .option(…)
     .option(“hoodie.datasource.hive_sync.username”, “<user>”)
     .option(“hoodie.datasource.hive_sync.password”, “<password>”)
     .option(“hoodie.datasource.hive_sync.partition_fields”, “<partition_fields>”)
     .option(“hoodie.datasource.hive_sync.database”, “<db_name>”)
     .option(“hoodie.datasource.hive_sync.table”, “<table_name>”)
     .option(“hoodie.datasource.hive_sync.use_jdbc”, “false”)
     .mode(APPEND)
     .save(“/path/to/dataset”)
    
  • 支持Presto查詢MoR表時Hudi側的改造。

  • 其他與Writer Performance相關的缺陷修復。

    • 現在DataSource Writer避免了寫入后不必要的數據加載。
    • Hudi Writer現在利用spark的併發來加速小文件查找。

4. 感謝

感謝如下貢獻者(排名不分先後): @bhasudha,@yanghua ,@ddong ,@smarthi ,@afilipchik,@zhedoubushishi,@umehrot2,@varadar,@ffcchi,@bschell,@vinothchandar ,@shenh062326,@lamber-ken,@zhaomin1423,@EdwinGuo,@prashantwason ,@pratyakshsharma,@dengziming ,@AakashPradeep,@Jecarm ,@xushiyan ,@cxzl25,@garyli1019 ,@rolandjohann ,@nsivabalan,@leesf ,@jfrazee

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

聚甘新

一起玩轉微服務(5)——分層架構

領域驅動設計DDD(Domain Driven Design)提出了從業務設計到代碼實現一致性的要求,不再對分析模型和實現模型進行區分。也就是說從代碼的結構中我們可以直接理解業務的設計,命名得當的話,非程序人員也可以“讀”代碼。這與微服務設計中的約定優於配置不謀而合,如果你熟悉英文,那麼直接根據包名和類名就可以直接解讀出程序開發者所構建的業務的大概意圖。

領域模型包含一些明確定義的類型:

  • 實體是一個對象,它有固定的身份,具有明確定義的”連續性線索”或生命周期。通常列舉的示例是一個 Person(一個實體)。大多數系統都需要唯一地跟蹤一個 Person,無論姓名、地址或其他屬性是否更改。
  • l值對象沒有明確定義的身份,而僅由它們的屬性定義。它們通常不可變,所以兩個相等的值對象始終保持相等。地址可以是與 Person 關聯的值對象。
  • l集合是一個相關對象集群,這些對象被看作一個整體。它擁有一個特定實體作為它的根,並定義了明確的封裝邊界。它不只是一個列表。
  • l服務用於表示不是實體或值對象的自然部分的操作或活動。

領域模型在實現時可大可小,在業務的早期,在系統比較小的情況下,它有可能是一個類。當系統做大了以後,它可能是個庫。再做更大一點的時候,它可能是一個服務,給不同的應用去調用。

要將領域元素轉換為服務,可按照以下一般準則來完成此操作:

  • 使用值對象的表示作為參數和返回值,將集合和實體轉換為獨立的微服務。
  • 將領域服務(未附加到集合或實體的服務)與獨立的微服務相匹配。
  • 每個微服務應處理一個完整的業務功能。

領域模型又可以分為失血、貧血和充血3種。

  • 失血模型:基於數據庫的領域設計方式就是典型的失血模型,只關注數據的增刪改查。
  • 貧血模型:就是在domain object包含了不依賴於持久化的領域邏輯,而那些依賴持久化的領域邏輯被分離到server層。
  • 充血模型:充血模型跟貧血模型差不多,不同的是如何劃分業務邏輯,就是說,約大部分業務應該放到domain object裏面,而service應該是很薄的一層。

設計原則之分層架構

同一公司使用統一應用分層,以減少開發維護學習成本。應用分層這件事情看起來很簡單,但每個程序員都有自己的一套,哪怕是初學者,所以想實施起來並非那麼容易。

最早接觸分層架構的應該是我們最熟悉的MVC(Model-View-Controller)架構,將應用分成了模型、視圖和控制層,可以說引導了絕大多數開發者,而我們現在的應用中非常多的包括框架,架構設計都使用此模式。這后又演化出了MVP(Model-View-Presenter)和MVVM(Model-View-ViewModel)。這些可以說都是隨着技術的不斷髮展,為了應對不同場景所演化出來的模型。而微服務的每個架構都可以再細分成領域模型,下面看一下經典的領域模型架構。

它包括了Domain,Service Layer和Repositories。核心實體(Entity)和值對象(Value Object)應該在Domain層,定義的領域服務(Domain Service)在Service Layer,而針對實體和值對象的存儲和查詢邏輯都應該在Repositories層。值得注意的是,不要把Entity的屬性和行為分離到Domain和Service兩層中去實現,即所謂的貧血模型,事實證明這樣的實現方式會造成很大的維護問題。基於這種設計,工程的結構可以構造為:

– MicroService-Sample/src/

    domain

    gateways

    interface

    repositories

    services

當然,在微服務的架構中,每個微服務不必嚴格遵照這樣的規定,切忌死搬硬套,最重要的是理解,在不同的業務場合,架構的設計可以適當的做調整,畢竟適合的架構一定要具有靈活性。

分層的原則包括:

  • 文件夾分層法

應用分層採用文件夾方式的優點是可大可小、簡單易用、統一規範,可以包括 5 個項目,也可以包括 50 個項目,以滿足所有業務應用的多種不同場景。

  • 調用規約

在開發過程中,需要遵循分層架構的約束,禁止跨層次的調用。

  • 下層為上層服務

以用戶為中心,以目標為導向。上層(業務邏輯層)需要什麼,下層(數據訪問層)提供什麼,而不是下層(數據訪問層)有什麼,就向上層(業務邏輯層)提供什麼。

  • 實體層規約

Entity是數據表對象,不是數據訪問層對象;DTO 是網絡傳輸對象,不是表現層對象;BO 是內存計算邏輯對象,不是業務邏輯層對象,不是只能給業務邏輯層使用 。如果僅限定在本層訪問,則導致單個應用內大量沒有價值的對象轉換。以用戶為中心來設計實體類,可以減少無價值重複對象和無用轉換。

  • U 型訪問

下行時表現層是 Input,業務邏輯層是 Process,數據訪問層是 Output。上行時數據訪問層是 Input,業務邏輯層是 Process,  表現層就 Output。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

聚甘新

Mybatis Generator逆向工程的使用

一、MyBatis Generator簡介

    MyBatis Generator(MBG)是MyBatis和iBATIS的代碼生成器。它將為所有版本的MyBatis以及版本2.2.0之後的iBATIS版本生成代碼。它將審查數據庫表(或許多表),並將生成可用於訪問表的構件。這減少了設置對象和配置文件以與數據庫表交互的初始麻煩。MBG尋求對簡單CRUD(創建,檢索,更新,刪除)的大部分數據庫操作產生重大影響。您仍然需要為連接查詢或存儲過程手動編寫SQL和對象代碼。MyBatis Generator將生成:

  • 與表結構匹配的Java POJO。這可能包括:

    • 一個匹配表的主鍵的類(如果有主鍵)

    • 一個匹配表的非主鍵字段的類(BLOB字段除外)

    • 包含表的BLOB字段的類(如果表具有BLOB字段)

    • 用於啟用動態選擇,更新和刪除的類

    這些類之間存在適當的繼承關係。請注意,生成器可以配置為生成不同類型的POJO層次結構 – 例如,如果您願意,可以選擇為每個表生成單個域對象。

  • MyBatis/iBATIS兼容的SQL Map XML文件。MBG為配置中的每個表上的簡單CRUD函數生成SQL。生成的SQL語句包括:

    • insert 插入

    • update by primary key  按主鍵更新

    • update by example (using a dynamic where clause)  通過條件更新(使用動態where子句)

    • delete by primary key  按主鍵刪除

    • delete by example (using a dynamic where clause)  按條件刪除(使用動態where子句)

    • select by primary key  按主鍵查詢

    • select by example (using a dynamic where clause)  按條件查詢(使用動態where子句)

    • count by example  按條件查詢記錄總數

    根據表結構的不同,這些語句有不同的變體(例如,如果表沒有主鍵,則MBG不會通過主鍵功能生成更新)。

    • 適當使用上述對象的Java客戶端類。Java客戶端類的生成是可選的。MBG將為MyBatis 3.x生成以下類型的Java客戶端:

      • 適用於MyBatis 3.x映射器基礎結構的映射器接口

MBG將為iBATIS 2.x生成以下類型的Java客戶端:

    • 符合Spring框架的DAO

    • 僅使用iBATIS SQL映射API的DAO。這些DAO可以生成兩種:通過構造函數或setter注入提供SqlMapClient。

    • 符合iBATIS DAO框架的DAO(iBATIS的可選部分,現在不推薦使用此框架,我們建議您使用Spring框架)

    MyBatis生成器設計為在迭代開發環境中運行良好,並且可以作為Ant任務或Maven插件包含在連續構建環境中。迭代運行MBG時需要注意的重要事項包括:

  1. 如果存在與新生成的XML文件同名的現有文件,MBG將自動合併XML文件。MBG不會覆蓋您對其生成的XML文件所做的任何自定義更改。您可以反覆運行它,而不必擔心會丟失對XML的自定義更改。MBG將替換先前運行中生成的任何XML元素。

  2. MBG不會合併Java文件,它可以覆蓋現有文件或使用不同的唯一名稱保存新生成的文件。如果對生成的Java文件進行更改並以迭代方式運行MBG,則必須手動合併更改。當作為Eclipse插件運行時 ,MBG可以自動合併Java文件。

二、MyBatis Generator使用

1、新建MBG的配置文件generatorConfig.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
 
 
    <!--導入屬性配置-->
    <properties resource="generator.properties"></properties>
    <!--指定特定數據庫的jdbc驅動jar包的位置-->
    <!--<classPathEntry location="${jdbc.driverLocation}"/>-->
 
    <context id="default" targetRuntime="MyBatis3">
 
        <!-- optional,旨在創建class時,對註釋進行控制,false生成註釋,true無註釋 -->
        <commentGenerator>
            <property name="suppressDate" value="false"/>
            <property name="suppressAllComments" value="false"/>
        </commentGenerator>
 
        <!--jdbc的數據庫連接 -->
        <jdbcConnection
                driverClass="${jdbc.driverClass}"
                connectionURL="${jdbc.connectionURL}"
                userId="${jdbc.userId}"
                password="${jdbc.password}">
        </jdbcConnection>
 
 
        <!--
         默認false,把JDBC DECIMAL 和 NUMERIC 類型解析為 Integer,
         為 true時把JDBC DECIMAL 和 NUMERIC 類型解析為java.math.BigDecimal
        -->
        <!-- 非必需,類型處理器,在數據庫類型和java類型之間的轉換控制-->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
 
 
        <!-- Model模型生成器,用來生成含有主鍵key的類,記錄類 以及查詢Example類
            targetPackage     指定生成的model生成所在的包名
            targetProject     指定在該項目下所在的路徑|指定生成到的工程名稱
        -->
        <javaModelGenerator targetPackage="com.test.model"
                            targetProject=".\src\main\java">
            <!-- 是否允許子包,即targetPackage.schemaName.tableName -->
            <property name="enableSubPackages" value="false"/>
            <!-- 是否對model添加 構造函數 true添加,false不添加-->
            <property name="constructorBased" value="false"/>
            <!-- 是否對類CHAR類型的列的數據進行trim操作 -->
            <property name="trimStrings" value="true"/>
            <!-- 建立的Model對象是否 不可改變  即生成的Model對象不會有 setter方法,只有構造方法 -->
            <property name="immutable" value="false"/>
        </javaModelGenerator>
 
        <!--Mapper映射文件生成所在的目錄 為每一個數據庫的表生成對應的SqlMapper文件 -->
        <sqlMapGenerator targetPackage="com.test.mapper"
                         targetProject=".\src\main\java">
            <property name="enableSubPackages" value="false"/>
        </sqlMapGenerator>
 
        <!-- 客戶端代碼,生成易於使用的針對Model對象和XML配置文件 的代碼
                type="ANNOTATEDMAPPER",生成Java Model 和基於註解的Mapper對象
                type="MIXEDMAPPER",生成基於註解的Java Model 和相應的Mapper對象
                type="XMLMAPPER",生成SQLMapper XML文件和獨立的Mapper接口
        -->
        <javaClientGenerator targetPackage="com.test.mapper"
                             targetProject=".\src\main\java" type="XMLMAPPER">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
 
        <!--需要映射的數據庫的表名-->
        <table tableName="t_userinfo" domainObjectName="UserInfo"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false">
        </table>
    </context>
</generatorConfiguration>

2、新建generator.properties文件

jdbc.driverLocation=C:\\mysql-connector-java-5.1.43.jar
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.connectionURL=jdbc:mysql://localhost:3306/mybatis
jdbc.userId=root
jdbc.password=tiger

3、配置執行mybatis generator操作,這裡有兩種方式

第1種方式:如果使用maven項目就可以省去編寫Java啟動類,使用maven插件和配置文件pom.xml即可,插件啟動maven-generator,在pom.xml中添加maven-generator插件

<plugins>
    <!--myBatis逆向工程插件-->
    <plugin>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-maven-plugin</artifactId>
        <version>1.3.2</version>
        <configuration>
            <verbose>true</verbose>
            <overwrite>true</overwrite>
            <configurationFile>${project.basedir}/src/main/resources/generatorConfig.xml</configurationFile>
        </configuration>
        <dependencies>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.35</version>
            </dependency>
        </dependencies>
    </plugin>
</plugins>

點擊mybatis-generator:generate就能執行mybatis generator了

第2種方式:

1、如果不是maven項目添加該mybatis-generator-core-1.3.2.jar,編寫main方法指向mybatis逆向工程,我給依賴粘貼到下面了

<!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator-core -->
<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.3.2</version>
</dependency>

2、修改generatorConfig.xml文件,放開註釋的該配置

<classPathEntry location="${jdbc.driverLocation}"/>

3、然後編寫測試類執行

/**
 * 如果不是maven項目可以這樣生成
 */
public class MybatisGeneratorTest {
    public static void main(String[] args) throws InterruptedException, SQLException, IOException, InvalidConfigurationException, XMLParserException {
        List<String> warnings = new ArrayList<String>();
        //生成的java文件是否覆蓋
        boolean overwrite = true;
        //指定逆向工程配置文件
        //File configFile = new File("E:\\project\\mybatis-generator\\src\\main\\resources\\generatorConfig.xml");
        InputStream resourceAsStream = MybatisGeneratorTest.class.getClassLoader().getResourceAsStream("generatorConfig.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(resourceAsStream);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,callback, warnings);
        myBatisGenerator.generate(null);
    }
}

介紹完這兩種方式,查看數據表:

查看生成的實體類:


TIP:可以看出如果實體類想要遵循駝峰命名規範,數據庫表字段名設計需要用”_”來劃分

查看生成的文件信息:


TIP1:必須在<plugin></plugin>標籤里添加數據庫驅動,在其他地方添加無效,如果不添加會報找不到驅動錯誤,如過不在該插件添加數據庫依賴的話可以使用 <classPathEntry location=”${jdbc.driverLocation}”/> 來指定數據庫驅動位置。

TIP2:如果你在使用mybatis generator插件執行的時候報[ERROR] Failed to execute goal org.mybatis.generator:mybatis-generator-maven-plugin:1.3.2:generate (default-cli) on project mybatis-generator: <properties> resource generator.properties does not exist -> [Help 1]


儘管你的 <properties resource=”generator.properties”></properties>配置的沒有問題,但是還是找不到generator.properties,查看該配置,註釋掉


該配置會改變generatorConfig.xml中讀取generator.properties文件的默認路徑

TIP3:Mybatis Generator反向工程默認不會覆蓋生成的*.java文件。也可以設置覆蓋生成的*.java文件,在反向工程插件mybatis-generator-maven-plugin添加該配置<overwrite>true</overwrite>則會覆蓋生成的*.java文件,如圖


    Mybatis Generator不會覆蓋你的mapper.xml文件,MBG會合併追加到mapper.xml和你自定義的存在一起,但是如果你修改MBG第一次默認生成的SQL(MBG生成的CRUD),MBG會重新把自己生成的CRUD恢復默認,說白了,MBG只會覆蓋他自己生成的SQL,不會覆蓋你自定義的,你自定義的不變。。。如圖,他不會動你的自定義SQL,只會覆蓋Mybatis反向工程自己生成的SQL,前提MBG自動生成SQL語句的註釋要存在。


在最常見的用例中,MyBatis Generator(MBG)由XML配置文件驅動。配置文件告訴MBG

  • 如何連接到數據庫

  • 生成什麼對象,以及如何生成它們

  • 應使用哪些表生成對象

官方MBG配置文件詳解地址:http://mybatis.org/generator/configreference/xmlconfig.html

附帶一個MBG的中文配置文件詳解:https://www.jianshu.com/p/e09d2370b796

 

更多Mybatis逆向工程的使用參考:http://www.mybatis.org/generator/index.html


● XStream學習手冊

● 別在 Java 代碼里亂打日誌了,這才是正確的打日誌姿勢!

● 高可用Redis服務架構分析與搭建

● 8 種方案,幫你解決重複提交問題!請拿走

● IDEA 解決 Maven 依賴衝突的高能神器,這一篇夠不夠?

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

FB行銷專家,教你從零開始的技巧

聚甘新