2014年7月3日木曜日

Jersey で Restful API をつくる

Jersey で Restful な API を作成する。

前提知識として、JAX-RS をさらっと認識しておくと良い。
何故 REST なのか、REST の何がうれしいかを見ておく。

JAX-RS 1 について : http://www.coppermine.jp/docs/programming/2012/12/jaxrs1.html

JAX-RS 2 について : http://www.coppermine.jp/docs/programming/2012/12/jaxrs2.html


JAX-RS 2 を見るにあたっては、CDI も見ておくと良い。

CDI :: https://blogs.oracle.com/nishigaya/entry/javaee6_understanding_cdi_part_1


1 と 2 どちらを用いるかは、作成するアプリケーションの API の複雑性・多様性によって決めると良い。

テキストのみを扱うシンプルな API に JAX-RS 2 の仕様を実装するのもどうかと思う。

Jersey1


ここでは、JAX-RS 1 の実装である、Jersey 1系 を使って、Servlet 環境を作成する。

Jersey 1系の最新 1.18 を以下からダウンロードできる。

https://jersey.java.net/download.html


ダウンロードしたファイルを解凍して、以下を Tomcat プロジェクトのライブラリとして登録する。


  • asm-3.1.jar
  • jersey-client-1.18.jar
  • jersey-core-1.18.jar
  • jersey-json-1.18.jar
  • jersey-server-1.18.jar
  • jersey-servlet-1.19.jar
  • jsr311-api-1.1.1.jar

次に、web.xml を準備する。WEB-INF/web.xml を以下の様に作成する。

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0"
metadata-complete="true">
<servlet>
<servlet-name>SC Recognize Your Issue Service</servlet-name>
<servlet-class>
com.sun.jersey.spi.container.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>jp.masazdream.scryi.server.api</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SC Recognize Your Issue Service</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>
view raw web.xml hosted with ❤ by GitHub



servlet タグには、servlet-name と対応する servlet-class を記載する。
これは、jerseyのクラスを設定する。init-paramには、jerseyを使うパッケージを設定する。

ここでは「jp.masazdream.scryi.server.api」である。
load-on-startup で、アプリケーションがコンテナにロードされるときに、初期化するよう設定する。

servlet-mapping では、servlet-name が一致した servlet にアクセスする url-patternを設定する。

ここでは、<アプリケーションコンテキスト名>/api/~ でアクセスする。

~ は、Jerseyによってアノテーションで記述する。

次に、jp.masazdream.scryi.server.api パッケージにクラスを記述する。

@Path("/communication")
public class HelloSCRYServer {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String sayHello() {
String result = null;
LocalTransaction tx = AppConfig.getLocalTransaction();
try {
tx.begin();
MCategoryDao dao = new MCategoryDaoImpl();
MCategory mcategory = dao.selectById(1);
result = mcategory.getCategoryCode();
tx.commit();
} finally {
// トランザクション
tx.rollback();
}
return result;
}
}


アノテーション「@Path」に続けて書いたのが、~部分である。
また、Doma を使った DAO 部分の記述をテストとして記述しているが、適当な文字列を返すほうがはじめの一歩の検証に良いだろう。


アプリケーションコンテキスト名は、Eclipse で作成したプロジェクトの場合、通常プロジェクト名がデフォルトで設定されるが、以下の手順で変更が可能である。

[プロジェクトプロパティ]-[Tomcat]-[全般タブ]-コンテキスト名

である。例えば、「test」と設定したとする。


Tomcatプロジェクト全般の設定については、以下が参考になる。

http://seesaawiki.jp/salyu/d/Eclipse%A4%CB%A4%AA%A4%B1%A4%EBTomcat%A4%CE%C0%DF%C4%EA

以上で、以下のURLからAPIをたたくことが可能である。

http://localhost:8080/test/api/communication



Doma gen で dao と entity を自動生成する

Doma に付属するツール Doma Gen で、dao と entity を自動生成する方法。

以下のチュートリアルが参考になります。



