2010/04/27

CSVファイルの読込

社内の受託案件で、以前作ったメール送信用のアプリを使いたいという要望があり、隙間の時間で少しずつカスタマイズしてます。その中で送信先のユーザー情報をCSVで一括取込したいというのがあったので、簡単にCSVを任意のBeanのListに変換するようなクラスを作ってみました。この機能はアノテーションを使うのでJava5以上が必要となります。

1. アノテーション
まずはBeanのフィールドに指定するアノテーションです。CSVを読み込んだときに、このアノテーションが宣言されているフィールドに対して、値を設定します。
  1. /** 
  2.  * CSVデータの保持対象を表すアノテーションです。 
  3.  * このアノテーションはビーンクラスのフィールドに対して宣言します。 
  4.  *  
  5.  * @author namiki 
  6.  *  
  7.  */  
  8. @Retention(RetentionPolicy.RUNTIME)  
  9. @Target(ElementType.FIELD)  
  10. @Documented  
  11. public @interface CsvColumns {  
  12.  /** 
  13.   * CSVのカラム順を指定します。 
  14.   *  
  15.   * @return カラム順 
  16.   */  
  17.  int index();  
  18. }  
indexでCSVの何カラム目を設定するか、指定します。

2. 任意のBean
続いてCSVの内容を設定するBeanです。
  1. public class Entity {  
  2.  @CsvColumns(index = 0)  
  3.  public Integer id;  
  4.   
  5.  @CsvColumns(index = 2)  
  6.  public String name;  
  7.   
  8.  @CsvColumns(index = 1)  
  9.  public String email;  
  10. }  
Seasar2には依存していませんが、基本的にSeasar2環境で使うことを想定しているため、publicフィールドを採用しています。

3. CSVParser
最後にCSVを読み込むためのクラスです。
  1. /** 
  2.  * CSVファイルを読み込み、型パラメータで指定されたクラスのリストを作成するクラスです。 
  3.  *  
  4.  * @author namiki 
  5.  *  
  6.  */  
  7. public class CsvFileParser<T> {  
  8.   
  9.     // ------------------------------------------------------------- [Constants]  
  10.   
  11.     /** CSVの区切り文字です。 */  
  12.     private static final String DELIMITER = ",";  
  13.   
  14.     // ------------------------------------------------------------ [Properties]  
  15.   
  16.     /** 読込対象ファイルです。 */  
  17.     private File target;  
  18.   
  19.     // ----------------------------------------------------------- [Constructor]  
  20.   
  21.     /** 
  22.      * コンストラクタです。 
  23.      *  
  24.      * @param target 
  25.      *            対象ファイル 
  26.      */  
  27.     public DcssCsvFileParser(File target) {  
  28.         this.target = target;  
  29.     }  
  30.   
  31.     // -------------------------------------------------------- [Public methods]  
  32.   
  33.     /** 
  34.      * CSVファイルを読込み、型パラメータで指定されたクラスのリストを返します。 
  35.      *  
  36.      * @param c 
  37.      *            型 
  38.      * @return リスト 
  39.      */  
  40.     @SuppressWarnings("unchecked")  
  41.     public List<T> readLines(Class<T> c) {  
  42.   
  43.         List<Object> list = new ArrayList<Object>();  
  44.         BufferedReader reader = null;  
  45.         try {  
  46.             reader = new BufferedReader(new FileReader(target));  
  47.             String line = null;  
  48.   
  49.             while ((line = reader.readLine()) != null) {  
  50.                 String[] array = line.split(DELIMITER);  
  51.   
  52.                 Object entity = c.newInstance();  
  53.                 Field[] fields = c.getFields();  
  54.                 for (Field field : fields) {  
  55.                     CsvColumns col = field.getAnnotation(CsvColumns.class);  
  56.                     if (null != col) {  
  57.                         if (field.getType() == Integer.class) {  
  58.                             field.set(entity, Integer  
  59.                                     .valueOf(array[col.index()]));  
  60.                         } else if (field.getType() == Long.class) {  
  61.                             field.set(entity, Long.valueOf(array[col.index()]));  
  62.                         } else {  
  63.                             field.set(entity, array[col.index()]);  
  64.                         }  
  65.                     }  
  66.                 }  
  67.                 list.add(entity);  
  68.             }  
  69.         } catch (Exception e) {  
  70.             throw new RuntimeException(e);  
  71.   
  72.         } finally {  
  73.             IOUtils.closeQuietly(reader);  
  74.         }  
  75.         return (List<T>) list;  
  76.     }  
  77. }  

4. 使い方
最後にこのクラスの使い方です。
  1. CsvFileParser<entity> parser = new CsvFileParser<entity>(file);  
  2. List<entity> list = parser.readLines(Entity.class);  
  3. </entity></entity></entity>  
GenericsやらClass指定やらが続いて、少し気持ち悪いですね…。ちょっとここら辺は直したいところですが、取り敢えずこんな感じでCSV読み込みを簡略化することができました。
ただあまりCSVの行数が多いとOutOfMemoryになってしまいそうなので注意が必要ですね。そこら辺も合わせてもう少し手を入れていこうと思います。

2010/04/23

またまた本を買う

休みの日にTVを見てたら村上春樹の「1Q84」のBook3が出版されたと大騒ぎになってました。
なんとなく騒がれてたのは知ってましたが、「1Q84」は読んでなかったのでこれを機に購入。残念ながらBook3は売り切れていましたが、Book1、Book2を読み終えた頃には買えるだろうと勝手に予測してます。
取り敢えずBook1は読了してBook2に突入していますが、読んでいるとなんとも不思議な感覚に陥ってしまいますね。思わず月を見上げたり、ソファの下を覗き込んだりしてしまいました。

あと、Amazonから「Seasar2徹底入門」が届きました。相変わらずこの方の文章は読み易く、分かり易いです。SAStrutsに関しては最近関わったプロジェクト群でよく使っていますが、まだまだ奥が深いです。自分自身この本で初めて知ったことなどもありますので、これをベースにまた社内用資料を作成していこうと思っています。

2010/03/19

書籍まとめ買い

やっぱり書籍から学ぶことは多いよなと思うところがあったので、久し振りにまとめ買いしてみました。

買ったのは以下の書籍たち。
  • Java 並行処理プログラミング ―その「基盤」と「最新API」を究める―
  • Effective Java 第2版 (The Java Series)
  • ソースコードリーディングから学ぶ Javaの設計と実装
  • アート・オブ・プロジェクトマネジメント ―マイクロソフトで培われた実践手法
  • 小さなチーム、大きな仕事―37シグナルズ成功の法則 (ハヤカワ新書juice)

