Java EEハンズオン事前準備手順(JJUG CCC 2015 Fall) #jjug_ccc #ccc_m1
JJUG CCC 2015 Fallが近づいてきました。
今回、僕は2時間のJava EEハンズオンを担当することになりました。
■脱Struts&独自フレームワーク!今からでも遅くないJava EE Web開発入門ハンズオン!
http://www.java-users.jp/?page_id=2064#M-1
このハンズオンは、StrutsなどのWebフレームワークは経験があるけど、Java EEは初めてという方を対象にしていますので、「Java EEってそもそも何?」という方も歓迎です!
基礎からの講義・解説をしながらハンズオンを進めますし、演習をサポートするチューターも何人か参加しますので、Java EE初心者でも安心のセッションです!
内容は、Java EEのWebフレームワークである「JSF」「JAX-RS」「Jersey MVC」を、実際にご自分のPCでプログラムを作りながら学ぶものになっています。
ハンズオン課題は、DBと連携した基本的なCRUDアプリケーションです。
前半50分がJSF、10分休憩を挟んで、後半50分がJAX-RS + Jersey MVCです。
参加にあたって当日必要なもの
- 事前準備済かつ十分に充電されたご自分のノートPC
事前準備について
ハンズオンに参加していただくにあたって、事前準備をお願いしています。
事前準備にはインターネット接続が必要ですが、会場にはWiFiはありません。また、ハンズオン当日は大人数なので、個人でWiFiルーターを持ち込まれる際も、混雑してつながりにくくなることが多いためです。
インターネット回線の速度やPCスペックにもよりますが、所用時間は30分前後です。
お手数ではありますが、下記の手順に沿ってご準備をお願いします。
- 手順1. 開発環境をインストールする
- 手順2. GitHubから演習用プロジェクトのクローンしてビルドする
事前準備についてご不明な点がありましたら、お気軽にご連絡ください。
ご連絡は、このブログ記事にコメントを書いていただくか、もしくは僕のTwitterアカウント@suke_masaまでよろしくお願いします。
手順1. 開発環境をインストールする
開発環境の概要
- JDK 8u66
- NetBeans 8.1 (8.0.2でも大丈夫です)
- Payara Web ML 4.1.1.154
- curl (MacやLinuxの場合は、OSに組み込まれているのでインストールの必要なし)
データベースは、Payara内包のJavaDBを利用するので、インストールする必要はありません。
データベースのテーブル作成は、JPAのスキーマ生成機能を利用するので、これも事前準備は必要ありません。
ビルドにはMaven、GitHubからのクローンにはGitを使いますが、これらはNetBeansに内包されているので、インストールする必要はありません。
JDK
下記のWebサイトから、お使いのOSに合わせた「JDK 8u66」のインストーラーをダウンロードして、実行してください。
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
環境変数JAVA_HOMEおよびPATHを設定してください。
NetBeans
下記のWebサイトから、お使いのOSに合わせたインストーラーをダウンロードして、実行してください。
ダウンロードバンドルは、「Java EE」または「すべて」を選択してください。
https://netbeans.org/downloads/
インストーラーを実行した後は、すべて「次へ」などでOKです。
Payara
Payaraは、英国C2B2社がGlassFish(Java EE参照実装サーバー)をベースに、バグフィックスや機能追加をして開発しているAPサーバーです。
使い方などは、GlassFishと全く変わりません。
下記のWebサイトから、「Payara-Web-ML 4.1.1.154 (Multi-Language Web Profile)」をダウンロードしてください。
http://www.payara.co.uk/payara_multi-language
ダウンロードしたZIPファイルは、適当なフォルダに展開してください。
次に、NetBeansにPayaraを認識させます。
NetBeansを起動したら、画面左の[サービス]タブを開き、[サーバー]を右クリック→[サーバーの追加]を選択します。
[サーバー]で[GlassFish Server]を選択し、[名前]の欄には適当な名前を入力後、[次>]をクリックします。
[インストール場所]の[参照]ボタンをクリックして、先ほどPayaraを展開したフォルダを選択します。僕の場合(下記の画像)ではフォルダ名を変えていますが、展開してそのままでは「payara41」というフォルダ名になっていますので、そのpayara41フォルダを選択してください。選択したら[ローカルドメイン]・[ライセンス契約を読んで同意しました...]を選択して、[次>]をクリックします。
デフォルトで下記の画像のような状態になっていますので、このまま[終了]をクリックします。
サーバーにPayaraが追加されました。
curl
curlは、HTTPリクエストを送るための簡易ツールです。JAX-RSのハンズオンで利用します。
MacやLinuxの場合は、OSに最初から組み込まれていますので、この手順はスキップしてください。
以下、Windows 7を前提に説明します。
下記のWebサイトから、ZIPファイルをダウンロードしてください。
http://curl.haxx.se/latest.cgi?curl=win64-nossl
ダウンロード後、ZIPファイルを適当なフォルダに展開してください。
その展開したフォルダ(curl.exeが入っているフォルダです)のパスを、環境変数PATHに追加してください。
コマンドプロンプトを開いて「curl」コマンドを実行し、下記のように表示されればOKです。
C:\User\User01 > curl curl: try 'curl --help' or 'curl --manual' for more information C:\User\User01 >
2. GitHubから演習用プロジェクトのクローンしてビルドする
クローン
今回、プロジェクトは4つ用意しています。
これらのプロジェクトをクローンして、ビルドするところまで実施してください。
これらはMavenプロジェクトで、初回ビルド時に依存ライブラリのダウンロードが実行されます。
GitHubアカウントの作成
今回のハンズオン用プロジェクトは、すべてGitHubにアップしています。
GitHubからプロジェクトをクローンするためには、ご自身のGitHubアカウントが必要になります。
お持ちでない方は、下記のURLからSign Upでアカウントを作成してください。
NetBeansでGitHubからjjug-logicプロジェクトをクローン
NetBeansを開いたら、画面上部のメニューから[チーム]-[Git]-[クローン]と選択します。
[リポジトリURL]に「https://github.com/MasatoshiTada/jjug-logic.git」と入力します。
[ユーザー]と[パスワード]は、ご自身のGitHubアカウント情報を入力します。
[クローン先]は、クローンしたプロジェクトを保存するローカルのフォルダ名を指定します。
入力したら、[次>]をクリックします。
[master]が選択されていることを確認して、[次>]をクリックします。
デフォルトのまま[終了]をクリックします。
[プロジェクトを開く]をクリックします。
画面上部のメニューから[表示]-[バージョン・ラベルを表示]を選択します。
プロジェクトを右クリックして[依存性でビルド]を選択します。
残り3つのプロジェクトをクローン
プロジェクトが何も選択されていない状態で、画面上部のメニューから[チーム]-[Git]-[クローン]と選択します。(一度、[プロジェクト]ウィンドウ内で右クリックすると、プロジェクトの選択が外れます)
[リポジトリURL]に「https://github.com/MasatoshiTada/jjug-jsf.git」と入力します。
その他の情報は、先ほど入力したものが残っていると思いますので、そのまま[次>]をクリックします。
exerciseとmasterという2つのリモート分岐がありますので、2つとも選択した状態にして[次>]をクリックします。
デフォルトのまま[終了]をクリックします。
[プロジェクトを開く]をクリックします。
プロジェクトを右クリックして、[Git]-[分岐/タグ]-[分岐に切り替え]を選択します。
[origin/exercise]を選択し、[新しい分岐としてチェックアウト]にチェックを入れて[切替え]をクリックします。
プロジェクト名の横に[exercise]という分岐名が表示されていることを確認したら、プロジェクトを右クリックして[依存性でビルド]を選択します。
プロジェクトを右クリックして[プロパティ]を選択すると、下記のようなプロパティのウィンドウが開きます。[実行]を選択し、[サーバー]で今回インストールしたPayara Web ML 4.1.1.154を選択後、[OK]をクリックします。
残り2つのプロジェクトについても、以上の手順を行ってください。
リポジトリURLは下記の通りです。
https://github.com/MasatoshiTada/jjug-jax-rs.git
https://github.com/MasatoshiTada/jjug-jersey-mvc.git
NetBeansでTODOコメントを一覧表示
ハンズオン用のソースコードは、所々を穴埋め形式にして、その箇所はTODOコメントを書いてあります。
NetBeansのTODOコメント一覧表示機能を使うと、ダブルクリックでその場所にジャンプできるので、ハンズオン中はこの機能を使います。
NetBeans画面上部のメニューから[ウィンドウ]-[アクション項目]と選択します。
黄色い四角のマークをクリックすると、現在選択中のプロジェクトのTODOのみが表示されます。
漏斗(フィルター)のマークをクリックして、[編集]を選択します。[タイプ]で[TODO]のみにチェックを入れます。
[説明]を1回クリックすると、TODOコメントが並び替えられて見やすくなります。
スライドのダウンロード
当日は下記のスライドでセッションを進めます。あらかじめダウンロードしてあると、手元でも資料を確認することができます。
www.slideshare.net
何か分からないことがあったら
手順は以上になりますが、何か分からないことがあったら僕までご連絡ください。出来る限りご対応します。
この記事へのコメントかツイッター(@suke_masa)でご連絡ください。
(多分、ツイッターの方が僕が気付くのが早いと思います)
これ以上の予習などは必要ありませんが、もし事前に動作確認などを行いたい場合は、各プロジェクトの分岐を[master]に切替え、[消去してビルド]後に[実行]でデプロイできます。
それでは、皆様のご参加をお待ちしております!
【全部俺】JavaのIDE、どれを使う?【3大IDE頂上決戦】
2015年現在では、おそらく以下の3つのいずれかだと思います。
(ちなみに、僕自身が使った経験がほとんどないので、VimやEmacsなどのエディタは今回のスコープから除外しますm( )m)
僕自身は、社会人になってからJavaを学び始め、新人研修はサクラエディタで受講し、それ以降はしばらく数年はEclipseのみを使っていました。
NetBeans歴は2年くらい、IntelliJ IDEA歴は1年くらいで、どのIDEも普段使いにはほぼ問題ない、というくらいのレベルです。
逆に言えば、どれもまだまだ使いこなせてないんじゃないかとも自分では思っています(^^;
その程度のレベルの僕ですが、現時点でどのような見解を持っているのか、それぞれのIDEの特徴を比較しつつ説明したいと思います。
Eclipse
良い点
- プラグインが豊富
- 様々な開発環境に対応可能
- 日本語化が容易
- 日本語情報も豊富、使っている人が多いのでノウハウが蓄積されている
なにがしかのフレームワークだったりAPサーバーだったりをサポートするプラグインは、三大IDEの中では真っ先に作られる印象です。
うーん?
- 他のIDEと比較すると、補完能力が弱い
- やや動きが重い
あくまで僕の感覚ですが、メモリ使用量はほぼ同じでも、IntelliJの方が軽快に動作する印象です。あまりスペックが高くないPCだと、その傾向が顕著です。
NetBeans
良い点
僕はJava EEをやりだした時はEclipseを使っていて、研修のコンテンツもすべてEclipseで作っていました。しかし、今一つやり辛さを感じていたので、全てNetBeansに置き換えました。もちろん最初は慣れませんでしたが、ものの数日で感覚は掴めました。
うーん?
- プラグインが少ない
- 多様な環境には対応できていない
例えば、Spring Bootをサポートするプラグインは、僕の知る限りありません。また、8.1が出るまでは、WildFly連携も不安定な感じでした。GlassFishなどのOracle製品との相性は良いです。
IntelliJ IDEA
良い点
- 補完が至る所で効く
- 何か書いた後のカーソル位置が「ここ!」というところに来てくれる
- JavaScriptサポート機能も強い
一言で言うと「コーディングがとても快適!」です。この点は、EclipseやNetBeansとは大きな差があります。
うーん?
- ぜんぶ英語(今のところ日本語化は不可能)
- コーディング以外の手順はやや複雑
- 基本的に有償
英語であることを差し引いても、使い方の手順を身に付けるには時間がかかりました。4カ月くらいでようやっと慣れたかな?という感じでした。
あと、三大IDEの中で唯一有償です。無償バージョンもあるのですが、これはAPサーバー連携やらHTML/JavaScriptやらのWeb開発機能が付いていません。
で、結局どれを使う?
いま僕は、これらのIDEを使い分けています。
Eclipseは研修用です。既存の研修コンテンツがEclipseプロジェクトが多いのと、受講者の方が慣れているという理由です。あと、前述の通りNetBeansはWildFly対応が今一つな感じがするので、Java EEでもWildFlyを使う時はEclipseです。
NetBeansは、Maven/GlassFish/JavaDB/JSF/JPAを使いたい時用です。
上記以外ではIntelliJ IDEAです。Gradleを使う時や、Java EEでもJPAを使わない(Domaなど)場合はIntelliJです。あとはSpringの場合もIntelliJです。
まとめ
どのIDEもそれぞれ良いところがあり、でも特定の環境だとやり辛さを感じたりで、まだどれか1つに統一できていないのが、僕の現状です。
ただ、もともとはEclipse一辺倒だった自分自身の反省なのですが、「食わず嫌い」ではもったいないと思います。
上述の通りEclipseにはEclipseの良さがあり、否定するつもりはありません。ただ、NetBeansやIntelliJ IDEAも良いIDEであり、少しずつつまみ食いしながら知っていって、その上で自分の感覚や開発環境などに応じて、ひとりひとりのベストなIDEを選べばいいんじゃないかというのが、僕の意見です!
この記事を読んだ方の「ベストなIDE」選びの一助になれば嬉しいです。
GlassFish/Payaraで@Transactional境界内で発生した例外が失われる現象が治ってた!
普段から僕は、Java EEやるときはGlassFish/Payaraを使っているのですが、1つだけ懸念事項がありました。それが表題の現象です。それがPayara 4.1.1.154(おそらくGlassFish 4.1.1)で治っていた、というお話です。
どんな現象?
Java EEでWeb作るときは、永続化層(JPA)・ビジネスロジック層(CDI/EJB)・プレゼンテーション層(JSF/JAX-RS)というような階層構造にすることが多いと思います。
永続化層
@Dependent public class EmployeeDao { @PersistenceContext(unitName = "empPU") private EntityManager em; @Transactional(TxType.REQUIRED) public Employee findById(Integer id) { // Employee emp = em.find(Employee.class, id); // return emp; throw new RuntimeException("ERROR IN DAO!!!"); } }
ビジネスロジック層
@Dependent public class EmployeeService { @Inject private EmployeeDao dao; @Transactional(TxType.REQUIRED) public Employee findById(Integer id) { Employee emp = dao.findById(id); return emp; } }
プレゼンテーション層
@Path("employees") public class EmployeeResource { @Inject private EmployeeService servicee; @GET @Path("{id}") public Response findById(@PathParam("id") Integer id) { Employee emp = service.findById(id); return Response.ok().entity(emp).build(); } }
永続化層で、わざとRuntimeExceptionをスローしています。
これを実行すると
これで実行してGlassFish/Payaraのログを見ると、javax.transaction.RollbackException
がスローされていると書かれています。RuntimeExceptionのスタックトレースは、表示されません。元の例外が失われてしまうのです。
なぜ、こうなるのか
上妻さんのブログで詳しく書かれています。昨年のGlassFishアドベントカレンダーの記事です。
GlassFish4.1をなおしてみた - 見習いプログラミング日記
簡単に言うと、javax.transaction.RollbackException
クラスにThrowable causeを引数に取るコンストラクタがそもそも定義されていないことや、トランザクションがロールバック確定であるにも関わらずコミットしようとするコードが存在していたことが問題でした。
この問題はとても悩みの種でした。研修中にビジネスロジック以下の層で例外が発生した時にデバッグするのがとても難しかったり、開発現場やシステムの実運用環境でコレが起こったら大変だよなあ・・・と思ったり。
Java EEの大きなメリットの1つは、トランザクション管理をAPサーバーに任せることができるという点だと僕は思っているので、これはどうしたもんかなあと、長い間思っていました。
Payara 4.1.1.154で治ってた!
先日、Payara 4.1.1.154がリリースされました。で、ぼんやりとリリースノートを読んでいたら、「Upstream Fixes」に"GLASSFISH-21172 - javax.transaction.RollbackException from @Transactional bean has no cause set"の文言があるじゃありませんか!
http://www.payara.co.uk/release_notes
そして、リンクを辿るとGlassFishのJIRAへ。
コメント一覧からさらにリンクを辿ると、ソースコードのDIFFが読めます。
https://java.net/projects/glassfish/sources/svn/revision/64135
おお、治ってる!しかも、上妻さんがブログに書かれていた修正案とほぼ、いや、全く同じ!
僕はPayara 4.1.1.154で動作確認しましたが、修正日付を見ると、おそらくGlassFish 4.1.1で修正されていたものと思われます。
で、修正後の挙動は?
これも上妻さんが書かれていた通りなのですが、@Transactional
境界内で発生した例外がそのままスローされます。上記のコードならRuntimeExceptionですね。
まとめ
- GlassFish/Payaraで例外が失われる現象が治っていました!
- 上妻さん凄すぎる!
10分で出来る!初めてのTwitter4J & Twitterアプリ作り方メモ in 2015-10-18
最近はJava EE系でもJAX-RS研修の担当が多くなっている関係上、OAuthやRESTクライアントにも興味が出てきました。ということで、初めてTwitter4Jでプログラムを作ってみました。参考にしたのは、@kikutaro_さんのこちらのブログです。
Twitter4Jを使ったら10分でつぶやきJavaプログラムが作れました! ~NetBeans編~ - Challenge Java EE !
分かりやすくまとまっていて、基本的な手順は今も変わっていないのですが、管理画面のUIが変わっていたので、本日時点での画面キャプチャ付きで解説します。
目標
Twitter4Jを使って、Javaのmain()メソッドからつぶやきを送信する!
Twitter Developer登録
プログラムを作る前に、Twitter Developer登録を行って、アプリ開発に必要なアクセストークンなどの情報を取得します。この手順は、JavaやTwitter4Jによらないはずです。
まず、下記のTwitterのDeveloperサイトにアクセスします。
すると、トップ画面が表示されます。画面右上のリンクから、ご自分のTwitterアカウントでサインインしてください。このアカウントは、普段のつぶやきなどで使っているアカウントでOKです。
トップ画面で下の方にスクロールして、[TOOLS]-[Managing Your App]をクリックします。
すると、アプリケーションの管理画面に移ります。まだ何もアプリケーションを作っていないので、[Create New App]ボタンだけが表示されます。
では、[Create New App]ボタンをクリックします。すると、アプリケーションの作成画面に移ります。適当なアプリケーション名、アプリの説明、自分のブログなどのURLを入力します。[Callback URL]は必須項目ではないので、今回は空欄のままにします。
上記の画面で下の方にスクロールすると、[Developer Agreement]が記載されているので、内容を確認した上で[Yes, I agree]にチェックを入れて、[Create your Twitter app]ボタンをクリックします。
そうすると、下記のような画面が表示されます。[Consumer Key]の部分は、後で使います。実際には英数字&記号の羅列が表示されていますが、秘密の情報のため画像を加工して伏せています。
次に、この画面の[Keys and Access Tokens]タブをクリックします。表示されている[Consumer Key (API Key)]と[Consumer Secret (API Secret)]という2つの文字列は、後で使います。
この画面で下の方にスクロールして、[Create my access token]ボタンをクリックします。
画面が遷移するので、また下の方にスクロールすると、[Access Token]と[Access Token Secret]が表示されています。この2つの文字列も、後で使います。
以上で、準備は完了です。
Twitter4JでJavaプログラムを作る
さて、後は簡単です。IDEはEclipseでもNetBeansでもIntelliJ IDEAでも何でも構いません。ビルドツールは、MavenまたはGradleを使います。
まず、Twitter4Jを依存性に含めます。
Gradleならこうです。
compile 'org.twitter4j:twitter4j-core:4.0.4'
<dependency> <groupId>org.twitter4j</groupId> <artifactId>twitter4j-core</artifactId> <version>4.0.4</version> </dependency>
次に、src/main/resourcesに「twitter4j.properties」というプロパティファイルを作ります。そして、下記のように記述します。
debug=true #右辺にConsumer Key (API Key)をコピーして貼り付ける oauth.consumerKey=XXXXXXXXXXXXXXXX #右辺にConsumer Secret (API Secret))をコピーして貼り付ける oauth.consumerSecret=XXXXXXXXXXXXXXXXXX #右辺にAccess Tokenをコピーして貼り付ける oauth.accessToken=XXXXXXXXXXXXXXXX #右辺にAccess Token Secretをコピーして貼り付ける oauth.accessTokenSecret=XXXXXXXXXXXXXXXX
src/main/javaにクラスを作ります。菊田さんのブログのプログラムそのままです(^^;
import twitter4j.*; public class UserInfoMain { public static void main(String[] args) throws TwitterException { Twitter twitter = TwitterFactory.getSingleton(); User user = twitter.verifyCredentials(); System.out.println(user.getName()); System.out.println(user.getScreenName()); System.out.println(user.getFriendsCount()); System.out.println(user.getFollowersCount()); Status status = twitter.updateStatus("Twitter4Jから初めてのツイート! #twitter4j"); } }
これだけです。いざ、実行します。すると・・・
多田真敏 suke_masa 63 199 . . . (他にもデバッグログがいっぱい表示されます)
おおお、自分のユーザー情報が表示されて、つぶやきが投稿されてる!
まとめ
- Developer登録をして、アクセストークンなどの情報を取得する
- アクセストークンなどは、コピーしてtwitter4j.propertiesに貼り付ける
- Twitter4J使えば、REST APIやらJSONやら認証やらのコードは書かなくてOK!
感想
あまりに簡単すぎて感動すら覚えました・・・。
Twitter4Jのソースも、これから読んでみたいと思います!
Payara(GlassFish)でMariaDBのJDBC接続プールが作成できない
表題の通りです。
環境
- MacBook Air (OS X El Capitan)
- MariaDB 10.1.x
- Payara Web 4.1.153
- JDK 1.8.0_60
試していませんが、おそらくWindowsやGlassFishでも同じ現象が起こると思います。
現象
- MariaDBのJDBCドライバのJARを「<PAYARA_HOME>/glassfish/lib」や「<PAYARA_HOME>/glassfish/domains/domain1/lib」に置く
- Payaraを起動してlocalhost:4848にアクセスし、管理コンソールからURL/USER/PASSWORDを設定する
という一般的な手順で作成しても、MariaDBへのpingが通りません。
ping時のPayara管理コンソール(ブラウザ上)でのエラーメッセージは、こんなのが出ます。
Access denied for user ‘’@’localhost’ (using password: NO)
どうやら、ユーザー名やパスワード設定が間違っている時に出てくるMariaDBのエラーメッセージのようなのですが、全く同じURL/USER/PASSWORDでメインメソッドから普通にJDBC接続したら出来ました。 (URL/USER/PASSWORDはPayara管理コンソールからコピペしたので間違いないはずです)
Payaraのプロパティ名も間違えていません。
しかも、ごくたまにpingが通ります。しかし、またすぐにpingが通らなくなります。
対策
今回は、MariaDBをPayaraで使うこと自体が目的ではないので、MySQL 5.6に変更しました。そうすると無事にJDBC接続プールが作成できました。
原因は分かりませんが、Payara管理コンソールで選択するときのDBの種類にはMariaDBは入っていないし、Payara(というか恐らくGlassFish)はMariaDBに対応していないのかもしれません。 (完全に僕の憶測です)
注意
この問題は、あくまでPayara+MariaDBの組み合わせによるものです。
【注意事項あり】Doma 2だけどCDI/EJB使ってJTAでトランザクション管理したい!そしてJAX-RSでREST作りたい!
Doma 2とは?基本的な使い方は?
Doma 2は、SQLを外部ファイルに書くことができるORマッパーです。ネイティブSQLが書けること、依存ライブラリが無い事、国産 OSSで日本語ドキュメントが充実している事などが魅力です。
下記の記事もご参考になさってください。2014年のJavaアドベントカレンダー向けに書いたものです。
美しき青きDoma!~SQLとIDEが奏でる美しきORマッピング~ - Java EE 事始め!
Domaでは、プログラマが作るのはHogeDaoインターフェイスだけで、その実装クラスHogeDaoImplはGradleでビルド時にAnnotation Processorで自動生成されます。
CDI/EJB使ってHogeDao型のフィールドにHogeDaoImplのインスタンスをインジェクションしたり、JTAでトランザクション管理するには、実装クラス側(HogeDaoImpl)に@RequestScoped
(CDI)や@Stateless
(EJB)などのアノテーションを付加する必要があります。
しかし、実装クラスはビルド時に生成されるし、生成後に手作業でアノテーションを付けても、再ビルド時に上書きされて消えてしまいます。
アノテーションをつける方法がずっと分からなかったのですが、遂にやり方が分かったのでご紹介します。
ソースはGitHubに公開しています。
MasatoshiTada/doma-jaxrs · GitHub
APサーバーはPayara Web 4.1.153ですが、Jerseyには依存しないように書いたので、WildFlyでもそのまま動作します(9.0.1.Finalで確認済み)。DBはMySQL 5.6、IDEはIntelliJ IDEA 14.1.5です。
CDI/EJBとJTAについて
これ以降の内容は、CDI/EJBとJTAの知識が前提となります。下記の資料が参考になります。
JavaDayTokyo2015での寺田さんの発表資料です。CDIについて詳しくまとまっています。
http://www.oracle.co.jp/jdt2015/pdf/2-2.pdf
@opengl-8080さんのJTAの解説ブログです。いつもながら勉強になります。
アノテーションをつける答えは@AnnnotateWith
ツイッターである方々のやり取りを見ていたら、Doma 2の公式ドキュメントの一部が目に入りました。
http://doma.readthedocs.org/ja/stable/config/#id22
@AnnnotateWith
というアノテーションを使っていますね。これがポイントです。
1.アノテーションの自作
まず、こんなアノテーションを自作します。自作するアノテーション名は任意ですが、公式ドキュメントに従って@InjectConfig
という名前にしておきます。
CDIの場合
package com.example.dao.config; import org.seasar.doma.AnnotateWith; import org.seasar.doma.Annotation; import org.seasar.doma.AnnotationTarget; import javax.enterprise.context.Dependent; import javax.inject.Inject; @AnnotateWith(annotations = { @Annotation(target = AnnotationTarget.CLASS, type = Dependent.class) , @Annotation(target = AnnotationTarget.CONSTRUCTOR, type = Inject.class) }) public @interface InjectConfig { }
EJBの場合
package com.example.dao.config; import org.seasar.doma.AnnotateWith; import org.seasar.doma.Annotation; import org.seasar.doma.AnnotationTarget; import javax.ejb.Stateless; import javax.inject.Inject; @AnnotateWith(annotations = { @Annotation(target = AnnotationTarget.CLASS, type = Stateless.class) , @Annotation(target = AnnotationTarget.CONSTRUCTOR, type = Inject.class) }) public @interface InjectConfig { }
注意!! Payara 4.1.1.154までのAPサーバーでは、CDI+JPA以外のORマッパー(というよりJDBC)では、JTAが正しく動作しない可能性があることが判明しました。なので、現段階ではEJBを使ってください。このことについては、PayaraのGitHubにIssueとして報告済みです。次バージョンでの改善を期待しています。WildFlyでは、CDIでもEJBでも正しく動作します。 参考URL @OpenGL_8080さんのブログ(コメント欄をご確認ください) JavaEE使い方メモ(JTA) - Qiita PayaraのGitHub Payara does NOT rollback when RuntimeException occurs in CDI @Transactional method using JDBC · Issue #505 · payara/Payara · GitHub
@Annotation
のtype属性はアノテーション名.class、target属性はtypeで指定したアノテーションを付加する対象を表します。
この例だと、クラスに@Dependent
、コンストラクタに@Inject
を付けるということになります。
2. 自作アノテーションをDaoインターフェイスに付加
次に、自作した@InjectConfing
アノテーションを、自作Daoインターフェイスに付加します。
package com.example.dao; import com.example.dao.config.InjectConfig; import com.example.entity.Employee; import org.seasar.doma.Dao; import org.seasar.doma.Script; import org.seasar.doma.Select; import java.util.List; import java.util.Optional; @Dao @InjectConfig public interface EmployeeDao { @Script void create(); @Select Optional<Employee> selectById(Integer empId); @Select List<Employee> selectLikeName(String name); }
3.Gradleでビルド
Gradleでビルドすると、EmployeeDao
インターフェイスの実装クラスが生成されます。
CDIの場合
package com.example.dao; /** */ @javax.enterprise.context.Dependent() @javax.annotation.Generated(value = { "Doma", "2.5.0" }, date = "2015-10-15T20:48:55.929+0900") public class EmployeeDaoImpl extends org.seasar.doma.internal.jdbc.dao.AbstractDao implements com.example.dao.EmployeeDao { /** * @param config the config */ @javax.inject.Inject() public EmployeeDaoImpl(org.seasar.doma.jdbc.Config config) { super(config); }
EJBの場合
package com.example.dao; /** */ @javax.ejb.Stateless() @javax.annotation.Generated(value = { "Doma", "2.5.0" }, date = "2015-10-15T20:48:55.929+0900") public class EmployeeDaoImpl extends org.seasar.doma.internal.jdbc.dao.AbstractDao implements com.example.dao.EmployeeDao { /** * @param config the config */ @javax.inject.Inject() public EmployeeDaoImpl(org.seasar.doma.jdbc.Config config) { super(config); }
@AnnnotateWith
で指定した通り、クラスに@Dependent
または@Stateless
、コンストラクタに@Inject
が付加されています。これで、このEmployeeDaoImpl
を他のクラスにCDIでインジェクトできるようになります。
また、コンストラクタには@Inject
が付加されています。
引数のConfig
インターフェイスは、データソースなどを保持するもので、後ほど実装クラスを作成します。
Config
実装クラスを作成して@ApplicationScoped
などを付加しておけば、この実装クラスのインスタンスが、コンストラクタインジェクションされます。
4. Config
実装クラスの作成
package com.example.dao.config; import org.seasar.doma.jdbc.Config; import org.seasar.doma.jdbc.dialect.Dialect; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import javax.sql.DataSource; @ApplicationScoped public class AppConfig implements Config { @Inject private DataSource dataSource; @Inject private Dialect dialect; @Override public DataSource getDataSource() { return dataSource; } @Override public Dialect getDialect() { return dialect; } }
@ApplicationScoped
を付加してCDI管理ビーン(このクラスはEJBでなくでOKです)にします。アプリケーションに関する設定情報を保持するクラスなので、スコープはアプリケーションスコープが適切と思われます。
DataSource
とDialect
については、別途プロデューサークラスを作成して、@Inject
で取得できるようにしています。
package com.example.dao.config; import org.seasar.doma.jdbc.dialect.Dialect; import org.seasar.doma.jdbc.dialect.MysqlDialect; import javax.annotation.Resource; import javax.enterprise.context.Dependent; import javax.enterprise.inject.Produces; import javax.sql.DataSource; import java.io.Serializable; @Dependent public class DataSourceProducer implements Serializable { @Resource(lookup = "jdbc/sandbox") private DataSource dataSource; private Dialect dialect = new MysqlDialect(); @Produces public DataSource getDataSource() { return dataSource; } @Produces public Dialect getDialect() { return dialect; } }
このクラスもEJBでなくてOKです。
訂正とお詫び:@Resource
でJDBCリソースを取得する際、name
属性を使っていたのですが、正しくはlookup
属性でした。訂正してお詫びします。
Config
実装クラスが複数ある場合は?
データソースが複数ある場合など、Config
実装クラスが複数になる可能性があると思います。その場合はQualifierを自作して、それをConfig
実装クラスと@AnnotatedWith
に追加すれば良いはずです。
@AnnotateWith(annotations = { @Annotation(target = AnnotationTarget.CLASS, type = Dependent.class) , @Annotation(target = AnnotationTarget.CONSTRUCTOR, type = Inject.class) , @Annotation(target = AnnotationTarget.CONSTRUCTOR_PARAMETER, type = 自作Qualifier1.class }) public @interface InjectConfig1 { }
@ApplicationScoped @自作Qualifier1 public class AppConfig1 implements Config { // 省略 }
@AnnotateWith(annotations = { @Annotation(target = AnnotationTarget.CLASS, type = Dependent.class) , @Annotation(target = AnnotationTarget.CONSTRUCTOR, type = Inject.class) , @Annotation(target = AnnotationTarget.CONSTRUCTOR_PARAMETER, type = 自作Qualifier2.class }) public @interface InjectConfig2 { }
@ApplicationScoped @自作Qualifier2 public class AppConfig2 implements Config { // 省略 }
JTAでトランザクション管理する
DataSource
は@Resource
で取ってきているし、DaoImplクラスはCDI管理にできたので、後は簡単です。
CDIの場合
package com.example.service; import com.example.dao.EmployeeDao; import com.example.entity.Employee; import com.example.resource.dto.EmployeeDto; import javax.enterprise.context.Dependent; import javax.inject.Inject; import javax.transaction.Transactional; import java.io.Serializable; import java.util.Optional; @Dependent public class EmployeeService implements Serializable { @Inject private EmployeeDao employeeDao; @Transactional(Transactional.TxType.REQUIRED) public Optional<EmployeeDto> selectById(Integer empId) { Optional<Employee> employeeOptional = employeeDao.selectById(empId); return employeeOptional.map(this::convertToDto); }
EJBの場合
package com.example.service; import com.example.dao.EmployeeDao; import com.example.entity.Employee; import com.example.resource.dto.EmployeeDto; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.inject.Inject; import java.io.Serializable; import java.util.Optional; @Stateless public class EmployeeService implements Serializable { @Inject private EmployeeDao employeeDao; @TransactionAttribute(TransactionAttributeType.REQUIRED) public Optional<EmployeeDto> selectById(Integer empId) { Optional<Employee> employeeOptional = employeeDao.selectById(empId); return employeeOptional.map(this::convertToDto); }
@Inject
でインジェクトすると、EmployeeDaoImpl
のインスタンスが注入されます。で、メソッドに@Transactional
を付加すればOK!
JAX-RSでRESTを作る
package com.example.resource; import com.example.resource.dto.EmployeeDto; import com.example.service.EmployeeService; import org.hibernate.validator.constraints.NotBlank; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.validation.constraints.Pattern; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @Path("employees") @RequestScoped public class EmployeeResource { @Inject private EmployeeService employeeService; @GET @Path("{empId}") @Produces(MediaType.APPLICATION_JSON) public Response selectById(@PathParam("empId") @Pattern(regexp = "[1-9][0-9]*") String empIdStr) { Integer empId = Integer.valueOf(empIdStr); EmployeeDto employeeDto = employeeService.selectById(empId) .orElseThrow(() -> new NotFoundException("該当する社員が見つかりませんでした")); return Response.ok(employeeDto).build(); } }
感想
これで去年からの疑問点が解決しました。ようやっとスッキリです。
あとはDomaの使い方さえ知れれば、怖いものなし・・・のはず!
併せて読みたい
うらがみさんのブログ。Doma+JAX-RS連携について書かれています。
Thymeleaf + JAX-RS + DomaをGlassFishで試してみる - 裏紙
今回利用した、@siosioさん作のIntelliJ IDEA Doma Support Plugin
IntelliJ IDEA用のDomaプラグイン作ってみた - しおしお
Doma公式ドキュメント
Welcome to Doma — Doma 2.0 ドキュメント
Domaにこの機能が入った経緯のブログ記事です。