中國科學院率先國際發布2018全球30公尺解析度森林覆蓋分佈圖

摘錄自2019年11月20日中新網北京報導

中國科學院空天資訊創新研究院(中科院空天院)今(20)日發佈消息,該院團隊經過科技攻關,近日在國際上率先獲得並發佈2018年全球30公尺解析度森林覆蓋分佈圖(Global Forest Cover Map,GFCM)。

中科院空天院介紹,該研究團隊經過近3年的科技攻關,基於美國陸地衛星系列衛星資料和中國國產高解析度衛星資料,構建了全球高精度森林和非森林樣本庫,利用機器學習和大資料分析技術實現全球森林覆蓋高精度自動化提取,在國際上率先獲得2018年全球30公尺解析度森林覆蓋分佈圖。

研究團隊利用隨機分層抽樣的方式在全球範圍選取精度驗證樣區(樣區的選擇兼顧不同地表覆蓋類型和森林類型分區)進行精度驗證,精度驗證結果表明,團隊所獲2018年全球30公尺解析度森林覆蓋分佈圖的總體精度約為90.94%,可以為相關機構、管理部門提供基礎資料和產品支撐,對於加強森林的管理和利用、應對全球變化以及實現與森林相關的可持續發展目標等具有十分重要的意義。

【其他文章推薦】

AVX代理商NICHICON代理商授權有哪幾家?

※哪家廠商儀器租賃較便宜,可彈性租期?

※怎麼檢測NBR手套規格是否有符合國家認証規定?

大樓隔熱紙施工分享說明,教你如何善用空間裝潢、設計 !

※買不起高檔茶葉,精緻包裝茶葉罐,也能撐場面!

塑膠射出模具射出成型過程大公開 !

無視巴黎氣候協定 全球化石燃料產量遠高於限制

摘錄自2019年11月20日中央通訊社報導

聯合國和頂尖研究團體今天(20日)發布報告警告,全球已規劃或準備進行的石油、天然氣和煤炭產量,將遠遠超越抑制全球暖化讓地球維持適合人居所需的產量目標。

聯合國環境規劃署(UN Environment Programme)和4個氣候變遷研究中心聯合發布報告指出,全球預計生產的化石燃料總量,較為了讓地表溫度較工業革命前水準升高不超過攝氏2度所容許的燃燒量,超出達50%。

若將地表升溫幅度限制在攝氏1.5度,則計劃中的化石燃料產量將較容許數量超出1倍多。2015年達成的巴黎氣候協定,要求將全球暖化限制在遠低於攝氏2度水準,可能的話僅升溫攝氏1.5度。

儘管截至目前全球僅升溫攝氏1度,但全世界已出現逐漸增強的致命熱浪、洪災和超級風暴,而超級風暴因海平面加速上升而破壞力更強大。研究人員警告,煤炭、石油和天然氣供應的「過度投資」,與未來數十年必須大幅縮減溫室氣體排放的目標,兩者直接相衝突。

聯合國去年發布的報告斷定,若要抑制地表升溫僅攝氏1.5度,則全球二氧化碳排放必須在2030年底前減少45%,並於2050年底前達到「淨零排放」。

斯德哥爾摩環境研究所(Stockholm Environment Institute)美國中心主任賴薩魯斯(Michael Lazarus)表示:「我們首次展現,巴黎(氣候協定的)溫度目標,和各國煤炭、石油和天然氣的生產計畫及政策,兩者間落差有多巨大。」

【其他文章推薦】

※找工作! 想知道堆高機駕駛日薪是多少嗎? 哪裡有職缺?幫你快速媒合

※隨時健康喝好水,高品質飲水機推薦,優質安全有把關

新北市轉軸代工廠商詢價平台?

防爆隔熱紙規格資訊說明

※好的茗茶,更需要密封性高的茶葉罐,才能留住香氣!

※【找人才】台北塑膠射出廠徵選技師,薪資優,福利佳

大自然工程師河狸將修築堤壩 助英格蘭抗水患

摘錄自2019年11月20日中央通訊社倫敦報導

業務涵蓋歷史古蹟與鄉村管理的英國保育組織「國家信託」(National Trust)今天(20日)宣布,預定明年初在英格蘭南部兩地施放天生會修築堤壩的歐亞河狸,協助對抗水患。其中一地的計畫經理伊爾德利(Ben Eardley)指出:「河狸修築的堤壩在乾季可儲水,此外還有助降低下游暴洪、減少河岸侵蝕,攔截淤泥也可改善水質。」

河狸素有「大自然工程師」美譽,牠創造的濕地環境可供小至昆蟲、大至野禽等許多物種棲息。這些河狸將生活在有柵欄隔離林地,專家將監測棲地變化。

「國家信託」計畫於2025年前讓2萬5000公頃土地重新成為大量野生動植物的棲地。英國氣象局(Met Office)資料顯示,英格蘭北部近幾週遭逢嚴重水患,部分地區創下有紀錄以來最潮濕秋季。英格蘭光是今天早上就有18起水患警報,另有58起可能淹水警告。

【其他文章推薦】

※有廠商專門客製化橡膠製品嗎?

噴霧洗滌塔實際應用案例分享

※票選推薦煮婦最愛手壓封口機,省荷包不犧牲品質

※選購空壓機需注意八大事項 !

※DIY自行施工建築隔熱紙,簡易教學大公開 !

※選擇好的茶葉罐,有效地保持茶葉的鮮度與風味!

中國衛健委發布10月報告 法定傳染病59萬餘例