doma-genをダウンロードしたら、必要なファイルをプロジェクトにコピーします。

  • doma-gen-build.xml : プロジェクトトップに配置
  • doma-gen-1.xx.x.jar : WEB-INF/lib に設置(アプリケーションとしては不要なので、別の場所の方が良いのかも)
  • freemaker-2.x.xx.jar : 同上
後は、接続するDBに対応したドライバーを準備します。ここでは、MySQLを使いました。

  • mysql-connector-java-x.x.xx-bin.jar : WEB-INF/lib に設置

ant でビルドしますので、適当に ant をインストールしておきましょう。バージョンの制限は見た限りありませんでした。


ビルドしたいプロジェクトに合わせて、doma-gen-build.xml を編集します。
今回は tomcat プロジェクトに設置しました。

<?xml version="1.0" encoding="UTF-8"?>
<project name="doma-gen-example" default="gen" basedir=".">
<property name="javaDestDir" value="WEB-INF/src"/> POJO出力ディレクトリトップ。
<property name="sqlDestDir" value="WEB-INF/src"/> SQL出力ディレクトリトップ。
<property name="dialectName" value="mysql"/> 方言名。ここではMySQL用です。
<property name="driverClassName" value="com.mysql.jdbc.Driver"/> MySQL用ドライバークラス名。
<property name="url" value="jdbc:mysql://localhost:3306/sc_ryi"/> DB接続URL
<property name="user" value="****"/> DB接続用アカウント。
<property name="password" value="******"/> DB接続用パスワード。
<property name="entityPackageName" value="jp.masazdream.scryi.server.db.entity"/> エンティティパッケージ。
<property name="daoPackageName" value="jp.masazdream.scryi.server.db.dao"/> Daoパッケージ
<property name="configClassName" value="jp.masazdream.scryi.server.db.config.AppConfig"/> AppConfigクラス。ここはクラスです。
<property name="sqlTestClassName" value="jp.masazdream.test.scryi.server.db.SqlTest"/> SqlTestクラス。
<property name="sqlFilePattern" value="META-INF/**/*.sql"/>
<path id="classpath">
<fileset dir="WEB-INF/lib"/> 各種ライブラリを設置したディレクトリ。
</path>
<taskdef name="gen" classname="org.seasar.doma.extension.gen.task.Gen" classpathref="classpath" loaderref="loader"/>
<taskdef name="genTest" classname="org.seasar.doma.extension.gen.task.GenTest" loaderref="loader"/>
<typedef name="entityConfig" classname="org.seasar.doma.extension.gen.task.EntityConfig" loaderref="loader"/>
<typedef name="daoConfig" classname="org.seasar.doma.extension.gen.task.DaoConfig" loaderref="loader"/>
<typedef name="sqlConfig" classname="org.seasar.doma.extension.gen.task.SqlConfig" loaderref="loader"/>
<typedef name="sqlTestConfig" classname="org.seasar.doma.extension.gen.task.SqlTestConfig" loaderref="loader"/>
<target name="gen">
<gen
dialectName="${dialectName}"
driverClassName="${driverClassName}"
url="${url}"
user="${user}"
password="${password}">
<entityConfig
destdir="${javaDestDir}"
packageName="${entityPackageName}"
/>
<daoConfig
destdir="${javaDestDir}"
packageName="${daoPackageName}"
configClassName="${configClassName}"
/>
<sqlConfig
destdir="${sqlDestDir}"
/>
</gen>
</target>
<target name="genTest">
<genTest
dialectName="${dialectName}"
driverClassName="${driverClassName}"
url="${url}"
user="${user}"
password="${password}">
<sqlTestConfig
destdir="${javaDestDir}"
testClassName="${sqlTestClassName}">
<fileset dir="${sqlDestDir}">
<include name="${sqlFilePattern}"/>
</fileset>
</sqlTestConfig>
</genTest>
</target>
</project>
ant はコマンドラインから実行します。

ant -f doma-gen-build.xml

