顯示具有 Java 標籤的文章。 顯示所有文章
顯示具有 Java 標籤的文章。 顯示所有文章

2016/07/27

iBatis遇到Postgresql的JSON/JSONB type

Postgresql等資料庫在較新的版本中,都提供了JSON/JSONB這樣的data type,這提供了開發者更動態的便利性。關於JSON/JSONB可以參考這裡,本文就不贅述。

如果是使用iBatis來做ORM,又該如何與JSON做mapping呢
這裡記錄一下我的試做

我的table結構是
CREATE TABLE public.test
(
  oid bigint NOT NULL,
  data text,
  jdata1 json,
  jdata2 jsonb,
  CONSTRAINT "PK_TEST" PRIMARY KEY (oid)
)


Java class是
public class CTest {
private long oid;
private String data;
private String jdata1;
private String jdata2;
  // getter/setter略
}

則我們可以用下列的iBatis mapper XML
<resultMap id="test" type="CTest">
<result property="oid" column="oid"/>
<result property="data" column="data"/>
<result property="jdata1" column="jdata1"/>
<result property="jdata2" column="jdata2"/>
</resultMap>
<insert id="newTest" parameterType="Map" statementType="PREPARED">
insert into test (oid, data, jdata1, jdata2)
values (#{oid}, #{data}, cast(#{jdata1} as json), cast(#{jdata2} as jsonb));
</insert>
<select id="getTest" resultMap="test" parameterType="Map" statementType="PREPARED">
select * from test where oid=#{oid};
</select>


這樣就可以用下列的iBatis mapper程式來存取table了
public void newTest() throws Exception {
JSONObject o1 = new JSONObject();
o1.put("name", "Yoyo Chen");
o1.put("age", 25);
o1.put("qualified", true);
Map map = new HashMap();
map.put("oid", 333);
map.put("data", o1.toJSONString());
map.put("jdata1", o1.toJSONString());
map.put("jdata2", o1.toJSONString());
execute("newTest", map);
}

public CTest getTest(long oid) {
Map map = new HashMap();
map.put("oid", oid);
CTest t = (CTest) selectOne("getTest", map);
return t;
}


ps. execute()與selectOne()是我的methods,主要是包裝了iBatis的methods

2016/06/07

Socket Options

開發網路程式的朋友都應該用過socket,而socket中除了基本的方法與屬性外,還有不少options。如果觀察Socket的JavaDoc,你一定會發現有很多關於SO_XXXX、IP_XXXX、TCP_XXXX之類的屬性。這些屬性多半與通訊的連線成功或傳輸關係不是那麼大。但是,如果程式需要關注到效能,那這些屬性就會扮演重要的角色。

從Java的角度來檢視一下這些options:


IP_TOS


TOS(Type-Of-Service)的值必須介於[0..255]間。RFC 1394中則定義了下列

  • 0x00 -- normal service
  • 0x02 -- minimize monetary cost
  • 0x04 -- maximize reliability
  • 0x08 -- maximize thrughput
  • 0x10 -- minimize delay

程式可依需要將這些值做bitwise運算,來設定通訊時的等級(例如0x14是指minimize delay and maximize reliability)。
不過並不是所有的設備都會依照這個option的設定來進行傳輸,但是有需要還是可以設上。

SO_LINGER

linger是拖延的意思,這會影響Socket.close()的速度。
如果觀察TCP State Diagram,當我們做close socket的動作時,連線兩端還是會做許多複雜的動作。其中active close這端在TIME_WAIT會等待 2*MSL (Maximum Segment Lifetime)的時間,來確保送出的資料可以被另一端接收到。在RFC 793中提到了MSL是120秒。不同的OS與應用會去調整這個值,目前多數的Linux、BSD及Windows是30秒。

對於寫server-side程式的人來說,linger時間太長,你的程式雖然已經close這個socket了,但是在底層並沒有真的關掉,還在TIME_WAIT的狀態,造成了程式在短時間內沒有辦法再bind到這個port。
現在的internet速度與穩定性已經比早年好太多了,應該不需要這麼長的linger time了。如果你的程式需要較多的socket開關操作時,可以考慮把linger time設小一點。

如果把linger time設為0呢?則程式會送出設有RST flag的封包給對方,可以達到快速結束連線的效果。但是,對方的程式可能會發生 "Connection reset by peer" 的錯誤。

SO_TIMEOUT

這個時間是Socket.getInputStream().read()會被block的等待時間長度。如果read等待超過這個時間長度,則會丟出java.net.SocketTimeoutException。
SO_TIMEOUT的值須大於0,若為0,則視為無止盡。
這個值設太小,就容易常發生timeout的現象;若設太大,有時程式又會被block太久,反應速度變長。所以如何拿捏就要看應用類型而定了。

SO_SNDBUF / SO_RCVBUF

對底層的傳送/接收緩衝區大小的建議值。
這只是建議值,底層會依照所設定的值,在參照實際系統狀況來決定這兩個緩衝區的大小。
前輩建議,如果是在通訊較不好的環境,或是瞬間傳送的速度很快,那SO_SNDBUF可以設大些,這樣可以確保TCP sliding window夠大,足以容納一值無法成功送出的資料。
如果是電腦的處理速度慢,那SO_RCVBUF就要設大些,免得來不及處理,資料掉了。

SO_KEEPALIVE

如果socket建立了,但是長時間(2小時,視OS實作而定)沒有進行資料傳送,這個連線很可能會被中間的某個網路設備中止。為了避免這情形發生,如果啟用了SO_KEEPALIVE,則socket會自動發出一個keepalive probe給對方,對方也會回應回來,讓網路設備知道這個連線仍在使用中。
對有些應用或環境來說,2小時真的太長了(尤其是行動網路,有些系統商大概不會讓你站用連線這麼久),而且對Java也無法變更改時間值,所以許多應用就會自己實作ping/pong的機制來當作keep-alive。

SO_OOBINLINE

當連線有許多資料排隊要送時,如果有一緊急事件要插隊通知對方,就可以用 OOB (Out-Of-Band) Inline。啟用SO_OOBINLINE,並可透過Socket.sendUrgentData來即時送出 1-byte的資料給對方。
不過,直到Java 8,都沒有看到可以接收urgent data的方法。所以...

TCP_NODELAY

在提這項之前,要先瞭解Nagle's algorithm。簡單的說,想想一家貨運公司,如果一收到小貨物就馬上出車送貨,對公司來說整個營運效能就不高,對整個道路來說就會增加車次,容易造成路上塞車。
若是貨運公司將這些收到的小貨物累積到一定的量才出車送貨,那公司的營運效能才能提高,不會造成浪費。但是這就會造成最前面的幾件貨物要等很久才會被送出。
TCP_NODELAY就是停用Nagle's algorithm,來提高throughput。
如果所開發的系統常會傳送小資料,這個選項就該納入考慮。


以上做為工作上的紀錄,方便未來查閱。也希望對大家都有些幫助。

2016/05/05

JavaMail透過GMail送信遇到的問題

這樣的技術文件,網路上應該很多了,那些詳細的實作就不贅述
套著網路上的資訊,使用了下面的參數


結果遇到了下列的exception

javax.mail.MessagingException: Can't send command to SMTP host;
  nested exception is:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at com.sun.mail.smtp.SMTPTransport.sendCommand(SMTPTransport.java:1564)
at com.sun.mail.smtp.SMTPTransport.sendCommand(SMTPTransport.java:1551)
at com.sun.mail.smtp.SMTPTransport.ehlo(SMTPTransport.java:935)
at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:426)
at javax.mail.Service.connect(Service.java:310)
at javax.mail.Service.connect(Service.java:169)
at javax.mail.Service.connect(Service.java:118)
at com.ruby.vtun.entity.GeneralMail.send(GeneralMail.java:133)
at com.ruby.vtun.entity.GeneralMail.main(GeneralMail.java:161)
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1884)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:270)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1439)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:209)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:878)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:814)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1016)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:702)
at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:122)
at com.sun.mail.util.TraceOutputStream.write(TraceOutputStream.java:114)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
at com.sun.mail.smtp.SMTPTransport.sendCommand(SMTPTransport.java:1562)
... 8 more
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
at sun.security.validator.Validator.validate(Validator.java:260)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:326)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:126)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1421)
... 19 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:196)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:268)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380)
... 25 more