まだ読んだことなかったの!?と言われてしまいそうなラインナップですね…orz
今までなんとなくの理解で来てしまっているので、一度きちんと名著と言われるものでしっかり勉強しなおそうと思った次第です。

一通り読み終えたら感想などを記していきたいと思います。

2010/03/18

EclipseでApache Felix

OSGi実装であるApache Felixを触る機会があったので、Eclipse上で動作させる方法をメモしておきます。

1. Apache Felixのダウンロード
公式サイトから downloads->Felix Framework Distributionをダウンロードします。
現時点での最新版は2.0.4。 Windows環境なので、zip版をダウンロードして、任意の場所に展開します。

2. Projectの作成
続いてEclipseでJavaプロジェクトを作成します。
File -> New -> Java Project
Project name: felix-tutorial
として[Next]をクリック。
次のパネルでは
Default output folder: felix-tutorial/target/classes
として[Finish]をクリック。
次にプロジェクトのプロパティでソースフォルダをsrc/main/javaとsrc/main/resourcesに変更します。
ここら辺は後々Mavenプロジェクト化するときのことを考えてのことなので、
Mavenを使わない場合ではデフォルトのままで問題ないです。

3. Felixのコピー

1.でダウンロードしたzipファイルを展開するとfelix-framework-2.0.4というディレクトリが作成されますが、
この配下のbin、bundle、confを2.で作成したプロジェクトの直下にコピーします。
コピーしたら、 bin/felix.jarをビルドパスに追加してください。

4. Felix Shellの起動
次に Eclipse上でFelix Shellを起動します。
Run -> Run ConfigurationsでJava Applicationに起動構成を追加します。
Name: felix-tutorial - run
Project: felix-tutorial
Main class: org.apache.felix.main.Main
※Include system libraries when searching for a main classにチェック
Classpathタブを開いて、User Entriesにfelix.jarが追加されていることを確認してください。
追加されていない場合は[Add JARs]をクリックして追加してください。

ここまでが完了したら、[Apply]をクリック、[Run]をクリックしてください。
Console に「Welcome Felix」とメッセージが表示されれば成功です。
このとき、プロジェクト直下にfelix-cacheというディレクトリが作成されますが、これはFelixが利用するディレクトリです。

5. バンドルの作成
Felixのチュートリアルそのまま。
  1. package net.masa.felix.tutorial1;  
  2.   
  3. import org.osgi.framework.BundleActivator;  
  4. import org.osgi.framework.BundleContext;  
  5. import org.osgi.framework.ServiceEvent;  
  6. import org.osgi.framework.ServiceListener;  
  7.   
  8. /** 
  9.  * @author namiki 
  10.  * 
  11.  */  
  12. public class Activator implements BundleActivator, ServiceListener {  
  13.   
  14.     @Override  
  15.     public void start(BundleContext context) throws Exception {  
  16.         System.out.println("Starting to listen for service events.");  
  17.         context.addServiceListener(this);  
  18.     }  
  19.   
  20.     @Override  
  21.     public void stop(BundleContext context) throws Exception {  
  22.         context.removeServiceListener(this);  
  23.         System.out.println("Stopped listennig for service events.");  
  24.   
  25.     }  
  26.   
  27.     @Override  
  28.     public void serviceChanged(ServiceEvent event) {  
  29.         String[] obj = (String[]) event.getServiceReference().getProperty(  
  30.                 "objectClass");  
  31.         if (ServiceEvent.REGISTERED == event.getType()) {  
  32.             System.out.println("Service of type " + obj[0] + " registered.");  
  33.         } else if (ServiceEvent.UNREGISTERING == event.getType()) {  
  34.             System.out.println("Service of type " + obj[0] + " unregistered.");  
  35.         } else if (ServiceEvent.MODIFIED == event.getType()) {  
  36.             System.out.println("Service of type " + obj[0] + " modified.");  
  37.         }  
  38.     }  
  39.   
  40. }  

次にMANIFEST.MFを作成します。
src/main /resources/META-INF/tutorial1の配下にMANIFEST.MFを作成します。内容は以下の通りです。

Manifest-Version: 1.0
Bundle-Name: Service listener example
Bundle-Description: A bundle that displays messages at startup and when service events occur
Bundle-Vendor: Apache Felix
Bundle-Version: 1.0.0
Bundle-Activator: net.masa.felix.tutorial1.Activator
Import-Package: org.osgi.framework

バンドルの実体はjarなので、jarファイルを作成するためにここではantを利用します。
プロジェクト直下にbuildディレクトリを作成し、build.xmlを作成します。内容は以下の通りです。
  1. <project name="felix-tutorial" default="jar" basedir="..">  
  2.     <property name="src.dir" value="src/main/java">  
  3.     <property name="resources.dir" value="src/main/resources">  
  4.     <property name="manifest.file" value="${resources.dir}/META-INF/tutorial1/MANIFEST.MF">  
  5.     <property name="classes.dir" value="target/classes">  
  6.     <property name="dist.dir" value="target/jar">  
  7.     <property name="jar.name" value="tutorial1.jar">  
  8.   
  9.     <target name="jar" depends="prepare">  
  10.         <jar jarfile="${dist.dir}/${jar.name}" basedir="${classes.dir}" manifest="${manifest.file}" update="false">  
  11.     </jar></target>  
  12.   
  13.     <target name="prepare">  
  14.         <mkdir dir="${dist.dir}">  
  15.         <delete>  
  16.             <fileset dir="${dist.dir}">  
  17.                 <include name="${jar.name}">  
  18.             </include></fileset>  
  19.         </delete>  
  20.     </mkdir></target>  
  21. </property></property></property></property></property></property></project>  

もうちょっと考えなければいけませんが今は簡単のためにこれで。

6. バンドルのインストール
Felix Shellで以下のコマンドを実行します。
start file:/c:/%workspacepath%/felix-tutorial/target/jar/tutorial1.jar
%workspacepath% には自分のworkspaceのパスを入れてください。
実行してコンソールに「Starting to listen for service events.」というメッセージが表示されれば成功です。
停止・アンインストールについては、Felix Shellでpsコマンドを実行してIDを確認し、
stop %ID%
uninstall %ID%
で行うことができます。 Felix Shellを停止する場合hはshutdownコマンドを実行します。

