seasar2とかで実装しているWEBアプリをAngularJS+JavaEE(JPA、JAX-RS)でリプレイスしたい!なんて話をちらほら聞くので、実際どんな感じになるのか作成してみました。
バックエンドだけ開発していた人間が考えると、こうなってしまうんだなという結果に終わってます。。
ソースはこちらです。
https://github.com/ko-aoki/angularJS_practice/tree/forQiita
参考にした書籍は以下でした。
実際に作り始めると、やはり書籍の情報だけでは全然不足していて、しんどかった。
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を使用していて、ファイルが増えたらこちらに追記。
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
寺田さんの記事とか参考にしてNetBeansで自動生成。
#実装
###ログイン
ユーザID、パスワードを入力して「ログイン」を押下すると
サーバアクセスし、ユーザマスタを参照して次画面遷移するか、エラーメッセージを出力。
####AngularJS
テンプレートファイルと、それに対応したコントローラファイルを作成する。
- 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リクエストは発生しません。
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に変換してくれる。
@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クラスに転記。
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を利用しています。
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ベースのフレームワークを使用していたので、かなり理解し辛かったです。