北京路燈變充電樁 望每天為一萬輛車充電

北京市首批路燈充電樁改造已經展開,位於昌平區京密北路上的八十八盞路燈,從傳統高壓鈉燈改造為LED燈,可讓十幾輛電動汽車同時充電;此外,昌平區內還設置了八個純電動汽車慢充樁,專供電動車充電,充電時間約為四、五個小時,且白天晚上皆可充電。北京市科委指出,希望今年內能達成北京五環內建成五公里半徑的快速充電網路和每天為一萬輛車充電的目標。   除北京市內,連接北京與上海全程1262公里的京滬高速公路也將開通全線快速充電系統。平均每五十公里將設一座充電站,最快三十分鐘可充電完成。同等里程電費支出為燃油車的一半,全程充電費不到人民幣400元(約合新臺幣2052元)。   目前中國大陸的國家電網已完成2.4萬個充電樁,形成「兩縱一橫」網路,規模為世界之最。但據報導,其目前已建成的四百多座充電站幾乎沒有盈利,處於全線虧損,而在深圳市營運的七座充電站,每年虧損額超過1000萬人民幣(約5132.3萬元)。

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

通用推雪佛蘭 Bolt 欲與定價相當的 Tesla Model 3 抗衡

  美國底特律汽車展於 1 月 12 日開幕,通用汽車(GM)新款電動車雪佛蘭 Bolt(Chevrolet Bolt)首度亮相,預料未來將與電動車大廠特斯拉(Tesla)激烈競爭。   雪佛蘭 Bolt 每次充電可行駛 200 英里,達到先前油電混合車款 Volt 的 4 倍之多, 電池由韓國樂金化學(LG Chem)所製造,預計於 2017 年上市,售價約為 3 萬美元,可望與同樣預定 2017 年上市,售價約 3.5 萬美元的 Model 3 車款競相抗衡。根據知情人士表示,Bolt 為掀背式(Hatchback)設計,外型傾向跨界車款。   通用汽車盼能藉雪佛蘭 Bolt,加強消費者對該品牌具備完整產品線的印象,也希望能將此車款擴及至包括中國等全球市場。     (Source:)

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

【其他文章推薦】

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

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

python高級-閉包-裝飾器

閉包內容:

  1. 匿名函數:能夠完成簡單的功能,傳遞這個函數的引用,只有功能

  2. 普通函數:能夠完成複雜的功能,傳遞這個函數的引用,只有功能

  3. 閉包:能夠完成較為複雜的功能,傳遞這個閉包中的函數以及數據,因此傳遞是功能+數據

  4. 對象:能夠完成最複雜的功能,傳遞很多數據+很多功能,因此傳遞的是數據+功能

———————————————————

  1. 對全局函數進行修改:在函數當中加global,在閉包中外邊中的變量加nonlocal

  2. 閉包定義:有兩個函數嵌套使用,裏面的函數可以使用外面函數所傳輸的參數,最後可傳遞的是裏面函數的結構與數據(個人理解)。

  3. 最後閉包可以在python中引申出裝飾器 ———————————————————

 1 def closure():
 2     # 在函數內部再定義一個函數,
 3     # 並且這個函數用到了外邊函數的變量,那麼將這個函數以及用到的一些變量稱之為閉包
 4     def closure_in(x):
 5         print('---------我是打不死的%s--------' %x)
 6     return closure_in
 7  8 x = closure()
 9 x('小強')
10 11 print('*'*20)
12 # -----加餐---------
13 def closure_1(a,b,c):
14     def closure_on(x):
15         print('-----%s加餐-------' %b)
16         print(a*x + c)
17     return closure_on
18 19 demo = closure_1(2,'小強',3) #傳closure_1函數
20 demo(4) #傳clsure_on函數
21 22 #注:函數不加括號,調用的是函數本身【function】;函數加括號,調用的是函數的return結果。

 

裝飾器內容:

代碼要遵守‘開放封閉’原則;對已經寫好的函數遵守封閉,對功能擴展遵守開放;

 1 # 裝飾器的作用:為了對原來的代碼上進行擴展
 2 def decoration(func):
 3     def call_func():
 4         print('-------正在裝飾 -------' )
 5         func()
 6     return call_func
 7  8 #@decoration     #--->demo_new = decoration(demo)
 9 def demo():
10    print('demo----')
11 12 demo_new = decoration(demo)
13 demo_new()

 

使用裝飾器來測試一個函數的運行時:

 1 import time
 2 def set_func(func):
 3     def call_func():
 4         start_time = time.time()
 5         func()
 6         stop_func = time.time()
 7         print(‘alltimes is %f’ %(stop_func-start_fun))
 8     return call_func
 9 @set_func
10 def test1():
11     print(‘——-test1———’)    
12 test1()
13 14 #等價於:
15 @set_func==test1 = set_func(test1)
16

 

1. 沒有參數,沒有返回值的函數進行裝飾:
 1 def set_func(func):
 2     def call_func():
 3         print(‘———test2——-’)
 4         print(‘———-test3——’)
 5         func()
 6     return call_func
 7     
 8 @set_func
 9 def test1():
10     print(‘——test1——-   ’)

 

2. 對有參數無返回值的函數進行裝飾:
 1 def set_func(func):
 2     def call_func(a):  #
 3         print(‘———test2——-’)
 4         print(‘———-test3——’)
 5         func(a) #
 6     return call_func
 7     
 8 @set_func
 9 def test1(num):
10     print(‘——test1——- %d    ’ %num)
11 12 test1(100) —->call_func(100)
13 test1(200)——>call_func(200)

 

復現裝飾器原理:

————————————————————————-