追記:
build.xmlの表示がおかしいですね。
「Blogger SyntaxHighlighter」が悪さをしてそうなんですが、
回避方法が不明なのでそのままにしておきます。
本来は、property要素は1行で記述していますし、target要素も適宜終了させています。

2010/02/19

mobyletでSMTP認証を利用する

前回の記事でmobyletではSMTP認証を利用したメール送信ができないと書きましたが、いくつかのクラスを拡張することによってSMTP認証の利用は可能となります。その方法についてまとめてみました。

1. MobyletMailConfig
mobylet標準のメール設定保持クラス(MobyletMailConfig)では、SMTP認証を行う場合のプロパティ(mail.smtp.auth)の設定を行えないため、MobyletMailConfigの拡張クラスを作成します。
  1. /** 
  2.  * メール送信時にSMTP認証を行うように設定するための設定情報クラスです。 
  3.  *  
  4.  * @author namiki 
  5.  *  
  6.  */  
  7. public class MyMobyletMailConfig extends MobyletMailConfig {  
  8.   
  9.     /** SMTP認証設定のキーです。 */  
  10.     private static final String KEY_SMTP_AUTH = "mobylet.smtp.auth";  
  11.   
  12.     /** SMTP認証の初期値です。 */  
  13.     private static final String DEF_SMTP_AUTH = "false";  
  14.   
  15.     /** 
  16.      * コンストラクタです。 
  17.      */  
  18.     public MyMobyletMailConfig() {  
  19.         super();  
  20.     }  
  21.   
  22.     /** 
  23.      * SMTP認証設定を取得します。 
  24.      *  
  25.      * @return SMTP認証設定 
  26.      */  
  27.     public String getSMTPAuth() {  
  28.         String smtpAuth = props.getProperty(KEY_SMTP_AUTH);  
  29.         if (StringUtils.isEmpty(smtpAuth)) {  
  30.             smtpAuth = DEF_SMTP_AUTH;  
  31.         }  
  32.         return smtpAuth;  
  33.     }  
  34.   
  35.     @Override  
  36.     public Session createSession() {  
  37.         Properties sessionProperties = new Properties();  
  38.         sessionProperties.setProperty("mail.host", getHost());  
  39.         sessionProperties.setProperty("mail.smtp.host", getHost());  
  40.         sessionProperties.setProperty("mail.smtp.localhost", getHost());  
  41.         sessionProperties.setProperty("mail.smtp.port", getPort());  
  42.         sessionProperties.setProperty("mail.smtp.user", getUser());  
  43.         sessionProperties.setProperty("mail.smtp.pass", getPassword());  
  44.         sessionProperties.setProperty("mail.smtp.auth", getSMTPAuth());  
  45.         return Session.getInstance(sessionProperties);  
  46.     }  
  47.   
  48. }  


2. MobyletMailInitializer
mobylet標準のコンポーネント初期化クラスであるMobyletInitializerでは、1.で作成したMailConfigクラスを生成できないので、MobyletMailInitializerの拡張クラスを作成します。
  1. /** 
  2.  * Mobyletでメール送信を行うためのコンポーネント初期化クラスです。 
  3.  
  4.  * Mobylet標準の{@link org.mobylet.mail.initializer.MobyletMailInitializer} 
  5.  * が行う初期化処理に加えて、 SMTP認証の設定を行うクラスを初期化します。 
  6.  *  
  7.  * @author namiki 
  8.  *  
  9.  */  
  10. public class MyMobyletMailInitializer extends MobyletMailInitializer {  
  11.     @Override  
  12.     public void initialize() {  
  13.         super.initialize();  
  14.         SingletonUtils.put(new MyMobyletMailConfig());  
  15.     }  
  16. }  


3. mobylet.mail.properties
mobyletのメール設定ファイルであるmobylet.mail.propertiesに新しいプロパティ設定を追加します。
  1. mobylet.smtp.host=192.168.1.1  
  2. mobylet.smtp.port=587  
  3. mobylet.smtp.user=sample@example.com  
  4. mobylet.smtp.password=aaaaa  
  5. # 新しいプロパティです。  
  6. mobylet.smtp.auth=true  


4. MobyletMessage
次にMobyletMessageを拡張します。MobyletMessageはjavax.mail.Sessionを保持していますが、アクセス修飾子がprotectedとなっているため、後述するMobyletMailerのサブクラスからアクセスできないので、アクセスできる状態にするように拡張します。
  1. /** 
  2.  * {@link org.mobylet.mail.message.MobyletMessage}では{@link javax.mail.Session} 
  3.  * にアクセスできないため、{@link javax.mail.Session}へのアクセスを確保するためのメッセージクラスです。 
  4.  *  
  5.  * @author namiki 
  6.  *  
  7.  */  
  8. public class MyMobyletMessage extends MobyletMessage {  
  9.   
  10.     private Session localSession;  
  11.   
  12.     /** 
  13.      * コンストラクタです。 
  14.      *  
  15.      * @param toCarrier 
  16.      *            キャリア情報 
  17.      * @param session 
  18.      *            セッション情報 
  19.      */  
  20.     public MyMobyletMessage(Carrier toCarrier, Session session) {  
  21.         super(toCarrier, session);  
  22.         this.localSession = session;  
  23.     }  
  24.   
  25.     /** 
  26.      * セッション情報を返却します。 
  27.      *  
  28.      * @return 
  29.      */  
  30.     public Session getSession() {  
  31.         return this.localSession;  
  32.     }  
  33.   
  34. }  


5. MobyletMailer
実際にメールを送信するMobyletMailerの拡張クラスを作成します。MobyletMessageのサブクラスを返却するcreateMessage()と実際にメール送信を行うsend()を実装します。
  1. /** 
  2.  * mobyletのメール送信でSMTP認証を行うメール送信クラスです。 
  3.  *  
  4.  * @author namiki 
  5.  *  
  6.  */  
  7. public class MyMobyletMailer extends MobyletMailer {  
  8.   
  9.     /** 
  10.      * 指定された送信先アドレスをもとに、メッセージを作成します。 
  11.      *  
  12.      * @param to 
  13.      *            送信先アドレス 
  14.      * @return メッセージ 
  15.      */  
  16.     public static MyMobyletMessage createMessage(String to) {  
  17.   
  18.         MailConfig config = SingletonUtils.get(MailConfig.class);  
  19.         Session session = config.createSession();  
  20.         // Carrier  
  21.         MailCarrierDetector carrierDetector = SingletonUtils  
  22.                 .get(MailCarrierDetector.class);  
  23.         Carrier carrier = carrierDetector.getCarrierByAddress(to);  
  24.         // Message  
  25.         MyMobyletMessage message = new MyMobyletMessage(carrier, session);  
  26.         return (MyMobyletMessage) message.to(to);  
  27.   
  28.     }  
  29.   
  30.     /** 
  31.      * 指定されたメッセージをSMTP認証でメールを送信します。 
  32.      *  
  33.      * @param message 
  34.      *            メッセージ 
  35.      */  
  36.     public static void sendBySMTP(MyMobyletMessage message) {  
  37.   
  38.         Session session = message.getSession();  
  39.         Transport transport = null;  
  40.         try {  
  41.             transport = session.getTransport("smtp");  
  42.             transport.connect(session.getProperty("mail.smtp.host"), session  
  43.                     .getProperty("mail.smtp.user"), session  
  44.                     .getProperty("mail.smtp.pass"));  
  45.             transport.sendMessage(message.construct(), message  
  46.                     .getAllRecipients());  
  47.             transport.close();  
  48.   
  49.         } catch (Exception e) {  
  50.             e.printStackTrace();  
  51.         }  
  52.     }  
  53. }  


