2015年12月11日 星期五

java char to unicode hex

記錄一下~
java 讀檔 抓 字元, 轉成四碼的內碼表示~
(通常都是在轉那些不可見字元)


    private String charToHex(char c)  {
        String hex = Integer.toHexString(c & 0xffff);
        if(hex.length() <  4)  {
            for(int i = 0 ; i < 4-hex.length() ; i++)  {
                hex = "0"+hex;
            }
        }
        return hex;
    }

2015年12月1日 星期二

apache httpd disable directory listing and error page

apache  httpd 預設在安裝時,
www/html/xxxx 下若是該資料夾,沒有index.html...index.xxxx等等~
打網址就會變成列出檔案列表~

當然是說放資料給人抓是挺方便的,只是有時後想藏一點感覺比較安全~
關掉檔案列表的方式是~可以每個資料夾去設定~
但我是覺得從源頭改掉比較快...

vim /etc/httpd/conf/httpd.conf
<Directory "/xxxxxxxxx">
....
     Options -Indexes FollowSymLinks

主要就是
Options -Indexes (原本沒有- ,加個-就是)

存檔後重起 httpd 應該就可以了~


設定 error page
vim /etc/httpd/conf/httpd.conf

在最底下的 virtual host 打開

<VirtualHost *:80>
...
    DocumentRoot /var/www/html
...

ErrorDocument 404 /404.html
ErrorDocument 500 /500.html
ErrorDocument 502 /502.html
ErrorDocument 503 /503.html
ErrorDocument 504 /504.html

</VirtualHost>

再重起server就好囉~

2015年11月24日 星期二

screen rotated on mobile and change background image

手機網頁,跨裝置的網頁,可以找一些 cross device 的 css framework...
搭配方塊排版的設計,就可以得到相當不錯的結果~

我覺得手機網頁最大的重點就是,好好的設定meta viewport
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1"/>
如果在意某些瀏覽器放大縮小會讓版面爛掉的話,就加上 user-scalable=0...
然後自己做個字型放大功能一.一b...

另外就手機有直式跟橫式的差別~~
(PC基本上都是橫式)
若背景是張圖片的話,然後....想要像APP那樣子,做個比較漂亮的轉換的話~
當然首先要有兩張圖~
ex: background_h.jpg(橫), background_v.jpg(直)

然後在 window.onresize (螢幕大小切換)
但~~直接改寫  window event 是件很可怕的事情~所以要用 event addEventListener 的方式實作較好~
參考網路上的資源修改成

var addEvent = function(object, type, callback) {
    if (object == null || typeof(object) == 'undefined') return;
        if (object.addEventListener) {
            object.addEventListener(type, callback, false);
        } else if (object.attachEvent) {
            object.attachEvent("on" + type, callback);
        } else {
            object["on"+type] = callback;
        }
    };
   
    addEvent(window, "resize", function(event) {
        //check screen width and height
        var oh = $(window).height();
        var ow = $(window).width();
        var sz = 'h';
        if(oh > ow)  {
            sz = 'v';
        }         
  
            $('.content').css('background', 'url(imgs/background_'+sz+'.jpg) no-repeat');
            $('.content').css('background-position', 'center center');
            $('.content').css('background-size', 'cover');
       
    });


如果想要在轉換時,做個過場動畫...
可以用jquery 的 animate,但...因為圖是現load的,
在網路不穩的手機上~其實蠻讓人困擾的就是...

想想最簡單的方式,應該就是做一張大張的圖,直接符合橫,寬的版...
然後背景圖定位放在中間~
(缺點就是那張圖會很大張!還有一些美感的問題就是...)

2015年11月8日 星期日

centos apache httpd gzip

最近在寫js~然後就發現開始越寫越肥~
js 做成 min...省一點~
如果是有要用到字型檔的,就不知道該怎麼好orz...
(中文的字型檔爆肥T^T)

看了很久~好像也沒什麼比較好的方式~
就試用一下 gzip, 來結省傳輸的空間~
(頻寬=$$...T_T)

gzip 要看有支援的瀏覽器~
http://schroepl.net/projekte/mod_gzip/browser.htm
原理大概就是 web server 丟個有壓縮過的 給 browser client...
然後browser client 再解壓~

現在的 browser 都比較先進了,主要就是看server有沒有開啟囉~
找了幾篇文章~覺得這個寫得比較好~就用他的方法~
https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-mod_deflate-on-centos-7

先check 有沒有裝模組~
apachectl -t -D DUMP_MODULES |grep deflate

現在的版本預設應該都是有的~
再把設定檔寫進去~
vim /etc/httpd/conf.d/mod_deflate.conf
<filesMatch "\.(js|html|css)$">
    SetOutputFilter DEFLATE
</filesMatch>
DeflateCompressionLevel 1

重起httpd
service httpd restart

接著就可以測試~~
wget --header="Accept-Encoding: gzip" http://<your_server_ip>/jquery-1.11.3.js

看他下下來的檔案大小就可以知道真的有變小QQ~

或者開firebug/chrome 開發者工具的 網路 應該也可以發現檔案變小囉~~

2015年11月3日 星期二

prevent scroll by keydown arrow down...and div focus

補個最近的筆記~
其原由是想替 div加一些hot key 來做事情...

一般的純 div 預設是沒有 focus 的功用~
就算你直接寫 $('#aaa').focus() ,他還是不理你~

重點是要先加個屬性~tabIndex=0
可以參照這裡的說明~蠻詳細的~
https://www.paciellogroup.com/blog/2014/08/using-the-tabindex-attribute/

< 0  為不能被 focus/tab,就沒辦法被focus(就普通div預設並沒有設定)
 >0 設定tab 的順序,文中是不建議使用,是怕亂跳亂掉吧~
=0 大部份的input, button 預設都有~

------------------------------------------------------------------------
另外,想取到目前被 focus 的元件~可以使用傳統 js 取得就好~
document.activeElement
方便又好用~

要另外設定 focus 的 css,要:focus 和:active 雙管其下~比較不會有問題~
(聽說chrome 好像吃  active 不吃 focus,沒特別試就是)
.mycss:focus, .mycss:active {
    border: solid 1px blue;
}

------------------------------------------------------------------------
在一般的行為下,按箭頭的上/下是會捲動整個頁面的scroll bar...
就跟page up/down一樣~

不過有時後~需要在對特定的元件按 上下左右 鍵時~
會連動到畫面的scroll bar~就~一整個很乾尬XD...
所以就變成重點在~ 需要各自有 上下左右 的元件的 keydown listener 下手~
document.xxxxxx.onkeydown =
function(event)  {
       var keycode = event.keyCode;
       ..............
       event.preventDefault();
       return false;
};

總之,重點是在
1. 要使用 onkeydown listener (因為這是第一個觸發的,如果用 keyup就來不及了)
2.  event.preventDefault(); 和 return false; 防止其他瀏覽器接下去的行為~

大概就是降子~

2015年10月27日 星期二

Google Compute Engine 試用感想(更新)


更新: google cloud 兩個月到期了XD...

google cloud 到試用到期(60天)後,會自動把VM shutdown。然後 release  你的IP...
但VM內容資料是有被保留下來的。
你要在事先先升級帳號(準備付銀彈),應該就會保留下來~

優點是:不會自動跟你收錢。
缺點是:IP被搶走了Orz...還得跑去把domain name 重定一輪就是...若是原本就是上線的系統,要注意....IP跑了可是非常麻煩~~

至於EC2是,試用到期(一年),並不會自動幫你關VM...反正就是直接開始扣錢~
所以想要一毛不拔,要記得自己上去關機VM。

至於目前,打算就先付費用google cloud一陣子吧。

-------------------------------------------------------------------------------------
Google Compute Engine 就差不多相對於 Amazon EC2...

跑去試用了一下下~就說一下簡單的感想~

帳號:(兩邊差不多)
Google 那邊需要一個 Google 帳號~ EC2 是要一個 email 帳號~
兩邊都要 key 信用卡才可以啟用~


介面:(Google勝)
Google 介面 中文化程度算高的(有八成以上吧)!當然文件還是英文的~
另外Google 介面我覺得順蠻多的~還有做網頁link到 ssl,console,還蠻新鮮的體驗~~
除了網頁可以access,還有gcloud這種東西~(但我覺得好難用=.=a,最簡單的就是在網頁上的 ssl sudo處裡好後,之後用putty看起來比較親切)


免費試用:(看人)
Google 免費試用目前是~免費 兩個月(部份vm,服務)裡面可用額度最高 300美元~
EC2 還是那個最迷你的vm,免費一年,還有一些服務試用~
(因為我主要都是用vm,iaas的服務,其它的沒在試)