只要遇到@函數 裝飾器(這句話),在程序中就已經執行了!!
3. 不定長參數的函數裝飾:
 1 def set_func(func):
 2     def call_func(*args,**kwargs):  #
 3         print(‘———test2——-’)
 4         print(‘———-test3——’)
 5         func(*args,**kwargs) #(拆包)將元祖拆開,每個進行傳輸;
 6         #func(args,kwargs)—>不行,相當於傳遞了兩個參數:一個元祖,一個字典。
 7     return call_func
 8     
 9 @set_func
10 def test1(num,*args,**kwargs):
11     print(‘——test1——- %d    ’ %num)
12     print(‘——test1——-   ’ , args)
13     print(‘——test1——- ’ ,kwargs )
14     
15 test1(100)  
16 test1(100,200)
17 test1(100,200,300,mm=100)

注意:*args保存不定長參數,以元祖保存,**kwargs保存字典形式(mm=…)

4.對應的返回值參數進行裝飾、通用裝飾器:
 1 #通用裝飾器
 2 def set_func(func):
 3     print(“開始進行裝飾———-”)
 4     def call_func(*args,**kwargs):  #
 5         print(‘———test2——-’)
 6         print(‘———-test3——’)
 7         return func(*args,**kwargs) #(拆包)將元祖拆開,每個進行傳輸;如果沒有return ret返回none。
 8         #func(args,kwargs)—>不行,相當於傳遞了兩個參數:一個元祖,一個字典。
 9     return call_func
10     
11 @set_func
12 def test1(num,*args,**kwargs):
13     print(‘——test1——- %d    ’ %num)
14     print(‘——test1——-   ’ , args)
15     print(‘——test1——- ’ ,kwargs )
16     return ‘ok’    #—-返回給上面的func(),然後return func—ret
17     
18 ret = test1(100)
19

 

5. 多個裝飾器對同一個函數進行裝飾:
 1 def add_qx(func):
 2     print(“——開始進行裝飾權限1———-”)
 3     def call_func(*args,**kwargs):  #
 4         print(‘這是權限驗證1’)
 5         return func(*args,**kwargs)
 6     return call_func
 7     
 8  9 def add_xx(func):
10     print(“——開始進行裝飾xx功能———-”)
11     def call_func(*args,**kwargs):  #
12         print(‘這是xx權限驗證’)
13         return func(*args,**kwargs)
14     return call_func
15     
16 @add_qx
17 @add_xx
18 def test1():
19     print(‘——test1——-’) 
20 21 test1()
22

 

首先執行第一個,但是第一個裝飾器下面不是函數(裝飾器原則:下面必須是函數,否則不執行),所以第一個函數先等待,等第二個裝飾器執行后形成函數在交給第一個裝飾器;所以運行結果是:

  1. 開始進行裝飾xx的功能,

  2. 開始進行裝飾權限1,

  3. 這是權限驗證1,

  4. 這是xx權限驗證,

  5. ——-test1——-,

——————裝飾器練習—————- 輸出格式:<td><h1>haha</h1></td>

 1 def set_func_1(func):
 2     def call_func():
 3         return ‘<h1>’ + func() + ’</h1> 4     return call_func
 5     
 6  7 def set_func_2(func):
 8     def call_func():
 9         return ‘<td>’ + func() + ’</td>10     return call_func
11     
12 @set_func_1()
13 @set_func_2()
14 def get_str():
15     return ‘haha’
16     
17 print(get_str()) 

18 最後執行的效果: <h1><td>haha</td></h1>
6. 用類對函數進行裝飾(了解):
 1 class Test(object):
 2     def __init__(self,func):
 3         self.func = fun
 4         
 5     def __call__(self):
 6         print(‘這裡是裝飾器的功能。。。。’)
 7         return self.func()
 8         
 9 @Test
10 def get_str():
11     return ‘haha’
12     
13 print(get_str())

 

以上就是裝飾器與閉包的全部內容,希望有所收穫,如果有錯誤,希望指出,感謝!!

 

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

【其他文章推薦】

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

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

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

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

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

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

常見的索引模型淺析

  索引的出現是為了提高數據庫查詢的效率,就像書的目錄一樣。常見的索引模型有哈希表、有序數組、B+樹。

  • 自適應哈希索引(AHI)

  哈希表是一種常見的數據結構,即通過哈希算法計算出一個数字在表中的位置,並將数字存入該表。哈希索引就是通過哈希表來實現的,一般情況下查找時間複雜度為O(1)。InnoDB會監控對錶上各索引頁的查詢,會自動根據訪問的頻率和模式為某些熱點頁建立哈希索引,所以又叫自適應哈希索引,訪問模式一樣指查詢的條件一樣。

  比如我們維護一張身份證信息和用戶姓名的表,需要根據身份證號查詢姓名,哈希索引大概是這樣的:

  哈希索引適合只有等值查詢的場景,例如select * from T where index_col = ‘##’。哈希索引是無序的,如果需要區間查詢,那就要把所有數據掃描一遍。

  • 有序數組索引

  有序數組在等值查詢和區間查詢場景中效率都很高,同樣用上面的表舉例,索引大概是這樣的:

  要查詢某條數據或者區間的時候,使用二分法時間複雜度為O(logN)。但如果需要在中間更新數據時,那麼就要移動後面所有的數據。有序數組索引只適用於靜態存儲引擎,比如保存2019年度學校所有學生信息。

  • B+樹索引

  B+樹是為磁盤或其他直接存取輔助設備設計的一種平衡查找樹。下面是一顆高度為2的B+樹,所有記錄都在恭弘=叶 恭弘子結點上順序存放,恭弘=叶 恭弘子結點通過指針相連。

  B+樹索引就是B+樹在數據庫中的實現,由於B+索引在數據庫中具有高扇出性,在數據庫中B+樹的高度一般為2~4層。查找某一鍵值的行記錄時最多只需要2~4次IO。以InnoDB的一個整数字段索引為例,這顆B+樹大概是1200叉樹,這裏N叉樹的N取決於數據塊的大小。高度為4的時候就可以存1200的3次方個值,大概為17億。考慮到樹根的數據塊總是在內存中,一個10億行的表上一個整数字段的索引,查找一個值最多只需要訪問3次磁盤。

  在InnoDB存儲引擎中,表是根據主鍵順序存放的。根據恭弘=叶 恭弘子結點內容,B+樹索引又分為聚簇索引和輔助索引。

    • 聚簇索引:按照每張表的主鍵構造一顆B+樹,恭弘=叶 恭弘子結點的key是主鍵值, value是該行的其他字段值,聚簇索引的恭弘=叶 恭弘子結點也稱為數據頁。
    • 輔助索引:恭弘=叶 恭弘子結點的內容是主鍵的值。

