2013年12月15日 星期日

android - actionBar中的Overflow Menu(海苔條)出現的時機

參考:Adapting Android 4.0 to Older Hardware and Apps

http://developer.android.com/design/patterns/actionbar.html


發現有時候Overflow Menu(海苔條)會出現有時候又不會出現,後來看了上方參考的官方文件終於懂了:

首先解釋一下,Action OverFlow也就是俗稱的『海苔條』就是這個東西:
也就是上圖編號『4』最右邊的那個三個垂直排列點的按鈕啦~

依照官方文件說明,當裝置擁有『實體menuKey』時,那麼這條就不會出現,而要出現menu選單就是在手機上按下『實體menuKey』;當裝置沒有『實體menuKey』時,ActionBar的最右方(如上圖),就會出現Overflow Menu(海苔條),替代『實體menuKey』



以下是官方說明截取:

Phones with physical navigation keys

Android phones with traditional navigation hardware keys don't display the virtual navigation bar at the bottom of the screen. Instead, the action overflow is available from the menu hardware key. The resulting actions popup has the same style as in the previous example, but is displayed at the bottom of the screen.
我拿手邊的三個裝置試驗,試驗的app為『play商店』,『gmail』的確平板沒有『menuKey』的狀況下就會出現Overflow Menu(海苔條),反之另外兩隻手機因為有『menuKey』所以就沒顯示了 google查詢actionbar overflow force還有不少討論,看來有不少人都需要這種需求呢。
以這篇文章來說似乎不推薦強制去變更這個規則讓Overflow Menu(海苔條)一定要出現,畢竟這違反了android的設計想法(不過我就是有遇到客戶希望能出現,後來被我說這個是android的規範給打退想法的<其實是因為我不知道要如何讓她強制出現啦...汗,臺北等公車就有實現這個做法,不過我還沒成功就是...)

android - 使用ActionBar時啟動出現 java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity...

參考:ActionBarCompat: java.lang.IllegalStateException: You need to use a Theme.AppCompat

使用ActionBar如下:


public class MainActivity extends ActionBarActivity 
...

啟動時出現以下錯誤:

12-15 17:27:09.154: E/AndroidRuntime(422): FATAL EXCEPTION: main
12-15 17:27:09.154: E/AndroidRuntime(422): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.androidtest/com.example.androidtest.MainActivity}: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
12-15 17:27:09.154: E/AndroidRuntime(422): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2663)
12-15 17:27:09.154: E/AndroidRuntime(422): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679)

解決方式:
將res/values/styles.xml下的parent屬性改為Theme.AppCompat的其中之一,例如:
"@style/Theme.AppCompat.Light"

<style name="AppBaseTheme" parent="@style/Theme.AppCompat.Light">
...


2013年11月23日 星期六

hsqldb - 使用date型態時一直出現錯誤java.sql.SQLDataException: data exception: invalid datetime format: yyyyMMdd...

參考:Chapter 10. Built In Functions

使用hsqldb+mybatis執行以下的sql cmd:


UPDATE TABLE      SET TRANS_NET_DIS=0,TRANS_SALES=0,TRANS_ADJ_DIS=0,TRANS_FEE=0,TRANS_DIS=0,TRANS_NET_SALES=0,TRANS_ADJ_SALES=0,TRANS_RECEIPT=0       WHERE STMT_DATE=to_date('20121230','yyyyMMdd') AND SEQE='00' AND MERCH='999160691' AND ORGG='822'


然後出現以下的錯誤:

### Cause: java.sql.SQLDataException: data exception: invalid datetime format: yyyyMMdd
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:23)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:150)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:49)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:43)
at com.sun.proxy.$Proxy6.updateIsKeyData(Unknown Source)
at com.ctbc.transfile.sql.mybatis.UpdateIsKeyData.execute(UpdateIsKeyData.java:21)
at com.ctbc.transfile.sql.mybatis.UpdateIsKeyData.execute(UpdateIsKeyData.java:1)
at com.ctbc.util.sql.MyBatisUtil.execute(MyBatisUtil.java:42)
at com.ctbc.transfile.loadtask.check.KeyChecker.rmvAndUpdateKeyExistsBeans(KeyChecker.java:89)
at ut.com.ctbc.transfile.loadtask.check.KeyCheckerTest.testRmvKeyExistsBeans_Exists(KeyCheckerTest.java:211)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.sql.SQLDataException: data exception: invalid datetime format: yyyyMMdd
at org.hsqldb.jdbc.Util.sqlException(Unknown Source)
at org.hsqldb.jdbc.Util.sqlException(Unknown Source)
at org.hsqldb.jdbc.JDBCPreparedStatement.fetchResult(Unknown Source)
at org.hsqldb.jdbc.JDBCPreparedStatement.execute(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.apache.ibatis.logging.jdbc.PreparedStatementLogger.invoke(PreparedStatementLogger.java:55)
at com.sun.proxy.$Proxy8.execute(Unknown Source)
at org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler.java:41)
at org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler.java:66)
at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:45)
at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:100)
at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:75)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:148)
... 33 more
Caused by: org.hsqldb.HsqlException: data exception: invalid datetime format: yyyyMMdd
at org.hsqldb.error.Error.error(Unknown Source)
at org.hsqldb.error.Error.error(Unknown Source)
at org.hsqldb.HsqlDateTime.toJavaDatePattern(Unknown Source)
at org.hsqldb.HsqlDateTime.toDate(Unknown Source)
at org.hsqldb.FunctionCustom.getValue(Unknown Source)
at org.hsqldb.FunctionSQL.getValue(Unknown Source)
at org.hsqldb.RangeVariable$RangeIteratorMain.getFirstRow(Unknown Source)
at org.hsqldb.RangeVariable$RangeIteratorMain.initialiseIterator(Unknown Source)
at org.hsqldb.RangeVariable$RangeIteratorMain.next(Unknown Source)
at org.hsqldb.StatementDML.executeUpdateStatement(Unknown Source)
at org.hsqldb.StatementDML.getResult(Unknown Source)
at org.hsqldb.StatementDMQL.execute(Unknown Source)
at org.hsqldb.Session.executeCompiledStatement(Unknown Source)
at org.hsqldb.Session.execute(Unknown Source)
... 47 more

後來找到hsqldb的官方說明,直接找到下方的Table 10.1. TO_CHAR, TO_DATE and TO_TIMESTAMP format elements部分,可以看到允許的都是『大寫』,因此將上述執行的yyyyMMdd改為YYYYMMDD,就可以解決這個問題了.


2013年11月18日 星期一

mybatis - 使用Oracle時出現Error updating database. Cause: java.sql.SQLSyntaxErrorException: ORA-00911: 字元無效



