2019年5月15日 星期三

docker

docker version     看版本,我的是18.09.2
docker info           比 version 多很多資訊

docker run hello-world 不加tag,预设为「:latest」
表示執行 hello-world 這個鏡像,有就執行;沒有就去下載並執行

docker search 鏡像名     搜尋鏡像
--no-trunc        不截斷描述
--limit(后接数字,预设为25)       只顯示多少個

docker pull 鏡像名:tag    下載鏡像 (tag不加一樣為 :latest )

tag 要到官網搜尋,以tomcat為例,如下:

每一個都是tag,所以下 docker pull tomcat:7.0.94-jre7 即可
如果 docker search 或 pull 太慢,Windows 10 可在右下叫出 docker 設定,並增加網址,如下:

按 Basic 有他的格式,和 linux 的 /etc/docker/daemon.json 裡一模一樣
再使用 docker info 時,接近最下面有 Registry Mirrors: 可看到,表示設定成功了

docker images 看目前有什麼鏡像
 --all             顯示所有鏡像
--quiet         只顯示 Image ID
--digests      顯示摘要
--no-trunc    不截斷 Image ID

docker rmi   移除鏡像,多個用空格格開
docker rmi --force $(docker images-qa)   一次刪除多個鏡像

docker run -it [imageID|imageName] /bin/bash
exit 離開 Ctrl+P+Q 暫時離開
--name 容器別名(不指定會隨機給一個)
-p      為 port 8888:8080,左邊為想要給的 port;右邊為程式的 port
-P      隨機 port
-d      背後執行,但如果沒有進程在跑,會立即關閉
docker run -d imageID /bin/sh -c "while true; do echo hello ooo; sleep 2; done"

docker ps  顯示正在執行的容器
--all       顯示全部容器,包括執行中,已關閉
--latest  顯示最新的容器
--last 次數  或者 -n 次數    顯示最後幾個容器
--quiet     只顯示容器編號
--size   顯示總大小

docker start 容器名 啟動曾經啟動過的容器
docker restart 容器名
docker stop 溫柔關閉容器
docker kill 強制關閉容器
docker rm 刪除容器 -f 刪還沒啟動的
docker rm --force $(docker ps --all --quiet)
docker ps -all -quiet | xargs docker rm
docker logs --follow -timestamps --tail 容器 id
docker top 容器id      看運行的進程
docker inspect 容器名:看容器內的細節
docker attach 重新進入container
docker exec --tty 容器id linux命令, 加 -tty 會顯示的比較好看,不一樣要有
docker exec /bin/bash 同attach
docker cp 容器id:路徑 本機路徑
docker commit --message=訊息 --author=作者 容器id 命名空間/自定義名稱:tag
因為鏡像一關閉就一切還原了,所以使用 commit 可新增一個自己的鏡像
除了可以使用 commit 外,還可以使用 run 的 -v 參數和 docker file

-v參數:

docker run -it -v /hostFolder: /ContainerFolder imageName /bin/bash
docker run -it -v /hostFolder: /ContainerFolder:ro imageName /bin/bash 容器裡只讀
docker inspect
docker start containerId
ro 是 read Only的意思,不加預設是可讀寫
如果遇到權限問題,可加 --privileged=true 試試
在 Windows10 我沒試出來,都是格式不對的錯



docker file:

from ubuntu    -> ubuntu 為 image 名稱
VOLUME ["/container1", "/container2"]   -> 同 -v,根目錄建兩個資料夾來同步,匿名的方式
cmd echo "finish"
cmd /bin/bash

docker build --file /mydocker/Dockerfile --tag zzyy/ubuntu(namespace) .
如果檔名取 Dockerfile 可不加-f
在 Windows10 也是沒用

-t imageName 可以生成映象檔

RUN yum install -y vim 安裝 vi
RUN yum install -y net-tools 安裝網路相關

WORKDIR 一進入 linux 的路徑

此時主機要交互的資料夾要看 inspect 裡的 volumes

CMD ["ls", "-l"]
ENTRYPOINT ["ls", "-l"]
都是執行命令,但 CMD 是執行而已;而 ENTRYPOINT 是追加
docker run 容器ID -l 可以看出差別