我們用一個例子來說明上面的概念,創建一張表,在字段k上有索引:

create table T(
id int primary key, 
k int not null, 
name varchar(16),
index (k))engine=InnoDB;

  表中R1~R5的(ID,k)值分別為(100,1)、(200,2)、(300,3)、(500,5)和(600,6),兩顆B+樹如下,可以明顯看到這兩個顆樹的區別。

  使用普通索引查詢時,例如 select * from T where k = 2,需要先搜索k索引樹,得到ID的值為200;再到ID索引搜索一次。這個過程就叫做回表,非主鍵索引查詢需要多搜索一棵樹。

  參考資料:《MySQL實戰45講》

       《MySQL技術內幕:InnoDB存儲引擎》第二版

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

實時web應用方案——SignalR(.net core)

何為實時

先從理論上解釋一下兩者的區別。

大多數傳統的web應用是這樣的:客戶端發起http請求到服務端,服務端返回對應的結果。像這樣:

 

也就是說,傳統的web應用都是客戶端主動發起請求到服務端。

那麼實時web應用呢?它不需要主動發起請求,服務端可以主動推送信息到客戶端。

舉栗子的話,實時聊天工具、web遊戲等都可以算是實時應用。

什麼是SignalR

如果想做一個實時應用,最好用web socket。很早以前我也寫過web socket的實現方式,但不夠全面,這裏再補上一篇。

來說說signalR,它是一款開源的實時框架,可以使用三種方式實現通信(long polling、server sent events、web socket)。它很好的整合了底層技術,讓我們可以不用關注底層技術實現而把精力聚焦在業務實現上。一個完整的signalR包括客戶端和服務端,服務端支持net core/net framework,還支持大部分客戶端,比如瀏覽器和桌面應用。

回落機制

為了兼容不同瀏覽器(客戶端)和服務端,signalR採用了回落機制,使得它可以根據情況協商使用不同的底層傳輸方式。假如瀏覽器不支持web socket,就自動降級使用sse,再不行就long polling。當然,也可以禁用這種機制,指定其中一種。

三種通信方式

long polling(長輪詢)

長輪詢是客戶端發起請求到服務端,服務器有數據就會直接返回。如果沒有數據就保持連接並且等待,一直到有新的數據返回。如果請求保持到一段時間仍然沒有返回,這時候就會超時,然後客戶端再次發起請求。

這種方式優點就是簡單,缺點就是資源消耗太多,基本是不考慮的。

server sent events(sse)

如果使用了sse,服務器就擁有了向客戶端推送的能力,這些信息和流信息差不多,期間會保持連接。

這種方式優點還是簡單,也支持自動重連,綜合來講比long polling好用。缺點也很明顯,不支持舊的瀏覽器不說,還只能發送本文信息,而且瀏覽器對sse還有連接數量的限制(6個)。

web socket

web socket允許客戶端和服務端同時向對方發送消息(也就是雙工通信),而且不限制信息類型。雖然瀏覽器同樣有連接數量限制(可能是50個),但比sse強得多。理論上最優先使用。

進入正題

開始之前,還需要了解RPC和Hub的概念。

RPC:全程Remote Procedure Call,字面意思遠程服務調用,可以像調用本地方法一樣調用遠程服務。前端可以調用後端方法,後端也可以調用前端方法。

Hub:基於RPC,接受從客戶端發過來的消息,也同時負責把服務端的消息發送給客戶端。客戶端可以調用Hub裏面的方法,服務端可以通過Hub調用客戶端裏面的方法。

好了,概念已經理解清楚了,接下來上代碼。

在項目里新增Hub類:

using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

namespace SignalRDemo.Server
{
    public class SignalRHub : Hub
    {
        /// <summary>
        /// 客戶連接成功時觸發
        /// </summary>
        /// <returns></returns>
        public override async Task OnConnectedAsync()
        {
            var cid = Context.ConnectionId;

            //根據id獲取指定客戶端
            var client = Clients.Client(cid);

            //向指定用戶發送消息
            await client.SendAsync("Self", cid);

            //像所有用戶發送消息
            await Clients.All.SendAsync("AddMsg", $"{cid}加入了聊天室");
        }
    }
}

為了讓外部可以訪問,我們還需要一個控制器。在控制器里聲明隨便建一個:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using SignalRDemo.Server;
using System.Threading.Tasks;

namespace SignalRDemo.Controllers
{
    public class HomeController : Controller
    {
        private readonly IHubContext<SignalRHub> _countHub;

        public HomeController(IHubContext<SignalRHub> countHub)
        {
            _countHub = countHub;
        }

        /// <summary>
        /// 發送信息
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task Send(string msg, string id)
        {
            await _countHub.Clients.All.SendAsync("AddMsg", $"{id}:{msg}");
        }
    }
}

再然後進入StartUp設置端點:

endpoints.MapHub<SignalRHub>("/hub");