摘錄自2019年11月20日中新社北京報導

中國國家衛生健康委員會今(20)日發佈資料顯示,2019年10月中國內地共報告法定傳染病597,610例,死亡2,147人。

消息稱,在10月報告的法定傳染病中,甲類傳染病無發病、死亡報告。此類傳染病也稱為強制管理傳染病,包括鼠疫、霍亂兩種。

同期,乙類傳染病中傳染性非典型肺炎、脊髓灰質炎、人感染高致病性禽流感、白喉和人感染H7N9禽流感無發病、死亡報告,其餘21種乙類傳染病共報告發病291,746例,死亡2,145人。乙類傳染病中,報告發病數居前5位的病種依次為病毒性肝炎、肺結核、梅毒、淋病以及痢疾,占乙類傳染病報告病例總數的92%。

丙類傳染病方面,共報告發病305,864例,死亡2人。丙類傳染病也稱為監測管理傳染病,包括流行性感冒、流行性腮腺炎、麻風病等11種。丙類傳染病報告發病數居前3位的病種依次為手足口病、其他感染性腹瀉病和流行性感冒,占丙類傳染病報告病例總數的91%。

【其他文章推薦】

示波器鮮為人知的使用技巧?

※各大百貨每波促銷贈品活動,限量知名LOGOL型資料夾,獨家販售中!!

飲水機皆有含淨水功能嗎?

※選購空壓機需注意八大事項 !

真空封口機該不該買?使用心得分享

大樓隔熱紙施工分享說明,教你如何善用空間裝潢、設計 !

保育有成 合歡山松雪樓驚見水鹿現身

摘錄自2019年11月17日公視報導

台灣水鹿現身合歡山公路附近,這次被拍到的地點,是在合歡山松雪樓的後方、往合歡東峰步道的山坡草原。由於野生水鹿沒有天敵,專家估計,目前全台的族群數量,在三到五萬隻上下。雖然這反映了保育工作的成功,但也會擔心,未來衍生人鹿衝突,或車禍事件。

從合歡山松雪樓到小風口一帶,在當地的工作人員或山友,最近常有機會巧遇野生台灣水鹿。這次被拍到成年公水鹿的地點,鄰近松雪樓後方往合歡東峰步道,下方就是台14線甲公路。

東海大學生科系教授林良恭表示,「我覺得應該不是食物來源(靠近人類),應該是牠數量增加,所以牠到處擴充,造成整個水鹿就很容易在公路邊邊活動。以合歡山講的話,它又沒有道路管制,它晚上車子還是可以動,那如果一不小心發生車禍的話,真的是很麻煩的事情。」

林良恭指出,目前野生台灣水鹿族群大約在三到五萬之間,牠是台灣特有亞種,國內最大型的草食動物,屬於珍貴稀有保育類動物,大多棲息在海拔一千公尺以上林地,主要分佈在中央山脈及花東山區,9-11月是發情期,而玉山國家公園近年也有林木,遭水鹿啃咬,造成「環狀剝皮」死亡情形。

專家指出,台灣水鹿沒有天敵,有族群擴張的情形,而在合歡山台14甲線的水鹿現蹤,提醒相關單位,未來要注意安全防護問題,免得發生路殺或人車安全事故。

【其他文章推薦】

※專業客製化禮物、贈品設計,辦公用品常見【L夾】搖身一變大受好評!!

示波器探測執行效能最佳化的8大秘訣

※選用哪種桶裝水,外宿露營超方便?

※(全省)堆高機租賃保養一覽表

※DIY自行施工建築隔熱紙,簡易教學大公開 !

※票選推薦煮婦最愛手壓封口機,省荷包不犧牲品質

韓媒:台灣值得被納入全球氣候變遷體系

摘錄自2019年11月19日中央社報導

韓國「韓民族新聞」和英文報The Korea Times今(19日)同步刊載中華民國行政院環保署長張子敬署名的投書專文,呼籲國際社會接納台灣成為全球氣候變遷體系的一員。張子敬在分別以Taiwan: valuable partner in fighting climate change和「台灣也應當參與全球氣候變遷協約」為標題,向韓國報紙投書闡述台灣欲加入全球氣候變遷體系的立場。

專文指出,台灣整合中央相關部會工作,制訂「國家氣候變遷調適行動方案」,從災害、維生基礎設施、水資源、國土安全、海岸、能源及產業、農業、健康等8個面向,建構因應氣候變遷的韌性體制;在醫療領域上特別著重強化醫療衛生及防疫系統預防、減災、應變及復原能力,維護全民健康並優先保障弱勢住民。

另外在生態保育領域上,將維護農業生產資源及生物多樣性,加強監測與預警機制、強化天然災害救助及保險體系、整合科技提升農林漁牧產業抗逆境能力,並完善自然保護區經營管理、建構長期生態監測體系、強化物種及基因的多樣性保存與合理利用,以確保糧食安全並建構適應氣候風險的永續農業。

專文認為,台灣因政治成見被排除在國際組織之外,是相當不公平的,非但不符合氣候公約籲請所有國家對全球氣候變遷進行廣泛合作的精神,忽視巴黎協定強調「氣候正義」及呼籲各國採取氣候行動的重要性,更違背聯合國憲章宗旨,也弱化國際架構而對世界造成傷害。

專文強調,面對國際社會,台灣是負責任、肯貢獻的真誠朋友,樂於分享在環境治理制度、防災預警系統、能源效率提升技術、科技創新運用等相關領域的經驗,台灣努力希望能讓世界更美好,而台灣也真的值得被納入全球氣候變遷體系的一員。