假設我有以下的Mapper.xml:
  
        INSERT INTO UPDATETIME (FILE_NAME, LASTUPDATED) 
     VALUES ( #{filename}, SYSDATE);
  

執行時出現錯誤:
### The error may involve com.ctbc.transfile.sql.mybatis.IKeyCheckMapper.updateUpdateTime-Inline
### The error occurred while setting parameters
### SQL: UPDATE UPDATETIME      SET    LASTUPDATED = SYSDATE      WHERE  FILE_NAME=?;
### Cause: java.sql.SQLSyntaxErrorException: ORA-00911: 字元無效


原因很簡單,尾端不要加上分號(;)即可.

(不知道為什麼oracle語法明明就是要加分號在mybatis上卻不需要加...)


mybatis - Mapper.xml語法參數使用方式

參考:Mapper XML Files

有兩種使用方式:

  1. 使用#{data}
    使用$就等同使用JDBC中的PreparedStatement方式設定參數,如下:
     
     < select id="selectPerson" parametertype="int" resulttype="hashmap">
      SELECT * FROM PERSON WHERE ID = #{id}
     </ select>
    

    類似於我們直接使用以下的方式:
     
    // Similar JDBC code, NOT MyBatis…
    String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
    PreparedStatement ps = conn.prepareStatement(selectPerson);
    ps.setInt(1,id);
    

    此種方式最明顯的好處就是防止SQL injection
  2. 使用${data}
    此種方式等於簡單的字串相加,所以假設以上的ID是字串形態的參數,那麼我們就必須在前後加上單引號('):
     
      < select id="selectPerson" parameterType="int" resultType="hashmap">
        SELECT * FROM PERSON WHERE ID = '${id}'
      < /select>
    

    所以上述的方式僅僅是組成SQL COMMAND:SELECT * FROM PERSON WHERE ID = 'iddata'直接送到sql去執行,
    最明顯的問題就是SQL injection,因此在mybatis的官方說明上也標注僅可能避免使用此方式.

2013年11月12日 星期二

Quartz - 出現org.quartz.SchedulerException: Problem instantiating class...


問題:出現org.quartz.SchedulerException: Problem instantiating class 導致JOB無法順利被初始化執行


請檢查Job Class是否有實做default construction

2013年11月9日 星期六

maven - 如何新增自定義的3rd party dependency library

參考:guide-3rd-party-jars-local

需求:我需要新增一個自己定義的library jar(非公開),讓maven pom.xml可以在dependency中使用

做法:
   首先打開cmd,使用以下指令將自定義的jar加入

mvn install:install-file -Dfile=<path-to-file> -DgroupId=<group-id> \ -DartifactId=<artifact-id> -Dversion=<version> -Dpackaging=<packaging>

以上參數說明如下:

  • -Dfile:欲加入的jar所在位置
  • -DgroupId:group-id  
  • -DartifactId:artifact-id
  • -Dversion:version
  • -Dpackaging:壓縮的方式,在這裡直接填入jar

範例如下:
 mvn install:install-file -Dfile=/Users/wuanne/workspace/3rd-java-lib/piwi/piwi.jar -DgroupId=com.tw.piwi -DartifactId=piwi -Dversion=1.0.0 -Dpackaging=jar

之後在pom.xml中可如下方式使用:


  
             
  
   com.tw.piwi
   piwi
   1.0.0
  
            
       

maven - 使用eclipse執行時出現 copy-dependencies is not supported by m2e

參考:http://stackoverflow.com/questions/8271106/m2e-copy-dependencies-is-not-supported-by-m2e

需求:maven的pom.xml中使用maven-dependency-plugin,來使用第三方jar,卻報以下錯誤:
copy-dependencies is not supported by m2e


方式:
  1.
  先在pom.xml 中的<build></build>中加入

     
 
            
                
                    org.eclipse.m2e
                    lifecycle-mapping
                    1.0.0
                    
                        
                            
                                
                                    
                                        org.apache.maven.plugins
                                        maven-dependency-plugin
                                        [2.0,)
                                        
                                            copy-dependencies
                                        
                                    
                                    
                                        
                                    
                                
                            
                        
                    
                
            
          


2.右鍵->run as->Maven Install

  等待步驟2跑完後即可解決

2013年11月4日 星期一

android - logcat只記錄ERROR訊息(不使用eclipse)


  1. 在console執行adb logcat *:E
  2. 將手機連上電腦(一定要在執行完畢1後再連接)

Android - 出現 E/AndroidRuntime(25261): java.lang.RuntimeException: Unable to instantiate activity ComponentInfo....java.lang.ClassNotFoundException


Q:出現 E/AndroidRuntime(25261): java.lang.RuntimeException: Unable to instantiate activity ComponentInfo....java.lang.ClassNotFoundException

A:
首先檢查AndroidManifest.xml中的Activity name參數是否有設定錯誤(package name or activity class name),但發現並無錯誤,
最後找到是因為我有使用第三方的SDK Project,但是並沒有正確設定好,請參照下圖:

2013年10月29日 星期二

Android - 出現 Unable to execute dex: Multiple dex files define ...

Q:execute時出現Unable to execute dex: Multiple dex files define ...
A:清除不必要或重複的Lib,之後重新refresh->clean

2013年10月23日 星期三

Android - 使用JSON的時候出現org.json.JSONException: Value  of type java.lang.String cannot be converted to..


參考:解决org.json.JSONException: Value  of type java.lang.String cannot be converted to JSONArray

問題:使用android的時候發現一個怪問題,當我使用new JSONObject(jsonstr)時會報exception:

org.json.JSONException: Value  of type java.lang.String cannot be converted to...

而且這個情況只有在2.x(沒實際每個版本測試過,估計是3.0以下都會)出錯,在4.1則正常

解答:因為在我使用的情況是,json字串為http回傳的內容,只要將這個輸出的html編碼改為UTF8 without BOM編碼格式即可.

(但是一般win的筆記本似乎無法更改BOM編碼,因此我是另外下載notepad++更改的,請見上述參考連結)

2013年10月12日 星期六

mac - 更改PATH設定


  1. open terminal
  2. sudo nano /etc/paths
  3. 輸入密碼 後enter
  4. 在最後一行上加入欲加入的新PATH路徑
  5. control+x 並選擇Y 離開
  6. 開啓另一個新的terminal,使用 echo $PATH查看是否已加入新的位置

oracle - how to run sqlldr from remote server

需求:需要執行在遠端伺服器(非本機)上的oracle 使用sqlldr匯入資料

方式:

sqlldr [USER]/[PWD]@//[IP:PORT]/[SID] control=[control file]

[]中的參數都是需要替換為自行使用的

需注意的是執行的本機也須安裝sqlldr tools否則會顯示command not found的錯誤

2013年10月6日 星期日

opencsv - 使用CsvToBean.parse()總是無法成功


參考致上述網址code如下:
 
ColumnPositionMappingStrategy strat = new ColumnPositionMappingStrategy();
strat.setType(Country.class);
String[] columns = new String[] {"countryName", "capital"}; // the fields to bind do in your JavaBean
strat.setColumnMapping(columns);
 
CsvToBean csv = new CsvToBean();
 
String csvFilename = "C:\\sample.csv";
CSVReader csvReader = new CSVReader(new FileReader(csvFilename));
 
List list = csv.parse(strat, csvReader);
for (Object object : list) {
    Country country = (Country) object;
    System.out.println(country.getCapital());
}
 

package net.viralpatel.java;
 
public class Country {
    private String countryName;
    private String capital;
 
    public String getCountryName() {
        return countryName;
    }
 
    public void setCountryName(String countryName) {
        this.countryName = countryName;
    }
 
    public String getCapital() {
        return capital;
    }
 
    public void setCapital(String capital) {
        this.capital = capital;
    }
}

以上Country class為inner class
但執行以上code時csv.parse() (LINE11)都會報錯:
java.lang.RuntimeException: Error parsing CSV!

仔細追了一下opencsv source發現,會在MappingStrategy.createBean()時發生
InstantiationException的錯誤,試驗一下發現原來inner class使用reflection的newInstance()都會出現這問題

因為opencsv中的parse會直接使用newInstance() create new instance,因此使用的TYPE不可為INNER CLASS
至於有沒有可以使用inner class的方式要再查查看

2013年10月5日 星期六

javamail - 使用SMTP(非SSL)寄出信件時一直收到javax.net.ssl.SSLHandshakeException...


參考:Java Mail: SSLHandshakeException when sending email on port 25 without SSL


需求:
  最近一個小工具需使用到JAVAMAIL,奇怪的是我明明使用SMTP(非SSL)寄信,卻一直報SSL的錯誤:

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:1420)
    at com.sun.mail.smtp.SMTPTransport.sendCommand(SMTPTransport.java:1408)
    at com.sun.mail.smtp.SMTPTransport.ehlo(SMTPTransport.java:847)
    at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:384)
    at javax.mail.Service.connect(Service.java:275)
    at javax.mail.Service.connect(Service.java:156)


查了半天才發現,只要關閉下列的Properties設定參數即可:
props.put("mail.smtp.starttls.enable", "true");
(把上一行取消就可)

2013年9月21日 星期六

MyBatis - 出現錯誤org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: Could not find resource jdbc.properties

需求:使用mybatis時出現以下錯誤:
org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource jdbc.properties




以下為我的mybatis-config檔案:

  
  
      
  
  
  

而我的jdbc.properties放在外部的config資料夾下(並非src package之下),因此產生這個錯誤,解決的方法是在eclipse中將此config資料夾加入classpath下:
打開project->properties設定
在build path的source中加入該config資料夾就可以了


2013/10/19更新:
上述方式若還是不行,有另一種方法如下圖,在欲加入的的folder按右鍵->Build Path->Use as Source Folder


2013年9月20日 星期五


參考:How To Set Environment Variables On Mac OS X新手問題, 關於.bash_profile 問題

需求:要在mac OS上設定環境變數