完成以後,配置signalr客戶端:

setupConn = () => {
    conn = new signalR.HubConnectionBuilder()
        .withUrl("/hub")
        .build();

    conn.on("AddMsg", (obj) => {
        $('#msgPanel').append(`<p>${obj}</p>`);
    });

    conn.on("Finished", () => {
        conn.stop();
        $('#msgPanel').text('log out!');
    });

    conn.on("Self", (obj) => {
        $('#userId').text(obj);
    });

    conn.start()
        .catch(err => console.log(err));
}

要注意withUrl裏面的路徑就是之前設置好的端點。

運行效果:

 

 

 Hub還支持組操作,比如:

//將用戶添加到A組
await
Groups.AddToGroupAsync(Context.ConnectionId, "GroupA");
//將用戶踢出A組
await Groups.RemoveFromGroupAsync(Context.ConnectionId, "GroupA");
//向A組所有成員廣播消息
await Clients.Group("GroupA").SendAsync("AddMsg", "群組消息");

更多操作請參考官方文檔。

本文演示demo的源碼見git,地址:https://gitee.com/muchengqingxin/SignalRDemo.git

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

2020/6/11 JavaScript高級程序設計 DOM

DOM(文檔對象模型)是針對HTML和XML文檔的一個API(應用程序接口)。他描繪了一個層次化的節點樹,允許開發人員添加、移除和修改頁面的某一部分。

10.1 節點層次

DOM將任何HTML和XML文檔描繪成一個由多層節點構成的結構。

文檔節點(Document)是每個文檔的根節點。文檔節點只有一個子節點(HTML文檔中實終是<html>),我們稱之為文檔元素(每個文檔只能有一個文檔元素)。文檔元素是文檔的最外層元素,其他所有元素都包含在文檔元素中。

每一段標記都能通過樹中一個節點來表示,包括特性、文檔類型、註釋等,共有12種節點類型。這些類型都繼承自一個基類型

10.1.1 Node類型

 JavaScript中的所有節點類型都繼承自Node類型,所有的節點類型都共享相同的基本屬性和方法。

nodeType屬性:表明節點的類型(12種)

eg:Node.ELEMENT_NODE(1);  //元素節點

通過該屬性可以確定一個節點的類型,可以通過類型字面量判等,也可以通過数字值比較。

if (someNode.nodeType == Node.ELEMENT_NODE){  //在IE中無效
    alert("Node is element.");
}

if (someNode.nodeType == 1){  //適用於任何瀏覽器
    alert("Node is element.");
}

1. nodeName和nodeValue屬性

可以了解節點的具體信息。

對於元素節點,nodeName保存的始終是標籤名,nodeValue的值始終是null。

2. 節點關係

 

  • 屬性
1 childNodes屬性 保存NodeList對象(類數組,但不是數組),這個對象也有length屬性。可以通過方括號,也可以通過item()方法訪問節點。可以通過Array.prototype.slice()方法將其轉換為數組。
2 parentNode屬性 指向父節點。
3 previousSibling / nextSibling屬性 訪問同一列表中的其他屬性。即前一個和后一個同胞節點。
4 firstChild / lastChild屬性 指向childNodes列表的第一個和最後一個節點。
  • 方法
1 hasChildNodes() 檢驗是否存在子節點。存在時返回true。
2 ownerDocument() 指向整個文檔的文檔節點。方便直接到達頂端。

3. 操作節點

1 appendChild() 向childNodes列表的末尾添加一個節點。返回新的節點。由於任何一個節點不能同時出現在多個位置上,所以當傳入的節點是父節點的子節點時,這個節點會變成最後一個子節點。
2 insertBefore()

將節點插入到childNodes列表中一個特定的位置。接收兩個參數:要插入的節點和作為參照的節點。

插入節點后,被插入節點會成為參照節點的前一個同胞節點,同時被方法返回。省略參照節點時與appendChild()執行相同的操作。

3 replaceChild() 替換節點(複製所有的關係指針)。接收兩個參數:要插入的節點和要替換的節點。被替換的節點將從文檔樹中移除,但仍然在文檔中,只是沒有了自己的位置(指針)
4 removeChild() 移除節點。返回被移除的節點。同樣被移除的節點仍然在文檔中。

PS1:使用這幾個方法必須取得父節點(使用parentNode屬性)。

PS2:不是所有類型的節點都有子節點。在不支持子節點的節點上調用這些方法,會拋出錯誤。

4. 其他方法

1 cloneNode()

創建調用這個方法的節點的一個完全相同的副本。接受一個布爾值參數,表示是否執行深複製(true則執行深複製)。

  • 深複製:複製節點以及整個子節點樹
  • 淺複製:只複製節點本身

複製后返回的節點歸文檔所有,沒有為他指定父節點。要通過其他的方法把他加入到文檔中。

IE>9及其他瀏覽器會計入空白節點。

2 normalize()

處理文檔樹中的文本節點。

  • 出現文本節點不包含文本 => 刪除空白文本節點
  • 接連出現兩個文本節點 => 合併為一個文本節點

10.1.2 Document類型

Document類型表示文檔。

  • document對象是HTMLDocument的一個實例,表示整個HTML頁面。
  • document對象是window對象的一個屬性,可以作為全局對象來訪問。

Document節點的特徵:

  • nodeType的值為9;
  • nodeName的值是”#document”;
  • nodeValue和parentNode的值為null;
  • ownerDocument的值為null。

1. 文檔的子節點

1 DocumentType(最多一個) <!DOCTYPE>標籤,可以通過document.doctype屬性來訪問他的信息。
2 Element(最多一個)

文檔元素<html>。

通過documentElement屬性childNodes列表(在無處理指令的情況下是firstChild)訪問可快速找到html元素。