6. 使い方
最後にこれらのクラスを使ったメール送信の方法です。
  1. /** 
  2.  * Mobyletの機能を利用して、メール送信を行うロジッククラスです。 
  3.  *  
  4.  * @author namiki 
  5.  *  
  6.  */  
  7. public class MobyletMailSenderLogic {  
  8.   
  9.     // ------------------------------------------------------------ [Properties]  
  10.   
  11.     /** メールメッセージオブジェクトです。 */  
  12.     private MailMessageDto messageDto;  
  13.   
  14.     // -------------------------------------------------------------- [Accessor]  
  15.   
  16.     /** 
  17.      * @param messageDto 
  18.      *            the messageDto to set 
  19.      */  
  20.     public void setMailMessageDto(MailMessageDto messageDto) {  
  21.         this.messageDto = messageDto;  
  22.     }  
  23.   
  24.     // -------------------------------------------------------- [Public methods]  
  25.   
  26.     public void send() {  
  27.   
  28.         if (messageDto.to.length <= 0) {  
  29.             throw new RuntimeException("送信先アドレスが定義されていません。");  
  30.         }  
  31.   
  32.         for (int i = 0; i < messageDto.to.length; i++) {  
  33.             MyMobyletMessage message = MyMobyletMailer  
  34.                     .createMessage(messageDto.to[i]);  
  35.   
  36.             MessageBody body = new MessageBody();  
  37.             body.setText(MyEmojiUtils  
  38.                     .convertEmojiCharByKey(messageDto.bodyText));  
  39.   
  40.             message.from(messageDto.from).subject(  
  41.                     MyEmojiUtils.convertEmojiCharByKey(messageDto.subject))  
  42.                     .setBody(body);  
  43.   
  44.             MyMobyletMailer.sendBySMTP(message);  
  45.   
  46.         }  
  47.     }  
  48. }  

上記のMailMessageDtoは送信元メールアドレス、送信先メールアドレス、件名、本文を保持するためのクラスです。MessageDto.toの数だけ処理を繰り返していますが、これは送信先のキャリアによって絵文字の文字コードを変更する必要があるためです。

以上がmobyletでSMTP認証を利用する場合の方法となります。かなり大急ぎで作ったものなので、エラーハンドリングをほとんど無視していたり、作り自体まだまだ詰めが甘いと思いますが、参考になれば幸いです。

SAStruts+Mobyletのメール送信問題点まとめ

SAStruts+mobyletという環境で、絵文字入りのメールを携帯に向けて送信する機能を作っています。
mobyletは携帯向けWebアプリ用のフレームワークですが、WebアプリはPCで閲覧されるのを前提にしているので画面はSAStrutsを利用して作成し、メール送信の部分だけmobyletを利用するようにしています。

絵文字入りメールを3キャリア(Docomo、au、SoftBank)に送った場合、携帯側では正常に絵文字が表示されますが、3キャリア以外に送った場合(Gmail)だと絵文字部分がimgタグに変換されてしまいます。この事象について調べてみた結果をまとめてみました。

まずmobyletでメール送信を行う場合は以下のような処理を行います。
  1. // 送信先メールアドレス  
  2. MobyletMessage message = MobyletMailer.createMessage("sample@docomo.ne.jp");  
  3. MessageBody body = new MessageBody();  
  4. body.setText();  
  5. message.from("sample@example.com")  
  6.         .subject()  
  7.         .setBody(body);  
  8. MobyletMailer.send(message);  

MobyletMailer#send()ではMobyletMessage#construct()を呼び出しており、そこからは以下のような流れで処理が呼ばれます。(テキストメールの場合)

  • MobyletMessage#construct()

  • MobyletTextMailBuilder#build()

  • MobyletTextMailBuilder#buildSimpleTextMail()

  • MobyletTextMailBuilder#buildTextPart()

  • DataHandlerUtils#getDataHandler()

  • MailEmojiUtils#convert()

  • MobyletPrintWriter#write()


このうち、MobyletPrintWriterでは、useImageEmojiという変数で絵文字を画像として扱うかという判定を行っており、このuseImageEmojiはmobylet.xmlでemojiタグが宣言されている場合にtrueとなります。useImageEmoji==trueの場合、絵文字をimgタグに置き換えるという処理が行われます。
このMobyletPrintWriterがメール送信時だけに使われているならば、判定基準を変更すれば問題なさそうですが、実際はレスポンスを書き出す処理にも使われているため、単純に変更するというのでは上手くいきません。どこかで差し替えができるのが理想ですが、そういった作りにはなっていないため、どうしようかと考え中です。

またこれは別件となりますが、mobyletではSMTP認証を行うメールを送信することができません。MobyletMailConfigでmobylet.mail.propertiesを読み込み、その値を基にjavax.mail.Sessionを生成しますが、そのSession生成時にSMTP認証を行う場合に必要なmail.smtp.authというプロパティが指定できないためです。こちらについても無理矢理ですが、MobyletMailInitializer、MobyletMailConfig、MobyletMessage、MobyletMailerの拡張クラスを作ることでSMTP認証を行うメール送信が可能となります。