如果是有點想要租的人,可以用google,可以先試用到不少功能(阿就兩個月而已),可以讓你先估計,如果你想要租xx加xx,一個月會花多少錢~現在大概搞懂為啥是兩個月了~因為至少會有一個 足月~

如果還是希望完全免費的話,就還是用慢慢的EC2 吧,有一年時間。
(但是真的很慢阿Orz...)


費用:(看人吧,我只用免費的)
這邊不得不要說一下,amazon好像有簡體的費用說明~google 連到的還是英文的~
原則兩邊都有不同的地區(美國/歐洲/亞洲,兩邊都有),價格也都不同~
但是 amazon的地區 比Google多了一些點~

Google 有個特別的 最高減免30% 計算,就是一整個月的第一周沒優惠,連開到第二~四周優惠x,y,z%,總之就是如果「Server 連開一整個月」可以得到最多的優惠。(希望你開了就不要停機降子)

Amazon 之前看比較優惠的就是 預存實例,比較像「預付卡」那樣,先買多少時數(by機型),然後在 一年/三年 內用完。

當然如果沒有搞到優惠的情況下,個人覺得挺貴的一u一a...但在有優惠的時後~就看實際情況了~

另外~頻寬、固定IP、加掛硬碟等等,都是額外計費的~只是因為個人搞不到這種大事業~所以沒認真比過就是O_oa...


VM Image:(Google有純正的centos耶)
EC2標榜他們有幾百個instance 的image,不過~老實說,當年在那邊翻簡直是大海撈針Orz...
另外有要版權的VM...兩邊都會收額外的費用~

話說,不知道是不是我的錯覺,Google上搞 MS 的OS,貴得有點離譜...不知道是不是故意的@@a...(雖說因為租不起高級server,現在也都不選MS那種吃效能的怪物就是)
EC2上之前讓偶不太高興的是,有redhat,卻沒有centos,是怕自己人打自己人嗎=_=...但他自己特別提供的linex vm..我覺得lib不是很齊~網路資源沒centos多...
所以看到Google 上寫著好好的 Centos 6...就開起來用了XD...

Google 比較特別是,他有挑一些特定用途的配套VM+Image+硬碟,讓一般人在選擇時比較容易入手。不過他們家推的底層是 那幾間 不認識的合作廠商~(試開了svn 配套vm,裡面掛的是 debian+Bitnami,開是很方便沒錯啦,但是完全不熟設定運作。而且資訊是散在那個論壇裡,不好入門,最後還是覺得專心搞centos就好)


結論就是~總之先用了再說:)...

2015年10月18日 星期日

SimpleDateFormat parse wrong ??!!

過了這麼多年,第一次發現原來 SimpleDateFormat 是會表人的XD...
先說愛用是 jdk 1.6.0_33...

總之寫個範例就知道了Orz...

        SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd");
       
        try  {
            System.out.println(sf.parse("20151010"));
            System.out.println(sf.parse("2015-10-10"));
            System.out.println(sf.parse("20-15-10-10"));
        }  catch(Exception e)  {
            e.printStackTrace();
        }

結果居然...
Sat Oct 10 00:00:00 CST 2015
Fri Oct 31 00:00:00 CST 2014
Sun Nov 05 00:00:00 CST 19

只有第一個是對的,但...居然都沒進 Exception...
如果前端輸入沒擋好,後端就要GG了~

是說解法也還還好~但真說都沒想到過,就是~
sf.setLenient(false);

但是為啥 jdk 不讓他預設就是 false就沒事了Orz...

2015年10月17日 星期六

spring4 @ResponseBody json date format

預設的 @ResponseBody  的日期型態 在 json 時是回傳 時間long值~

然後想改一下,就突然發現事情沒有想得那麼簡單Orz...

先講最簡單也是最基礎的寫法如下~
使用 MappingJacksonHttpMessageConverter
但是~他不知道在那一版就被 @deprecated 掉~猜大概是 3.x吧~
然後要講清楚,spring 的 json 是用 Jackson 的,若是使用 MappingJacksonHttpMessageConverter,就要搭配 jackson 1.x 版使用...jar檔大概是 jackson-core-asl-1.8.0.jar, jackson-mapper-asl-1.8.0.jar 之類的...

    <!-- messageConverters beans -->   
    <beans:bean id="stringHttpMessageConverter"
        class="org.springframework.http.converter.StringHttpMessageConverter">
    </beans:bean>
   
    <!-- orgin for @ResponseBody json converter use jackson 1.x  -->
    <beans:bean id="mappingJacksonHttpMessageConverter"
        class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
    </beans:bean>
   
    <!--
        AnnotationMethodHandlerAdapter messageConverters for @ResponseBody
    -->
    <beans:bean
        class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <beans:property name="messageConverters">
            <beans:list>
                <beans:ref bean="stringHttpMessageConverter" />
                <beans:ref bean="mappingJacksonHttpMessageConverter" />
            </beans:list>
        </beans:property>
    </beans:bean>


為了趕上時代,所以....=.=b,就決定使用新的 converter...
MappingJackson2HttpMessageConverter
很微妙的多了個 2...Google網路上有許多寫法,但...貼上去都會死XD...最後總算試出一套可以用Orz...
在此前提,要記得先抓 Jackson 2.x 的 jar檔(好像release到2.6+了),然後他們官網居然不放 binary 的jar...就去jar檔網站下載了比較快Orz...其實2.x版很多喔~不過就第一眼google到是 2.2的就可以動...要注意的是~他 2.x 包的 jar 分類跟 1.x 不一樣了!有三個~
jackson-annotations-2.2.4.jar
jackson-core-2.2.2.jar
jackson-databind-2.2.4.jar

然後spring 的設定也要改囉~從網路上貼來的,再加一點點中式英文註解XD...
(這套的設定要 spring 3.2+ 才能的樣子, xsd 有不一樣)
這個的mvc 還多寫了好幾道,算是很方便@@~

    <!-- messageConverters beans -->   
    <beans:bean id="stringHttpMessageConverter"
        class="org.springframework.http.converter.StringHttpMessageConverter">
    </beans:bean>
    <!--
        AnnotationMethodHandlerAdapter messageConverters for @ResponseBody
    -->
    <beans:bean
        class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <beans:property name="messageConverters">
            <beans:list>
                <beans:ref bean="stringHttpMessageConverter" />
                <beans:ref bean="acmJacksonConverter" />
            </beans:list>
        </beans:property>
    </beans:bean>
   
    <!-- @ResponseBody json Date format needs jackson-core/databind/annoation.jar 2.x -->
    <!-- set JSON date format to ISO-8601 e.g. 1970-01-01T00:00:00.000+0000 -->
    <beans:bean id="sourceObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper"/>
    <beans:bean id="acmObjectMapper" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <beans:property name="targetObject" ref="sourceObjectMapper"/>
        <beans:property name="targetMethod" value="disable"/>
        <beans:property name="arguments" value="WRITE_DATES_AS_TIMESTAMPS"/>
    </beans:bean>
    <beans:bean id="acmJacksonConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
        <beans:property name="objectMapper" ref="acmObjectMapper"/>
    </beans:bean>
   
    <mvc:annotation-driven>
        <mvc:message-converters>
            <!-- We configure the Jackson mapper to output dates in ISO801 format. This requires adding our
            customized Jackson mapper to the list of Spring MVC message converters. But, if we just add our bean here
            all by itself, it will handle requests it should not handle, e.g. encoding strings.  So we need to add the
            other standard message converters here too, and make sure to put the customized Jackson converter AFTER the
            string converter. -->
   
            <beans:bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
            <beans:bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"/>
            <beans:bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
            <beans:bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
            <beans:bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
            <beans:ref bean="acmJacksonConverter"/>
            <beans:bean class="org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter"/>
            <!-- atom feed requires com.sun.syndication package ...   -->
            <!--<bean class="org.springframework.http.converter.feed.AtomFeedHttpMessageConverter"/>-->
            <beans:bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
            <beans:bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
            <beans:bean class="org.springframework.http.converter.xml.Jaxb2CollectionHttpMessageConverter"/>
            <!-- marshalling converter requires spring oxm -->
            <!--<bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"/>-->
        </mvc:message-converters>
    </mvc:annotation-driven>

在此他 default 的 date format 就會變成 1970-01-01T00:00:00.000+0000...
若是需要 特別指定 某vo 裡面的 date指定~ 也很方便~只要直接加 annotation..
import com.fasterxml.jackson.annotation.JsonFormat;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private Date birthDate;

雖然把這套件加進去有點麻煩~但對開發上來說還蠻好用的~~

2015年10月13日 星期二