docker run -it --name 子容器名 --volumes-from 父容器名 namespace

https://docs.docker.com/engine/reference/builder/

2019年4月30日 星期二

小心使用 split

String s1 = ",,,";
System.out.println(s1.split(",").length); // 0
String s2 = "a,,,";
System.out.println(s2.split(",").length); // 1
String s3 = ",a,,";
System.out.println(s3.split(",").length); // 2
String s4 = ",,a,";
String[] split = s4.split(",");
System.out.println(split.length); // 3
Arrays.stream(split).forEach(System.out::println); // 空空a
    
String s5 = "abcde";
System.out.println(s5.split("").length); // 5


※只會分割非分割符的前面,如果将 a 改成空格也是一樣的結果

※分割符什麼都沒有時,會每個字符都分割

※s1 長度是 0,所以使用 s1.split(",")[0] 會報 ArrayIndexOutOfBoundsException

2019年4月23日 星期二

分頁插件-PageHelper (Mybatis3.x 十四)

文檔

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.8</version>
</dependency>


※全域設定

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <property name="param1" value="value1" />
    </plugin>
</plugins>


※測試

/*
Page<Object> page = PageHelper.startPage(2, 3); // 每頁三條記錄,顯示第二頁,
List<Emp> emp = mapper.getEmp();
emp.stream().forEach(x -> System.out.println(x.getEname()));
// SELECT * FROM ( SELECT TMP_PAGE.*, ROWNUM ROW_ID FROM ( select * from emp ) TMP_PAGE) WHERE ROW_ID <= ? AND ROW_ID > ? 
System.out.println("當前頁:" + page.getPageNum()); // 2
System.out.println("每頁顯示幾條記錄:" + page.getPageSize()); // 3
System.out.println("總共幾筆記錄:" + page.getTotal());
System.out.println("總共有幾頁:" + page.getPages());
*/
    
PageHelper.startPage(3, 5);
List<Emp> emp = mapper.getEmp();
PageInfo<Emp> pageInfo = new PageInfo<>(emp);
emp.stream().forEach(x -> System.out.println(x.getEname()));
System.out.println("當前頁:" + pageInfo.getPageNum());
System.out.println("每頁顯示幾條記錄:" + pageInfo.getPageSize());
System.out.println("當前頁有幾條記錄(最後一頁時,可能和 PageSize()不同):" + pageInfo.getSize());
System.out.println("總共幾筆記錄:" + pageInfo.getTotal());
System.out.println("總共有幾頁:" + pageInfo.getPages());
    
System.out.println("當前頁的第一筆是全部記錄的第幾筆記錄:" + pageInfo.getStartRow());
System.out.println("當前頁的最後一筆是全部記錄的第幾筆記錄:" + pageInfo.getEndRow());
System.out.println("上一頁是第幾頁:" + pageInfo.getPrePage()); // 沒有上一頁顯示 0
System.out.println("下一頁是第幾頁:" + pageInfo.getNextPage()); // 沒有下一頁顯示 0
System.out.println("是否是第一頁:" + pageInfo.isIsFirstPage());
System.out.println("是否是最後一頁:" + pageInfo.isIsLastPage());
System.out.println("是否有上一頁:" + pageInfo.isHasPreviousPage());
System.out.println("是否有下一頁:" + pageInfo.isHasNextPage());


※PageHelper.startPage 必須寫在呼叫之前才有用

※PageInfo 如果沒有配合 PageHelper,那就是看 SQL 下什麼,回來就有幾筆

※看原始碼其實就有說明每一個欄位的作用了

2019年4月20日 星期六

一、二級快取,整合 Ehcache 2.x (Mybatis3.x 十三)

<setting name="cacheEnabled" value="true" /> <!-- 二級快取的開關 -->
<setting name="localCacheScope" value="STATEMENT" /> <!-- 一級快取的範圍,SESSION為開;STATEMENT為關 -->

※一級快取預設是 SESSION,表示 SqlSession;二級快取預設是 true