追記:
メールにimgタグが埋め込まれてしまう件については、mobylet.xmlからemojiタグの宣言を削除するようにして回避しました。
そもそもemojiタグを宣言している理由としては、メールの確認画面で絵文字を表示させるのにEmojiDesigner#getImageEmoji()を呼び出しているためでしたが、このメソッドを真似たコードをユーティリティクラスに定義してそちらを使うように変更しました。
  1. /** 画像が含まれるディレクトリのパスです。 */  
  2. private static final String IMG_PATH = "/sastruts/images/";  
  3.   
  4. /** imgタグのプリフィックスです。 */  
  5. private static final String PREFIX_IMG = "<img src="\"";<br">  
  6. /** imgタグのサフィックスです。 */  
  7. private static final String SUFIX_IMG = "\" />";  
  8.   
  9. /** 
  10.  * 指定された絵文字情報をもとにimgタグを生成します。 
  11.  *  
  12.  * @param emoji 
  13.  *            絵文字情報 
  14.  * @return imgタグ 
  15.  */  
  16. public static String getImageEmoji(Emoji emoji) {  
  17.     char[] codes = emoji.getCodes();  
  18.     if (codes != null && codes.length == 1) {  
  19.         return PREFIX_IMG + IMG_PATH  
  20.                 + Integer.toHexString((int) codes[0]).toUpperCase()  
  21.                 + ".gif" + SUFIX_IMG;  
  22.     }  
  23.     return null;  
  24. }  

これで3キャリアにはそれぞれの絵文字が、それ以外のキャリアには「〓」が埋め込まれたメールが送信できるようになりました。
ただ、画像のパスが宣言されている場合に、メールにimgタグが埋め込まれるという作りは少々違うんじゃないかと思いますがどうでしょうか。

2010/02/16

GAE/j開発環境構築

社内システムをappengine上に構築しようかと考えています。
最近Wicketについて調べているのもその一環なんですが、一度開発環境構築手順をまとめておきます。
正式な手順については公式サイトにドキュメントがありますので、そちらを参照してください。本記事はダイジェスト版です。

1. Java、Eclipseのインストール
Javaをインストールしていない場合はこちらから最新版をダウンロードして、インストールしてください。現在はJDK6 update18が最新版です。JavaEEがバンドルされているものもありますが、SEでOKです。
次にEclipseですが、現在の最新版は3.5となっていますが、GooglePlugin for Eclipseは3.3/3.4しかサポートしていませんのでこちらからEclipse3.4をダウンロードしてください。ダウンロードするのは「Eclipse IDE for Java Developers」でOKです。

2. Eclipseの設定
ダウンロードしたEclipseを展開して起動してください。次にGooglePlugin for Eclipseをインストールします。
Windows版の場合は、[Help]->[Software Updates...]->[Available Software]->[Add site]で表示されたダイアログに以下のURLを入力してください。
http://dl.google.com/eclipse/plugin/3.4

入力が終わったら再起動するかどうか聞かれるので[Yes]をクリックして再起動します。

これでEclipseを使ってappengine用のアプリケーション開発が行えます。

参考:Subversiveプラグインのインストール
最後におまけでSubversiveプラグインのインストール方法です。
[Software Updates...]の[Add site]で以下のURLを入力してください。
http://download.eclipse.org/technology/subversive/0.7/update-site/

Subversive Siteの下にある「Subversive Integration Plug-in's」と[Subversive SVN Team Provider Plugin]->[Subversive SVN Team Provider]にチェックを入れてインストールします。
次に再度[Add site]から以下のURLを入力してください。
http://www.polarion.org/projects/subversive/download/eclipse/2.0/update-site/

[Subversive SVN Connectors]にチェックを入れてインストールします。
Eclipseを再起動すると、Perspectiveに「SVN Repository Exploring」が追加され、CVSと同様にSVNを利用することができます。

2010/02/15

Wicketでページテンプレートを利用