【其他文章推薦】

※影響示波器測試準確度的五大因素

※無毒橡膠墊片哪裡買的到?

※飲用桶裝水到底安不安全? 破解錯誤迷思!

※十大封口機人氣排行榜-烘焙必備幫手!

連續封口機購物網-不怕你比價,就怕你買貴!

※買不起高檔茶葉,精緻包裝茶葉罐,也能撐場面!

【其他文章推薦】

※影響示波器測試準確度的五大因素

※無毒橡膠墊片哪裡買的到?

※飲用桶裝水到底安不安全? 破解錯誤迷思!

※十大封口機人氣排行榜-烘焙必備幫手!

連續封口機購物網-不怕你比價,就怕你買貴!

※買不起高檔茶葉,精緻包裝茶葉罐,也能撐場面!

mybatis精講(三)–標籤及TypeHandler使用

目錄

話引

  • 前兩張我們分別介紹了Mybatis環境搭建及其組件的生命周期。這些都是我們Mybatis入門必備技能。有了前兩篇的鋪墊我們今天就來深入下Mybatis, 也為了填下之前埋下的坑。

XML配置標籤

概覽


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration> 
    <!--引入外部配置文件-->
    <properties resource=""/>
    <!--設置-->
    <settings/>
    <!--定義別名-->
    <typeAliases>
        <package name=""/>
    </typeAliases>
    <!--類型處理器-->
    <typeHandlers/>
    <!--對象工廠-->
    <objectFactory/>
    <!--插件-->
    <plugins/>
    <!--定義數據庫信息,默認使用development數據庫構建環境-->
    <environments default="development">
        <environment id="development">
            <!--jdbc事物管理-->
            <transactionManager type="JDBC"/>
            <!--配置數據庫連接信息-->
            <dataSource type="POOLED"/>
        </environment>
    </environments>
    <!--數據庫廠商標識-->
    <databaseIdProvider/>
    <mappers/>
</configuration>

  • 上面模板列出了所有xml可以配置的屬性。這裏plugins是一個讓人哭笑不得的東西。用的好是利器,用的不好就是埋坑。接下來我們來看看各個屬性的作用

properties

  • 該標籤的作用就是引入變量。和maven的properties一樣。在這裏定義的變量或者引入的變量,在下面我們是可以童工${}使用的。

子標籤property


<properties>
  <property name="zxhtom" value="jdbc:mysql://localhost:3306/mybatis"/>
</properties>

<dataSource type="POOLED">
<property name="driver" value="${zxhtom}"/>
<dataSource>
  • 上述的配置就可以直接使用zxhtom這個變量。

resource

  • 除了上述方法我們還可以通過引入其他properties文件,就可以使用文件里的配置變量了。

<properties resource="mybatis.properties"/>

程序注入

  • 最後還有一種我們在構建SqlSessionFactory的時候重新載入我們的Properties對象就可以了。另外三者的優先級是從低到高

settings


configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  • 上面代碼是我們在XMLConfigBuilder解析settings標籤的代碼。從這段代碼中我們了解到settings子標籤。
參數 功能 可選值 默認值
autoMappingBehavior 指定Mybatis應如何自動映射列到字段上。
NONE : 表示取消自動映射
PARTIAL:只會自動映射沒有定義嵌套結果集映射的結果集
FULL : 自動映射任意複雜的結果集
NONE、PARTIAL、FULL PARTIAL
autoMappingUnknownColumnBehavior 指定識別到位置列或屬性的時間
NONE : 什麼都不做
WARNING:日誌會報警(前提是日誌設置了显示權限)
FAILING : 拋出異常。
NONE, WARNING, FAILING NONE
cacheEnabled 該配置影響的所有映射器中配置的緩存的全局開關 true|false true
proxyFactory 指定Mybatis創建具有延遲加載能力的對象所用到的代理工具未指定時將自動查找 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING not set
lazyLoadingEnabled 延時加載全局開關
開啟時:級聯對象會延時加載;級聯標籤中可以通過fetchType來定製覆蓋此選項
true|false false
aggressiveLazyLoading 啟用時:對任意延遲屬性的調用會使帶有延遲加載屬性的對象分層性質完整加載,反之按需加載 true|false true
multipleResultSetsEnabled 是否允許單一語句返回多結果集 true|false true
useColumnLabel 確切的說當映射找不到參數時會使用列標籤(數據庫列名)代替別名去映射 true|false true
useGeneratedKeys 允許 JDBC 支持自動生成主鍵,需要驅動兼容。 如果設置為 true 則這個設置強制使用自動生成主鍵,儘管一些驅動不能兼容但仍可正常工作(比如 Derby) true|false false
defaultExecutorType 配置默認的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句(prepared statements); BATCH 執行器將重用語句並執行批量更新 SIMPLE REUSE BATCH SIMPLE
defaultStatementTimeout 設置超時時間,決定驅動等待數據庫響應的秒數 整數 null
defaultFetchSize 設置數據庫resultSet讀取數據方式,默認全部加載進內存,設置該屬性可以設置一次性讀多少條數據進內存 整數 null
mapUnderscoreToCamelCase 是否開啟自動駝峰命名規則(camel case)映射,即從經典數據庫列名 A_COLUMN 到經典 Java 屬性名 aColumn 的類似映射。 true|false false
safeRowBoundsEnabled -允許在嵌套語句中使用分頁 true|false false
localCacheScope 一級緩存。mybatis默認對同一個sqlsession中數據是共享的。一個sqlsession調用兩次相同查詢實際只會查詢一次。就是因為該屬性為SESSION , STATEMENT則針對的是每一條sql SESSION|STATEMENT SESSION
jdbcTypeForNull 當沒有為參數提供特定的jdbc類型時,為空值則指定JDBC類型。在新增時我們沒有設置參數,這個時候就會根據此參數天長。加入設置VARCHAR,那麼我們新增的數據沒傳參數則為空字符 NULL|VARCHAR|OTHER OTHER
lazyLoadTriggerMethods 指定具體方法延時加載 方法 equals,clone,hashCode,toString
safeResultHandlerEnabled 允許在嵌套語句中使用分頁 true|false true
defaultScriptingLanguage 動態SQL生成的默認語言 org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver
defaultEnumTypeHandler mybatis默認的枚舉處理類
callSettersOnNulls 指定當結果集中值為null的時候是否調用映射對象的setter(put)方法。
useActualParamName 允許使用他們的編譯后名稱來映射,3.4.2后默認true.在xml中#{0}則報錯。設置為false,則#{0}代表第一個參數#{n}第n個 true|false true
returnInstanceForEmptyRow 當返回行的所有列都是空時,MyBatis默認返回 null。 當開啟這個設置時,MyBatis會返回一個空實例。 請注意,它也適用於嵌套的結果集 (如集合或關聯)。(新增於 3.4.2) true|false false
logPrefix 指定 MyBatis 增加到日誌名稱的前綴。