document.body屬性可以指向<body>元素(因為該元素使用頻率高,為了便於開發增添該屬性)。

3 ProcessingInstruction 表示處理指令。
4 Comment 註釋。

2. 文檔信息

作為HTMLDocument的實例,document對象還有一些特殊的屬性。

1 title <title>元素中的文本,是當前頁面的標題。
2 URL 完整的URL。
3 domain

頁面的域名。僅domain可以設置。但有一定的限制:

  • 不能設置為URL中不包含的域
  • 如果域名一開始是“鬆散的”(wrox.com),那麼就不能再將其設置回“緊繃的”(p2p.wrox.com)

作用:將每個頁面的document.domain設置為相同的值,就可以互相訪問對方包含的JavaScript對象了。(解決跨域問題)

4 referrer 鏈接到當前頁面的那個頁面的URL。在沒有來源頁面的情況下是空字符串。

3. 查找元素

1 getElementById() 參數為要取得元素的ID。找到返回該元素,沒有找到返回null。如果有多個id值相同,則只會返回第一個。
2 getElementByTagName() 參數為要取得元素的標籤名。返回元素的NodeList。在HTML文檔中,返回的是HTMLCollection對象。可以通過方括號或者item()方法來訪問。
3 nameItem() HTMLCollection對象的方法。通過元素的name屬性取得集合中的項(第一項)。同時HTMLCollection對象還支持按名稱訪問項。
4 getElementByName() 返回帶有給定name屬性的所有元素(一個HTMLCollection)。

4. DOM的一致性檢測

ducument.implementation屬性:提供關於實現了DOM 哪些部分的信息的對象。

 他有一個方法,hasFeature()。接收兩個參數:要檢測的DOM功能的名稱和版本號。如果支持給定名稱和版本的功能,則返回true。

檢驗結果true不意味着現實與規範一致,最好除了檢測hasFeature()之外,同時使用能力檢測

5. 文檔寫入

write() / writeln():接受一個字符串,即寫入輸出流中的文本。write()會原樣寫入,writeln()會在字符串末尾添加一個換行符(\n)。這兩個方法可以向頁面中動態的加入內容。

//在頁面加載過程中輸出當前的日期和時間
document.write("<strong>" + (new Date()).toString() + "</strong>");

同時還可以用來動態的包含外部資源,例如JavaScript文件等。