※SESSION 表示每個一個 Session 中的所有語句都可以共享; STATEMENT 表示只有當前的  SQL 語句的結果可以共享 (看最下面的一、二級快取)


測試類
InputStream is = null;
SqlSession sqlSession = null;
SqlSession sqlSession2 = null;
try {
    is = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
    sqlSession = factory.openSession();
    DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
    //一級快取
    Dept dept1 = mapper.getDeptById(40);
    //sqlSession.clearCache();清一級快取
    //增刪改操作之後也不會有快取
    Dept dept2 = mapper.getDeptById(40);
    //Dept dept2 = mapper.getDeptByIdAndLoc(40, "BOSTON");
    System.out.println(dept1 == dept2);
    
    //二級快取
    /*
    sqlSession2 = factory.openSession();
    DeptMapper mapper2 = sqlSession2.getMapper(DeptMapper.class);
    Dept dept2 = mapper2.getDeptById(40);
    System.out.println(dept1 == dept2);
    */
}
// ...

注意 dept1 == dept2,如果資料庫沒有值得到的結果是 null,那兩個判斷是 true


※一級快取:

1.指同一個 SqlSession 做查詢的快取
2.只有 where 條件一樣時,才有快取(如上的例子,就算回傳結果一樣,還是沒快取)
兩個查詢之間有增刪改和呼叫 SqlSession 的 clearCache 方法,都不會有快取


※二級快取:

1..指不同 SqlSession 做查詢的快取
2.使用 <cache /> 設定二級快取,其中的屬性可看官網的介紹,其中 readOnly 預設是 false,如果是 false,resultType 的 java bean 必需 implements Serializable,否則會報 java.io.NotSerializableException 的錯,而反序列化後,SQL 就是兩條了,就沒有快取了
3.二個查詢之間必須要將第一個快取 close()後,才會將一級快取裡的值移到二級快取,所以不關閉就不會成功
select 標籤有 useCache,當全域的二級快取是開時,可以針對不想二級快取的 select 關閉
4.<cache-ref> 使用在用另外一支 xml 的 cache


※一、二級快取

在 select 標籤中,還有個 flushCache 可以針對一、二級快取做清除的動作

如果一級快取是 STATEMENT;二級快取是 true,一級快取 close()後會將資料移到二級快取,但一級快取之間還是無法共享

sqlSession = factory.openSession();
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
    
//一級快取(SqlSession快取)
Dept dept1 = mapper.getDeptById(40);
Dept dept2 = mapper.getDeptById(40);
System.out.println(dept1 == dept2); // false
    
sqlSession.close();
    
sqlSession2 = factory.openSession();
DeptMapper mapper2 = sqlSession2.getMapper(DeptMapper.class);
    
Dept dept3 = mapper2.getDeptById(40);
//System.out.println(dept1 == dept3);
System.out.println(dept1 == dept3); // false
System.out.println(dept2 == dept3); // true

※如果注解 dept2,那 dept1 == dept3 會是 true,因為 STATEMENT 代表當前的語句



※整合 Ehcache2.x

官方文檔,要下載 ehcache2 和 mybatis 和 ehcache 的 jar 檔, 在根目錄要有 ehcache.xml,可去 ehcache 官網下載,至於設定的意思,可以看到 This file can also be downloaded from 後面有個超連結,會有解釋的;或者 2.x 的 PDF 裡都有
要使用時,只要在二級快取的標籤有個 type,指定為 ehcache 就可以了

<cache readOnly="true" type="org.mybatis.caches.ehcache.EhcacheCache" />

※因為使用 <cache> 標籤,所以還是二級快取,一切的行為都和二級快取一樣,但 ehcache.xml 有一個 <diskStore path="aaa.bbb">,這裡面打的 aaa.bbb 並不是 aaa 目錄下有 bbb 目錄,而是 aaa.bbb 的目錄,會出現在 src 下,也可改成磁碟的路徑,這個檔案就算將二級快取關閉,還是會出現,但是行為還是二級快取的行為

※P.S. 我在整合 Ehcache 3.x 的時候,我試了最低的版本和目前最高的版本,XML 都有問題,都會報 Element <ehcache:config> does not allow attribute "xmlns:ehcache" 或 Element <config> does not allow attribute "xmlns:xsi"