別名

  • 別名是mybatis為我們項目中類起的一個名字,類名往往會很長所以別名就方便我們平時的開發。Mybatis為我們內置了一些類的別名:byte、short、int、long、float、double、boolean、char等基礎類型的別名。還有其的封裝類型、String,Object,Map,List等等常用的類。
    org.apache.ibatis.type.TypeAliasRegistry這個類中幫我們內置了別名。可以看下。自定義別名也是通過這個類進行註冊的。我們可以通過settings中typeAliases配置的方式結合@Alias。或者掃描包也可以的。

TypeHandler

  • 這個接口就四個方法

public interface TypeHandler<T> {

  /**
   * 設置參數是用到的方法
   */
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}
  • 可以理解成攔截器。它主要攔截的是設置參數和獲取結果的兩個節點。這個類的作用就是將Java對象和jdbcType進行相互轉換的一個功能。同樣的在org.apache.ibatis.type.TypeHandlerRegistry這個類中mybatis為我們提供了內置的TypeHandler。基本上是對於基本數據和分裝對象的轉換。
  • 下面我們隨便看一個TypeHandler處理細節
public class BooleanTypeHandler extends BaseTypeHandler<Boolean> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setBoolean(i, parameter);
  }

  @Override
  public Boolean getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    boolean result = rs.getBoolean(columnName);
    return !result && rs.wasNull() ? null : result;
  }

  @Override
  public Boolean getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    boolean result = rs.getBoolean(columnIndex);
    return !result && rs.wasNull() ? null : result;
  }

  @Override
  public Boolean getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    boolean result = cs.getBoolean(columnIndex);
    return !result && cs.wasNull() ? null : result;
  }
}
  • setParameter是PreparedStatement進行設置成boolean類型。getResult分別通過三種不同方式獲取。在這些方法里我們可以根據自己也無需求進行控制。常見的控制是枚舉的轉換。傳遞參數過程可能是枚舉的name,但是傳遞到數據庫中要枚舉的index.這種需求我們就可以在TypeHandler中實現。我們書寫的typeHandler之後並不能被識別,還需要我們在resultMap中的result標籤中通過typeHandler指定我們的自定義Handler.

自定義TypeHandler

  • 承接上文我們說道枚舉的轉換。下面我們還是已學生類為例。學生中性別之前是boolean類型。現在我們採用枚舉類型。但是數據庫中存的還是數據,01.

EnumTypeHandler

  • 在TypeHandlerRegister類中申明了默認的枚舉類處理器是private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
  String s = rs.getString(columnName);
  return s == null ? null : Enum.valueOf(type, s);
}
  • 我們通過這個方法可以看出,這個枚舉處理器適合已枚舉名稱存儲的方式

EnumOrdinalTypeHandler

  • 在Enum中還有一個屬性oridinal。這個表示枚舉中的索引。然後我們通過查看Mybatis提供的處理器發現有個叫EnumOrdinalTypeHandler。我們很容易聯想到的就是這個處理器是通過枚舉的所以作為數據庫內容的。在SexEnum中MALE存儲到數據庫中則為0.注意這個0不是我們的index.而是MALE的索引。如果將MALE和FEMAEL調換。那麼MALE索引則為1.
  • 因為默認的是EnumTypeHandler。所以想用EnumOrdinalTypeHandler的話我們要麼在resultMap中sex字段指定該處理器。要不就通過配置文件typeHandlers註冊進來。(將處理器與Java類進行綁定。mybatis遇到這個Java對象的時候就知道用什麼處理器處理)

<typeHandlers>
    <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.github.zxhtom.enums.SexEnum"/>
</typeHandlers>

SexTypeHandler

  • 上面的不管是通過名稱存儲還是通過索引存儲都不太滿足我們的需求。我們想通過我們的index存儲。那麼這時候我們就得自定義處理邏輯了。Mybatis處理器都是繼承BaseTypeHandler。因為BaseTypeHandler實現了TypeHandler.所以我們這裏也就繼承BaseTypeHandler。