java/tomcat find class file path

之前好像有寫過用 當下的 class 找他的實體檔案路徑在那~
不過當然這都在一個前提下~ 該 class/resource 沒有被包在 jar 檔裡~
(在jar檔裡的話~阿就通通zip在一起了,那來的各別路徑Orz...)

所以以下一律以散的class來講~
一般正常來說,取該 class/或是resource/甚至只是要個檔案路徑,可以用這種寫法~
String javaPath = ClassLoader.getSystemResource(".").getPath();

但是在 tomcat 下, SystemResource是被改寫過的(會null)~不過用另一招可以再拿到~
String libPath =  this.getClass().getClassLoader().getResource(".").getPath();
--> 這個會拿到 tomcat lib 的那個 path...WEB-INF/lib/

String javaPath =  this.getClass().getClassLoader().getResource("log4j.properties").getPath();
--> 是個爛解~不過這樣就可以拿到 WEB-INF/classes/ 的實體路徑
(你不會說你不用 log4j 吧!!)

String somewherePath =  this.getClass().getClassLoader().getResource("test/files").getPath();
--> 如果你有建個該建的資料夾在那是可以拿到  WEB-INF/classes/test/files/

老實說~用ClassLoader這個蠻容易被裱的...

不過通常大家若在 webapps 下,是可以用 ServletContext 來取得 webapps/xxx/ 的那個實體路徑...

但是我想通用於一般跟在 tomcat 下的情況~目前是這樣寫著來用~
          URL ul = ClassLoader.getSystemResource(MY_LOACTION);
               if(ul == null)  {
                    //tomcat or other webapps has no SystemResource
                    path = this.getClass().getClassLoader().getResource(MY_LOACTION).getPath();
                    //System.out.println(this.getClass().getClassLoader().getResource(".").getPath());
                    //System.out.println(this.getClass().getClassLoader().getResource("log4j.properties").getPath());
                }  else  {
                    path = ul.getPath();
                }


2015年10月8日 星期四

spring4 + mybatis3 sample (簡易版) - mybatis with mapper config

寫設定檔應該是比較多人早期的用法~
(ibatis也是寫設定檔,然後跟 mybatis ...長得很像,但有微妙的不同!!)

首先他的 mapper xml resource 會定在主要的 mybatis-config 裡
      <mappers>
          <mapper resource="xxx/xxx/xxx/xxx/sample/SampleConfigDao.xml"/>
      </mappers>

然後在其對應的路徑下就是要有這麼一個 xml。
namespace 要 對應到 其 interface 的 class name
/WEB-INF/classes/xxx/xxx/xxx/xxx/sample/SampleConfigDao.xml

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

<mapper namespace="xxx.xxx.xxx.xxx.sample.SampleConfigDao" >
  
    <select id="selectSampleConfig" parameterType="java.lang.String" resultType="java.util.HashMap">
        SELECT * FROM aa where name = #{name}
    </select>
  
</mapper>


所以對應的dao(mapper)要跟 xml 對應到~ namesapce,method name,input/output
/WEB-INF/classes/xxx/xxx/xxx/xxx/sample/SampleConfigDao.java

import java.util.*;
import org.springframework.stereotype.Repository;

@Repository
public interface SampleConfigDao {
   
    public List<HashMap> selectSampleConfig(String name);
}


和 annotation 的差別在於, annoatation 可以直接把 xml的內容,寫在 java (interface) 內,省了兩個檔~
但是 annoation 有個問題就是,總體要看到這系統用了幾個檔,就不太容易看~只能靠search...
寫設定檔~是很囉唆~加個(改個)功能,要動到 3隻 以上的程式 (mybatis-config.xml,xxx ineterface,xxx.xml)

總之軟體就是沒有100分的解~這樣工程師才不會失業阿Orz...

spring4 + mybatis3 sample (簡易版) - mybatis annotation

因為現在 annotation 很紅~
所以就試了一下~真的超簡短~~~
不過重點在於前面的設定要搞好....

複習 在 spring bean 要設定:
<bean id="myBatisMapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
       <property name="basePackage" value="xxx.xxx.xxx.xxx"/>
       <property name="annotationClass" value="org.springframework.stereotype.Repository"/>
    </bean>

SampleDao

import java.util.*;
import org.springframework.stereotype.Repository;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

@Repository
public interface SampleDao {
   
    @Select("SELECT * FROM aa")
    public List<HashMap> selectSample();
}

spring4 + mybatis3 sample (簡易版) - spring controller/service

基本上還是 spring 的 controller 和 service...一般般

Controller/Action

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.apache.log4j.Logger;

//import javax.servlet.http.HttpServletRequest;

import java.util.*;

@Controller
public class SampleController {

    Logger logg = Logger.getLogger(this.getClass());
    String logId = "log";
    String logName = "log";

    @Autowired
    SampleService theService;

    @RequestMapping(value={"/test/list"}, method = RequestMethod.GET)
    public @ResponseBody List fn1(Model model){
        System.out.println("??enter controller");
        return theService.test1();
    }
   
    @RequestMapping(value={"/test/list2"}, method = RequestMethod.GET)
    public @ResponseBody List fn2(Model model){
        System.out.println("??enter list2");
        return theService.test2();
    }
}


Service

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Service
public class SampleService {

    @Autowired
    SampleDao dao = null;
   
    @Autowired
    SampleConfigDao dao2 = null;

    @Transactional
    public List test1()  {
        System.out.println("??enter service.test1");
        return dao.selectSample();
    }
   
    @Transactional
    public List test2()  {
        System.out.println("??enter test2");
        return dao2.selectSampleConfig("ABC");
    }
}

spring4 + mybatis3 sample (簡易版) - 系統設定檔

想說 spring4 之前寫過,然後 ibatis 也寫了幾個~
那 spring4 + mybatis3 應該沒啥問題 (mybatis 以前是 ibatis)...
然後~就發現被表了Orz...

總之~還是湊了一個超簡易的版本~

jar lib,就是 spring4 的那一拖拉庫,
加上 mybatis-3-xx.jar(要找一下,他github是放 source,直接 build 好 release 的 jar 後來是在別的地方找到的)。
還有一個  mybatis-spring-xx.jar...
另外因為後端我習慣接 postgresql...但早期的 jdbc driver(8.4) 在此會發生 setQueryTimeOut method not implemented 的問題,總之~換成新的 driver(9.4) 就可以解掉(但是在那版才加的就不清楚)

首先講一下我的檔案位置

webapps/demo/WEB-INF/web.xml
                                         /classes
                                         /classes/mybatis/mybatis-config.xml     <-- mybatis 主要設定檔
                                         /classes/**/*Dao.xml        <-- mapper 內容的檔案
                                         /conf             <--  spring 設定檔區
                                         /lib