經過被蹂躪了一陣子,總算發現是我的防毒軟體Avast作怪!把Avast關掉,就可以正常送信了。
要解決這問題,除了關掉Avast外,可以透過更改Avast的設定來解決,因為目前是使用port 587來送,只要把587從Avast的設定中拿掉,就可以正常送信了

2014/11/12

ListView自我更新多次

剛剛開發一個有ListView的Activity,從SQLite資料庫取出資料,透過adapter將資料顯示在ListView上
資料庫中的測試資料總共有20筆
當畫面進入到這個Activity時,發現得花超過三四秒,才能將這些items顯示出來 (ASUS Nexus 7)

一開始以為是SQLite的效能太差,所以要花這麼長的時間,可是才20筆資料就花這麼多時間,效能也未免太差了
(select * from XXX order by YYY desc)
進入debug模式,發現其實從資料庫撈資料的速度很快,顯然不是SQLite的問題

經過一番追蹤,發現adapter裡的getView被呼叫了80次
才20筆資料,getView被叫了80次,也就是做了4次refresh
為什麼?

經過一番折騰與閱讀,總算知道主要原因是那個ListView的height設定為wrap_content,Android為了要將高度調整能wrap content,可能就必須不斷做載入與調整,也因此才會refresh了多次。
當把height改成match_parent時,果然這個現象就不會發生了。