public class SexTypeHandler extends BaseTypeHandler<SexEnum> {


    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, SexEnum parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i,parameter.getIndex());
    }

    @Override
    public SexEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int i = rs.getInt(columnName);
        return SexEnum.getSexEnum(i);
    }

    @Override
    public SexEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int i = rs.getInt(columnIndex);
        return SexEnum.getSexEnum(i);
    }

    @Override
    public SexEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int i = cs.getInt(columnIndex);
        return SexEnum.getSexEnum(i);
    }
}

typeHandler注意點

  • 在編寫自定義處理器的時候我們得之處Javatype、jdbctype。兩者不是必填。但至少得有一個。正常我們默認javatype是必填的。
  • 填寫的方式有三種
    • 通過MappedTypes、MappedJdbcTypes分別指定javatype、jdbctype
    • 通過在mybatis-config.xml中配置typeHandlers進行註解。裏面也有這兩個屬性的配置。
    • 通過在mapper.xml的resultmap中再次指定某個字段的typehandler.
  • TypeHandler為我們提供了Java到jdbc數據的轉換橋樑。極大的方便了我們平時的開發。讓我們開發期間忽略數據的轉換這麼糟心的事情。

# 加入戰隊

微信公眾號

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

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

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

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

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

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

簡單的學習,實現,領域事件,事件存儲,事件溯源

為什麼寫這篇文章

自己以前都走了彎路,以為學習戰術設計就會DDD了,其實DDD的精華在戰略設計,但是對於我們菜鳥來說,學習一些技術概念也是挺好的
經常看到這些術語,概念太多,也想簡單學習一下,記憶力比較差記錄一下實現的細節

領域事件

1.領域事件是過去發生的與業務有關的事實,一但發生就不可更改,所以存儲事件時只能追加

3.領域事件具有時間點的特徵,所有事件連接起來會形成明顯的時間軸

4.領域事件會導致目標對象狀態的變化,聚合根的行為會產生領域事件,所以會改變聚合的狀態

在聚合根裏面維護一個領域事件的聚合,每一個事件對應一個Handle,通過反射維護一個數據字典,通過事件查找到指定的Handle

領域事件實現的方式:目前看到有3種方式,MediatR,消息隊列 ,發布訂閱模式

eShopOnContainers 中使用的是MediatR

ENode 中使用的是EQueue,EQueue是一個純C#寫的消息隊列

使用已經寫好的消息隊列Rabbitmq ,kafka

事件存儲,事件溯源,事件快照

事件存儲:存儲所有聚合根裏面發生過的事件

1.事件存儲中可以做併發的處理,比如Command 重複,領域事件的重複

2.領域事件的重複通過聚合根Id+版本號判斷,可以在數據庫中建立聯合唯一索引,在存儲事件時檢測重複,記錄重複的事件,根據業務做處理

3.這裏要保證存儲事件與發布領域事件的一致性

如何保證存儲事件與發布領域事件的一致性

先存儲事件然後在發布領域事件,如果發生異常,就一直重試,一直到成功為止,也可以做一定的處理,比如重試到一定的次數,就通知,進行人工處理

我選擇了CAP + Policy + Dapper

事件溯源:在事件存儲中記錄導致狀態變化的一系列領域事件。通過持久化記錄改變狀態的事件,通過重新播放獲得狀態改變的歷史。 事件回放可以返回系統到任何狀態

聚合快照:聚合的生命周期各有長短,有的聚合裏面有大量的事件,,事件越多加載事件以及重建聚合的執行效率就會越來越低,快照裏面存儲的是聚合

1.定時存儲整個聚合根:使用定時器每隔一段時間就存儲聚合到快照表中

2.定量存儲整個聚合根:根據事件存儲中的數量來存儲聚合到快照表中

事件溯源的實現方式

1.首先我們需要實現聚合In Memory,

2.在CommandHandler中訂閱 Command命令,

創建聚合時 ,在內存中維護一個數據字典,key為:聚合根的Id,value為:聚合

修改,刪除,聚合時,根據聚合根的Id,查詢出聚合

如果內存中聚合不存在時:根據聚合根的Id 從聚合快照表中查詢出聚合,然後根據聚合快照存儲的時間,聚合根Id,查詢事件存儲中的所有事件,然後回放事件,得到聚合最終的狀態

記錄遇到的問題

由於基礎非常的差,所以實現的方式都是以最簡單的方式來寫的,存在許多的問題,代碼中有問題的地方希望大家提出來,讓我學習一下

代碼的實現目前還沒有寫快照的部分,也沒有處理EventStorage中的命令重複與聚合根+版本號重複,具體的請看湯總的ENode,裏面有全部的實現

1.怎樣保證存儲事件,發布事件的最終一致性

2.怎麼解析EventStorage中的事件,回放事件

先存儲事件,當事件存儲成功之後,在發布事件

存儲事件失敗:就一直重試,發布事件失敗,使用的是CAP,CAP內部使用的是本地消息表的方式,如果發布事件失敗,也一直重試,如果服務器重啟了,Rabbitmq裏面消息為Ack,消息沒有丟,重連後會繼續執行

