SIerっぽいWEBアプリをAngularJS+JavaEEで組んでみた(その1)

seasar2とかで実装しているWEBアプリをAngularJS+JavaEE(JPA、JAX-RS)でリプレイスしたい！なんて話をちらほら聞くので、実際どんな感じになるのか作成してみました。
バックエンドだけ開発していた人間が考えると、こうなってしまうんだなという結果に終わってます。。

ソースはこちらです。
https://github.com/ko-aoki/angularJS_practice/tree/forQiita

参考にした書籍は以下でした。

* [Beginning Java EE 6 GlassFish 3で始めるエンタープライズJava (Programmer’s SELECTION)](http://www.amazon.co.jp/Beginning-Java-EE-Programmer%E2%80%99s-SELECTION-ebook/dp/B0088L9I4W/ref=sr_1_1?ie=UTF8&qid=1407675020&sr=8-1&keywords=javaee)
* [AngularJSアプリケーション開発ガイド](http://www.amazon.co.jp/AngularJS%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E9%96%8B%E7%99%BA%E3%82%AC%E3%82%A4%E3%83%89-Brad-Green/dp/4873116678/ref=sr_1_1?ie=UTF8&qid=1407675160&sr=8-1&keywords=angular)

実際に作り始めると、やはり書籍の情報だけでは全然不足していて、しんどかった。
OracleはJavaEEにかなり力入れている感じだけど、JavaEE6以降、特にJPAの日本語情報が不足しているなあと。

#機能概要
ざっくり動くレベルで、以下の機能を実装。

* ログイン
* メニュー
* 条件検索画面（検索結果明細を表示）
* データ登録画面
* コード照会画面(ページ検索)

#使用アプリ

* NetBeans8
業務だとeclipseを10年以上使用しているのですが、JavaEEならNetBeansかなと採用。
自動生成機能が強力でした。

* WebStorm8
AngularJSのコード補完があったり。NetBeansも対応していたかな？
まだ使いこなせてないです。。

#フォルダ構成

`
+---src //Javaソース
|   \---java
|    +---base
|    |   \---bean
|    +---bean
|    +---entity
|    +---form
|    +---repository
|    +---resource
|    +---service
|    \---util
+---test //テストソース。karmaなどもここ。
\---web
    +---css
    +---data
    +---images
    +---img
    +---js
    |   +---controllers //AngularJSコントローラ
    |   +---directives //AngularJSディレクティブ
    |   +---services //AngularJSサービス
    |   \---vendor //AngularJS,jQueryなどライブラリファイル
    +---partials //AngularJSテンプレート
    +---templates //AngularJSテンプレート(ディレクティブ用)
    \---WEB-INF
`
#設定ファイル(的なもの)
###JavaScript

* main.js

RequireJSを使用していて、ファイルが増えたらこちらに追記。

```js:main.js
require.config({
	paths: {
        'jquery': 'vendor/jquery/jquery',
        'jquery.treeview': 'vendor/jquery/jquery.treeview',
        'angular': 'vendor/angular/angular',
        'angularRoute': 'vendor/angular/angular-route',
        'angularResource': 'vendor/angular/angular-resource',
        'angularMocks': 'vendor/angular-mocks/angular-mocks',
        'domReady': 'vendor/requirejs/domReady'
	},
	shim: {
        'jquery' : {'exports' : 'jquery'},
        'jquery.treeview' :['jquery'],
        'angular' : {'exports' : 'angular'},
		'angularRoute': ['angular'],
        'angularResource': { deps:['angular'] },
		'angularMocks': {
			deps:['angular'],
			'exports':'angular.mock'
		}
	}
});

require([
        'angular',
        'app',
        'domReady',
        //ファイルを追加したらここに追記
        'services/dtoSrv',
        'controllers/loginCtrl',
        'controllers/menuCtrl',
        'controllers/mntMstUserCtrl',
        'controllers/mntMstUserRegCtrl',
        'controllers/mntMstUserRegConfirmCtrl',
        'controllers/codeDeptCtrl',
        'directives/csngPage',
        'directives/csngCodeDept'
    ],
    function (angular, app, domReady) {
        'use strict';
        app.config(['$routeProvider',
            function($routeProvider) {
//ルートの定義。画面を追加したらここで追記
                $routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'LoginCtrl'});
                $routeProvider.when('/menu/:roleId', {templateUrl: 'partials/menu.html', controller: 'MenuCtrl'});
//中略
                $routeProvider.otherwise({redirectTo: '/login'});
            }
        ]);
        domReady(function() {
            angular.bootstrap(document, ['MyApp']);
            $('html').addClass('ng-app: MyApp');
        });
    }
);
```

###JavaEE

* persistence.xml

[寺田さんの記事](http://yoshio3.com/2010/02/26/%E3%81%93%E3%81%93%E3%81%8B%E3%82%89%E5%A7%8B%E3%82%81%E3%82%8B-java-ee-6-2/)とか参考にしてNetBeansで自動生成。

#実装
###ログイン
ユーザID、パスワードを入力して「ログイン」を押下すると
サーバアクセスし、ユーザマスタを参照して次画面遷移するか、エラーメッセージを出力。

####AngularJS

テンプレートファイルと、それに対応したコントローラファイルを作成する。

* login.html

```:login.html
<h1>ログイン</h1>
<ul>
   <li ng-repeat="message in messages">
          <p>{{message}}</p>
   </li>
</ul>
<br>
<span>ユーザID　：</span><input type="text" ng-model="loginUserId"/>
<br>
<span>パスワード：</span><input type="text" ng-model="password"/>
<br>
<button class="btn" ng-click="login()">ログイン</button>
```

* loginCtrl.js

コントローラファイルではJAX-RSにアクセスし、結果次第でエラー出力するか、画面遷移しています。
$resourceでresourceオブジェクトを生成し、getでサーバアクセス。
$location.pathを使用すると、HTTPリクエストは発生しません。

```js:loginCtrl.js
define(['controllers','angularResource'],
    function(controllers) {
        controllers.controller('LoginCtrl', ['$scope', '$http', '$location', '$resource',
            function ($scope, $http, $location, $resource) {
                $scope.login = function login() {
                    var login = $resource('webresources/login/:loginUserId,:password',
                                    {
                                        'loginUserId': '@loginUserId',
                                        'password': '@password'
                                    }
                                );
                    login.get(
                        {'loginUserId': $scope.loginUserId, 'password': $scope.password},
                        function (data) {
                            if (data.result === "error") {
                                $scope.messages = data.messages;
                            } else {
                                $location.path("menu/" + data.roleId);
                            }
                        }
                    );
                };
            }]);
    });
```
####JavaEE
* LoginResource.java

AngularJSのアクセス先となるリソースクラス。
@Pathで示されるリクエストURLのフォーマットにより、対応するクラス、メソッドが決定。
loginCtrl.jsの'login で"webresources/login/ユーザ,パスワード"の形式でリクエストされると、
loginメソッドが実行される。
@InjectでDI。
@Produces("application/json")で、戻り値のJavaオブジェクトをJSONに変換してくれる。

```java:LoginResource.java

@Path("login")
public class LoginResource {

    @Inject
    private LoginService loginService;
    
    public LoginResource() {
    }

    @GET
    @Path("{userId},{password}")
    @Produces("application/json")
    public LoginForm login(@PathParam("userId") String userId, @PathParam("password") String password) {
       return loginService.login(userId, password);
    }

}
```
* LoginServiceImpl.java

リソースクラスから呼び出されるサービスクラス。
既存Javaアプリの移行という観点で、戻り値にformクラスを指定することにしました。
Repositoryクラスを利用し、Entityクラスを取得し、Formクラスに転記。

```java:LoginServiceImpl.java
public class LoginServiceImpl implements LoginService{

    @Inject
    private MstUserRepository mstUserRep;
    
    public LoginForm login(String userId, String password) {
       
        List<MstUser> rec = mstUserRep.findByUserId(userId);
        LoginForm form = new LoginForm();
        if (rec.isEmpty()) {
            form.setMessages(Arrays.asList("ユーザが存在しません"));
            form.setResult("error");
        } else {
            if (password.equals(rec.get(0).getPassword())) {
                form.setRoleId(rec.get(0).getRoleId().getRoleId());
                form.setResult("ok");
            } else {
                form.setMessages(Arrays.asList("パスワードが一致しません"));
                form.setResult("error");
            }
        }
        return form;

    }

}
```

* MstUserRepositoryImpl.java

エンティティを操作するリポジトリクラス。
正直、DAOとの違いが理解できてないです。。

@PersistenceContextでpersistence-unitを指定。ここは共通化できそうな。。
findByUserIdではNamedQueryを利用しています。

```java:MstUserRepositoryImpl.java

public class MstUserRepositoryImpl implements MstUserRepository{
    @PersistenceContext(name = "common-app-javaee7PU")
    private EntityManager em;

    /**
     * ユーザIDでユーザを検索します.
     * @param userId
     * @return 
     */
    @Override
    public List<MstUser> findByUserId(String userId) {
       Query query = em.createNamedQuery("MstUser.findByUserId", MstUser.class);
       query.setParameter("userId", userId);
       List<MstUser> rec = query.getResultList();
       return rec;
    }
//中略
    /**
     * ユーザ情報を更新します.
     * @param user 
     */
    @Override
    @Transactional(Transactional.TxType.REQUIRED)
    public void update(MstUser user) {
        em.merge(user);
    }

}

```

* MstUser.java

こちらも、NetBeansで、テーブルを指定して自動生成。

#まとめ
旧来のJavaフレームワークでは、JSP+アクションクラスで実装していた箇所がAngularJSで置き換えられることになります。
実際に書いてみると、以前より画面-ロジックの見通しが良くなった印象を受けました。JSPは表示できるようになるまで時間がかかるので、それがなくなるのも大きい。
JAX-RSはアノテーションだけでやりたいことができてしまう。昔作成したソースを思い出して遠い目をしちゃう感じです。
JPAはMyBatisとか、生SQLベースのフレームワークを使用していたので、かなり理解し辛かったです。

[続きます](http://qiita.com/ko-aoki/items/bccedd77964a2a258130)