document.write("<script type=\"text/javascript\" src=\"file.js\"> + "<\/script>");

PS:由於不能直接包含字符串”</script>”(這樣會導致該字符串被解釋為腳本的結束,後面的代碼無法運行),所以要將這個字符串分開寫。

在頁面被呈現的過程中,會直接輸出內容。如果在文檔加載結束后(window.onload)再調用write(),那麼輸出的內容會重寫整個頁面。

方法open()close()分別用於打開和關閉網頁的輸出流。

10.1.3 Element類型

Element類型提供了對元素標籤名、子節點及特性的訪問。Element節點具有以下特徵:

  • nodeType值為1;
  • nodeName的值為元素的標籤名;
  • nodeValue的值為null;
  • parentNode可能是Document或Element;

tagName屬性:返回訪問元素的標籤名(與nodeName相同)。 => 在HTML中標籤名始終以全部大寫表示,需要檢驗標籤類型時最好調用toLowerCase()方法。

1. HTML元素

所有HTML元素都由HTMLElement類型表示。HTMLElement類型直接繼承自Element並添加了一些屬性。

  • id,元素在文檔中的唯一標識;
  • title,有關元素的附加說明信息,一般通過工具提示條显示出來;
  • dir,語言的方向(”ltr”,即left-to-right)或“rtl”;
  • className,與元素的class特性對應,為元素制定的CSS類。

2. 取得特性

 

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

【其他文章推薦】

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

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

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

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

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

IOT設備SmartConfig實現

一般情況下,IOT設備(針對wifi設備)在智能化過程中需要連接到家庭路由。但在此之前,需要將wifi信息(通常是ssid和password,即名字和密碼)發給設備,這一步驟被稱為配網。移動設備如Android、iOS等扮演發送wifi信息的角色,簡單來說就是移動應用要與IOT設備建立通信,進而交換數據。針對配網這一步驟,市面上一般有兩種做法:

  • AP連接方式:IOT設備發出AP(Access Point,可理解為路由器,可發出wifi)信息;移動設備STA(Station,可以連接wifi)連接到IOT設備AP,接着就可以發送wifi(家庭路由的wifi)信息給設備了。另外,也可互換角色,及移動設備釋放熱點,IOT設備進行連接。
  • SmartConfig(一鍵配置)方式:不需要建立連接,移動設備將wifi信息(需提前獲取)寫入數據包,組播循環發出此數據包;IOT設備處於監聽所有網絡的模式,接收到UDP包后解析出wifi信息拿去連網。

可以發現,SmartConfig不需建立連接,步驟較少,實現起來也較容易,並且用戶也無需進行過多的操作。本文的IOT設備基於ESP32開發板,解釋原理及實現如何通過Android APP發出UDP包實現SmartConfig。知識點:計算機網絡、UDP、組播、DatagramSocket

一、網絡知識回顧

計算機網絡分層結構如下:

  • 應用層:體系中的最高層。任務是通過應用程序間的交互來完成特定網絡應用。不同的網絡應用對應不同的協議:如HTTPDNSSMTP。其交互的數據單元稱為報文。
  • 運輸層:複雜向兩台主機中進程直接的通信提供通用的數據傳輸服務,使用端口作為向上傳遞的進程標識,主要有TCP和UDP。
  • 網絡層:負責為分組交換網絡上的不同主機提供通信服務,使用IP協議。
  • 網絡接口層:包括數據鏈路層和物理層,傳輸單位分別是幀和比特。

1. IP協議

IP(Internet Protocol)協議是網絡層的主要協議。其版本有IPv4(32位)、IPv6(128位)。與IP協議配套使用的還有地址解析協議(ARP)、網際控制報文協議(ICMP,重要應用即常見的PING,測試連通性)、網際組管理協議(IGMP)。

IP數據報格式,由首部和數據部分兩部分組成:

IP地址分類如下:

1.1 兩級IP地址

IP地址是每一台主機唯一的標識符,由網絡號和主機號組成。A、B、C三類均為單播地址(一對一),D類為多播地址(一對多)。

1.2 三級IP地址

在兩級中新增了子網號字段,也稱為劃分子網。其方法是從主機號借用若干位作為子網號。

子網掩碼:是一個網絡或子網的重要屬性,可通過子網掩碼計算出目的主機所處於哪一個子網。若沒有劃分子網,則使用默認子網掩碼(A類255.0.0.0、B類255.255.0.0、C類255.255.255.0)

1.3 無分類編址

無分類編址(CIDR)也稱為構造超網,使用網絡前綴+主機號規則,並使用斜線標明前綴位數,如:

128.15.34.77/20
1.4 IP多播

多播又稱為組播,提供一對多的通信,大大節約網絡資源。IP數據報中不能寫入某一個IP地址,需寫入多播組的標識符(需要接收的主機與此標識符關聯)。D類地址即為多播組的標識符,所以多播地址(D類)只能作為目的地址。分為本局域網上的硬件多播、互聯網多播兩種。

多播使用到的協議

  • IGMP(網際組管理協議):讓連接在本地局域網上的多播路由器(能夠運行多播協議的路由器)知道本局域網上是否有主機(進程)參加或退出了某個多播組。
  • 多播路由選擇協議:用於多播路由器之間的協同工作,以便讓多播數據報以最小的代價傳輸。

2. UDP協議

運輸層向上面的應用層提供通信服務,通信的端點並不是主機而是主機中進程,使用協議端口號標識進程(如HTTP為80)。UDP協議是運輸層中重要的兩個協議之一。

  • UDP是無連接的
  • UDP使用盡最大努力交付,不保證可靠交付
  • UDP是面向報文的
  • UDP沒有擁塞控制
  • UDP支持一對一,一對多,多對一和多對多
  • UDP首部開銷小

二、Java中的UDP

1. Socket

socket是在應用層和傳輸層之間的一個抽象層,它把TCP/IP層複雜的操作抽象為幾個簡單的接口供應用層調用已實現進程在網絡中通信。簡單來說,socket是一種接口,對傳輸層(TCP/UPD協議)進行了的封裝。

socket通信

  • TCP socket:需建立連接,TCP三次握手,基於流的通信(InputStrea和OutputStream)
  • UDP socket:無需建立連接,基於報文的通信。可以組播的形式發出報文,適合本場景中的配網步驟。

2. Java中的socket

2.1 類解釋

Java為Socket編程封裝了幾個重要的類(均為客戶端-服務端模式):

Socket
實現了一個客戶端socket,作為兩台機器通信的終端,默認採用TCP。connect()方法請求socket連接、getXXXStream()方法獲取輸入/出流、close()關閉流。

ServerSocket
實現了一個服務器的socket,等待客戶端的連接請求。bind()方法綁定一個IP地址和端口、accept()方法監聽並返回一個Socket對象(會阻塞)、close()關閉一個socket

SocketAddress # InetSocketAddress
前者是一個抽象類,提供了一個socket地址,不關心傳輸層協議;後者繼承自前者,表示帶有IP地址和端口號的socket地址。

DatagramSocket
實現了一個發送和接收數據報的socket,使用UDP。send()方法發送一個數據報(DatagramPacket)、receive()方法接收一個數據報(一直阻塞接至收到數據報或超時)、close()方法關閉一個socket。

DatagramPacket
使用DatagramSocket時的數據報載體。

2.2 UDP實例

SmartConfig採用UDP實現,所以在前述知識的基礎下,先編寫一個例子熟悉java udp的使用,首先建立服務端的代碼:

public class UDPServer {
    /**
     * 設置緩衝區的長度
     */
    private static final int BUFFER_SIZE = 255;
    /**
     * 指定端口,客戶端需保持一致
     */
    private static final int PORT = 8089;

    public static void main(String[] args) {
        DatagramSocket datagramSocket = null;
        try {
            datagramSocket = new DatagramSocket(PORT);
            DatagramPacket datagramPacket = new DatagramPacket(new byte[BUFFER_SIZE], BUFFER_SIZE);
            while (true) {
                // 接收數據報,處於阻塞狀態
                datagramSocket.receive(datagramPacket);
                System.out.println("Receive data from client:" + new String(datagramPacket.getData()));
                // 服務器端發出響應信息
                byte[] responseData = "Server response".getBytes();
                datagramPacket.setData(responseData);
                datagramSocket.send(datagramPacket);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (datagramSocket != null) {
                datagramSocket.close();
            }
        }
    }
}

客戶端發出數據報:

public class UDPClient {
    /**
     * 指定端口,與服務端保持一致
     */
    private static final int PORT = 8089;
    /**
     * 超時重發時間
     */
    private static final int TIME_OUT = 2000;
    /**
     * 最大重試次數
     */
    private static final int MAX_RETRY = 3;

    public static void main(String[] args) throws IOException {
        try {
            byte[] sendMsg = "Client msg".getBytes();
            // 創建數據報
            DatagramSocket socket = new DatagramSocket();
            // 設置阻塞超時時間
            socket.setSoTimeout(TIME_OUT);
            // 創建server主機的ip地址(此處使用了本機地址)
            InetAddress inetAddress = InetAddress.getByName("192.168.xxx.xxx");
            // 發送和接收的數據報文
            DatagramPacket sendPacket = new DatagramPacket(sendMsg, sendMsg.length, inetAddress, PORT);
            DatagramPacket receivePacket = new DatagramPacket(new byte[sendMsg.length], sendMsg.length);
            // 數據報文可能丟失,設置重試計數器
            int tryTimes = 0;
            boolean receiveResponse = false;
            // 將數據報文發送出去
            socket.send(sendPacket);
            while (!receiveResponse && (tryTimes < MAX_RETRY)) {
                try {
                    // 阻塞接收數據報文
                    socket.receive(receivePacket);
                    // 檢查返回的數據報文
                    if (!receivePacket.getAddress().equals(inetAddress)) {
                        throw new IOException("Unknown server's data");
                    }
                    receiveResponse = true;
                } catch (InterruptedIOException e) {
                    // 重試
                    tryTimes++;
                    System.out.println("TimeOut, try " + (MAX_RETRY - tryTimes) + " times");
                }
            }
            if (receiveResponse) {
                System.out.println("Receive from server:" + new String(receivePacket.getData()));
            } else {
                System.out.println("No data!");
            }
            socket.close();
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

* 發現客戶端收到的數據被截斷了,這是因為沒有重置接收包的長度,在服務端datagramPacket.setLength()可解決。

三、SmartConfig

根據前面的socket相關應用,基本想到如何實現一鍵配置。在實際應用中,原理一樣,只是增加了組播(這一點需要和IOT設備端共同確定,數據的格式也需協定)。在實現中,需要針對不同IP組播地址發出循環的UDP報文,增加設備端接收到的可能性;同時APP也要開啟服務端程序監聽發出數據報的響應,以此更新UI或進行下一步的數據通信。相關核心代碼如下:

// 對每一個組播地址循環發出報文 
while (!mIsInterrupt && System.currentTimeMillis() - currentTime < mParameter
         .getTimeoutGuideCodeMillisecond()) {
     mSocketClient.sendData(gcBytes2,
             mParameter.getTargetHostname(),
             mParameter.getTargetPort(),
             mParameter.getIntervalGuideCodeMillisecond());
     // 跳出條件,發出UDP報文達到一定時間
     if (System.currentTimeMillis() - startTime > mParameter.getWaitUdpSendingMillisecond()) {
         break;
     }
}

組播地址設置:

public String getTargetHostname() {
    if (mBroadcast) {
        return "255.255.255.255";
    } else {
        int count = __getNextDatagramCount();
        return "234." + (count + 1) + "." + count + "." + count;
    }
}

完整代碼省略(利益相關,代碼匿了^_^),基本思路很簡單。最終的實現是IOT設備收到UDP發出的wifi信息,並以此成功連接wifi,連接服務器,進而綁定賬號。

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

mybatis緩存之一級緩存(二)

這篇文章介紹下mybatis的一級緩存的生命周期

一級緩存的產生

一級緩存的產生,並不是看mappper的xml文件的select方法,看下面的例子

mapper.xml

    <select id="getById" resultType="entity.TempEntity">
       select * from  temp where id = #{id}
    </select>

test

    @Test
    public  void testSelectAsUpdate() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = build.openSession();
        sqlSession.update("dao.Temp03Dao.getById", 1);
        sqlSession.update("dao.Temp03Dao.getById", 1);
    }

執行結果

2020-06-26 17:33:27,899 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 17:33:27,922 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
2020-06-26 17:33:27,923 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 17:33:27,923 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)

我們可以看到執行了2次查詢。說明並沒有產生緩存。說明和sqlsession調用的方法是有關係的

只有調用上圖中的方法才會產生一級緩存

一級緩存的銷毀

1.關閉session

這個是根據debug看到的一級緩存的最終結構。下面是整個依賴的類圖

test

 @Test
    public  void test() throws IOException, NoSuchFieldException, IllegalAccessException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = build.openSession();
        TempEntity tempEntity1 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
        logger.info(tempEntity1);

        Field executorField = sqlSession.getClass().getDeclaredField("executor");
        executorField.setAccessible(true);
        CachingExecutor  cachingExecutor = (CachingExecutor) executorField.get(sqlSession);

        Field declaredField = cachingExecutor.getClass().getDeclaredField("delegate");
        declaredField.setAccessible(true);
        SimpleExecutor simpleExecutor  = (SimpleExecutor) declaredField.get(cachingExecutor);

        Field localCacheField = simpleExecutor.getClass().getSuperclass().getDeclaredField("localCache");
        localCacheField.setAccessible(true);
        PerpetualCache perpetualCache = (PerpetualCache) localCacheField.get(simpleExecutor);

        Field cacheField = perpetualCache.getClass().getDeclaredField("cache");
        cacheField.setAccessible(true);
        Map<Object,Object> map= (Map<Object, Object>) cacheField.get(perpetualCache);
        logger.info("緩存關閉前");
        for (Map.Entry<Object,Object> objectObjectEntry:map.entrySet()){
            logger.info(objectObjectEntry.getKey() + "===" + objectObjectEntry.getValue());
        }
        sqlSession.close();
        logger.info("緩存關閉后");

        for (Map.Entry<Object,Object> objectObjectEntry:map.entrySet()){
            logger.info(objectObjectEntry.getKey() + "=" + objectObjectEntry.getValue());
        }
    }

運行結果

2020-06-26 17:38:52,777 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 17:38:52,801 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
2020-06-26 17:38:52,824 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
2020-06-26 17:38:52,824 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
2020-06-26 17:38:52,825 INFO [TempTest] - 緩存關閉前
2020-06-26 17:38:52,826 INFO [TempTest] - -1654591322:461730790:dao.Temp03Dao.getById:0:2147483647:select * from  temp where id = ?:1:dev===[TempEntity{id=1, value1='11111', value2='aaaaa'}]
2020-06-26 17:38:52,827 INFO [TempTest] - 緩存關閉后

可以看到session關閉后,緩存就不存在了

2.Commit提交

test

    @Test
    public  void testCommit() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = build.openSession();
        TempEntity tempEntity1 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
        logger.info(tempEntity1);
        sqlSession.commit();
        TempEntity tempEntity2 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
        logger.info(tempEntity2);
        logger.info(tempEntity1 == tempEntity2);

    }

運行結果

2020-06-26 17:40:40,821 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 17:40:40,846 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
2020-06-26 17:40:40,862 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
2020-06-26 17:40:40,862 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
2020-06-26 17:40:40,863 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 17:40:40,863 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
2020-06-26 17:40:40,864 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
2020-06-26 17:40:40,864 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
2020-06-26 17:40:40,864 INFO [TempTest] - false

說明sqlSession.commit時會清空緩存

3.Rollback

test

    @Test
    public  void testRollback() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = build.openSession();
        TempEntity tempEntity1 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
        logger.info(tempEntity1);
        sqlSession.rollback();
        TempEntity tempEntity2 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
        logger.info(tempEntity2);
        logger.info(tempEntity1 == tempEntity2);

    }