これで、WEB-INF/src 配下に DAO、Entityクラスが自動生成出来ます。

2014年7月1日火曜日

Doma を使って Java の DAO を楽にする。

○javaアプリケーション の DAO に何を使うか? Doma が良さそう。


How to use doma ? ということで、


JavaのDaoフレームワークである、Doma(ここでは Doma1 を)を導入します。
Domaは2waysql方式で、発行したSQLがわかりやすく、ローカルトランザクションをサポートしています。

大きな連続した命令を行う必要がないアプリケーションであれば、十分な機能を有しています。

また、他のライブラリへの依存が無いこともよいです。

Doma -----------

データ型:http://doma.seasar.org/reference/basic.html
エンティティ定義:http://doma.seasar.org/reference/entity.html
トランザクション:http://doma.seasar.org/reference/transaction.html
アノテーション:http://doma.seasar.org/reference/apt.html
Daoインターフェース:http://doma.seasar.org/reference/dao.html

■DB生成

MySQLにテーブルを構築します。
今回は、テスト用に1つテーブルを作成しました。

ex)
○テーブル
use beta_doma_table;
CREATE TABLE `beta_doma_table`.`beta_doma` (
  `beta_doma_id` INT NOT NULL AUTO_INCREMENT,
  `beta_doma_message` VARCHAR(255) NULL,
  `beta_doma_number` INT NULL,
  `beta_doma_created_at` TIMESTAMP NULL,
  `beta_doma_updated_at` TIMESTAMP NULL,
  `beta_doma_deleted_flag` TINYINT NULL DEFAULT 0,
  `beta_doma_deleted_at` TIME NULL DEFAULT NULL,
  PRIMARY KEY (`beta_doma_id`),
  UNIQUE INDEX `beta_doma_id_UNIQUE` (`beta_doma_id` ASC))
ENGINE = InnoDB
COMMENT = 'test for doma table';

○テスト用データ
use beta_doma_table;
insert into beta_doma (beta_doma_message, beta_doma_number, beta_doma_created_at, beta_doma_updated_at) values('test1こんにちは', 101, now(), now());
insert into beta_doma (beta_doma_message, beta_doma_number, beta_doma_created_at, beta_doma_updated_at) values('test2ありがとう', 102, now(), now());
insert into beta_doma (beta_doma_message, beta_doma_number, beta_doma_created_at, beta_doma_updated_at) values('test3すばらしい!', 103, now(), now());
insert into beta_doma (beta_doma_message, beta_doma_number, beta_doma_created_at, beta_doma_updated_at) values('test4確かに', 104, now(), now());
insert into beta_doma (beta_doma_message, beta_doma_number, beta_doma_created_at, beta_doma_updated_at) values('test5緩やかに', 105, now(), now());
insert into beta_doma (beta_doma_message, beta_doma_number, beta_doma_created_at, beta_doma_updated_at) values('test6そうですね', 106, now(), now());
insert into beta_doma (beta_doma_message, beta_doma_number, beta_doma_created_at, beta_doma_updated_at) values('test7反対です', 107, now(), now());
insert into beta_doma (beta_doma_message, beta_doma_number, beta_doma_created_at, beta_doma_updated_at) values('test8。。。888888', 108, now(), now());

■データソースの生成
以下の用にDBへのアクセス情報を保持しておきます。
MySQLの方言も吸収します。

public class AppConfig extends DomaAbstractConfig {
protected static final LocalTransactionalDataSource dataSource = createDataSource();
protected static final Dialect dialect = new MysqlDialect();
public DataSource getDataSource() {
return dataSource;
}
public Dialect getDialect() {
return dialect;
}
protected static LocalTransactionalDataSource createDataSource() {
SimpleDataSource dataSource = new SimpleDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/beta_doma_table?useUnicode=true&characterEncoding=utf8");
dataSource.setUser("****");
dataSource.setPassword("****");
return new LocalTransactionalDataSource(dataSource);
}
public static LocalTransaction getLocalTransaction() {
return dataSource.getLocalTransaction(defaultJdbcLogger);
}
}
view raw AppConfig.java hosted with ❤ by GitHub
■Entityの生成
テーブルに対応したEntityを生成します。
自動生成するツール Doma Gen もあるようなので、後日試します。