設定檔內容:
/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <display-name>anping</display-name>
    <!-- The definition of the Root Spring Container shared by all Servlets
        and Filters -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/conf/root-context.xml</param-value>
    </context-param>

    <!-- Sets the default profile to use in the absence of any profiles set
        at deployment time. -->
    <context-param>
        <param-name>spring.profile.default</param-name>
        <param-value>dev</param-value>
    </context-param>

    <!-- Creates the Spring Container shared by all Servlets and Filters -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

   
    <!-- Handles requests into the application -->   
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/conf/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
   
       
    <security-constraint>
        <web-resource-collection>
            <url-pattern>/*</url-pattern>
            <http-method>PUT</http-method>
            <http-method>DELETE</http-method>
            <http-method>HEAD</http-method>
            <http-method>OPTIONS</http-method>
            <http-method>TRACE</http-method>
        </web-resource-collection>
        <auth-constraint></auth-constraint>
    </security-constraint>
</web-app>


Spring 主檔(application.xml)
/WEB-INF/root-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- Root Context: defines shared resources accessible to all other web
        components -->

    <!-- Turn on support for @Annotation-based configuration e.g. @Inject -->
    <context:annotation-config />

    <!-- Loads application properties -->
    <import resource="properties.xml" />
   
    <!-- Loads controller/component -->
    <import resource="servlet-context.xml" />
   
    <!-- Loads model(db) -->
    <import resource="data.xml" />
       
</beans>



servlet-context.xml、properties.xml 就spring 用的一般般沒啥問題~
主要差在 與 DB model 的設定
/WEB-INF/conf/data.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
    xmlns:ctx="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

   
   <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName">
            <value>org.postgresql.Driver</value>           
        </property>
        <property name="url">           
            <value>jdbc:postgresql://127.0.0.1:5432/db1?charSet=utf-8</value>
        </property>
        <property name="username">
            <value>xxxxxxx</value>
        </property>
        <property name="password">
            <value>xxxxxxx</value>           
        </property>
        <property name="timeBetweenEvictionRunsMillis" value="300000" />
        <property name="numTestsPerEvictionRun" value="6" />
        <property name="minEvictableIdleTimeMillis" value="1800000" />
        <property name="initialSize" value="3" />
        <property name="maxActive" value="100" />
        <property name="maxIdle" value="10" />
        <property name="minIdle" value="1"/>
        <property name="maxWait" value="5000" />       
        <property name="poolPreparedStatements" value="true" />
        <property name="maxOpenPreparedStatements" value="100" />
    </bean>
   
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
    </bean>
   
    <bean id="myBatisMapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
       <property name="basePackage" value="xxx.xxx.xxx.xxx"/>
       <property name="annotationClass" value="org.springframework.stereotype.Repository"/>
    </bean>
   
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean> 
   
   
</beans>


重點在  sqlSessionFactory 要指向 dataSource 和  configLocation
然後因為我要利用 annoation 的寫法~所以有加 myBatisMapperScanner
basePackage 必填, 但他不同於 spring 可以 ** 的用法,好像~只要寫入 上幾層的 pacakge就行了(跟 log4j 的 package 制定法較像)
annotationClass ,一般通用就是 spring 的 Repository
總合來說,就是~ class 有  @Repository 且 在  basePackage下 的就會被自動掃進來~

mybatis 的主設定檔
是說原本想把他搬去跟 conf 同一層,但是google了一下,有人說~把這個檔放在外面是有什麼理由嗎!?
後來我想想也是~因為他裡面還是會因需要一直加 mapper resourse 進來~那些內容也得跟程式對應。在 src 底下是比較好維護~(conf一般都是指系統架構整體用的 config,的確是不要去搞一個客制的比較好)
/WEB-INF/classes/mybatis/mybatis-config.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>
    <settings>
        <setting name="cacheEnabled" value="true" />
        <setting name="lazyLoadingEnabled" value="true" />
        <setting name="multipleResultSetsEnabled" value="true" />
        <setting name="useColumnLabel" value="true" />
        <setting name="defaultExecutorType" value="REUSE" />
        <setting name="defaultStatementTimeout" value="25000" />
    </settings>

    <mappers>
          <mapper resource="xxx/xxx/xxx/xxx/sample/SampleConfigDao.xml"/>
      </mappers>
</configuration>


後續~


2015年10月2日 星期五

oracle function 日期 流水號

這次要用 oracle function 來實作 日期流水號~(每天從1開始跑)
以前都習慣用AP實作,不過因為這邊是多台又同時,會造成號碼重覆的問題~
聽說改了幾次寫法還是衝突,最後只好來試試...唯一的瓶頸-->就是那台DB...

我不太知道英文的關鍵字要怎麼下~中翻英去找,結果都蠻慘的~
不過用中文的話~打「oracle 日期 流水號」...就可以看到不少的參考~

不過我說是參考...因為照貼結果感覺都很慘就是XD...

這次的解法,是用DB 開 table 來記(不是做DB 的流水號)
因為覺得去reset seq 是一件很蠢的事情~感覺上也不符合seq的精神....

table,主要的欄位就是「日期」,跟「現在號碼」,為了通用,再多加個「流水號類別 」...
原理就是~指定類別,若日期內有號碼,就把號碼+1丟出。若table沒有那個日期,就丟1,insert一筆新的日期,和號碼1進去。
(理論上比較好的想法是降子,不過後來因為 ooxx的考量...我是不用insert,而是日期不同,就把原本的資料蓋成 【新的日期】,跟把號碼變成1。變形後可以減少筆數~當然是前端 AP的日期不能亂丟就是~若在跨日其實是會出事的XD,不過要是這事情不會發生就算了~)

----修正版~
後來想一想,還是改個寫法~先撈值出來看一下日期再做處理~

CREATE OR REPLACE FUNCTION FN_XXXX_SEQ(P_CTYPE IN VARCHAR2, P_DT IN VARCHAR2)
  RETURN VARCHAR2 IS V_SEQ VARCHAR2(10);
 
BEGIN
  BEGIN
    Select l.DT INTO P_DT From OWNER.TB_AAA l where l.TYPE = P_TYPE ;  
    if P_DT is not null and P_DT >= to_char(SYSDATE,'YYYYMMDD') then 
        UPDATE OWNER.TB_AAA S
           SET S.SN = LPAD(to_number(S.VALUE2)+1, 4, '0')
        WHERE S.TYPE = P_CTYPE
         RETURNING substr(S.DT, 3) || S.SN INTO V_SEQ;
   else
         UPDATE OWNER.TB_AAA S
         SET S.SN = '0001',
             S.DT = to_char(SYSDATE,'YYYYMMDD')
       WHERE S.TYPE = P_CTYPE
       RETURNING substr(S.DT, 3) || S.SN INTO V_SEQ;
    end if;
 
  END;

  COMMIT;
 
  RETURN V_SEQ;
END FN_XXXX_SEQ;

function實作上,基本上就是,你來我就先update 一次,然後把 序號 給到 V_SEQ內。
P_DT會先暫存 DB目前的日期(想做個Varible,一直GG..就算了Orz..),然後看是要用舊的資料,還是改新值給1...(日期若比資料庫小的,一律就以資料庫的時間為主~)

然後都寫成 function了,那就組好流水號格式送出去,AP端不再加工了~

雖說 oracle 對數字format 也有其他的寫法,不過 LPAD,好像比較正確~

另外,原本是要採用 EXCEPTION的判斷寫入(感覺得這個才比較正統)
但是~不知道為什麼~就是一直都掉不進  NO_DATA_FOUND
https://docs.oracle.com/cd/B10500_01/appdev.920/a96624/07_errs.htm
換了好幾個Exception也都不是...

事後,寫了個多Thread,模擬壓測的程式狂取號~似乎寫到function內,沒有重覆號的問題~原本想說不行就要DB lock(但是那個太危險了),但目前看起來是OK的樣子就是~

java call oracle funtion

一般常用都是用 PreparedStatement 來處理 SQL...
不過如果要 Call procedure 或 function的話,就要用其他的寫法~
我這邊是用 CallableStatement 來處理

另外,看了一下網路資源,似乎不同家的DB,下法也不一樣~總之這邊用oracle的~

           Connection conn = getDataSource().getConnection();
           
            CallableStatement cs = null;
            String callsql = "{? = call FN_XXXXX(?,?) }";
            cs = conn.prepareCall(callsql);
            cs.registerOutParameter(1, java.sql.Types.VARCHAR);
            cs.setString(2, "AAA");
            cs.setString(3, "BBB");
            cs.execute();

            re = cstamt.getString(1);

總之就是output取值,要用 registerOutParameter 來放,執行後再取值出來~

在oracle裡面,似乎將這些東西分得很多類  function/package/procedure....
但是雖然我覺得都是差不多的東西阿一u一a...好啦~有的是整包,有的是單一功能~等等~
java來 call,寫法都差不多,會差在 語法怎麼下的差別就是~

(但是最難的還是去擠出那個DB Function的語法,sql語法很難debug阿阿阿阿阿~)

2015年9月14日 星期一

custom annotation for method

先說,為了做 junit,是要做給別人看的~
所以文件要求的項目,格式都得要有~

但是......
因為工程師都很討厭寫文件...
因為工程師也很懶得寫註解...
因為懶得寫那些制式log...

所以就寫了一個 annotation ,來處裡~

很一般的annotation的作法~

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface JUnitTestAnnotation {
   
    String testName() default "";
    String testInput() default "";
    String testResult() default "";
    String testMemo() default "";
}

呼叫annotation的method
@JUnitTestAnnotation (
        testName="查詢-無條件查詢",
        testInput="無輸入",
        testMemo="查詢資料",
        testResult="數筆資料"
    )
    public void testQueryList1() {
        this.logStart();

然後~就可以開始掉 java.lang.reflact 的書包了~
在一開始 load 的時後,把全部的method annotation load進來
        Method[] ms = this.getClass().getMethods();
        for(int i = 0 ; i < ms.length ; i++)  {
            Method m = ms[i];
            if(m.isAnnotationPresent(JUnitTestAnnotation.class))  {
                JUnitTestAnnotation ano = m.getAnnotation(JUnitTestAnnotation.class);
                mpAno.put(m.getName(), ano);
            }
        }

然後在呼叫 logStart() 裡面,先取到「現在call 的method name」
StackTraceElement 是個很有趣的東西,把他的內容印出來。會有種熟析的fu...
原則上,第1個會是 「現在的method」,第2個就會是「前面 call 這個method 」的method...
(這真是個很妙的寫法~宅力又上升了XD)
        //StackTraceElement[] se = Thread.currentThread().getStackTrace();
        //for(int i = 0 ; i < se.length ; i++)  {
        //    System.out.println("????   ["+i+"]="+se[i].getMethodName());
        //}
        String m = null;
        try  {
            m = Thread.currentThread().getStackTrace()[2].getMethodName();
        }  catch(Exception e)  {
            return;
        }

然後拿到現在呼叫 logStart 的 method,就可以拿到他的 annotation...
        if(mpAno.containsKey(m))  {
            JUnitTestAnnotation an = (JUnitTestAnnotation)mpAno.get(m);
            logger.info("===Junit method: " + cName +"."+m);
            logger.info("===測試名稱: " + an.testName());
            logger.info("===輸入參數: " + an.testInput());
            logger.info("===預期結果: " + an.testResult());
            if(an.testMemo() != null && an.testMemo().length() > 0)  {
                logger.info("===備註: " + an.testMemo());
            }
        }

\(^_^)/然後就可以很方便的產生一堆制式的log了~

2015年9月11日 星期五

oracle 備忘

出來混總是會碰到的~其實我對甲骨文的印象很不好~又肥又胖又貴...
(快十年前,硬碟還很小,RAM 2G就很強大的時後,oracle一裝就馬上讓電腦慢到爆!)
(然後買了Java...又把人家搞爛)

不過還是又看到他了一.一|||...
是說用的方法網路資源很多,就當個記錄就是~

好吧Orz...先說一下工具的部份。
工具大家似乎都是使用 PL/SQL Developer。不過他要吃 oracle 的 ora 檔,還得要先裝一個oracle client(還好有人有現成的copy來用就沒差)...
目前 oracle 官網放的是叫 SQL Developer,老實說,難用到不知道要怎麼用,然後他連接的方式並不會產生ora檔~所以...就直接放棄他了一.一...
另外我平常使用的是小松鼠squirrel-sql,是一個用純 jdbc 實作出來的opensource...當然是很陽春,不過輕量簡單~若只是做查詢的簡單工作,又不想裝太多是個選擇。不過他抓不到 schema內的 REMARK(註解),在看會很不方便。

最後~我是用 PLSQL Developer 啦,因為要做點管理的工作Orz...
另外他對 function 等的編輯畫面好很多~

建立table~可以透過介面新增。工具好的話都不會有啥問題~
(工具 用介面建,之後匯出SQL給上線的server用就好)
就是建好要 grant 給其他 user 使用。
grant select, insert, update, delete on XXX_table to ZZZ_user;

因為有用到 synonym 的部份,要把有 owner(schema) 的轉成 public 也可以讀到~
create or replace public synonym XXX_table  for YY_owner.XXX_table;

另有做Trigger Log table的方式是~
先建一個 要 trigger 的 table,一般是 XXX_table_LOG,做好跟開 table 一樣的事。
寫 trigger,可以參照
https://docs.oracle.com/cd/E17952_01/refman-5.1-en/create-tablespace.html
因為有前人的照抄,搞懂語法後是還蠻容易的就是~
唯一要注意的就是,因為我這邊用的統一都是在 BEFORE 之前Trigger,在 insert 時要寫入LOG時,因為有些欄位是有帶預設值的,所以要記得用NVL判斷一下,已免應該有值,但寫了null~

CREATE OR REPLACE TRIGGER TG_XXX_table_LOG
  BEFORE INSERT OR DELETE OR UPDATE
  on YY_owner.XXX_table
  REFERENCING NEW AS NEW OLD AS OLD
  for each row
declare
  -- local variables here
begin
  IF updating THEN
    INSERT INTO YY_owner.XXX_table_LOG
      (Col_A,
       Col_B,
       LOG_DATE,
       LOG_ACTION)
    VALUES
      (:NEW.Col_A,
       :NEW.Col_B,
       SYSDATE,
       'U');
  ELSIF inserting THEN
    INSERT INTO YY_owner.XXX_table_LOG
      (Col_A,
       Col_B,
       LOG_DATE,
       LOG_ACTION)
    VALUES
      (:NEW.Col_A,
       NVL(:NEW.Col_B,'Y'),
       SYSDATE,
       'I');
  ELSE
    INSERT INTO YY_owner.XXX_table_LOG
      (Col_A,
       Col_B,
       LOG_DATE,
       LOG_ACTION)
    VALUES
      (:OLD.Col_A,
       :OLD.Col_B,
       SYSDATE,
       'D');
  END IF;
end;

話說...Oracle他喵的居然....把空字串當 null...一整個無言了...
所以平常常用的  IFNULL(COL_A, '') != ''
在 oracle 裡要寫成  NVL(COL_A, '') is not null
Orz...

Oracle 在下sql時,對於日期的比對 (時間區間),
無法使用  XXX_DATE >= '2015-10-02 00:00:00' 這種的寫法來下~
要利用轉日期型態  to_date('2015-10-02 00:00:00', 'YYYY-MM-DD HH24:MI:SS') 來表示 ,
要注意的是他的 format 跟一般用字也不一樣~~
可參考
http://www.techonthenet.com/oracle/functions/to_date.php




2015年9月3日 星期四

email tool: Thunderbird

最近因為買了新的nb來工作用~
新nb最麻煩的總是會碰到OFFICE的問題...
以前都可以拿公司的來灌,不過~這次公司沒有給,就只好自己生辦法~
是說也不太想弄窮人版的~就試著找些方法~

word/excel,這兩個可以用nb內建的Office Starter,有點半殘,不過能開能寫就好了~
pptx的問題,可以弄個 ppt viewer 來看(如果只是要看而已的話)
不然就是裝個OpenOffice...只是會跑版~不過因為工作性質應該用不到做ppt就想說沒差~

然後就是一定會碰到的收信問題...
一般都是用Outlook...不過因為偶也不想花錢~找了一下~
就打算用 Mozilla做的 Thunderbird...(有Mozilla掛保證,感覺就安心許多)
https://www.mozilla.org/en-US/thunderbird/

用了一下,覺得他很像就是把Firefox拿來改一改成收信軟體XD...
預設版本可以收一般 POP跟IMAP的信~
如果mail server是 MS的 exchange server,就要另外再加plugin~
我裝的是這種~
話說~ exquilla 是要收錢的Q.Q~可以試用一段時間,之後一個帳號一年10鎂吧~
https://addons.mozilla.org/en-us/thunderbird/addon/exquilla-exchange-web-services/
在設定中,要輸入exchange server的 EWS url...自動掃應該是沒用的....
https://{exchange server name}/EWS/Exchange.asmx

在Outlook上,一般只要你打 server name就可以了,在 thunderbird 上得生出一個網址來才行~
然後他也會把server上的通訊錄抓下來喔~我覺得還蠻不錯用的啦~

裡面讓偶試了有點久的功能,就是寄信給多個人時....好像自己在收信者那邊打會有問題~
要在寫信的那個視窗,打開通訊錄功能,從那邊加入,這樣多人才比較正常....

總之~我覺得還可以用啦^^~

2015年8月25日 星期二

eclipse mars ant not support jdk 1.6

每次到了新環境,就得再建一次開發工作環境~

然後~每次這時後的 Eclipse,都會讓人吐血一次...誰叫偶換地方的速度跟他改版的速度一樣=_=a...
這也是我討厭他的地方...

唉~總之現在可以download下來的版本是 mars...
mars有個前提,要使用jdk7以上的版本才能啟動~
(對於 死忠 jdk 1.6的偶來說,真是個很麻煩的部份。總之~先灌1.6,最後再灌1.7以上的版本,ide就可以順利的開起來了)

說起來也不只是因為1.6是sun留下的最後一大版,另一個重要的原因是~阿就以前的系統都是以1.5/1.6為大宗(因為這兩版的相容性做得比較好)

先不爽一下1.7的問題,jdk1.7後拔掉一內建在1.6內的 sun的package,造成前人用這些method的都會有Class NotFound 的問題...解法有兩種:
一是把編譯運行的環境都改成jdk6,這是比較好的作法。
二是如果只是為了開發,又不想回去重裝jdk,就是引用6 裡的 rt.jar...或是看是在6裡面的那個jar,多個jar當lib就過了~

然後~因為這次的專案build,只能用ant跑~(因為前人寫了個很精美的build.xml...)
所以...就發現,這mars版本的ant 在跑指定jdk為 6時,會出現 不支援舊 jdk的訊息...
google了一下,目前,他是個bug...沒有什麼解....

所以~就....只好去找原生的ant下來(apache ant)...
http://ant.apache.org/bindownload.cgi
download zip下來,解開。好像看作業系統,windows去環境變數設定JAVA_HOME到 jdk6 的位置去~就可以了~
然後就開個cmd....
cd 到那個專案的build資料夾(ex: cd D:\workspace\myproject\build)
執行ant指令(ant.bat) (D:\apache-ant-1.9.6\bin\ant)
預設是跑build.xml...
總之要加別的就自己加指令~
ex: 指定task (ant mytask)
可參照
https://ant.apache.org/manual/running.html

目前看起來是自己另外call ant能跑起來了就好Orz...




2015年7月4日 星期六

一個募款計畫~FREE Up Your Imagination! Web Design Tool for All.

今年和朋友一起做了個網頁工具的募款計畫~

https://www.indiegogo.com/projects/free-up-your-imagination-web-design-tool-for-all/x/11203093#/story

FREE Up Your Imagination! Web Design Tool for All.

目前預定的scope大概就如這張圖(是說現在也只有這張圖可以貼XD)...
 
那個工具網頁在我的想法來說,簡單的說就是一個免費的網頁設計工具。以拖拉,即時preview一些網頁的樣子(因為是用網頁做網頁,誤差會小一點點。因為HTML的css定起來就是那樣子囉....)

而之後會做幾樣功能,做 icon/button 圖,選色這些基本的項目,而在排版的部份其實說真的小難~要整合的項目,是還~有點多,如果不靠後端server,只有純HTML,不知道可以寫到什麼程度就是O_Oa...

聽說,統計募款的成功率不到 10%,不過還是做做看,畢竟要是有募到就可以做比較多的事情,或是說早一點做出來XD...假日或休閒時還要一直寫code,想起來是也有點悲慘|||Orz...

雖說是英文的平台,但如果有人用繁中留言的話,那也可以用繁中回答喔XD...
(是說我的英文蠻爛的Orz...)

至於未來會不會有這塊的技術心得感想,可能就還是看經費吧~
其技術主要是以 HTML5 為主,其實寫 js 在某方面來說也是挺有趣的就是^_^a...
 

2015年6月11日 星期四

ibatis 未開啟游標CRSR0001

「未開啟游標CRSR0001」
我覺得這個問題是個鬼...
然後因為Google不認識這個鬼...所以只好自己抓...

先註明一下這邊使用的版本:
ibatis 2.3.4
ibatis dao 2


原本都好好的沒事,然後就有一天被念說畫面出Exception了(很好心的寫了中文字,但...沒幫助阿|||Orz...照翻成英文,Google也不認識~Google只認識 沒關,沒有沒開的阿~囧rz)

因為程式很久沒改過,當然先懷疑是不是有人亂Key資料~DB看來看去,資料也很正常~

然後拿相同的SQL直接去DB查~整叢好好的(台)
所以DB資料錯誤問題排除...

再來就只好看程式了(不能推卸責任了Orz...)開啟全部的Log來看~發現其實他查回來的ResultSet 也是完整的,一筆也沒掉...
那所以一定是 ibatis 將資料轉成 Bean物件出事...

可是=.=|||...ibatis都照標準範本寫,是可以改什麼阿阿阿Orz...

是說我覺得這明明是一個很蠢的問題,筆數不過3xx就掛....又不是幾千萬(是說其他撈出來幾萬的function也活得好好的阿@@)...然後蠢問題的解答通常也很蠢...所以往蠢的方向去想...

把原本的 resultMap 改成 resultClass... 就...好...了...
(先說這 resultMap也是單純到不行的 1欄位 對 1欄位,還沒有做額外查詢)

-----------------------------------------
是說解法果然是很蠢...不過這說明了 ibatis 對於 resultMap 與 resultClass的處理方式明顯不一樣,先說我沒去讀他的source code,只從結果推論。

 resultClass 是比較偏像我心中的 ResultSet 拿出來就持續 next 往下填資料。

而 resultMap 則可能是依其對應的 pojo 與DB回傳欄位 的結構定義,先預支了一堆資源什麼的,然後不知道什麼蠢問題,造成他在 mapping時 出錯了~(筆數這麼少,欄位對應1對1這麼單純還出錯真是...)
-----------------------------------------

總之,結論就是,如果可以用 resultClass 實作,請優先使用 resultClass,減少不必要的困擾~~(是說找到的sample code,覺得 resultMap的還挺多的,當然是說map彈性比較大就是~)


2015年5月11日 星期一

CSS3 resize and catch change event

在 CSS3 開始,div 也可以使用 resize的參數了~
http://www.w3schools.com/cssref/css3_pr_resize.asp


以非常簡單的設定方式,就可以得到一個隨意拖拉變大變小的區域~

BUT...又來了~
雖然CSS3 已經開放這個功能,但是tag上的onresize event 卻還沒搞好Orz...
當然是說只是單純不需要與 js 互動的話是沒問題,但是通常都還是要防呆之類的,抓不到事件觸發就很無言...

網路上有些作法討論,後來我是覺得暴力破解真是~~有點強過頭Orz...
(唯一的缺點就是,效能吃比較重吧,但是看跑起來似乎蠻流暢的就降子吧XD)
來源在
http://stackoverflow.com/questions/8082729/how-to-detect-css3-resize-events

http://stackoverflow.com/questions/1397251/event-detect-when-css-property-changed-using-jquery/1397500#1397500

簡單的說,就是只要css(style)的值一變動,就會跳進這個event,管你變長變短,變醜變漂亮,全都逃不掉的窮舉法~

試作的example如下:(有搭配jquery在用)
<style>
#test  {
    width: 200px;
    height: 200px;
    border: dotted 1px #888888;
    resize: both;
    overflow: auto;
}
</style>

<script>
$(function() {
    document.getElementById('test').addEventListener('DOMAttrModified', function(e){
        if (e.attrName === 'style') {
            //console.log('prevValue: ' + e.prevValue, 'newValue: ' + e.newValue);
            divInfo(this);
        }
    }, false);
   
});
function divInfo(elem)  {
   
    var w = $(elem).outerWidth(true);
    var h = $(elem).outerHeight(true);
    var x = $(elem).offset().left;
    var y = $(elem).offset().top;
   
    var msg = '('+x+','+y+')==>'+w+'*'+h;
    $('#info').html(msg);
}
</script>

<body>
<div id="info">test info</div>
<div id="test" >TEST</div>
</body>

2015年5月6日 星期三

jquery outerHight sometimes wrong

先來看這個這個 jquery method
https://api.jquery.com/outerHeight/
裡面有圖解他取的範圍~

原則上,一般大家都會取用 ex: $('#test').outerHeight(true),加上margin的計算~

不過,有時候他會出現很奇妙的數字...Google關鍵字也可以讓你找到不少人有一樣的問題。
但是,通常要用到這個method時,大多是計算位置,或是自動修正高度 scrollbar用,一但出現奇妙的數字就會非常Orz...

看了一下Google網路上說法,有時候是老IE的基本元件 padding/margin 本身 default就有問題,可以導入 reset.css 之類的解決。

不過我碰到的倒不是這樣子,但最後也是用改 版面的 css 來解決,我想會讓他出錯的,應該是某些特定 版面排版 設定造成的(並不是單一個div,而是整體的使用,導致他誤判或是什麼的吧,但不容易想到去調別人~所以發生時,一定會覺得很鬼XD...)

講一下在我出錯的case裡,基本結構是:
<div>   <!--  margin here --> O  -->
    <table>   <!-- margin here --> X  -->
        <thead>....</thead>  <!--  here  getOuterHight(true)   -->
        <tbody>....</tbody>
    </table>
</div>

而我在抓 thead 的 高度時GG了~
outerHeight 明顯的數字變高,但是我在 thead 裡的 tr, th, td 等,其實都沒動到height/margin/padding 等項目...一整個搞不懂為啥他喵的旁邊的table算對,就是這一個算錯~

後來比了很久的source code,發現出錯的那個 table tag,有定了 margin/border...然後正覺得table的 上下margin,加起來的數字還跟多出來的高度很接近...果然拿掉 table的margin,計算後就正確了!

所以在結構上,整個table的外框與間隔,應該在外層再用一個div包起來,用div的外框與間隔來做比較好。table與table內的型態,就讓他單純點的不要搞太多設定會比較好~~

不過這表示說,outerHight這個method,可能也會受到 parent/child 等的影響,除了找該元件的內容外,相連的元件設定也該找找看,說不定會比較快找到鬼QQ~



2015年5月4日 星期一

Html5 Storage

在HTML5的規格內,加入了一項新的東西,就是Storage
http://www.w3schools.com/Html/html5_webstorage.asp
如w3school的說明~ 比cookie好~就是打算來取代cookie的部分功能~

用法上已經改得相當簡單,把他想成是map。
只要setItem('test', 'tttt'),getItem('test'),clear()...
就已經可以滿足很多的使用情境~

另外有localStorage,與sessionStorage的 內容值存取範圍差別,
在測試底下,sessionStorage,就大概是指用同一個頁面access的範圍
(開啟新分頁,或是瀏覽器重開就會不見)
而localStorage則是在同一瀏覽器上都可以運作,但是無法跨瀏覽器。
若要跨瀏覽器還是只能靠Server端儲存~

更詳盡的說明可以參考
http://apress.jensimmons.com/v5/pro-html5-programming/ch11.html

不過在打算使用這功能時,在規劃時要注意的是,到底能存多少資料在裡面?
查了一些網路文章,結論就是看各家瀏覽器的實作...而瀏覽器目前最少的應該是5M...
(應該會隨著時代而慢慢增加吧~如果網頁應用發展的好的話~~)
相較之下,原本的cookie 大概在4k左右,加上有分scope,在應用上會比較安全點,怎麼說Storage應該都是比較好的選擇~



貼一下簡單的測試程式
var supportStorage = false;
function checkStorageSupport() {
  //sessionStorage
  if (window.sessionStorage) {
    //alert('This browser supports sessionStorage');
    $('#show_session').html(sessionStorage.getItem('test'));
  } else {
    //alert('This browser does NOT support sessionStorage');
  }
  //localStorage
  if (window.localStorage) {
    //alert('This browser supports localStorage');
    supportStorage = true;
    $('#show_local').html(localStorage.getItem('test'));
  } else {
    //alert('This browser does NOT support localStorage');
  }
}

function saveIconList()  {
    if(!supportStorage)  {
        return;
    }

    var h1 = $('#myinput').html();
    localStorage.setItem('test', h1);
    sessionStorage.setItem('test', h1);
}

2015年4月28日 星期二

Crystal Report repeat group header when change page

還是先嫌棄Crystal Report一下...真是很難用阿阿阿~
加上奇怪的中文翻譯~害我爬文爬很久,就是看不到像樣的鬼... 造成更多的誤解!!!!!

這次的需求其實也算合理,就是xls table式的排版,然後資料列太多,在下一頁上方顯示表頭。
一般情況應該很簡單,就是把表頭做在Crystal Report內的「頁首」就好~

BUT...人生就是有個BUT!
我的報表是有group分類的........天曉得group那時候換下個group...
搞不好人家剛好分段下面,下一頁又是漂亮的開始~所以基本上~這就會變成是動態的~
前一頁有完整結束,就不要重覆表頭,前一頁資料還沒完就show表頭...

查了很久,Crystal Report並沒有內建可以動態做到分頁順便幫你重複顯示表頭或區段的設定~所以...只好再度使用奇怪的手段一.一|||...

首先版面設計,要先做規劃,新增一區「頁首b」,並開立公式做狀態轉換
「頁首a」            原頁首
「頁首b」            重複表頭的樣式
「群組首#1」     群組資訊+表頭
「細目」              @group_flag
「群組尾#1」     @group_flag_end
「報表尾」


運用採用Status Diagram的概念, 定一個Shared的變數,當資料列在「細目」時,其flag=2,當在「群組尾」結束時回歸flag=1。(Crystal Report的數字 init是1...)
這樣當我在換頁時,就可以看flag,若為2就是還在畫細目中,要重覆表頭。1的話表示群組結束或開始,不須顯示表頭。

公式內容相當簡單如下:
@group_flag
Shared numberVar flag;
page := 2;
''
(最後一行要打上空字串'',才不會被看到,但在開發預覽可以不用打,方便看值的變化)

@group_flag_end
Shared numberVar flag;
page := 1;
''

最後在設計區,右鍵打開「區段專家」,選擇「頁首b」區段
右邊Tab「一般」,有個「抑制顯示(無截取細目)」<--頁首只有這個可以編輯,算了就加減用就是,項目的右邊有個「x-2」的圖示,點圖示後就可以用公式寫法來控制要顯示(false)跟隱藏(true)
注意這邊是結果要回傳true/false,跟一般寫回傳字串數字不同。
Shared numberVar flag;
if page > 1 then
false
else
true

這邊編寫完成後,應該會看到小圖示「x-2」會變色。(應該只是標明有額外撰寫公式判斷,但他變紅色一整個就像在說寫錯了一樣)。然後該項設定的checkbox(勾)是沒有作用的,所以不要勾起來(勾起來設計頁面會看不到區段內容,要再拉表單會造成困擾)

接著就可以測試「頁首b」是否有正常的運作了~建議可先寫簡單的幾個字~看動態有沒有正確出現再去拉對齊苦工。






2015年4月27日 星期一

Html5 drag anywhere

後記:
目前支援這個功能的,測試後FireFox還是最完整。
Chrome是半殘,dragover(判別該區塊是否可以drop的部份)在本機上運作是正常的,但要是放到網路上的網頁就會取不到值event.dataTransfer的資料,然後就會出現問題。
基本上就是在 drag_over時,讓他抓不到event.dataTransfer.getData時,讓他event.preventDefault(); 是就可以解決Chrome的問題(但這樣就不會出現禁止放置的圖示,弱了一點)
但還好drop時抓值是正確的,變成drop時要判斷好一點,誰能放誰不能放,擋掉就可以解決了。
-----------------------------------------------------------------

以往要做到網頁元件的拖拉,第一個想到的就是 jquery ui (jquery ui真的非常強大@@/)

但現在有個新的選擇,HTML5就開始內建這個功能了~
好站w3school 點出~在 IE9 應該就可以使用了~
http://www.w3schools.com/html/html5_draganddrop.asp

先說的是~HTML的用法與 jquery 不同~可能概念上也有點差異~
目前感覺起來,jquery是偏functional的使用(比較好上手)~
而HTML5在整體上似乎定義的更清楚(功能比較完整喔)~

先來看HTML5上的操作行為~找到一個網站寫得很讚~
這網站做為讀HTML5的參考書應該不錯~
http://apress.jensimmons.com/v5/pro-html5-programming/ch9.html

在HTML5上實作,要求一定要同時定義上 drag 和 drop 的部分才能運作。
(jquery可以只寫 draggable)
所以若只要 drag 的功能時,重點把他想成全部(body)都可以 drop 就是了~

另外在HTML5上,drag的每個階段,都要自己撰寫定義,缺點是有點麻煩,好處是會寫的話,變化可以很多,還有會顯示一個小icon (口跟禁止)來表示能不能放在那邊(小icon也可以改圖),相對來說UX是做得比較完整的~

HTML碼
一般可隨便拖的都是浮動在網頁上 ,Style基本上就是 position:absolute; float:left。
另外加入 HTML5 新attribute  draggable="true"
<div id="mydrag" draggable="true"
    style="position:absolute; float:left; right: 10px; top: 50px; width:50px; height:50px; border: solid 1px #AAAAAA;">Drag Me</div>


自訂function
//在開始拖拉時,就先將該項目之id,位置寫進 dataTransfer,以便後面判斷取用
function drag_start(event) {
    var style = window.getComputedStyle(event.target, null);
    event.dataTransfer.setData("pos",
        (parseInt(style.getPropertyValue("left"),10) - event.clientX) + ',' + (parseInt(style.getPropertyValue("top"),10) - event.clientY));
    var dragdid = event.target.id;
    event.dataTransfer.setData("did", dragdid);
}
//在放開時,將該div改變位置移動放到新的位置
function drop(event) {
    var offset = event.dataTransfer.getData("pos").split(',');
    var dm = document.getElementById( event.dataTransfer.getData("did") );
    dm.style.left = (event.clientX + parseInt(offset[0],10)) + 'px';
    dm.style.top = (event.clientY + parseInt(offset[1],10)) + 'px';   
    event.preventDefault();
    return false;
}
//不限制也不管他,(ondragover、dragover 這兩個行為還是不一樣,但在此期望的操作方式上,可以一起用)
function drag_over(event) {
    event.preventDefault();
    return false;
}

元件設定初始,可利用 js 的方式加入,
或是在該 div 上直接寫上event, ex : dragstart="drag_start(event)",
不過在外面統一加入比較好分工~
    var dm = document.getElementById('mydrag');
    dm.addEventListener('dragstart',drag_start,false);
    document.body.addEventListener('ondragover',drag_over,false);
    document.body.addEventListener('dragover',drag_over,false);
    document.body.addEventListener('drop',drop,false);

要注意的是~雖然已經將body設為可以drop的部分~
但是~若是網頁沒有填滿整個螢幕(通常是高的部分,沒到底的話),是不能放的XD...

2015年3月10日 星期二

apache poi XLS formula

一般常見報表都是純資訊提供~
阿不過也是看user的習慣啦,這次就做了滿滿公式的報表Orz...
(雖然有一部分的xls公式,user已經寫好了,但是有些還是得自己推~囧/)
總之~就因為poi能做,就只好把他做出來了~囧/...

poi的公式非常的簡單好寫,一行就解決^_^b~
只要把xls上能跑的公式,把前面的(=)去掉,copy進來就馬上用的嚇嚇叫~~
cell.setCellFormula( "SUM(A1:A10)" );

唯一要處理的還是所謂的儲存格格式(DataFormat),依其情況再set就可以了~
但是一定要處理,不然就會看到很多可愛的######出來鬧場一.一a....

另外若是這格公式算出來的值(數字)還要再被其它格拿去計算的話~
建議是在公式包上  VALUE( )  ,確保出來是數字(有問題會被轉為0),以免後續計算會出現 #DIV/0! 之類的怪東西,或是在整欄加總(SUM)不正確的問題~

但若是非數字的項目,就不要再加VALUE了,因為加了一定轉成數字,然後就又看到GG...

這次比較特殊用到的公式叫 SUMIF,e.g:
SUMIF(A1:A20, ">=0", B1:B20)
翻譯出來叫做,判斷欄位A1~A20,若是內容 >=0 ,就加總 B1~B20
會記錄一下是覺得~網路上有些範例真的有點詭異一.一|||...




apache poi 3.2 xls date

在做xls時,難免都會用到日期的時候~
因為個人不太喜歡預設的表示式,所以常常都故意寫出時用字串來表示~
不過~夜路走多了還是會碰到鬼一.一|||...
總之~整理一下xls上關於日期的用法~~

註明,這是poi 3.2的版本,因為在3.6後,使用的method有更動~我看用法是差不多,但是method取用的object改了~

在之前,首先要了解,日期在CELL裡的型別是  HSSFCell.CELL_TYPE_NUMERIC ,是數字型態。
而讓它在XLS上能正常以日期顯示是因為 DataFormat (儲存格格式)的關係,預設是 yyyy/m/d


read XLS Date:
        HSSFCell cell = ....;
        if(cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC)  {
            Date st10 = readCellDate(cell);
        }


write XLS Date:
先制定特定的CellStyle,不知道format字串的話,可以用XLS內的儲存格格式點開找範例跟試用~特別聲明民國年顯示用法為("[$-404]e/m/d")
        cellDateROC = workbook.createCellStyle();
        cellDateROC.cloneStyleFrom(cellBorderLeft);
        cellDateROC.setDataFormat( workbook.createDataFormat().getFormat("[$-404]e/m/d"));
       
        cellDate = workbook.createCellStyle();
        cellDate.cloneStyleFrom(cellBorderLeft);
        cellDate.setDataFormat( workbook.createDataFormat().getFormat("yyyy/m/d"));

產生XLS cell
                    cell.setCellStyle(cellDateROC());
                    cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC);
                    cell.setCellValue( new Date() );

當初一定要寫成日期格式是因為,有公式要用到就只好認了@@~

2015年3月3日 星期二

db2 sql substring (tag like string)

通常欄位資料都是一格一種資料,不過有時候會有像備註備忘~
或是不想加欄位,找了既有不用的某個欄位來塞一堆更奇怪的東西~
等ooxx的原因~

所以就是解析字串format,拿值出來的時候~就是只能用substring的作法~~
因為DB SQL通常只能一道做完,沒有人在split後又再取的...
使用符號分隔萬一順序沒標到分隔也會抓不出來~
所以在多種資料,硬要擠在同一欄時~我大多會使用xml的 內容 表示方法。
(substring時,頭尾都會不一樣,不用怕index用到別人的)

但如果資料庫支援 json格式的話~會傾向使用 json...因為現在json在 程式語言 parser比 xml好用~

總之~這次就是用 tag形式儲存就是了...
內容範例為: 欄位名(MEMO)
<NAME>Alex</NAME><ID>9527</ID>

取 NAME
SUBSTR(MEMO, LOCATE('<NAME>',MEMO)+6, LOCATE('</NAME>',MEMO)-LOCATE('<NAME>',MEMO)-6 )

取ID
SUBSTR(MEMO, LOCATE('<ID>',MEMO)+4, LOCATE('</ID>',MEMO)-LOCATE('<ID>',MEMO)-4 )

要注意的就是LOCATE(indexOf)  之後要加減其字的長度就是囉~
若是需要數字 或其他型態, 就是將字串再轉型期望的樣子。

2015年2月11日 星期三

iceface datatable groupon selectOneMenu

在datatable內,有個GroupOn可以合併項目(rowspan),但是若是剛好那格是下拉選單要傳值收值,這時候~就會出現神奇的問題...假設是rowspan=3,雖然畫面只顯示一個,但實際上jsf他還是產生了三個,然後只顯示了第一個在上面....所以操作畫面,下拉變值,再怎麼改都只會改到一個而已~

那就會想到說要用 ValueChangeEvent 來讓他改一個也改其他兩個看不到的值。但因為jsf層級的問題,採用event.setPhaseId 來觸發其他值的變更~好像就可以的樣子...

但是~最好笑的就是~畫面輸入完麻後,重點總是要按送出(submit)才有意義阿~~
然後不知道........jsf的submit就是會去把所有的event全觸發一次....接著....他媽的就看到值又跳掉了阿阿阿阿阿~真的是改到翻桌(/-_-)/_|___|_...

花了很久的時間,一度還改成用Map來Group By那個輸入~但搞得是有點複雜,資料轉換多次很容易出錯~
後來終於想到一個比較簡單的方式...
重點就在於拿UIInput 來處理:
一是判斷是否為顯示(顯示的那個才是畫面操作變更到的那個),依此才改變層級切換值
一是傳入groupOn的資訊,這樣才會知道那些是要一起變動的資料

------jsp------
<datatable ....>
<ice:column groupOn="#{row.pid}">
    <ice:selectOneMenu value="#{row.dtype}" valueChangeListener="#{bean.changeType}" partialSubmit="true">
        <f:attribute name="pid" value="#{row.pid}" />
        <f:selectItems value="#{bean.lstType}" />
    </ice:selectOneMenu>
</ice:column>

------java------
import javax.faces.event.ActionEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.ValueChangeEvent;
import javax.faces.component.UIInput;

public void changeType(ValueChangeEvent event) {
        if(event == null || event.getNewValue() == null)  {
            return ;
        }
       
        if(((UIInput)event.getSource()).isRendered())  {
            if (event.getPhaseId() != PhaseId.INVOKE_APPLICATION) {
                event.setPhaseId(PhaseId.INVOKE_APPLICATION);
                event.queue();
                return;
            }
        }
       
        String pid = ((UIInput)event.getSource()).getAttributes().get("pid").toString();
        String choose = (String)event.getNewValue();

        ....依相同的 reference id去更新 datatable model裡的dtype(choose)
    }

2015年2月3日 星期二

java 利用enum 做參數設定

現在參數都流行用外部設定~
不過這次不想,就試著使用 enum 這種東西來定義看看~
先定義自訂項目的基本資訊~
public enum CustData {
   
    Group("Group", true, "xxx");
   
    private String pId;
    private boolean isActive;
    private String callMethod;
    prviate Object cache;
   
    private CustData(String id, boolean active, String callMethod)  {
        this.pId = id;
        this.isActive = active;
        this.callMethod = callMethod;       
    }

    public String getPid() {
        return pId;
    }

    public boolean isActive() {
        return isActive;
    }

    public String getCallMethod() {
        return callMethod;
    }

   
    public void setCache(Object obj)  {
        cache = obj;
    }

    public Object getCache()  {
        return cache;
    }
}

在Pool裡Init時,就把相關的全讀進去~
其實偶想紀錄的重點只在 Object[] all = c.getEnumConstants();
總之就是取得所有定義的物件

            Class c = Class.forName("mypackage.CustData");
            Object[] all = c.getEnumConstants();
            for(int i = 0 ; i < all.length ; i++)  {
                CustData cd = (mypackage.CustData)all[i];
                Object obj = loadCustData(cd);
                cd.setCache(obj);
                .......
            }

是說~用enum有什麼差別??...
修改彈性不太好(當然是比不上寫設定檔)
程式可讀性是高了點,因為可以定一些有意義的名稱,常數上應該是比較好看~

只是說,有時候做非開放式系統,也不用說什麼都做到設定檔上去,
有時候寫死增加別人偷code的難度,好像也是一種生存理論XD...