Apache WicketStrutsのTilesのように画面のレイアウトを共通化する方法を調べてみました。
(via Apache Wicket -> Examples -> Markup inheritance

1. BasePage.html
まずベースとなるHTMLを作成します。ここではBasePage.htmlとします。
  1. <div id="header">  
  2. ヘッダーです。  
  3. </div>  
  4. <div id="body">  
  5.   <wicket:child>  
  6.   ここに本文が入ります。  
  7.   </wicket:child>  
  8. </div>  
  9. <div id="footer">  
  10. フッターです。  
  11. </div>  

ポイントとなるのは<wicket:child/>です。
このタグで囲った部分が各コンテンツによって差し替えられますので、上記の場合だとヘッダーとフッターはこのままで、ボディの部分がコンテンツによって変化することになります。

2. BasePage.java
次にBasePage.htmlのページクラスを作成します。
  1. /** 
  2.  * 共通テンプレート用のページクラスです。 
  3.  * 
  4.  * @author namiki 
  5.  * 
  6.  */  
  7. public abstract class BasePage extends WebPage {  
  8.     public BasePage() {  
  9.     }  
  10.     public BasePage(final PageParameters params) {  
  11.     }  
  12. }  

見ての通り特に何もしていません。今後、ヘッダーやフッターに共通の機能を埋め込む場合は必要となりますが、今のところは何もしないままにしておきます。

3. SubPage.html
次にBasePage.htmlにコンテンツを埋め込む側のSubPage.htmlを作成します。
  1. <div id="header">  
  2. サブのヘッダーです。  
  3. </div>  
  4. <div id="body">  
  5.     <wicket:extend>  
  6.     テンプレートを利用したページのサンプルです。  
  7.   
  8.     <span wicket:id="messages"></span>  
  9.     </wicket:extend>  
  10. </div>  
  11. <div id="footer">  
  12. サブのフッターです。  
  13. </div>  

ポイントとなるのは<wicket:extend/>です。
BasePage.htmlの<wicket:child/>で囲まれた部分に、SubPage.htmlの<wicket:extend/>で囲まれた部分が埋め込まれます。ここでは独自のヘッダーやフッターを定義していますが、実際に動作させた場合はBasePage.htmlのヘッダー・フッターが利用されます。

4. SubPage.java
最後にSubPage.htmlのページクラスを作成します。このクラスではBasePage.javaを継承する必要があります。それによってBasePage.htmlとSubPage.htmlの継承関係を表します。
  1. /** 
  2.  * SubPage.html用のページクラスです。 
  3.  *  
  4.  * @author namiki 
  5.  *  
  6.  */  
  7. public class SubPage extends BasePage {  
  8.     public HelloWicketPage(final PageParameters params) {  
  9.  add(new Label("messages""Hello, wicket world!!"));  
  10.     }  
  11. }  

これで画面レイアウトを共通化することができました。

2010/02/13

車の点検

ライフくんがイグナイター交換後かなり調子を取り戻してきてくれましたが、念には念を入れてディーラーさんで12ヶ月点検を行ってもらいました。

エンジン内部についてはメインリレーとイグニッションスイッチがだいぶ古くなっているので交換、足回りもアウトボードブーツがだいぶ痛んでいるようなので交換した方が良いと勧められたため、再来週にもう一度パーツ交換で入院です。

車体自体はだいぶ安く譲ってもらったものの、なんだかんだでお金が掛かってしまいましたが、まぁこんなものでしょう。これでしばらくは安心して乗れる、はず。

2010/02/09

Wicket on GoogleAppEngine

GoogleAppEngine(以下、GAE)上で動作するWebアプリフレームワークを調べています。Apache Strutsに関しては特に苦労することなく動作させることができたので、次はApache Wicketに挑戦してみます。

1. appengine-web.xml
appengine-web.xmlを編集して、セッションを利用可能にします。appengine-web.xmlに以下の内容を追記します。
  1. <sessions-enabled>true</sessions-enabled>  


2. web.xml
web.xmlの内容は以下の通りです。
  1. <!--xml version="1.0" encoding="utf-8"?-->  
  2. <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <brbr="">    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"  
  3.     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee  
  4. http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">  
  5.     <filter>  
  6.         <filter-name>wicket.yonko</filter-name>  
  7.         <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>  
  8.         <init-param>  
  9.             <param-name>applicationClassName</param-name>  
  10.             <param-value>net.masa.wicket.YonkoWicketApplication</param-value>  
  11.         </init-param>  
  12.     </filter>  
  13.     <filter-mapping>  
  14.         <filter-name>wicket.yonko</filter-name>  
  15.         <url-pattern>/*</url-pattern>  
  16.     </filter-mapping>  
  17. </web-app>  


3. WicketApplication
次にWicketApplicationクラスを作成します。GAEでWicketを動作させる場合、以下のポイントがあります。

  • WicketApplication#newSessionStore()でHttpSessionStoreを返すようにさせる

  • WicketをDEPLOYMENTモードで動作させる

  • リソースの更新チェックを停止させる


Wicketのデフォルト設定では、セッションオブジェクトをファイルに保存します。が、GAEではファイルの作成は制限されていますので、HttpSessionに保存するように設定します。
次にWicketのdevelopmentモードではThreadを生成してリソースの更新チェックを行いますが、GAEではThreadの生成を制限しているため、deploymentモードで動作させる必要があります。これにより、Threadの生成を停止できます。
上記のポイントを踏まえ、実際のソースは以下の通りとなります。
  1. /** 
  2.  * GAE/j用WicketApplicationクラスです。 
  3.  *  
  4.  * @author namiki 
  5.  *  
  6.  */  
  7. public class YonkoWicketApplication extends WebApplication {  
  8.   
  9.     /** リクエスト・レスポンスおよびHTMLのエンコードです。 */  
  10.     private static final String ENCODE = "UTF-8";  
  11.   
  12.     /** HTMLテンプレートファイルのベースフォルダです。 */  
  13.     private static final String RESOURCE_FOLDER = "WEB-INF";  
  14.   
  15.     /** HTMLテンプレートファイルの本来の階層から除去するパスです。 */  
  16.     private static final String PAGES_PATH = "net/masa/wicket";  
  17.   
  18.     /** 
  19.      * コンストラクタです。 
  20.      */  
  21.     public YonkoWicketApplication() {  
  22.     }  
  23.   
  24.     /* 
  25.      * (non-Javadoc) 
  26.      *  
  27.      * @see org.apache.wicket.Application#getHomePage() 
  28.      */  
  29.     @Override  
  30.     public Class<!-- extends Page--> getHomePage() {  
  31.         return HelloWicketPage.class;  
  32.     }  
  33.   
  34.     /** 
  35.      * Wicketの動作モードを設定します。 
  36.  
  37.      * WicketをGAE/jで動作させる場合、{@link org.apache.wicket.Application.DEPLOYMENT} 
  38.      * モードで動作する必要があります。 
  39.      *  
  40.      * @see org.apache.wicket.protocol.http.WebApplication#getConfigurationType() 
  41.      */  
  42.     @Override  
  43.     public String getConfigurationType() {  
  44.         return Application.DEPLOYMENT;  
  45.     }  
  46.   
  47.     /** 
  48.      * 初期化処理です。 
  49.      *  
  50.      * @see org.apache.wicket.protocol.http.WebApplication#init() 
  51.      */  
  52.     @Override  
  53.     protected void init() {  
  54.         super.init();  
  55.         // リクエスト・レスポンスの文字コード設定  
  56.         getRequestCycleSettings().setResponseRequestEncoding(ENCODE);  
  57.         // HTMLテンプレートの文字コード設定  
  58.         getMarkupSettings().setDefaultMarkupEncoding(ENCODE);  
  59.         // リソースの更新チェックを停止  
  60.         getResourceSettings().setResourcePollFrequency(null);  
  61.   
  62.         // テンプレートファイルの場所を指定  
  63.         getResourceSettings().addResourceFolder(RESOURCE_FOLDER);  
  64.         getResourceSettings().setResourceStreamLocator(  
  65.                 new ResourceStreamLocator() {  
  66.                     @SuppressWarnings("unchecked")  
  67.                     @Override  
  68.                     public IResourceStream locate(Class clazz, String path) {  
  69.                         if (path.indexOf(PAGES_PATH, 0) != -1) {  
  70.                             IResourceStream located = super.locate(clazz, path  
  71.                                     .substring(PAGES_PATH.length() + 1));  
  72.                             if (located != null) {  
  73.                                 return located;  
  74.                             }  
  75.                         }  
  76.                         return super.locate(clazz, path);  
  77.                     }  
  78.                 });  
  79.   
  80.     }  
  81.   
  82.     /** 
  83.      * セッションストアを生成します。 
  84.  
  85.      * WicketをGAE/jで動作させる場合、セッション情報を{@link javax.servlet.http.HttpSession} 
  86.      * に格納する必要があります。 
  87.      */  
  88.     @Override  
  89.     protected ISessionStore newSessionStore() {  
  90.         return new HttpSessionStore(this);  
  91.     }  
  92. }  

上記init()内でHTMLテンプレートファイルの場所を指定しています。Wicketは通常の場合クラスファイルと同じ階層からHTMLテンプレートファイルを探しますが、RESOURCE_FOLDER(=WEB-INF)から探すように指定しています。また、PAGES_PATH(=net/masa/wicket/)は、パッケージ名から除去すべきパスを定義しています。よって、これらの設定からWicketはHTMLテンプレートファイルをWEB-INF/pagesから探すようになっています。

4. HelloWicketPage
次にWicketApplicationクラスが最初に呼び出すページを作成します。最初に呼び出すページはApplication#getHomePage()で定義しますが、上記の例ではHelloWicketPage.classを呼び出していますのでそのクラスを作成します。パッケージはnet.masa.wicket.pagesです。
  1. /** 
  2.  * 初回表示用画面クラスです。 
  3.  *  
  4.  * @author namiki 
  5.  *  
  6.  */  
  7. public class HelloWicketPage extends WebPage {  
  8.     public HelloWicketPage(final PageParameters params) {  
  9.         add(new Label("messages""Hello, wicket world!!"));  
  10.     }  
  11. }  

ここではHTMLに表示するメッセージを定義しています。
次にこのメッセージを表示するためのHTMLテンプレートファイルを作成します。
${project}\war\WEB-INF\pagesというディレクトリを作成し、その直下にHelloWicketPage.htmlを作成します。
  1. <span wicket:id="messages"></span>  

これで準備が整いましたのでEclipseから起動して、http://localhost:8888にアクセスしてみると、画面にはHelloWicketPage.javaで定義したメッセージが表示されます。

これでGAE上でWicketを動作させることができました。今後はこれをベースにして色々な機能を追加していこうと思います。

2010/02/08

Blogger SyntaxHighlighterの導入

SyntaxHighlighterは、ブログ上のソースコードの文法部分をハイライトしてくれるフリーウェアです。はてなダイアリーの場合は特に問題ありませんでしたが、blogger移転を機に少し調べてみました。

ここではBlogger向けのWigetとして提供されているFaziBear's Blogger Widgetsを使ってみます。
導入方法は、以下のURLにアクセスして、「Add to Blogger」をクリックするだけ。
FaziBear's Blogger Widgets
このブログにも導入してみたので、正常に動作するか以下のJavaコードで確認してみます。
  1. public class HelloMain {  
  2.    public static void main(String[] args) {  
  3.        System.out.println("hello, world.");  
  4.    }  
  5. }  

どうやら正常に動作しているようですね。対応しているプログラム言語はこちらで公開されていますので、Java以外の言語でも大丈夫なようです。

今回は簡単のためにFaziBear's Blogger Widgetsを導入してみましたが、こちらはSyntaxHighlighterのバージョンが1.5.1と少し古く、最新版がSyntaxHighlighterで公開されているので、近いうちに最新版を導入してみようと思います。

2010/02/07

クルマ

ここ最近トラブル続きで非常に苦しんでいました。
駐車場でエンジンが掛からないのに始まり、コンビニの駐車場でエンジンが掛からなかったり、彼女を送っていったはいいけど、その場でエンジン掛からなかったり、挙句には西湘BPの出口で止まってしまったり。
都合3回ほどレッカーのお世話になってしまいました。

が、それも昨日修理工場の方に見て頂き、やっと原因が判明しました。どうやらイグナイターというパーツがダメになってしまっているとのこと。新品だと結構高価なので、正常動作しているモノを中古から取ってきて交換するみたいです。

自分のライフはJA4というエンジン型なんですが、この型はイグナイターが結構壊れやすいらしいです。どうやらデスビがエンジンに直付けとなっているため、熱ですぐにヤラれてしまうみたい。イグナイターの他にも、イグニッションスイッチやメインリレーなども注意が必要です。

イグナイター交換が終わったら近所のディーラーに持ち込んで一通り点検してもらおうと思います。

追記:
今日、14時過ぎに修理工場の方が来てくれ、早速パーツ交換してくれました。交換したパーツは、デスビ、イグナイター、イグニッションコイルとのこと。工賃込みで20k。まだ試走してないけど、エンジンの始動が安定した気がします。

2010/02/04

WTPでTomcatのJNDIを利用する

環境はEclipse3.4.2、WTP3.0.4、Tomcat5.5.27。プロジェクトはMavenのmaven-archetype-webappで作成しました。DBはSQLServer2005です。
まずはsrc/main/webapp/META-INF配下にcontext.xmlを作成します。context.xmlの内容は以下の通り。
  1. <!--xml version="1.0" encoding="UTF-8"?-->  
  2. <context docbase="projectname" path="/projectname" reloadable="true" source="org.eclipse.jst.j2ee.server:sjp">  
  3.   <resource name="jdbc/sample" <brbr="">    auth="Container"  
  4.     type="javax.sql.DataSource"  
  5.     factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory"  
  6.     driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver"  
  7.     url="jdbc:sqlserver://127.0.0.1:1433;databaseName=pj;"  
  8.     username="sa"  
  9.     password="pj"  
  10.     maxActive="20"  
  11.     maxIdle="10"/>  
  12. </resource></context>  

上記のContext要素の内容は、Servers -> Tomcat v5.5 Server at localhost-config -> server.xmlからコピーしました。

次にsrc/main/webapp/WEB-INF/web.xmlに以下の内容を追加します。
  1. <resource-ref>  
  2.     <description>SQLServer2005 DataSource</description>  
  3.     <res-ref-name>jdbc/sample</res-ref-name>  
  4.     <res-type>javax.sql.DataSource</res-type>  
  5.     <res-auth>Container</res-auth>  
  6.     <res-sharing-scope>Shareable</res-sharing-scope>  
  7. </resource-ref>  

最後に%CATALINA_HOME%\common\lib配下にJDBCドライバのjarファイルを放り込むと、WTPで起動したTomcatでもJNDIによるDataSourceの取得を行うことができます。

ちなみにWTPでTomcatを設定した場合、以下のディレクトリをTomcat環境と見立ててServerを起動します。

  • workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0

2010/02/03

SQLServerで自動生成キーの取得

SQLServer2005で、IDENTITY値を利用したテーブルに対してInsertを行った場合に、生成されたキーを取得する方法です。いまのプロジェクトではdbutilsを利用しているので、QueryRunnerをオーバーライドして、専用のQueryRunnerを作成しました。
  1. public class GeneratedKeysReturnQueryRunner extends QueryRunner {  
  2.   
  3.     @Override  
  4.     public int update(Connection conn, String sql, Object[] params)  
  5.             throws SQLException {  
  6.         PreparedStatement stmt = null;  
  7.         ResultSet rs = null;  
  8.         ResultSetMetaData meta = null;  
  9.         int key = 0;  
  10.         try {  
  11.             stmt = this.prepareStatement(conn, sql);  
  12.             this.fillStatement(stmt, params);  
  13.             stmt.executeUpdate();  
  14.             rs = stmt.getGeneratedKeys();  
  15.             meta = rs.getMetaData();  
  16.             if (rs.next()) {  
  17.                 key = rs.getInt(meta.getColumnCount());  
  18.             }  
  19.         } catch (SQLException e) {  
  20.             this.rethrow(e, sql, params);  
  21.         } finally {  
  22.             close(stmt);  
  23.         }  
  24.         return key;  
  25.     }  
  26.   
  27.     @Override  
  28.     protected PreparedStatement prepareStatement(Connection conn, String sql)  
  29.             throws SQLException {  
  30.         return conn.prepareStatement(sql,  
  31.                 PreparedStatement.RETURN_GENERATED_KEYS);  
  32.     }  
  33. }  

J2SE1.4以上がサポートしているJDBC 3.0 APIにある、PreparedStatement.RETURN_GENERATED_KEYSを利用するのがポイントです。PostgreSQLやMySQL、Oracleで同じようなことが出来るのかは未検証です。
参考:MSDN 自動生成キーの使用

2010/02/02

FTPの実装

javaでFTPを実装する場合、commons-netを利用することで簡単に実装することができます。
  1. public class FtpUtils {  
  2.       
  3.     /** FTPホストのIPアドレスです。 */  
  4.     private static final String HOST_ADDRESS = "192.168.0.1";  
  5.   
  6.     /** FTPユーザー名です。 */  
  7.     private static final String USER_NAME = "ftpuser";  
  8.   
  9.     /** FTPログインパスワードです。 */  
  10.     private static final String PASSWORD = "password";  
  11.   
  12.     /** FTP転送先ディレクトリ名です。 */  
  13.     private static final String STORE_DIR = "/home/ftpuser/";  
  14.   
  15.     /** 
  16.      * 指定されたファイルを順次FTP転送します。 
  17.      * 
  18.      * @param files 
  19.      *            転送対象ファイル 
  20.      * @throws IOException 
  21.      *             FTP中に何らかの例外が発生した場合 
  22.      */  
  23.     public static void storeFiles(File[] files) throws IOException {  
  24.   
  25.         FTPClient client = new FTPClient();  
  26.         InputStream in = null;  
  27.   
  28.         try {  
  29.             connection(client);  
  30.             login(client);  
  31.   
  32.             for (File file : files) {  
  33.                 in = new FileInputStream(file);  
  34.                 client.storeFile(STORE_DIR + file.getName(), in);  
  35.                 IOUtils.closeQuietly(in);  
  36.             }  
  37.   
  38.         } finally {  
  39.             IOUtils.closeQuietly(in);  
  40.             client.disconnect();  
  41.         }  
  42.     }  
  43.   
  44.     /** 
  45.      * FTPコネクションを確立します。 
  46.      * 
  47.      * @param client 
  48.      *            クライアント 
  49.      * @throws IOException 
  50.      *             コネクションに失敗した場合 
  51.      */  
  52.     private static void connection(FTPClient client) throws IOException {  
  53.         client.connect(HOST_ADDRESS);  
  54.         if (!FTPReply.isPositiveCompletion(client.getReplyCode())) {  
  55.             throw new IOException("Connection failed.");  
  56.         }  
  57.     }  
  58.   
  59.     /** 
  60.      * FTPログインを行います。 
  61.      * 
  62.      * @param client 
  63.      *            クライアント 
  64.      * @throws IOException 
  65.      *             FTPログインに失敗した場合 
  66.      */  
  67.     private static void login(FTPClient client) throws IOException {  
  68.         if (!client.login(USER_NAME, PASSWORD)) {  
  69.             throw new IOException("Login failed.");  
  70.         }  
  71.     }  
  72. }  

上記の場合、FtpUtils#storeFiles()にFTPしたいFileオブジェクトの配列を引き渡すと、そのFileを順次転送します。

2010/02/01

SQLServerで変換デッドロックの回避

デッドロックといえば、2つのテーブル(ここではA、B)に対して、トランザクション1はA→Bの順で更新、トランザクション2はB→Aの順で更新しようとした場合に発生する、というのは良く知られていると思います。MicrosoftのTechNetによると、このデッドロックはサイクルデッドロックと言います。
こちらの「3.3 デッドロックの種類 2.サイクルデッドロック」を参照してください。

一方で1つのテーブルに対して、複数のトランザクションから参照・更新を行う場合もデッドロックが発生します。TechNetによると、こちらは変換デッドロックと言います。
こちらの「3.3 デッドロックの種類 1.変換デッドロック」を参照してください。

今回遭遇したのは変換デッドロックの方で、1つのテーブルに対して、2つのトランザクションから同一のキーで参照・更新を行おうとしてデッドロックが発生しました。今までこのタイプのデッドロックに遭遇したことはありませんでしたが、今回の対応で一応の回避策が分かったのでそれをまとめておきます。

JDBC接続文字列


リファレンス等を読むと、SQLServerを利用する場合のJDBC接続文字列は以下のようにします。

jdbc:sqlserver://192.168.1.1:1433;databaseName=dbnamae;

ただし、このままだとデータの読み出しにカーソルが使えませんので、以下のようにパラメータを追加する必要があります。

jdbc:sqlserver://192.168.1.1:1433;databaseName=dbnamae;selectMethod=cursor

selectMethod~が追加した部分となります。javaでSQLServerを利用する場合には、必ず追加しなければならない重要なパラメータです。

SELECT ~ FOR UPDATE


SQLServerではPostgreSQLなどで使うSELECT ~ FOR UPDATEが利用出来ません。その代わりにロックヒントなるものを追加して、更新ロックを掛けるとSELECT ~ FOR UPDATEと同等の処理を行うことができます。
  1. SELECT  
  2.     hoge.key  
  3.     , hoge.value  
  4. FROM  
  5.     hoge WITH(ROWLOCK, UPDLOCK)  
  6. WHERE  
  7.     hoge.key = 1;  

FROM句のテーブル名後ろにあるWITH~がロックヒントです。上記では行ロックと更新ロックを掛ける場合のSQL文となっています。ロックヒントで指定できるロックの種類は、こちらを参照してください。

たったこれだけのことなのに、えらく時間が掛かってしまいました。使い慣れていないDBはやっぱり怖いですね。気になるのはOracleやPostgreSQLではこの変換デッドロックと同じ現象が起こるのでしょうか。今までの経験と少し調べてみたところ見つかりませんでしたが。