@Entity(naming = NamingType.SNAKE_LOWER_CASE)
public class BetaDoma {
@Id
@Column(name = "beta_doma_id")
public Integer id;
@Column(name = "beta_doma_message")
public String message;
@Column(name = "beta_doma_number")
public Integer number;
@Column(name = "beta_doma_created_at")
public Timestamp createdAt;
@Column(name = "beta_doma_updated_at")
public Timestamp updatedAt;
@Column(name = "beta_doma_deleted_flag")
public Integer deleted_flag;
@Column(name = "beta_doma_deleted_at")
public Timestamp deletedAt;
}
view raw BetaDoma.java hosted with ❤ by GitHub

■Entityに対するDaoを生成
Entityに対する操作をDaoに定義します。
引数をカラム名と等しくする必要があります。

@Dao(config = AppConfig.class)
public interface BetaDomaTableDao {
/**
*
* @param beta_doma_id 必要事項:カラム名と一致
* @return
*/
@Select
BetaDoma selectById(Integer beta_doma_id);
@Update
int update(BetaDoma betaDomaTable);
}

■コンパイル時のアノテーションプロセス
プロジェクトプロパティの[JavaCompiler]-[Annotation Processing]-[Enable project specific settings]をonにします。

Generated source directoryには「.apt_generated」とします。

[Factory Path]には、domaのライブラリ(doma-1.XX.X.jar)を指定します。

■プロジェクトビルド
antでdomaをビルドします。
destにはビルド先を指定します。Daoインターフェースの実装クラス(Impl)がDomaによって生成されます。
targetには、jarが出来ます。

<project name="doma-tutorial" default="jar" basedir=".">
<property name="dest" value="src/target/build"/>
<property name="apt_generated" value="src/target/apt_generated"/>
<property name="src" value="src"/>
<property name="resources" value="src"/>
<path id="classpath">
<fileset dir="libs" includes="*.jar"/>
</path>
<target name="jar" depends="clean,copy,compile">
<jar jarfile="target/doma-own-test.jar" basedir="${dest}" />
</target>
<target name="clean">
<delete dir="target" failonerror="false"/>
<mkdir dir="target"/>
<mkdir dir="${dest}"/>
<mkdir dir="${apt_generated}"/>
</target>
<target name="compile">
<javac fork="yes" compiler="javac1.7" debug="on" encoding="UTF-8"
classpathref="classpath" srcdir="${src}" destdir="${dest}">
<compilerarg line="-s ${apt_generated}" />
</javac>
</target>
<target name="copy">
<copy todir="${dest}" filtering="true">
<fileset dir="${resources}">
<include name="META-INF/**" />
</fileset>
</copy>
</target>
</project>
view raw build.xml hosted with ❤ by GitHub


■生成jarをビルドパスに追加
targetに生成したjarをビルドパスに追加します。
JavaのアプリケーションからdomaのDAO機能を使うことが出来ます。


■テスト用コード
以下の用にEntityを取得し、更新が可能です。

public class Main {
public static void main(String[] args){
LocalTransaction tx = AppConfig.getLocalTransaction();
try{
tx.begin();
BetaDomaTableDao dao = new BetaDomaTableDaoImpl();
BetaDoma betaDomaTable = dao.selectById(2);
betaDomaTable.message = "renew message 更新したよ!";
betaDomaTable.number = 10000;
dao.update(betaDomaTable);
tx.commit();
}finally{
// トランザクション
tx.rollback();
}
}
}
view raw Main.java hosted with ❤ by GitHub
以上で、Domaの基本的な操作は完了です。
大きなデータベースの場合、Entityを手動で書くことが難しくなります。
そのため、Doma gen が力を発揮するのでしょう。