2014/09/15

Android APP無法取得GCM notification token

維護一支具有GCM notification功能的Android APP,因應客戶customization需要,必須更改package name以及修改manifest裡的root package定義,這會影響generated出來的R.java的package定義,因此,須再針對受影響的各程式更改import路徑

這樣的改法應該是最簡單的,比用refactor功能變更各package的name快多了 (之前也遇到一樣的需求,這樣改就OK了,也不會大幅改變svn裡的架構) 

不過卻遇到了一個問題,當device送資料到server時,GCM register ID(token)竟是空的! trace一下程式,我有一個GCMIntentService class是繼承GCMBaseIntentService,原來override上層的onRegistered是會被呼叫的,現在程式一執行,這個callback function竟然一直沒有被叫用,難怪token會是空的。 可是,這又是那裡出錯了?改的東西好像也沒幾個,而且那些應該都不會影響到這部份的功能呀。

找了許多資料,也做了一些嘗試,找到了一個因素,就是這個GCMIntentService必須要在menifest所定義的root package,例如說,menifest定義的package是com.abc.app,則這個service也要在com.abc.app,不可以在com.abc.app.service等其他的package中,即使manifest對service的宣告是指向com.abc.app.service也不行! 所以,只好再用refactor功能,把這個GCMIntentService移到它該在的package中,這樣執行起來就OK了!

2014/08/15

在Android中周期性更新ListView的內容

在一個Activity裡有一個ListView,需要每10秒鐘更新ListView的資料
用寫Java的傳統想法,寫出了這樣的程式
其中ListItemProducer.getInstance().getList()會傳回ArrayList<string>

ArrayAdapter adapter = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
  ...
  list = (ListView) findViewById(R.id.listView1);
adapter = new ArrayAdapter(ListActivity.this, android.R.layout.simple_list_item_1, ListItemProducer.getInstance().getList());
list.setAdapter(adapter);

timer = new Timer();
timer.schedule(new MyTask(), 5000, 10000);
}

class MyTask extends TimerTask {

@Override
public void run() {
adapter.notifyDataSetChanged();
}
}

一執行,當掉了,怎麼可能!?
這樣的Java程式我寫了無數次了,不應有錯呀
 靜下心仔細看了一下,原來出現"Only the original thread that created a view hierarchy can touch its views."的exception
Android只允許原始的thread去變動views,上面的MyTask是在另一個thread跑,不被允許去碰views
找找資料,Handler倒是個解決問題的好方向
於是加了這段

private Handler updateHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
adapter.notifyDataSetChanged();
}
};

然後把 MyTask改成

class MyTask extends TimerTask {
@Override
public void run() {
try {
updateHandler.sendEmptyMessage(0);
} catch (Exception e) {
e.printStackTrace();
}
}
}

讓MyTask在每次時間到時,透過handler來送message回原來的thread,讓原來的thread去做update views的工作

2014/04/18

10 Reasons why Java Rocks More Than Ever

我喜歡Java,從2000年開始使用Java到今天已經有14年了。對我而言,用Java來開發各種系統,都是很美好的經驗。

Java,對有些人來說,是種語言。但對我來說,Java不只單單是種語言,她還是個執行平台(JVM),更是個很漂亮的API。

今天看到這篇文章10 Reasons why Java Rocks More Than Ever,裡面提到的十點,真的點出了我也認同的Java十大優點:

Part 1: The Java Compiler
Java的結構讓Java程式在compile時可以快速且簡易,也沒有複雜的linking問題

Part 2: The Core API
經過十多年來的演進與累積,JDK core API無論在功能與效能上,都有明顯的進步。一般的程式,只要用core API幾乎就足夠了