執行結果

2020-06-26 17:42:23,793 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 17:42:23,833 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
2020-06-26 17:42:23,843 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
2020-06-26 17:42:23,843 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
2020-06-26 17:42:23,844 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 17:42:23,844 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
2020-06-26 17:42:23,845 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
2020-06-26 17:42:23,845 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
2020-06-26 17:42:23,845 INFO [TempTest] - false

sqlSession.rollback()也會清空緩存

4.update更新

這裡是在第一次查詢后,緊接着進行update操作。這裏與表無關。就是操作其它表,也會清空緩存。

test

    @Test
    public  void testForUpdate() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = build.openSession();
        TempEntity tempEntity1 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
        logger.info(tempEntity1);
        sqlSession.update("dao.Temp03Dao.updateById", 1);
        TempEntity tempEntity2 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
        logger.info(tempEntity2);
        logger.info(tempEntity1 == tempEntity2);

    }

運行結果

2020-06-26 17:45:43,997 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 17:45:44,034 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
2020-06-26 17:45:44,048 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
2020-06-26 17:45:44,049 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
2020-06-26 17:45:44,049 DEBUG [dao.Temp03Dao.updateById] - ==>  Preparing: update temp set value1 = 'ffffff' where id = ? 
2020-06-26 17:45:44,049 DEBUG [dao.Temp03Dao.updateById] - ==> Parameters: 1(Integer)
2020-06-26 17:45:44,050 DEBUG [dao.Temp03Dao.updateById] - <==    Updates: 1
2020-06-26 17:45:44,051 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 17:45:44,051 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
2020-06-26 17:45:44,052 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
2020-06-26 17:45:44,053 INFO [TempTest] - TempEntity{id=1, value1='ffffff', value2='aaaaa'}
2020-06-26 17:45:44,053 INFO [TempTest] - false