存儲事件,發布事件

    /// <summary>
    /// 存儲聚合根中的事件到EventStorage 發布事件
    /// </summary>
    /// <typeparam name="TAggregationRoot"></typeparam>
    /// <param name="event"></param>
    /// <returns></returns>
    public async Task AppendEventStoragePublishEventAsync<TAggregationRoot>(TAggregationRoot @event)
        where TAggregationRoot : IAggregationRoot
    {
        var domainEventList = @event.UncommittedEvents.ToList();
        if (domainEventList.Count == 0)
        {
            throw new Exception("請添加事件!");
        }

        await TryAppendEventStorageAsync(domainEventList).ContinueWith(async e =>
        {
            if (e.Result == (int)EventStorageStatus.Success)
            {
                await TryPublishDomainEventAsync(domainEventList).ConfigureAwait(false);
                @event.ClearEvents();
            }
        });
    }

    /// <summary>
    /// 發布領域事件
    /// </summary>
    /// <returns></returns>
    public async Task PublishDomainEventAsync(List<IDomainEvent> domainEventList)
    {
        using (var connection =
            new SqlConnection(ConnectionStr))
        {
            if (connection.State == ConnectionState.Closed)
            {
                await connection.OpenAsync().ConfigureAwait(false);
            }
            using (var transaction = await connection.BeginTransactionAsync().ConfigureAwait(false))
            {
                try
                {
                    if (domainEventList.Count > 0)
                    {
                        foreach (var domainEvent in domainEventList)
                        {
                            await _capPublisher.PublishAsync(domainEvent.GetRoutingKey(), domainEvent).ConfigureAwait(false);
                        }
                    }
                    await transaction.CommitAsync().ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    await transaction.RollbackAsync().ConfigureAwait(false);
                    throw;
                }
            }
        }
    }

    /// <summary>
    /// 發布領域事件重試
    /// </summary>
    /// <param name="domainEventList"></param>
    /// <returns></returns>
    public async Task TryPublishDomainEventAsync(List<IDomainEvent> domainEventList)
    {
        var policy = Policy.Handle<SocketException>().Or<IOException>().Or<Exception>()
            .RetryForeverAsync(onRetry: exception =>
            {
                Task.Factory.StartNew(() =>
                {
                    //記錄重試的信息
                    _loggerHelper.LogInfo("發布領域事件異常", exception.Message);
                });
            });
        await policy.ExecuteAsync(async () =>
        {
            await PublishDomainEventAsync(domainEventList).ConfigureAwait(false);
        });

    }

    /// <summary>
    /// 存儲聚合根中的事件到EventStorage中
    /// </summary>
    /// <returns></returns>
    public async Task<int> AppendEventStorageAsync(List<IDomainEvent> domainEventList)
    {
        if (domainEventList.Count == 0)
        {
            throw new Exception("請添加事件!");
        }
        var status = (int)EventStorageStatus.Failure;
        using (var connection = new SqlConnection(ConnectionStr))
        {
            try
            {
                if (connection.State == ConnectionState.Closed)
                {
                    await connection.OpenAsync().ConfigureAwait(false);
                }
                using (var transaction = await connection.BeginTransactionAsync().ConfigureAwait(false))
                {
                    try
                    {
                        if (domainEventList.Count > 0)
                        {
                            foreach (var domainEvent in domainEventList)
                            {
                                EventStorage eventStorage = new EventStorage
                                {
                                    Id = Guid.NewGuid(),
                                    AggregateRootId = domainEvent.AggregateRootId,
                                    AggregateRootType = domainEvent.AggregateRootType,
                                    CreateDateTime = domainEvent.CreateDateTime,
                                    Version = domainEvent.Version,
                                    EventData = Events(domainEvent)
                                };
                                var eventStorageSql =
                                    $"INSERT INTO EventStorageInfo(Id,AggregateRootId,AggregateRootType,CreateDateTime,Version,EventData) VALUES (@Id,@AggregateRootId,@AggregateRootType,@CreateDateTime,@Version,@EventData)";
                                await connection.ExecuteAsync(eventStorageSql, eventStorage, transaction).ConfigureAwait(false);
                            }
                        }
                        await transaction.CommitAsync().ConfigureAwait(false);
                        status = (int)EventStorageStatus.Success;
                    }
                    catch (Exception e)
                    {
                        await transaction.RollbackAsync().ConfigureAwait(false);
                        throw;
                    }
                }

            }
            catch (Exception e)
            {
                connection.Close();
                throw;
            }
        }
        return status;
    }

    /// <summary>
    /// AppendEventStorageAsync異常重試
    /// </summary>
    public async Task<int> TryAppendEventStorageAsync(List<IDomainEvent> domainEventList)
    {
        var policy = Policy.Handle<SocketException>().Or<IOException>().Or<Exception>()
            .RetryForeverAsync(onRetry: exception =>
            {
                Task.Factory.StartNew(() =>
                {
                    //記錄重試的信息
                    _loggerHelper.LogInfo("存儲事件異常", exception.Message);
                });
            });
        var result = await policy.ExecuteAsync(async () =>
          {
              var resulted = await AppendEventStorageAsync(domainEventList).ConfigureAwait(false);
              return resulted;
          });
        return result;
    }

    /// <summary>
    /// 根據DomainEvent序列化事件Json
    /// </summary>
    /// <param name="domainEvent"></param>
    /// <returns></returns>
    public string Events(IDomainEvent domainEvent)
    {
        ConcurrentDictionary<string, string> dictionary = new ConcurrentDictionary<string, string>();
        //獲取領域事件的類型(方便解析Json)
        var domainEventTypeName = domainEvent.GetType().Name;
        var domainEventStr = JsonConvert.SerializeObject(domainEvent);
        dictionary.GetOrAdd(domainEventTypeName, domainEventStr);
        var eventData = JsonConvert.SerializeObject(dictionary);
        return eventData;
    }

