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的設定中拿掉,就可以正常送信了

2016/02/03

你、我、他

記得在服役時,長官都交代不說「你我他」。那時也不知道原因,問班長他也說不出為什麼,只說從以前就這樣,不要問太多,遵守就對了!

經過這些年來的歷鍊,逐漸地端倪出這個規定真的是很受用的規定。
口語上少用「你我他」,多用「我們」,除非讚美他人時

因為你我他有高度針對性
當語氣拿捏不好時,很容易讓聽者有不同的詮釋
用「我們」,則讓人覺得你我是同一團隊,禍福與共
後面的那些話,聽起來就不那麼刺耳了

GCM SERVICE_NOT_AVAILABLE

今天又遇到怪事了
在修改一支Android APP,這支APP有用到GCM來做push notification,但是修改的點只有在UI的部份
修改完放到Samsung Galaxy Mega(4.2.2)一跑就當,以前沒發生過這問題
查了一下,是跑到GoogleCloudMessaging的register()就當,這段程式應該是很標準的寫法

if (gcm == null) {
    gcm = GoogleCloudMessaging.getInstance(parent);
}
String regId = gcm.register(Global.GCM_SENDER_ID);


丟出來的exception是SERVICE_NOT_AVAILABLE
java.io.IOException: SERVICE_NOT_AVAILABLE
at com.google.android.gms.gcm.GoogleCloudMessaging.register(Unknown Source)
at com.ruby.switch2go.manager.GCMHelper$GCMAsyncTask.doInBackground(GCMHelper.java:76)
at android.os.AsyncTask$2.call(AsyncTask.java:287)
at java.util.concurrent.FutureTask.run(FutureTask.java:234)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:856)

把程式放到ASUS Nexus 7(6.0.1)上跑,耶!一樣的程式,卻可以正常執行喔。所以應該不是程式的問題,那...難道是Android版本的問題?

再找來另一支6.0的手機,嗶~~~,還是SERVICE_NOT_AVAILABLE!
看來應該不是Android版本問題了
這幾台的Google Play Service都是8.4.89版的

再回到Samsung GALAXY Mega,用另一個類似但sender ID不同的程式來跑,卻又正常了

看了這篇Google Cloud Messaging is Extremely Unreliable for Push Notifications,手機端能檢查的都看過,好像也無解。

那~~~問題在那裡?真的是Google server-side的問題嗎?
是不是不同的phone token與sender ID會分派到不同的server去處理,而負責DUT的server剛好這時候有問題了?

果然,時間是最好的解藥。經過兩個多小時的等待,一切都恢復正常了。看來,是GCM server-side有問題了。