這裏還是在一個session會話中。記得之前有人給我說只要在一個session會話中,執行update不會清空緩存。這裏的代碼就證明了

5.clearCache 主動清除

test

    @Test
    public  void testClearCatch() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = build.openSession();
        TempEntity tempEntity1 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
        logger.info(tempEntity1);
        sqlSession.clearCache();
        TempEntity tempEntity2 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
        logger.info(tempEntity2);
        logger.info(tempEntity1 == tempEntity2);

    }

運行結果

2020-06-26 17:48:42,085 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 17:48:42,110 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
2020-06-26 17:48:42,124 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
2020-06-26 17:48:42,124 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
2020-06-26 17:48:42,125 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
2020-06-26 17:48:42,125 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
2020-06-26 17:48:42,126 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
2020-06-26 17:48:42,126 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
2020-06-26 17:48:42,126 INFO [TempTest] - false

一級緩存 臟讀問題

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

【其他文章推薦】

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

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

高雄鼓勵換電動機車 最高補助額達 2.3 萬

高雄市機車總數達 201 萬輛,成為都市的污染源之一,為了鼓勵機車族更換電動機車,高雄市政府環保局推動補助政策,換車最高可獲得 2.3 萬元的補助。   高雄市政府環境保護局空噪科長鄭嵐 26 日下午表示,根據交通部機動車輛統計資料,高雄市截至 103 年 12 月底掛牌的使用中機車,總數約 201 萬輛,為鼓勵民眾汰舊換購電動車輛,高雄市環保局持續推動淘汰補助方案,除了經濟部工業局補助純購電動機車,機車族可獲得補助 7,200 元到 10,000 元外,若搭配汰舊二行程機車,再加碼補助至 17,400 元到 23,000 元。   他表示,同時,為了提升友善充電環境,高雄環保局近年來也積極推動充電站架設,除了原有公用及私有充電站,103 年完成社區大樓與電動機車經銷商等 25 座,共有充電站 217 站,讓高雄市騎乘電動車市民處處皆有電可充,建構低碳生活圈。

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

【其他文章推薦】

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

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

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

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

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

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

中興攜手國家電網組合資公司 建成都無線汽車充電站

中興子公司中興新能源汽車與國家電網四川省電力公司達成協議,將在成都組建合資公司。此合資公司在傳統集中式充電模式的基礎上分佈式建設無線汽車充電站,第一批支援無線充電的社區巴士同日會在成都正式投入運營。   除社區巴士外,中興還將逐步推廣支持無線充電的大型公車專案,而房車的無線充電技術亦已成熟,用戶如有條件和需要,可對房車和停車廠進行改造,即可享有無線充電功能。

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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