2019年3月27日 星期三

Lombok 的使用

lombok 有很多註解,可以節省代碼,方便開發,但使用 Lombok 時,IDE 必須設定,否則編譯會過,但沒有效果(叫出 outline 活頁標籤可知道)


※Eclipse

maven:
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.6</version>
    <scope>provided</scope>
</dependency>
    
    
    
gradle:
provided group: 'org.projectlombok', name: 'lombok', version: '1.18.6'




maven 或 gradle 下載後,注意下載的位置

找到 jar 檔後,點兩下打開,然後 install/update,但安裝完成也沒提示,重啟 eclipse 才可以

可參考 官方文檔,比較重要的幾乎都在 lombok 包中

@Getter:可寫在 field 和 type,寫在 field 表示生成 getter;寫在 type 表示為所有 field 生成 getter
@Setter:可寫在 field 和 type,寫在 field 表示生成 setter;寫在 type 表示為所有 field 生成 setter
@RequiredArgsConstructor:只能寫在 type,如果有 final 欄位,建構子就必須要加,如果有多個 final 欄位,依寫的順序;都沒有 final 欄位,就等於空的建構子,不能與 @NoArgsConstructor 一起使用
@ToString:只能寫在 type,預設格式是「類名(欄位一=值, 欄位二=值…)」
@EqualsAndHashCode:生成 equals(Object)、hashCode()、canEqual(Object) 三個方法
@Data:以上五個註解合起來

@AllArgsConstructor:只能寫在 type,生成全部 field 的建構子,順序以寫的先後為主
@NoArgsConstructor:只能寫在 type,生成無參數的建構子,不能與@RequiredArgsConstructor 一起使用
@Accessors(chain=true):使用 setter 時,可以一直 set,這個和上面幾個註解是不同的包



※Intellij

1.Settings/Plugins 搜尋 lombok 並安裝好後重啟 Intellij
2.
3.增加 maven 或 gradle jar 檔

2019年3月16日 星期六

農曆年與天干地支轉換