在win上設定環境變數是非常方便的事情,然而對於我這個大懶人以前也不熟linux的一換到mac上需要設定就得花點心思,幸好沒有很麻煩,以下已安裝gradle 1.7為範例:

  1. 將下載好的gradle解壓縮後置放於指定位置下,我放在Users/wuanne/tools之下,如下圖
  2. 然後呼叫出終端機,執行以下指令,確認gradle是未安裝的情況:

    目前環境變數並沒有包含gradle的路徑
     

  3. 所以執行gradle會顯示指令不存在  
  4. 再來我們要建立bash_profile,如果之前完全沒建立過則不會有這個檔案,先輸入

  5. touch .bash_profile
    $vim .bash_profile


  6. 然後建立以下內容(按小寫a進入編輯模式):

    輸入完畢後按下esc->wq->enter存檔離開
  7. 鍵入exit離開目前終端機
  8. 再次打開終端機,確認是否安裝成功:

    環境變數已經包含我們置放gradle的目錄位置,執行gradle也不再顯示command not found
  9. 安裝成功XD

2013年9月19日 星期四

如何在mac OS上可以存取android手機資訊,並且eclipse專案時可以使用實機測試

參考:Android Transfer

這兩天發狠換了mac(窮到快被鬼抓走了...0rz..)第一件事情就是要恢復所有工作環境,
差不多都快弄完了卻發現兩個問題:


  1. 無法連接到我的android手機抓取資料,只能充電
  2. eclipse跑android project時進行測試無法使用實機進行測試
後來看到這篇文章Android Transfer 下載Android File Transfer後就可以解決上述問題囉~

話說回來買了mac確在寫android實在很詭異....無奈現在實在沒心力再多去負擔學習IOS開發了...android都還摸不熟也很難抽出時間....唉~~~

mac超新手繼續移植回工作環境中....

2013年9月13日 星期五

Android - Eclipse啟動時出現錯誤 Please ensure that adb is correctly located at %ADB_FILE_PATH%



今天在run android專案時一直出現錯誤:
 Please ensure that adb is correctly located at %ADB_FILE_PATH%
重啟Eclipse、電腦、手機都沒有用,後來找到了解決方法如下:


  1. 打開cmd -> netstat -aon|findstr "5037",找出占用ADB Port的Service,如下圖我的是被7928這個PID的程式占掉了

  2. 工作管理員砍掉這個Service,砍掉PID==7928的

  3. 重新啟動eclipse
問題解決

2013年9月11日 星期三

JAVA Reflection:如何取得Class的Super Class、Interfaces..


參考:http://tutorials.jenkov.com/java-reflection/classes.html#superclass

需求:
最近開始研究Spring對於他使用XML可以做DI(Dependency injection)的方式產生了興趣,其中產生bean之後的constructor parameter居然也可以使用XML直接設定而不需要在application中指定id名稱的特性...想來想去也只有Reflection可以辦到了,因此有了以下的小實驗:
我們需要達到兩項目標:
  • 取得指定Class的Super Class
  • 取得指定Class的implement Interface(s)
直接看Code:

我們定義了兩個Interface:ISuperA、ISuperB
 
public interface ISuperA {
}
 
public interface ISuperB {
}

接下來定義implement:
 
public class ImplementClass implements ISuperA, ISuperB {
}

呼叫看結果:
 
 public static void main(String[] args) {
  Class superclass = ImplementClass.class.getSuperclass();
  Class[] interfaces = ImplementClass.class.getInterfaces();
  
  System.out.println("superclass:"+superclass.getName());
  
  System.out.println("interfaces:");
  for(Class interface1 : interfaces)
   System.out.print("\t"+interface1.getName());

 }

結果:
 
superclass:java.lang.Object
interfaces:
 ISuperA ISuperB


補充說明,推測Spring的用法:
下面的Code來自Spring in Action 3rd - Listing 1.4、1.6、1.7:
package com.springinaction.knights;

public classBraveKnightimplementsKnight{
  privateQuestquest;
  public BraveKnight(Questquest){
    this.quest=quest;
  }
  public voidembarkOnQuest()throwsQuestException{
    quest.embark();
  }
}

XML設定:
  
  
    
  

  
  

Spring中呼叫方式如下:
package com.springinaction.knights;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public classKnightMain{
   public staticvoidmain(String[]args){
      ApplicationContextcontext = new ClassPathXmlApplicationContext("knights.xml");
      Knightknight=(Knight)context.getBean("knight");
      knight.embarkOnQuest();
   }
}

依照上述呼叫的Code我們可以看到,指定了XML中的"knight" create...ok這沒甚麼問題,"knight"直接對應到XML的bean id="knight",然而有趣的事情在於,需create的BraveKnight並非是使用default constructor直接建立,是有帶參數的!然而在上述呼叫中卻沒有看到指定bean id="quest"的部分。僅僅是在bean中設定了<constructor-arg ref="quest"/>就對應的到....而quest代表的並非Type型別。
因此如果按照上述的實驗,推測是先取得id="quest"的"com.springinaction.knights.SlayDragonQuest",接者取得"com.springinaction.knights.BraveKnight"中的Constructor,由Reflection機制可以取得各種Constructor的參數型別,只要能找到"一個參數",這裡我們找到的為public BraveKnight(Questquest),因此只要SlayDragonQues符合以下條件之一,那麼XML的指定條件就可成立:

  1. SlayDragonQues的Type為Questquest
  2. Questquest為SlayDragonQuest的super class
  3. Questquest為Interface且被SlayDragonQuest implement
最後答案是3(未列出Questquest Code),因此XML設定正確,可以被順利creation :)

Eclipse - 出現Access restriction: The type XXXX is not accessible due to restriction on required library

參考:http://www.digizol.com/2008/09/eclipse-access-restriction-on-library.html
需求:最近承接一個舊系統翻新的案子,由於舊系統使用了 sun.net.ftp.FtpClient 然而被報錯誤:Access restriction: The type FtpClient is not accessible due to restriction on required library C:\Program Files\Java\jre7\lib\rt.jar



  1. Eclipse->window->Preferences
  2. Java->Compiler->Errors/Warings
  3. Deprecated and restricted API->Forbidden reference將原本下拉Error改為Warring

其實這種作法還真是鴕鳥...視而不見...= =

Mybatis - 推薦網站



  1. http://mybatis.github.io/mybatis-3/getting-started.html
    MyBatis官方網站,寫得很仔細
  2. http://www.sivalabs.in/2012/10/mybatis-tutorial-part1-crud-operations.html
    寫得還蠻清楚仔細,且也附上基本UnitTest的方式

2013年9月7日 星期六

MyBatis - Update Data


UserMapper.xml
  
 UPDATE UserData 
 SET
    money = #{_money}
 WHERE userid = #{_userid}
    

UserMapper.java
public interface UserDataMapper{
 public int updatUserMoney(UserData);
}


User.java
public class UserData {
 private String _userid;
 private int     _money;

...
}

Call
   SqlSessionFactoryBuilder sqlSessionFactory = new SqlSessionFactoryBuilder()
    .build(inputStream);
   SqlSession sqlSession = sqlSessionFactory.openSession();
   BaseDataMapper mapper = sqlSession.getMapper(BaseDataMapper.class);
   int updateCount = mapper.updatUserMoney(UserData);
   sqlSession.commit(true); //important!

這裡要注意的是有兩個地方:

  1. 如果需要接收Update後的row column count,記得必須把Mapper的回傳type設定為int,這樣子在呼叫的時候才有辦法讓mybatis回傳row count
  2. 因為會影響實際數據,所以記得要做session.commit(true)否則無法正常更新

MyBatis - 使用resultType回傳單一SELECT資料時都是Null


今天用Mybatis的時候發生一個問題,回傳的Data怎樣都是Null,確認SQL語法無任何問題,資料也都正常,查了半天才發現是我使用上的錯誤:


使用code如下:
UserMapper.xml
  < select id="getByUserId" parametertype="String" resulttype="UserData">
     SELECT *
     FROM UserData 
     WHERE USERID = #{xx}
  </ select>  

UserMapper.java
public interface UserDataMapper{
 public UserData getByUserId(String userId);
}


UserData.java
public class UserData {
 private String _userid;
 private int     _money;

...
}

但是不管我傳入甚麼userid,getByUserId(userid)永遠回傳Null,
查了好久才發現我使用方式錯誤,其實我們在UserMapper.xml中定義的<resultMap>
(resultMap使用請見MyBatis - 使用resultMap(1))
這種方式並不適用在使用resultType的時候,因此我們的UserMapper.xml得改為如下:
  < select id="getByUserId" parameterType="String" resultType="UserData" >
     SELECT userid  as _userid,
   money as _money
     FROM UserData
     WHERE USERID = #{xx}
  < /select>  

還是得乖乖用 sql field as java field name的方式一一指定而不能只是使用 SELECT * 否則就會發生一直都回傳Null的問題。
不過我也是mybatis的初學者,所以說不定有方式只是我還沒發現用法而已。

2013/10/19更新:

可以回傳*,mybatis設定欄位的方式為查找setter,setter的規則為setParamName
,因此假設SELECT * FROM TEST 其中一個欄位名稱為paramName,那麼只要對應的resultType擁有 setParamName這個名稱的setter,mybatis就會呼叫此setter將paramName的value丟入assign囉 (大小寫不限)

2013年9月4日 星期三

MyBatis - 範例與步驟總結




  1. 建立config檔案,用來設定連線DB的envirimont各種設定
    jdbc.properties:
     
    jdbc.driver=org.hsqldb.jdbcDriver
    jdbc.url=jdbc:hsqldb:mem:my-project-test;shutdown=true
    jdbc.username=sa
    jdbc.password=
    

    mybatis-config.xml:
      
    < configuration>
      
      
          
      
      
      
        
          
          
            
            
            
            
          
        
      
      
        
      
    </ configuration>
    
  2. 建立Mapper的interface,並定義所有會被用來呼叫的CRUD等functions, 之後會與Mapper.xml對應
    UserMapper.java:
    public interface UserMapper{
    
     public void insertUser(User user);
     public User getUserById(String userId);
     public List getAllUsers();
     public void updateUser(User user);
     public void deleteUser(String userId);
    
    }
    
  3. 建立mapper.xml,每一個id都需與出現在步驟2的interface functions名稱相同,並定義好每一個functions所需處理的SQL  Command
    UserMapper.xml
    < mapper namespace="cmpnts.UserMapper">
    
      < select id='getUserById' parameterType='String' resultType='User'>
         SELECT 
          userid as Userid, 
          email as Email , 
          Password, 
          name as Name
         FROM USER 
         WHERE USERID = #{userId}
      </ select>
    
    
      
       
       
      
    
      
      < select id="getAllUsers" resultMap="userMap">
        SELECT 
          userid , 
          email , 
          Password, 
          name
         FROM USER 
      </ select> 
    
      
        INSERT INTO USER(userid,email, password, name)
        VALUES(#{_userid},#{_email}, #{_password}, #{_name})
        
      
      
      
        UPDATE USER 
        SET
         PASSWORD = #{_password},
         NAME = #{_name},
         EMAIL = #{_email}
        WHERE USERID = #{_userid}
      
      
      
      
        DELETE FROM USER WHERE USERID = #{userid}
      
    </ mapper>
    
  4. 如果會使用比較複雜的資料型態,且有在步驟2、3時使用,記得要定義好這些資料型態
    User.java:
    public class User {
     private String _userid;
     private String _name;
     private String _password;
     private String _email;
    
     public String getUserId() {
      return _userid;
     }
    
     public void setUserId(String userid) {
      this._userid = userid;
     }
    
     public String getName() {
      return _name;
     }
    
     public void setName(String username) {
      this._name = username;
     }
    
     public String getPassword() {
      return _password;
     }
    
     public void setPassword(String pwd) {
      this._password = pwd;
     }
     
     public String getEmaill(){
      return _email;
     }
     
     public void setEmaill(String mail){
      _email = mail;
     }
     
     @Override
     public String toString(){
      StringBuffer buf = new StringBuffer();
      buf.append("UserId:").append(this.getUserId());
      buf.append("\r\n\tname:").append(this.getName());
      buf.append("\r\n\tpassword:").append(this.getPassword());
      buf.append("\r\n\temail:").append(this.getEmaill());
      return buf.toString();
     }
    }
    
  • 呼叫main.java:
    public class Main {
    
     /**
      * @param args
      */
     public static void main(String[] args) {
      String resource = "mybatis-config.xml";
      InputStream inputStream;
      try {
       inputStream = Resources.getResourceAsStream(resource);
       SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
         .build(inputStream);
    
       createTableUser();
       getAllUsers(sqlSessionFactory);
       
       //insert user
       User nuser = new User();
       nuser.setUserId("nuid");
       nuser.setPassword("nuserpwd");
       nuser.setName("rosie");
       nuser.setEmaill("rosie@mail.com.tw");
       insertUser(sqlSessionFactory,nuser);
       System.out.println("---After---");
       getAllUsers(sqlSessionFactory);
       
       User uuser = getUserById(sqlSessionFactory,"Q12");
       uuser.setEmaill("Q12update@mail.com.tw");
       uuser.setPassword("Q12updatePwd");
       uuser.setName("Q12updateName");
       updateUser(sqlSessionFactory,uuser);
       System.out.println("---UpdateAfter---");
       getAllUsers(sqlSessionFactory);
       
       deleteUser(sqlSessionFactory,"Q12");
       System.out.println("---DeleteAfter---");
       getAllUsers(sqlSessionFactory);
       
      } catch (Exception e) {
       e.printStackTrace();
      }
     }
     
     private static User getUserById(SqlSessionFactory sqlSessionFactory,String userid){
      return UserDAO.getUserById(sqlSessionFactory,userid); 
     }
     
     private static void updateUser(SqlSessionFactory sqlSessionFactory,User user){
      UserDAO.updateUser(sqlSessionFactory,user);
     }
     
     private static void deleteUser(SqlSessionFactory sqlSessionFactory, String userid) {
      UserDAO.deleteUser(sqlSessionFactory, userid);
     }
     
     private static void getAllUsers(SqlSessionFactory sqlSessionFactory){
      List list = UserDAO.getAllUsers(sqlSessionFactory);
      for(User user : list)
       System.out.println(user);
     }
     
     private static void insertUser(SqlSessionFactory sqlSessionFactory, User user) {
      UserDAO.insertUser(sqlSessionFactory, user);
     }
    
     public static void createTableUser() {
      try {
       // DBUnitUtilities.createTable(create);
       Connection conn = DBUnitUtilities.createHsqlDBConnection();
       SqlGetResultMain sqlmain = new SqlGetResultMain(conn);
       sqlmain.setIsAutoCommit(false);
       int update = sqlmain.update(new CreateUser());
    
       conn = DBUnitUtilities.createHsqlDBConnection();
       MsSqlConnection dbunitConnection = 
         new MsSqlConnection(conn, null);
       DBUnitUtilities.insertTableDataFromExistingXml("res/UserDatas.xml",
         dbunitConnection);
       
       sqlmain.commit();
      } catch (Exception ex) {
       ex.printStackTrace();
      }
     }
    }
    
    
  • 2013年8月25日 星期日

    MyBatis - 如何一次取得SELECT 的大量資料

    參考:Result Maps
    需求:我們常常會有SELECT Query回傳大量資料列的情形,與之前指定userid只回傳單一User不同,該如何做呢?

    方式很簡單,首先我們新增一個新的function getAllUsers:


  • UserMapper.xml
    
     
      
       
       
      
    
    
      < select id="getAllUsers" resultMap="userMap">
        SELECT 
          userid , 
          email , 
          Password, 
          name
         FROM USER 
     < select>
    
    

    (原有getUserById在這邊先拿掉)請注意其中我們使用的屬性為resultMap而不是先前getUserById中使用的resultType,依照官方說明,resultMap與resultType只能選其中一個使用


  • UserDAO.java
    public class UserDAO {
       //...
        public static List getAllUsers(SqlSessionFactory sqlSessionFactory) {
     SqlSession sqlSession = sqlSessionFactory.openSession();
     try {
      UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
      return userMapper.getAllUsers();
     } finally {
      sqlSession.close();
     }
        }
    }
    

    跟之前使用getUserById()沒甚麼差異,唯一差別就是回傳型態變成了 List<user>
  • 呼叫方式如下:
     private static void getAllUsers(SqlSessionFactory sqlSessionFactory){
      List list = UserDAO.getAllUsers(sqlSessionFactory);
      for(User user : list)
       System.out.println(user);
     }
    
  • 使用方式非常簡單呢,比起以往自己寫JDBC API為了接收大量資料寫了一大堆Code又不好用,ResultMap的機制真的非常簡易又方便呢!

    MyBatis - 使用resultMap(1)


    這這一篇MyBatis - 基本安裝 + 使用Select有提過,mybatis使用了reflection的機制對應Field Name將DataBase的資訊映射至指定Class Data(User.class)中,那麼,如果像我習慣把class field name前面加上 _ 或 m_ 的識別符號,如下:
    public class User {
     private String _userid;
     private String _name;
     private String _password;
     private String _email;
    

    但是在SELECT Query依然維持:
         SELECT 
          userid as userid, 
          email as email , 
          password, 
          name as Name
         FROM USER 
    

    該怎麼做呢?

    需求: 我們需要一次回傳大量的SELECT資料,該怎麼做呢?

    這時候就要使用resultMap的機制了,我們將先前的UserMapper.xml更改為如下:
    
    
      < select id='getUserById' parameterType='String' resultType='User'>
         SELECT 
          userid as Userid, 
          email as Email , 
          Password, 
          name as Name
         FROM USER 
         WHERE USERID = #{userId}
      </ select>
    
    
      
       
       
      
    
      
    
    

    在這裡,我們加上了< resultmap></ resultmap>的設定,依照上面設定很容易可以看得出來,
    column代表的是SELECT 出來的 field name,而對應的property代表的就是User Class中的field name,如此一來,利用這個方式就不需要名字一定得相同囉。


    2013/9/7補充:
      此種方式只適用一次回傳大量資料的resultMap,並不適用SELECT單一資料的resultType,
    如果有需要,還是得乖乖指定回傳fieldName,詳情請見MyBatis - 使用resultType回傳單一SELECT資料時都是Null

    Java Reflection - 存取Field 資訊

    參考:Java 學習筆記 (10) - Reflection

    因為摸了MyBatis(請見MyBatis - 基本安裝 + 使用Select)後對於他可以利用Xml可以做各種設定以及回傳對應Data的方式相當感興趣,直覺是使用Reflection機制實做,因此有了這篇小實驗 :)

    需求:假設我們有一個User Class如下,我們需要能
    • 需求1 - 取得指定所有Public的Field資訊
    • 需求2 - 取得指定指定的Declare的Field資訊(包含private、protected)
     
    public class User {
     private String userid;
     private String name;
     private String password;
     private String email;
      //...other
    }
    

    1. 取得指定Class中所有Public的Field資訊(由於以上的User Class沒有任何public field,因此只列出使用方式)
          Class aClass = User.class;
          Field[] publics = aClass.getFields();
      
    2. 取得指定指定的Declare的Field資訊(包含private、protected)
      假設我們現在想要取得其中的userid與email,並對他其中一個已存在的實體Instance做修改,怎麼做?:
      • 取得User Class的useir、email Field:
          Class aClass = User.class;
          Field fuserid = aClass.getDeclaredField("userid"); 
          Field femail = aClass.getDeclaredField("email"); 
          fuserid.setAccessible(true);
          femail.setAccessible(true);
        
        由於我們希望能修改其中的內容,因此記得要呼叫setAccessible(true),讓該field可以被修改
      • 對已建立的實體Instance進行修改:
           Constructor cons = aClass.getConstructor(null);
           User u = (User) cons.newInstance(null);
           fuserid.set(user, "ifAfter");
           femail.set(user, "mail@After.com");

    以下為完整範例:

  • User.java
    package cmpnts;
    
    public class User {
     private String userid;
     private String name;
     private String password;
     private String email;
    
     public String getUserId() {
      return userid;
     }
    
     public void setUserId(String userid) {
      this.userid = userid;
     }
    
     public String getName() {
      return name;
     }
    
     public void setName(String username) {
      this.name = username;
     }
    
     public String getPassword() {
      return password;
     }
    
     public void setPassword(String pwd) {
      this.password = pwd;
     }
     
     public String getEmaill(){
      return email;
     }
     
     public void setEmaill(String mail){
      email = mail;
     }
     
     @Override
     public String toString(){
      StringBuffer buf = new StringBuffer();
      buf.append("UserId:").append(this.getUserId());
      buf.append("\r\n\tname:").append(this.getName());
      buf.append("\r\n\tpassword:").append(this.getPassword());
      buf.append("\r\n\temail:").append(this.getEmaill());
      return buf.toString();
     }
    }
    
  • FieldReflectionTest.java:
    package reflection;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    
    import cmpnts.User;
    
    public class FieldReflectionTest {
    
     public static void main(String[] args) {
      User user = new User();
      user.setUserId("idBefore");
      user.setName("name");
      user.setPassword("password");
      user.setEmaill("mail@before.com");
      
      try {
       alterFieldUserIdEmail(user);
      } catch (Exception e) {
       e.printStackTrace();
      }
     }
     
     public Field[] getPublicFields(){
      Class aClass = User.class;
      Field[] publics = aClass.getFields();
      return publics;
     }
     
     public static void alterFieldUserIdEmail(User user) throws Exception{
      System.out.println(user);
      
      Class aClass = User.class;
      Field fuserid = aClass.getDeclaredField("userid"); 
      Field femail = aClass.getDeclaredField("email"); 
      fuserid.setAccessible(true);
      femail.setAccessible(true);
      
      Constructor cons = aClass.getConstructor(null);
      User u = (User) cons.newInstance(null);
      fuserid.set(user, "idAfter");
      femail.set(user, "mail@After.com");
      System.out.println(user);
     }
    
    }
    


  • 執行結果:
    UserId:idBefore
    name:name
    password:password
    email:mail@before.com

    UserId:idAfter
    name:name
    password:password
    email:mail@After.com

    修改成功!!

    MyBatis - 基本安裝 + 使用Select

    1. 於eclipse建立New Project【TestMyBatis】
    2. eclipse中的BuildPath加入mybatis-xxxx.jar(我安裝這時候為mybatis-3.2.2.jar)
    3. 建立res資料夾,這個資料夾會存放所有mybatis的config、mapper...等XML設定
    4. 於eclipse的Build Path加入res路徑(Add Folder->選擇res資料夾)

    5. 於res下加入jdbc.properties,這是讓我們設定jdbc資料庫連線的各種設定;這邊我們使用hsqldb,方便測試使用:
      jdbc.driver=org.hsqldb.jdbcDriver
      jdbc.url=jdbc:hsqldb:mem:my-project-test;shutdown=true
      jdbc.username=sa
      jdbc.password=
      
    6. 於res下加入mybatis-config.xml
      
        
        
          
            
            
              
              
              
              
            
          
        
        
          
        
      
      

      這是主要讓mybatis設定環境連線參數的設定檔
    7. 於res下加入UserMapper.xml
      
        < select id="getUserById" parametertype="String" resulttype="cmpnts.User">
           SELECT 
            userid as userid, 
            email as email , 
            password, 
            name as Name
           FROM USER 
           WHERE USERID = #{userId}
        </ select>
      
      

      (以上select開頭的tag空格請自行刪除,因為不用空格會被Blogger判定為HTML select)
      因為這篇只做SELECT因此以上設定檔只列出SELECT,可以看到SQL的語法直接寫在Mapper設定中就好了!非常的方便! 甚至若有邏輯上判斷的需求也可以直接寫在這,詳細請看這Dynamic SQL
    8. 建立package cmpnts,並加入User Class
      package cmpnts;
      
      public class User {
       private String userid;
       private String name;
       private String password;
       private String email;
      
       public String getUserId() {
        return userid;
       }
      
       public void setUserId(String userid) {
        this.userid = userid;
       }
      
       public String getName() {
        return name;
       }
      
       public void setName(String username) {
        this.name = username;
       }
      
       public String getPassword() {
        return password;
       }
      
       public void setPassword(String pwd) {
        this.password = pwd;
       }
       
       public String getEmaill(){
        return email;
       }
       
       @Override
       public String toString(){
        StringBuffer buf = new StringBuffer();
        buf.append("UserId:").append(this.getUserId());
        buf.append("\r\n\tname:").append(this.getName());
        buf.append("\r\n\tpassword:").append(this.getPassword());
        buf.append("\r\n\temail:").append(this.getEmaill());
        return buf.toString();
       }
      }
      
    9. 加入UserMapper Class

      package cmpnts;
      import java.util.List;
      
      public interface UserMapper{
       //public void insertUser(User user);
       public User getUserById(String userId);
       //public List getAllUsers();
       //public void updateUser(User user);
       //public void deleteUser(Integer userId);
      }
      

      這是我們的UserMapper Interface,由於這篇我們只會使用SELECT,因此實際上使用到的只有getUserById(String userId);


      到這邊準備工作都完成了,可以開始呼叫測試看結果了。
    10. 呼叫方式如下:
      public static void main(String[] args) {
        String resource = "mybatis-config.xml";
        InputStream inputStream;
        try {
           inputStream = Resources.getResourceAsStream(resource);
           SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
           .build(inputStream);
      
           createTableUser();
                      
           User user = UserDAO.getUserById(sqlSessionFactory,"Q011");
           System.out.println(user);
      
        } catch (Exception e) {
           e.printStackTrace();
        }
      }
      

      其中的createTableUser()我使用HSQLDB來建立Table,由於跟MyBatis並無關聯因此就不列出了, 習慣使用其他DataBase的直接依照一般習慣建立Table使用即可。(記得上面的jdbc.properties也要更改為自行使用的DataBase設定)
    11. UserDAO.java
      public static User getUserById(SqlSessionFactory sqlSessionFactory, String userid) {
         SqlSession sqlSession = sqlSessionFactory.openSession();
         try {
       UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
       return userMapper.getUserById(userid);
         } finally {
       sqlSession.close();
         }
      }
      

      • 首先我們傳入在main中建立的SqlSessionFactory
      • 使用SqlSessionFactory建立SqlSession 
      • 利用SqlSession獲得UserMapper的對應
      • 呼叫 getUserById(),取得我們要的結果User
      • 關於各種mybatis物件的life time請參考Getting started下面的Scope and Lifecycle
    12. 以下就是main呼叫後取得的User Println()結果
      UserId:Q011
       name:Q011anne
       password:annepwd123
       email:anne@mail.piwi.com.tw
      

    以上看起來很神奇,其實說穿了就是使用Java Reflection的機制來做的(請見Java Reflection - 存取Field 資訊);
    實際上測試了一下,他是直接對應field name;也就是說在User.java中的各種field name: userid、name、password、email都要能與在UserMapper.xml中寫下的SELECT Query相符(大小寫不限):
    SELECT 
          userid as Userid, 
          email as Email , 
          password, 
          name as Name
         FROM USER 
         WHERE USERID = #{userId}

    而與method name(getter、setter)沒有關聯。

    2013/9/7補充:
       上述的SELECT語法,若沒有使用ResultMap一次傳回大量資料(請見此篇MyBatis - 使用resultMap、resultType自行定義回傳資料)則務必要使用如上述的 userid as userid 這種方式才有辦法對應的到,後面的 as userid,一定要使用回傳Java Type的field name,比如如果習慣field name使用_usierid、_eamil,就一定要寫成
    SELECT 
          userid as _userid, 
          email as _email, 
          password as _password, 
          name as _name
         FROM USER 
         WHERE USERID = #{userId}
    否則怎麼回傳都會是Null Data,倒是 #{userId}使用甚麼名稱都無所謂,使用 #{xx}也是可以的,應該是只要param順序對應到就可以

    但是,如果像我Class field都習慣前面加上 _name、_userid這種方式,DB的field又不能修改成這樣,該怎麼辦呢? 這時候可以藉助好用的resultMap機制達到喔,請見此篇MyBatis - 使用resultMap、resultType自行定義回傳資料

    2013年8月21日 星期三

    svn - 允許commit之後修改log

    參考:Subversion 不能修改紀錄訊息



    1. 切換到想要的repository/hooks
    2. 使用mv pre-revprop-change.tmpl pre-revprop-change
    3. chmod +x pre-revprop-change (將檔案加入執行權限)
    4. vi pre-revprop-change,修改後內容如下
      REPOS="$1"
      REV="$2"
      USER="$3"
      PROPNAME="$4"
      ACTION="$5"

      if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi

      echo "Changing revision properties other than svn:log is prohibited" >&2
      exit 1
    5. 存檔離開,就可以edit log了


    2013年8月16日 星期五

    Tomcat - 更改預設首頁

    需求:
    一般預設首頁都為index.html、index.jsp...我們需要增加預設首頁為index.shtml


    1. 找到對應的WEB-INF\web.xml
    2. 找到下面這一段
        
          index.shtml
          index.html
          index.htm
          index.jsp
          default.html
          default.htm
          default.jsp
        
      

      如上所示,加入想要的預設首頁完整名稱
    3. 存檔、重新開啟Tomcat

    c (windows)- 將char、wchar、tchar轉換為unsigned long、unsigned long long

    參考:MSDN - strtoul, _strtoul_l, wcstoul, _wcstoul_l  、  strtoull-replacement 、C语言的三种整型数据类型:int、short int和long int
    需求:

    1. 將char、wchar、tchar轉換為unsigned long (下面以TCHAR為範例):
        unsigned long len = 0;
        TCHAR queryBuf[32]  = _T("6588775699");
        len = _tcstoul(queryBuf,NULL,0);
      

      其餘對應為:strtoul, _strtoul_l, wcstoul, _wcstoul_l
    2. 將char、wchar、tchar轉換為unsigned long longg (下面以TCHAR為範例):
        unsigned long long len = 0;
        TCHAR queryBuf[32]  = _T("6588775699");
        len = _tcstoui64(queryBuf,NULL,0);
      

      其餘對應為:_strtoui64(), _wcstoui64() and _tcstoui64()
    3. 而使用printf、sprintf的unsigned long long的對應format為%llu:
                      
         unsigned long long ll =  6588775699;
         TCHAR tszHeader[MAX_FILENAME_LEN] = {0};
         _stprintf(tszHeader,_T("Range:bytes=%llu-"),ll);
      

    2013年8月15日 星期四

    android - 使用ListView(2) 定義Item按下去後的行為

    需求:
    承襲上一篇【使用ListView(1)】,現在我們需要定義當按下ListView中的Item後的事件行為:
    Item按下去後以Toast顯示物品名稱


    1. 建立 ItemClick
      此Class負責當Item按下去後需產生的行為,extend OnItemClickListener
      public class ItemClick implements OnItemClickListener {
       private Context    _context;
       private List< mydatainfo > _infoList;
       
       @SuppressWarnings("unused")
       private ItemClick(){
       }
       
       public ItemClick(Context context,List< mydatainfo > list){
        _context  = context;
        _infoList = list;
       }
      
       @Override
       public void onItemClick(AdapterView arg0, View arg1, int position, long arg3) {
        String text = _infoList.get(position).getName();
        Toast.makeText(_context, "按下"+text, Toast.LENGTH_SHORT).show();
       }
      }
      

    2. 將 ItemClick 設定至 ListView
      我們使用上一篇文章的Code,加入此行(將原先的code反註解即可):
      listview.setOnItemClickListener(new ItemClick(this, _datas));
      

      原先的onCreate()變更為如下:
       @Override
       protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list_view);
        
        _datas = new ArrayList< mydatainfo >();
        _listviewDataAdapter = new MyDataAdapter(this,_datas);
        
        addNewData(R.drawable.mycard_50_app,"MyCard點數50",50);
        addNewData(R.drawable.mycard_100_app,"MyCard點數100",100);
        
        ListView listview = (ListView) findViewById(R.id.listView1);
        listview.setAdapter(_listviewDataAdapter);
        listview.setOnItemClickListener(new ItemClick(this, _datas));
       }
       
       private void addNewData(int resourceid,String name,int price){
        Drawable  icon = getResources().getDrawable(resourceid);
        _datas.add(new MyDataInfo(name,price,icon)); 
       }
      

    2013年8月13日 星期二

    log4j - 如何設定log檔案依照日期來區分檔案名稱

    需求:
    希望log的紀錄依照日期的不同,每一個日期獨立一個檔案,類似
    serverlog-2013-08-11.log、serverlog-2013-08-12.log ... etc


    1. 寫一個專門處理的Logger class(可隨意,但我習慣這樣使用)
      package utilities;
      
      import java.text.SimpleDateFormat;
      import java.util.Date;
      
      import org.apache.log4j.Logger;
      import org.apache.log4j.PropertyConfigurator;
      
      public class Logger4j {
      
       static{
        initLoggerDateParam();
       }
      
       private static void initLoggerDateParam(){
           SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
           System.setProperty("log4j.date", dateFormat.format(new Date()));
       }
       private static Logger initLogger(){
        PropertyConfigurator.configure("infoerror.properties");
        Logger logger = Logger.getLogger(Logger4j.class);
        
        initLoggerDateParam();
        return logger;
       }
       
       public static void logInfo(String msg){
        initLogger().info(msg);
       }
       
       public static void logError(Exception ex){
        initLogger().debug(ex.getMessage(), ex.getCause());
       }
      }
        

      重點為其中的initLoggerDateParam()部分,在這裡我們設定一個系統變數log4j.date,為了提供給log4j設定檔使用
    2. 撰寫log4j設定檔infoerror.properties

    3. log4j.rootLogger=Info, A1, A2
      
      
      # A1 is set to be a ConsoleAppender
      log4j.appender.A1=org.apache.log4j.ConsoleAppender
      log4j.appender.A1.layout=org.apache.log4j.PatternLayout
      log4j.appender.A1.layout.ConversionPattern=[%d{yy/MM/dd HH:mm:ss}][%p][%C-%L] %m%n
      
      # A2 is set to be a file
      log4j.appender.A2=org.apache.log4j.DailyRollingFileAppender
      log4j.appender.A2.layout=org.apache.log4j.PatternLayout
      log4j.appender.A2.layout.ConversionPattern=[%d{yy/MM/dd HH:mm:ss}][%p][%C-%L] %m%n
      log4j.appender.A2.File=./log/gpserver-${log4j.date}.log
      
      

      重點為log4j.appender.A2.File=./log/appname-${log4j.date}.log 我們使用${log4j.date}提取出java中設定的系統變數log4j.date
    4. 重新啟動程式,log檔案就會在相對應的位置囉

    2013年8月8日 星期四

    tomcat - 支援中文字網址



    1. 更改conf\server.xml
    2. 找到<Connector port="80" ...的設定
    3. 加上 URIEncoding="UTF-8" 的屬性 <Connector port="8080" URIEncoding="UTF-8"  />

    android - tabview 無法顯示出 ICON的問題

    這兩天來練習使用TabView,由於我的目標是HoneyComb以下的版本也能使用,因此我採用的是在HoneyComb後已經被deprecated的作法,我參考此教學實作,遇到了個問題:

    就是我的Tab Icon無法正常顯示!!
    原先欲產生效果圖應該如下:
    但最後產出的變成:
    找了半天都搞不清楚為什麼Icon無法正常顯示!後來才發現是manifest的問題。我的AndroidManifest有這樣的定義:

        
    

    關鍵點就是這個android:targetSdkVersion="17"屬性,測試後發現數值在10(包含)以下,Icon都能正常顯示, 超過後就無法正常顯示,推測應該是因為11以後代表的就是HoneyComb版本,而這種使用TabView(extend Activity)的方式在HoneyComb為deprecated ,因此無法正常顯示。
    其實拿掉這個屬性也是沒有問題的

    Android_version 對應參考
    android新版本TabActivity作法

    2013年8月7日 星期三

    android - 檢查網路連線狀態

    這個功能應該算挺常見的,直接看Code:


    import android.content.Context;
    import android.net.ConnectivityManager;
    import android.net.NetworkInfo;
    
    public class SystemUtils {
    
     public static boolean checkNetworkStatus(Context context){
      ConnectivityManager conMgr =  (ConnectivityManager)context.getSystemService(context.CONNECTIVITY_SERVICE);
    
      if(conMgr.getActiveNetworkInfo() == null)
     return false;
      
      if(conMgr.getActiveNetworkInfo().getState() == NetworkInfo.State.CONNECTED  )
     return true;
      
      return false;
     }
    }
    
    

    之後記得在Manifest.xml加入permission:
    
    

    2013年8月5日 星期一

    android - 得知使用者按下Back Button並發出確認警告?

    需求: 有一個下載檔案的AsyncTask,若使用者按下Back Button時需要能退出並結束下載的Task: 直接看Code:
    1.在MainActivity上加入Back Button的監聽實做:
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         if(keyCode == KeyEvent.KEYCODE_BACK) {
       DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
           @Override
           public void onClick(DialogInterface dialog, int which) {
               switch (which){
               case DialogInterface.BUTTON_POSITIVE:
                finish();
                   break;
               case DialogInterface.BUTTON_NEGATIVE:
                   break;
               }
           }
       };
       AlertDialog.Builder builder = new AlertDialog.Builder(this);
       builder.setMessage("退出後現有下載的檔案進度將會停止,確定退出?").setPositiveButton("退出", dialogClickListener)
           .setNegativeButton("否", dialogClickListener).show();
       return true;
         }
         return super.onKeyDown(keyCode, event);
     }
    

    2.在下載檔案的MainActivity onDestroy()中加入:
     @Override
     protected void onDestroy(){
      super.onDestroy();
      DownloadFile.getNowDownloadTask().cancel(true);
     }
    

    2013年8月2日 星期五

    android exception - 出現java.net.UnknownHostException: Unable to resolve host

    使用

    URL url = new URL(sUrl[0]);
    URLConnection connection = url.openConnection();
    connection.connect();

    時出現java.net.UnknownHostException: Unable to resolve host

    加上permission:
    <uses-permission android:name="android.permission.INTERNET" />

    2013年7月30日 星期二

    Java NIO - Buffer基本function介紹flip()、rewind()、clear()、compact()、mark()、reset()


    參考:Java NIO Buffer
    搭配此範例圖參考:

    Write data to a Buffer:
    1. Write data from a Channel into a Buffer:
      int bytesRead = inChannel.read(buf); //read into buffer.
    2. 自行使用put()之類的方式寫入
      buf.put(127);
    Reading data from a Buffer:
    1. Read data from the buffer into a channel.
       
      //read from buffer into channel.
      int bytesWritten = inChannel.write(buf);
      
    2. 自行使用get()之類的方式讀取:
       
      byte aByte = buf.get();
      
    • flip()
      將Butter由write mode轉變為read moode,並將
      (1)limit設為當前position位置
      (2)position reset to 0
    • rewind()
      將position設定回0,但limit保持不變,因此可重新read所有的資料

    • clear()與compact():
    • clear()
      將position設定回0,limit設定至capacity,然而Buffer中資料並不是真的被清除,只是將這些mark改變,讓我們可以有足夠的空間重新寫入資料。但是請注意,雖然資料沒有被清除,但是因為mark被重置了,因此unread的資料將無法再次被讀取,若目前尚有未讀取資料但又需要在讀取之前write data,則可使用compact()代替clear()
    • compact()

    • mark()與reset():
    • mark():使用mark()則可將目前的position做標記(mark)
    • reset():將position 設定回之前所設定的標記(mark)處
    buffer.mark();
    //call buffer.get() a couple of times, e.g. during parsing.
    buffer.reset();  //set position back to mark.
    

    equals()與compareTo():


    • equals():滿足以下條件則2個buffer代表equals
      (1)type相同(char、byte、int etc..)
      (2)剩餘的【未讀】buffer大小(byte)相同,
      (3)剩餘的【未讀】buffer內容皆相同
      如上所述,equals()只比較剩餘【未讀】的buffer內容,並非整個buffer的內容
    • compareTo():與equals()相同,只比較剩餘的【未讀】buffer內容,通常做為排序使用;符合以下的情況代表【smaller】:
      (1)
      (2)所有的內容皆相同,但擁有較少的內容(?)

    2013年7月27日 星期六

    Java NIO - ByteBuffer基本屬性介紹 capacity、limit、position


    參考:Java NIO Buffer

    Once you need to read the data, you need to switch the buffer from writing mode into reading mode using the flip() method call

    Once you have read all the data, you need to clear the buffer, to make it ready for writing again. You can do this in two ways: By calling  or by calling compact().

    1. clear() :clears the whole buffer
    2. compact():only clears the data which you have already read. Any unread data is moved to the beginning of the buffer, and data will now be written into the buffer after the unread data.
    ByteBuffer實際上操作方式為:先write再read

    三個ByteBuffer屬性(可參考下面的圖來理解):
    The meaning of position and limit depends on whether the Buffer is in read or write mode. Capacity always means the same, no matter the buffer mode.




    1. capacity
      代表ByteBuffer的容量大小,一但滿了除非將他清空或是read,不然無法繼續寫入資料
    2. position
      1. write mode:起始時position index為0,隨者每一次寫入資料position位置都會變動,如寫了1byte則postion會index+1,index最多不能超過capacity -1
                read mode:當使用flip()將操作模式由write改為read,position會自動被reset至0,然               後隨者每一次read資料量不同position位置將會自動前進
    1. limit
    2.          write mode:代表共可寫入多少資料量,在此模式中香等於capacity      
               read mode:代表共有多少資料量可讀取,當使用flip()將操作模式由write改為read,
               limit會被設置於position的目前所在位置
    建立一個ByteBuffer使用allocate:
    ByteBuffer buf = ByteBuffer.allocate(48);
    其中的int參數就是代表欲建立的capacity大小。


    相關function使用方式待續....

    2013年7月20日 星期六

    Java IO: Exception Handling - 不安全的IO Exception處理(2) - 使用try-with-resources

    參考:Try-with-resources in Java 7  、 try-with-resources

    請注意這個機制只有java 7後才適用

    上篇,我們這次來看看java7的新機制(同樣是參考文章後的心得形式,若需詳細請直接連入上方參考文章連結)。


    先假設下面的code:
     
    
    public class TryWithResource implements AutoCloseable {
    
     private FileInputStream _input;
    
     public void doIt() throws WrapperException, FileNotFoundException {
      _input = new FileInputStream("datasheet/file.txt");
      throw new WrapperException("doIt has an Exception!!!");
     }
    
     @Override
     public void close() throws IOException, WrapperException {
      _input.close();
      throw new WrapperException("close has an Exception!!!");
     }
     }
    

    以下列方式呼叫使用:
     
     public static void withoutTryWithResources() throws WrapperException {
      TryWithResource tt = new TryWithResource();
      try {
       tt.doIt();
      } catch (Exception ex) {
       throw new WrapperException(ex);
      } finally {
       try {
        tt.close();
       } catch (IOException e) {
        throw new WrapperException(e);
       }
      }
     }
     public static void main(String[] args) {
      try {
       withoutTryWithResources();
      } catch (WrapperException wex) {
       wex.printStackTrace();
      }
     }
    

    會有以下結果:
    Exception in thread "main" java.lang.IndexOutOfBoundsException
    exception.WrapperException: close has an Exception!!!
     at exception.failsave.TryWithResource.close(TryWithResource.java:12)
     at exception.failsave.TryWithResource.withoutTryWithResources(TryWithResource.java:32)
     at exception.failsave.TryWithResource.main(TryWithResource.java:49)
    

    (上述的exception 行號每人都會不一樣)
    執行流程如下:

    1. doIt()
    2. throw new WrapperException("doIt...")
    3. first catch(Exception ex) block
    4. finally block
    5. close()
    6. throw new WrapperException("close...")

    最始祖的exception為應該為doIt()中出現的,但如同前一篇所說,始祖exception會被吃掉,因此,到了步驟6原先的始祖exception(步驟2)就消失了,這造成我們難以在第一時間發現最根本的問題點,因此我們可以使用java7的新機制,修改如下:

     
     public static void withResources() throws WrapperException {
      try (TryWithResource tt = new TryWithResource()) {
       tt.doIt();
      } catch (Exception e) {
       throw new WrapperException(e);
      }
     }
    
     public static void main(String[] args) {
      try {
    //   withoutTryWithResources();
       withResources();
      } catch (WrapperException wex) {
       wex.printStackTrace();
      }
     }
    

    而這次的exception 顯示為:
    exception.WrapperException: exception.WrapperException: doIt has an Exception!!!
     at exception.failsave.TryWithResource.withResources(TryWithResource.java:43)
     at exception.failsave.TryWithResource.main(TryWithResource.java:50)
    Caused by: exception.WrapperException: doIt has an Exception!!!
     at exception.failsave.TryWithResource.doIt(TryWithResource.java:15)
     at exception.failsave.TryWithResource.withResources(TryWithResource.java:41)
     ... 1 more
     Suppressed: exception.WrapperException: close has an Exception!!!
      at exception.failsave.TryWithResource.close(TryWithResource.java:21)
      at exception.failsave.TryWithResource.withResources(TryWithResource.java:42)
      ... 1 more
    

    (再一次提醒,exception中的行號每人不同)
    上述流程為:
    1. doIt()
    2. throw new WrapperException("doIt...")
    3. immediately call close() 
    4. close()
    5. Suppressed:throw new WrapperException("close...")  
    6. throw WrapperException("doIt...") (step 2)

    不僅少了很多恐怖的try catch跟finally還很清楚地馬上就知道始祖exception問題處。
    注意其中的Suppressed: exception.WrapperException: close has an Exception!!!(步驟5)
    由於使用了try-with-resources機制,因此後面發生在close()中的exception就會被suppress(壓制)。

    而try-with-resources機制也可以同時使用多個:
     
    private static void printFileJava7() throws IOException {
    
        try(  FileInputStream     input         = new FileInputStream("file.txt");
              BufferedInputStream bufferedInput = new BufferedInputStream(input)
        ) {
    
            int data = bufferedInput.read();
            while(data != -1){
                System.out.print((char) data);
        data = bufferedInput.read();
            }
        }
    }
    

    如果發生了問題,則destructor (就是呼叫close())的順序為反向的:

    1. bufferedInput.close()
    2. input.close()

    另外還有一個限制,使用在try-with-resources都必須在try(...)中宣告,而不能使用下列方式:
     
    private static void printFileJava7() throws IOException {
       FileInputStream     input = null;
       BufferedInputStream bufferedInput = null;
        try(  input         = new FileInputStream("file.txt");
              bufferedInput = new BufferedInputStream(input)
        ) {
    
            int data = bufferedInput.read();
            while(data != -1){
                System.out.print((char) data);
        data = bufferedInput.read();
            }
        }
    }
    


    因此只要implement AutoCloseable 這個interface,除了原生java的class以外,自己實作的class也同樣可以享受try-with-resources 的好處呢!

    以後解決這種惱人的io問題總算有解了!!exception handling真是個很重要的課題阿!!該去買文章作者的kindle電子書了Java Exception Handling
    話說好便宜阿作者真是佛心來者...

    Java IO: Exception Handling - 不安全的IO Exception處理

    參考:Fail Safe Exception Handling

    其實這篇等於是把參考文章直接以自己說法再寫一次當作心得筆記,想看詳細的話看上述參考文章比較妥當


    處理IO時常常發生個惱人的問題,假設下列code:
     
    public class WrapperException extends Exception {
    
     public WrapperException(IOException e) {
      super(e);
     }
    }
    

     
    InputStream input = null;
    
      try{
    
        input = new FileInputStream("myFile.txt");
    
        //do something with the stream
    
      } catch(IOException e){
        throw new WrapperException(e);
      } finally {
        try{
         input.close();
        } catch(IOException e){
           throw new WrapperException(e);
        }
      }
    

    假設"myFile.txt"這個檔案並不存在,那麼上述Code執行如下:

    1. throw java.io.FileNotFoundException() (at FileInputStream() constructor)
    2. catch(IOException e) block,rethrow WrapperException
    3. finally block
    4. 由於input未初始化成功,因此再次被finally中的try catch捕捉,然而這次拋出的為NullPointerException
    因此上述的exception顯示如下:

    Exception in thread "main" java.lang.NullPointerException
     at exception.FailSaveException.failSave(FailSaveException.java:30)
     at exception.FailSaveException.main(FailSaveException.java:15)
    

    原先造成exception的始祖,最該被拋出的FileNotFoundException()就這樣默默的被吃掉了....
    解決方式是:在finally的input.clode中加入null check:
    if(input != null) 
       input.close();
    

    然而讓我們假設另一個情況:
      假設檔案存在,input順利被建立,但在try catch中處理內容時因為某些原因再度造成io exception,此時順序會變為:

    1. throw some IOException in first try catch block
    2. first try catch catch it ,rethrow WrapperException
    3. finally block
    4. rethrow WrapperException again.....if input.close() also error
    然而要是執行到步驟3時,input.close()又再次因為某些不知明原因,再度throw IOException,則就會多了步驟4,再一次的,最關鍵的始祖IOException又被吃掉(步驟1)!!
    (上述假設無example code,推測是因為很難馬上弄出個簡單的input.close()也出問題的範例)
    而為了解決上述問題,讓我們這些苦命developer可以在第一時間就發現始祖exception,Java 7帶來了一個叫 try-with resource的機制。

    詳情請見下篇介紹XD

    2013年7月19日 星期五

    hsqldb - 如何擴展沒有的FUNCTION以適應其他sql ?

    參考:src_jrt_routines

    寫Test的時候遇到一個問題,我想在我的SQL Query中使用 LEN()這個mssql function:

     
      SELECT LEN(str) FROM TABLE where name='123'
    

    然而由於寫Test時使用的並非mssql而是使用hsqldb,hsqldb並不支援LEN(),
    而是使用LENGTH(),如果將我的SQL Query改為 LENGTH(),在正式上線時又會遇到
    MS-SQL並沒有LENGTH()的問題....因此該怎麼做比較好呢??

    原來hsqldb有擴展function的機制,就是為了預防這個問題的產生:

    1. create一個擴展的java class如下(注意function一定得為static,詳細使用對應的參數型別請參考上述參考頁):
    2.  
      package util.extend.mssql;
      
      public class MSSql {
       public static int len(String str) {
        return str.length();
       }
      }
      
    3. 並於實際執行有用到LEN的SQL Query的地方之前,使用下列SQL Query
       
            CREATE FUNCTION  len(str VARCHAR(20000))
              RETURNS INT 
       LANGUAGE JAVA DETERMINISTIC NO SQL
       EXTERNAL NAME 'CLASSPATH:util.extend.mssql.MSSql.len'
      

    4. 以上若完成,執行test,應該就能成功囉
    這機制真是太方便了!!!

    p.s話說回來完全找不到相關的中文討論阿....是說寫Test的人真的很少還是高手都直接找英文了呢....0rz...網路上查到的方式Create alias也是過時的方式,害我花了不少時間找...