解析EventStorage中存儲的事件

    public async Task<List<IDomainEvent>> GetAggregateRootEventStorageById(Guid AggregateRootId)
    {
        try
        {
            using (var connection = new SqlConnection(ConnectionStr))
            {
                var eventStorageList = await connection.QueryAsync<EventStorage>($"SELECT * FROM dbo.EventStorageInfo WHERE AggregateRootId='{AggregateRootId}'");
                List<IDomainEvent> domainEventList = new List<IDomainEvent>();
                foreach (var item in eventStorageList)
                {
                    var dictionaryDomainEvent = JsonConvert.DeserializeObject<Dictionary<string, string>>(item.EventData);
                    foreach (var entry in dictionaryDomainEvent)
                    {
                        var domainEventType = TypeNameProvider.GetType(entry.Key);
                        if (domainEventType != null)
                        {
                            var domainEvent = JsonConvert.DeserializeObject(entry.Value, domainEventType) as IDomainEvent;
                            domainEventList.Add(domainEvent);
                        }
                    }
                }
                return domainEventList;
            }
        }
        catch (Exception ex)
        {
            throw;
        }

注意事項

1.事件沒持久化就代表事件還沒發生成功,事件存儲可能失敗,必須先存儲事件,在發布事件,保證存儲事件與發布事件一致性
1.使用事件驅動,必須要做好冥等的處理
2.如果業務場景中有狀態時:通過狀態來控制
3.新建一張表,用來記錄消費的信息,消費端的代碼裏面,根據唯一的標識,判斷是否處理過該事件
4.Q端的任何更新都應該把聚合根ID和事件版本號作為條件,Q端的更新不用遵循聚合的原則,可以使用最簡單的方式處理
5.倉儲是用來重建聚合的,它的行為和集合一樣只有Get ,Add ,Delete
6.DDD不是技術,是思想,核心在戰略模塊,戰術設計是實現的一種選擇,戰略設計,需要面向對象的分析能力,職責分配,深層次的分析業務

感謝

雖然學習DDD的時間不短了,感覺還是在入門階段,在學習的過程中有許多的不解,經常問ENode群裏面的大佬,也經常@湯總,謝謝大家的幫助與解惑。

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

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

莫以權宜心態對待“夜經濟”

  不要經濟好時就打擊夜經濟特別是那些草根的夜經濟,經濟壞的時候又想到它們的作用。

  今年以來,北京、上海、成都、蘭州等多個城市出台相關政策,打造“夜間經濟”,促進消費升級,激發城市活力。上海甚至有兩個區設立了“夜間區長”,全市有十多位“夜生活首席執行官”。7月31日國常會也提出要鼓勵發展夜間經濟。

  夜晚是消費的重要時段,其在人們整體消費中占的比重之高,可能超乎意料。據媒體報道,北京海淀區一些區域的便利店在晚上9點以後仍然有相當於白天高峰期20%的客流量,一些IT人員集中的地方晚上12點客流量仍達白天高峰期的25%。

  社會生活網絡化更是極大地擴大了夜間的消費圖景。根據7月24日阿里巴巴發布的《夜經濟報告》,超過三分之一的網購消費額是在夜間產生的,晚上9點后的1小時是全天下單的高潮,網上查詢和繳納電費、公積金等費用則大部分發生在晚上10點過後。網商銀行的數據显示,晚上9點到10點是小微款申請的高峰,夜間業務佔到全部業務量的近三成。

  由此可見,不論是網下還是網上,夜間消費都有驚人的潛力,做好“夜經濟”這篇文章有利於挖掘消費潛力,有利於增加就業和穩定經濟,但發展夜經濟不能抱着權宜心態,不能僅僅視其為一時穩經濟的手段。在夜經濟問題上,要處理好市場與政府關係,城市管理與傳統生活方式之間不應對立,而對於缺乏夜生活習慣的北方城市來說,機會可能更大。

  前些年,一些城市過度追求環境整潔和視覺上的統一感,關閉了不少小商鋪、小飲食店等,這些草根性質的經濟單位承擔了普通居民很大一部分夜生活的需要,近些年來,曾一度全國流行的夜間飲食大排檔在各種治理之下基本上銷聲匿跡了。環境治理固然是必要的,但一刀切措施也製造了不方便,特別是影響了普通居民的生活和草根階層的就業。

  草根經濟活動很大程度上是社區重要紐帶,帶有文化性質,所以一刀切的影響不僅僅是經濟層面。目前各城市鼓勵“夜經濟”的政策,大多仍傾向於“高、大、上”,不論是飲食還是文化娛樂,都是往高端大氣上檔次上靠,其實夜間經濟更應該尊重傳統,對小商小販、小攤點小飲食點,只要不是嚴重擾民,完全沒有必要趕盡殺絕,自發形成的市場也要以引導和規範為主。

  很多人都提倡這種寬容,不過出發點是緩解當前經濟形勢下的就業衝擊,帶有權宜色彩。依我看,夜間經濟是一個城市成長過程中的一部分,應該在秩序和市場之間取得平衡,並且把這當作一個城市治理長期原則,不要經濟好時就嫌它們礙事,打擊夜經濟特別是草根的夜經濟,經濟壞的時候又想到它們的作用,不論經濟發展到什麼程度,草根經濟活動都不應該全部消滅。如果我們的城市建得越來越高大上,但最終失去了全部的煙火氣,社區的歷史和文化被大量切斷,沒有積澱,精神就是殘缺的,這也是一種失敗。

  前述報告還評出了中國“夜經濟”最活躍的十大城市,除北京外皆為南方城市,分別是上海、廣州、深圳、重慶、成都、杭州、東莞、蘇州、武漢。氣候可能是一個因素,但不是全部,報告引用的是近一個月的數據,夏天南北方之間氣候差異並不大,夜生活差異可能主要還是習慣問題。

  值得注意的是,北方城市天津在大麥網夜間演出“最活躍的五大城市”中排名第三,這或許與這座城市老百姓普遍熱愛曲藝有關。過去,北方人有“貓冬”的習慣,但追求生活的豐富多彩畢竟是人的本性,隨着城市基礎設施越來越好,寒冷對人的影響並非不可克服,如果政府加以引導,改善交通、取暖等條件,北方的夜生活和夜經濟的潛力可能比南方還要大。

(文章來源:證券時報)

(責任編輯:DF380)

【其他文章推薦】

實木地板、海島型地板、耐磨地板怎麼挑? 木地板三倍價差的秘密!!

※新屋購入,尋找台中室內設計師?是否可先免費估價丈量?

※挑好磚一點都不難!馬賽克磚挑選眉角小撇步!

※想知道北部最多平價、庫存出清的家具工廠推薦在哪裡?

※想知道大型演唱會、知名劇場的舞台設計是由哪位設計師一手操刀嗎?

這麼掙錢的培訓機構為何也跑路

  大額預付費消費事關公眾財產安全,有必要鑒金融監管手段來管理這些領域。

  自認為不屬於“雞血”的家長,但也免不了按照孩子自己的興趣,報了幾個培訓班。很不幸,這三、四個培訓班已遇到兩次麻煩:先是少兒美術班,自述遭遇不可抗因素需要關店,不過退了剩下的課時費;另一個是品牌少兒舞蹈班,全上海十幾家連鎖店一日之間全部關門,幾個股東當中跑路的跑路,沒跑路的則靜候家長與被拖欠工資教師找上門,但要錢沒有,愛報警報警,愛起訴起訴,據悉其底氣可能來自早已通過離婚財產切割手續完成了金蟬脫殼。最可恨的是,這公司關門前一天還在以優惠價為誘餌鼓動家長們續費或者報名,關門后仍然用公眾號信誓旦旦地發布一些虛假內容,混淆視聽,將事情朝經濟糾紛路上引,以干擾警方立案。

  在一般家長看來,這些幼教、少兒培訓行業掙錢之狠,已經到了令人咂舌的程度,那些知名品牌的連鎖培訓機構規模很大,吸金更是厲害,沒想到老闆還是跑路。這麼掙錢的商家仍然會出現“預售、圈錢和跑路”惡性循環當中,一定是環境或制度哪個地方出了問題。

  想來此事無非兩種可能性:一是生意不如我們想象中的掙錢,這點可能性不大,僅以上海的少兒舞蹈培訓來說,一到考級日各考點之熱鬧讓人驚嘆,平常上課也是人數眾多,收費不菲,想不賺錢都難;另外一種可能性,就是股東玩了什麼與自己能力不匹配的“花活”陷了進去,很早就見過有幾家店面敢言必稱“上市”的老闆,如果這樣現金奶牛般的生意還嫌錢慢,資金東挪西用到別的行業,嗆水乃至淹死也就不意外。只是老闆魯莽的後果讓預付費客戶來承擔,全無道理,如果考慮到這事情將影響到小孩子的考級,老闆更顯得無良。在法治口碑上佳的上海,這些經營者仍敢鋌而走險,其他地方類似商家膽量更是可以想象。

  一旦預付費,消費者實際上已喪失了根據經營者的履約情況進行救濟的機會,由於信息不對稱,消費者對於經營者未來是否會正確履行合同並不知情,賣方完全可以憑藉自己的信息優勢,隱瞞真實信息或者編造虛假信息為自己牟利,道德風險高企。從形式上看,預付式消費是消費者與經營者雙方真實意思的表示,經營者讓客戶預付費也不能稱為非法集資,但預付的這部分資金,基本上遊離於監管體系之外,給消費者帶來風險之外,亦有可能會擾亂社會秩序甚至造成金融風險。

  既然大額預付費消費事關公眾財產安全,那麼借鑒金融監管手段來管理這些領域,顯得很有必要。對照美國、日本和我國台灣地區等相關法律,當前我們從公法領域對預付費交易過程的規範,包括實體方面監管主體、監管職責和監管手段的規定,以及程序方面事先申報和審查、事中通知以及事後保障和救濟的規定,均有大幅提升的空間。

  例如,對預付費消費模式企業建立市場准入制度,設定標准,要求繳納保證金等,企業必須在滿足特定條件時才可以收受客戶預付費款項,這些條件可以是企業規模、資本充足率、流動性、技術設備條件,甚至成立年限、市場品牌等,以提升圈錢跑路的機會成本;再如預付費款項必須託管於銀行,保證預付資金不被捲走或者濫用。消費者在商家消費后,相應的預付款項才轉入經營者賬戶,如果沒有消費,則預付款項仍在資金託管銀行設置的專用賬戶中,一旦經營者出現經營問題,消費者可以向監管機構申請退款。

(文章來源:證券時報)

(責任編輯:DF380)

【其他文章推薦】

※居家隱形鐵窗安裝施作經驗分享

※純客製手工沙發,古典沙發,專業首選沙發工廠打造屬於您的居家品味!

※解決漏水、壁癌危機,找尋新竹舊屋翻新專業修繕專家

※分享木質地板DIY自行施工教學影片

※想要打造簡約、淡雅兼且收納空間的小資房,台中室內設計推薦哪一家?