<html>
    <head>
        <title>農曆年與天干地支轉換程式</title>
        <script>
            /**
            *    民國13年為甲子年,並不是民國1年為甲子年
            */
            window.onload = function() {
                document.getElementById('sl').checked = false;
            }
    
            var sky = ["", "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"];
            var land = ["", "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"];
            
            function sWord() {
                let sw = sky[document.getElementById('s').value];
                document.getElementById('sWord').innerText = typeof sw != 'undefined' ? sw : '';
            }
    
            function lWord() {
                let lw = land[document.getElementById('l').value];
                document.getElementById('lWord').innerText = typeof lw != 'undefined' ? lw : '';
            }
    
            /**
            *    勾選天干地支的顯示隱藏
            */
            function slCheck() {
                let sl = document.getElementById('sl').checked;
                let slView = document.getElementById('slView');
                let lunarSolarYear = document.getElementById('lunarSolarYear');
    
                if(sl) {
                    slView.style.display = 'block';
                    lunarSolarYear.style.display = 'none';
                    sWord();
                    lWord();
                } else {
                    slView.style.display = 'none';
                    lunarSolarYear.style.display = 'block';
                }
            }
    
            function display() {
                var num = document.getElementById('num').value;
                var sl = document.getElementById('sl').checked;
    
                if(!sl) {
                    // 農曆年轉天干地支
                    if(num <= 0) {
                        alert('請輸入正確的農曆年');
                        return;
                    }
                    if(num.length > 3) {
                        alert('輸入的農曆年不可超過千年');
                        return;
                    }
    
                    num = subtract60(num);
                    if(num.length == 1) num = '0' + num;
                    var skyResult = Number(num[1])-2 <= 0 ? sky[Number(num[1])+10-2]: sky[Number(num[1])-2];
                    alert(skyResult + land[getLandWord(Number(num))] + '年');
                } else {
                    // 天干地支轉農曆年
                    var s = document.getElementById('s').value;
                    var l = document.getElementById('l').value;
    
                    if(s > 10 || s <= 0) {
                        alert('天干必須在1~10之間');
                        return;
                    }
                    if(l > 12 || l <= 0) {
                        alert('地支必須在1~12之間');
                        return;
                    }
                    if((s % 2 == 1 && l % 2 == 0) || (s % 2 == 0 && l % 2 == 1)) {
                        alert('不正確的天干地支組合');
                        return;
                    }
    
                    var skyResult = Number(s) + 2;
                    if(skyResult >= 10) skyResult = skyResult - 10;
                    if(l.length == 1) l = '0' + l;
                    alert('農曆年為:' + getLandNumber(skyResult, l) + ' (或加減 60 的倍數)');
                }
            }
    
            /**
            *    取得地支數字
            */
            function getLandNumber(sky, land) {
                if(land.toString()[1] != sky) {
                    land = Number(land) + 12;
                    land = getLandNumber(sky, land)
                }
                return Number(land);
            }
    
            /**
            *    取得地支文字
            */
            function getLandWord(n){
                if(n>12) n = getLandWord(n-12);
                return n;
            }
    
            /**
            *    將農曆年縮小到60年之內
            */
            function subtract60(n){
                if(n>60) n = subtract60(n-60);
                return n.toString();
            }
        </script>
    </head>
    
    <body>
        <h1>農曆年與天干地支轉換程式</h1>
    
        是否使用天干地支轉農曆年<input type="checkbox" id="sl" onclick="slCheck()"><br />
        <span id='lunarSolarYear'>請輸入農曆年:
            <input type="number" id="num" style="width:70px" min='1' max='999' /><br />
        </span>
        <div id="slView" style='display:none'>
            請輸入天干:<input type="number" id="s" style="width:70px" min='1' max='10' onchange="sWord()" />
            <span id="sWord" style='color:blue'></span><br />
            請輸入地支:<input type="number" id="l" style="width:70px" min='1' max='12' onchange="lWord()" />
            <span id="lWord" style='color:blue'></span><br />
            1=甲, 2=乙, 3=丙, 4=丁, 5=戊, 6=己, 7=庚, 8=辛, 9=壬, 10=癸<br />
            1=子, 2=丑, 3=寅, 4=卯, 5=辰, 6=巳, 7=午, 8=未, 9=申, 10=酉, 11=戌, 12=亥<br />
        </div>
        <input type="button" value="確定" onclick="display()" />
    </body>
</html>

※農曆(陰陽合曆)13、73年為甲子年

※農曆年轉天干地支:個位數-2為天干,一直減到12之內為地支

※天干地支轉農曆年: 天干+2為個位數,地支較麻煩,以戊戌年來說
戊為5,加2為7,所以個位數為7
戌為11,11加多少的個位數會是7(天干的結果),必須加6才行,但還必須是12的倍數才行,60以下12的倍數有12、24、36、48,所以只有36,結果為11+36=47

2019年3月14日 星期四

Transaction (Spring 4.x 三)


<context:annotation-config />
<context:component-scan base-package="bean, dao, service" />
    
<bean id="ds"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource"
    p:driverClassName="oracle.jdbc.driver.OracleDriver"
    p:url="jdbc:oracle:thin:@127.0.0.1:1521:orcl" p:username="user-name"
    p:password="password" />
    
<bean id="jt" class="org.springframework.jdbc.core.JdbcTemplate"
    p:dataSource-ref="ds" />
    
<bean id="txm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="ds" />
</bean>
    
<tx:annotation-driven transaction-manager="txm"/>

※事務在第3版就有了,更之前的版本我不知道有沒有,寫在 4 並不代表 4 才有

※@Transaction 要配合<tx:annotation driven transaction-manager="" />,且只能使用在 public,否則不啟作用,也不報錯

※在這裡已經設定 JdbcTemplate,如果在下SQL時,自己用 DriverManagerDataSource 取得 JdbcTemplate 也是不啟作用的

※@Transaction 有個屬性 rollbackFor,不設定時為所有例外都會 rollback,如設定 RuntimeException.class,那只有 RuntimeException 時才會 rollback

※還有一些陷阱,高手已經寫好,可參考看看