Part 3: Open Source
這一點對我來說真的是獲益良多。Internet上充滿了一大堆好用的Java open source,你想得到或想不到的功能都有,讓我可以在很短的時間,就能做出符合需求的系統。不過對我也有些困擾,就是有太多類似功能的libraries/frameworks,讓我得花不少時間去評比與測試,來決定用那一個

Part 4: The Java Memory Model
Java在memory的規劃上完全支援了concurrency computing。這個只要用Java寫過threading就能體會它的好,連C++11與C11都加入了這樣的memory model了

Part 5: High-Performance JVM
JVM加上了JIT,讓這個雖然是虛擬機器(VM),但跑起來卻不笨重。有不少benchmark的評比,Java跑起來的速度不會差C/C++太多,尤其是在server environment,雖然有不少C/C++的忠實者不太同意,不過它的效能在一般用途上真的是夠好了

Part 6: Bytecode
bytecode對JVM來說,其實就是這個VM的machine code。所有的Java程式都要先compile成.class的file,裡面就是可以在JVM上執行的bytecode。現在也有其他語言(http://c2.com/cgi/wiki?OtherLanguagesForTheJavaVm)可以compile成bytecode了,所以不一定只能用Java語言才能寫出能在JVM上執行的程式了

Part 7: Intelligent IDEs
對Java programmers來說,這一點真的很幸福,像Eclipse, Netbeans這些IDE,功能強大,而且不用錢

Part 8: Profiling Tools
由於JVM的結構容易與外界溝通,所以Java的開發者有不少profiling工具,可以來量測CPU, memory等等的使用狀況,幫助做performance的調校

Part 9: Backwards Compatibility
當你寫了十幾二十年的程式,看到好久以前寫的程式,不用修改就能在新平台上執行,你就會知道那是何等值得感動的事
Java就是這麼可愛,不像某家公司,換個版就夠你改得要死

Part 10: Maturity With Innovation
Java從1995誕生至今也快20年了,它的成熟度如何,看看金融界使用的情況就知道了,連在火星上都有它的足跡。Java仍在演進,與時俱進地不斷創新功能,這也是為何有這麼多Java的死忠者願意跟著它走。

你喜歡Java嗎?Java還有很多我都還未接觸過的面,值得大家一起來學習與分享。

2014/03/21

FileDropJS 的 Java servier-side

最近要做drop file upload的系統,經過挑選,選擇了一個較light-weight的FileDrop (filedropjs.org)
不過它只有提供PHP server-side程式可參考,找了老半天,沒有看到Java的solution,於是只好自己來研究一下了
還好花一天的工就完成,在這裡做個記錄

protected void process(HttpServletRequest request, HttpServletResponse response) throws IOException {
PrintWriter writer = response.getWriter();
JSONObject obj = new JSONObject();

try {
saveRawDataToFile(request); // file upload by raw request

    obj.put(AbstractCommand.R_SUCCESS, true);
    obj.put(AbstractCommand.R_MSG, "upload completed");
} catch (IOException e) {
    obj.put(AbstractCommand.R_SUCCESS, false);
    obj.put(AbstractCommand.R_MSG, e.getMessage());
} finally {
    writer.println(obj.toString());
writer.flush();
}
}

protected void saveRawDataToFile(HttpServletRequest request) throws IOException {
String filename = request.getParameter("img") + ".png";
String outputFile = uploadPath + File.separator + filename;

InputStream in=request.getInputStream();
int size=request.getContentLength();

try {
OutputStream out=new FileOutputStream(outputFile);
byte[] chunk= new byte[size];
in.read(chunk);
out.write(chunk,0,size);
out.close();
} catch (Exception e) {
e.printStackTrace(System.out);
} finally {
in.close();
}
}

2014/01/07

JavaMail 遇到 MessagingException

使用JavaMail送信時,在開發的機器(WinXP)以及staging server(Ubuntu 11.04)上,都可以正常送出。但在正式server(CentOS 6.3)上,卻無法送出。檢視log file,發現有exception
javax.mail.MessagingException: 501 5.0.0 HELO requires domain address

關於這個exception,Oracle在官網上倒是有一些解說

回頭來瞭解SMTP protocol一下,參考了這一篇,猜測可能是OS沒有設domain name,用指令查了一下,果然是如此。所以問題不
在於OS,而應是安裝時有沒有去設domain name吧。
$ domainname
(none)


因為之後會deploy出去的環境,也不知道作業系統的管理者是不是會去設domain name,所以先從加property的方向來解決,看能不能work。


用這樣的方式,就可以正常送出mail了。
留個紀錄,以後如果再遇到,就不用找太久了。