8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Eclipse GlassFish と IntelliJ IDEAで Webアプリケーションの開発環境を構築するメモ

Last updated at Posted at 2020-01-07

概要

Eclipse GlassFish 5.1.0を利用したJakarta EE 8 Webアプリケーションの開発環境の構築手順をまとめたメモです。
IDEにはIntelliJ IDEAを使用します。

環境

  • Windows 10 Professional 1909
  • GlassFish 5.1.0
  • Oracle JDK 1.8.0_231
  • IntelliJ IDEA 2019.3

参考

用語のおさらい

Java EE

[JavaEE の概要] (https://www.oracle.com/technetwork/jp/java/javaee/overview/index.html)

Java Platform, Enterprise Edition(Java EE)はコミュニティ主導のエンタープライズ・ソフトウェアの標準です。Java EEはJava Community Processを使って開発されており、業界の専門家、営利団体とオープンソース団体、Javaユーザー・グループ、さらに数え切れないほど多くの個人が貢献しています。

EE4J

Eclipse Enterprise for Java (EE4J) is an open source initiative to create standard APIs, implementations of those APIs, and technology compatibility kits for Java runtimes that enable development, deployment, and management of server-side and cloud-native applications.
------------------------------------------------------------------------------------------------
Eclipse Enterprise for Java(EE4J)は、標準API、それらのAPIの実装、およびサーバー側およびクラウドネイティブアプリケーションの開発、展開、管理を可能にするJavaランタイム用の技術互換性キットを作成するためのオープンソースイニシアチブです。

[EE4J (Eclipse Enterprise for Java)] (https://projects.eclipse.org/projects/ee4j)は、Eclipse Foundationのトップレベルプロジェクトで、プロジェクト管理委員会(PMC)がJakarta EEの仕様、API、TCK、およびリファレンス実装の開発・管理を行います。

Jakarta EE

Jakarta EEは、[Jakarta EE Working Group] (https://www.eclipse.org/org/workinggroups/jakarta_ee_charter.php) (2018年4月設立)が管理するプラットフォームです。
Java EEがOracleからEclipse Foundationへ移管されることが決まったのが2017年9月、それから約2年後の2019年9月に最初のリリースである[Jakarta EE 8] (https://projects.eclipse.org/jakartaee/releases/8)がリリースされました(機能的にはJava EE 8と同じです)。

2018年2月26日にJava EEに代わる名称がJakarta EEに決まったと[アナウンス] (https://eclipse-foundation.blog/2018/02/26/and-the-name-is/)がありましたが、そのときに下記の名称も新しくなると告知されました。

  • Java EE → Jakarta EE
  • Glassfish → Eclipse Glassfish
  • Java Community Process (JCP) → Jakarta EE Working Group (Jakarta EE)
  • Oracle development management → EE4J, PMC

Eclipse GlassFish

[Eclipse GlassFish] (https://projects.eclipse.org/projects/ee4j.glassfish) 5.1.0は、Java EE 8互換と認定されたアプリケーションサーバで、2019年1月にリリースされました。
このバージョンは、GlassFishがOracleからEclipse Foundationへ移管されてからリリースされた最初のバージョンです(ただしAPIや実装は、[Oracle GlassFish Open source Edition 5.0.1] (https://javaee.github.io/glassfish/)と基本的に変わりません)。
次のバージョン 5.2.0は、Jakarta EE 8互換と認定されるということです。

なお、Jakarta EE 8互換と認定されたアプリケーションサーバに[Payara® Server] (https://www.payara.fish/software/payara-server/)もあります。

Eclipse GlassFishのインストールと起動

ここからGlassFishサーバのインストールから起動までの一連の手順を説明します。

※この記事では特に断りのない場合『Eclipse GlassFish』を『GlassFish』と記載します。

ダウンロード

2020年1月現在、Full Profile (Full Platform)とそのサブセットであるWeb Profileがダウンロードページよりダウンロードできます。
この2つのProfileの違いは[Java EE Web Profile vs Java EE Full Platform] (https://stackoverflow.com/questions/24239978/java-ee-web-profile-vs-java-ee-full-platform)と、[Java™ EE 8 のテクノロジー] (https://www.oracle.com/technetwork/jp/java/javaee/tech/index.html)、[Jakarta EE Web Profile 8] (https://jakarta.ee/specifications/webprofile/8/)などで確認できます。

この記事ではWeb Profile版を使用します。

補足:Web Profileのコンポーネント

Component Version JSR Compatible Implementations
[Servlet] (https://jakarta.ee/specifications/servlet/4.0/) 4.0 JSR-369 Eclipse GlassFish 5.1
[Server Pages (JSP)] (https://jakarta.ee/specifications/pages/2.3/) 2.3 JSR-245 Eclipse GlassFish 5.1
[Expression Language (EL)] (https://jakarta.ee/specifications/expression-language/3.0/) 3.0 JSR-341 EE4J Implementation of Expression Language 3.0.3
[Debugging Support for Other Languages] (https://jakarta.ee/specifications/debugging/1.0/) 1.0 JSR-45 Eclipse GlassFish 5.1
[Standard Tag Library (JSTL)] (https://jakarta.ee/specifications/tags/1.2/) 1.2 JSR-52 Eclipse GlassFish 5.1
[Server Faces (JSF)] (https://jakarta.ee/specifications/faces/2.3/) 2.3 JSR-372 Mojarra 2.3.13
[RESTful Web Services (JAX-RS)] (https://jakarta.ee/specifications/restful-ws/2.1/) 2.1 JSR-370 Eclipse Jersey 2.28
[WebSocket] (https://jakarta.ee/specifications/websocket/1.1/) 1.1 JSR-356 Eclipse GlassFish 5.1
[JSON Processing (JSON-P)] (https://jakarta.ee/specifications/jsonp/1.1/) 1.1 JSR-374 Eclipse JSON Processing 1.1.5
[JSON Binding (JSON-B)] (https://jakarta.ee/specifications/jsonb/1.0/) 1.0 JSR-367 Eclipse Yasson 1.0.3
[Annotations] (https://jakarta.ee/specifications/annotations/1.3/) 1.3 JSR-250 Jakarta Annotations
[Enterprise Beans (EJB)] (https://jakarta.ee/specifications/enterprise-beans/3.2/) 3.2 Lite JSR-345 Eclipse Glassfish 5.1
[Transactions (JTA)] (https://jakarta.ee/specifications/transactions/1.3/) 1.3 JSR-907 Eclipse GlassFish 5.1.0
[Persistence (JPA)] (https://jakarta.ee/specifications/persistence/2.2/) 2.2 JSR-338 EclipseLink 2.7.4
[Bean Validation] (https://jakarta.ee/specifications/bean-validation/2.0/) 2.0 JSR-380 Hibernate Validator 6.0.17
[Managed Beans] (https://jakarta.ee/specifications/managedbeans/1.0/) 1.0 Eclipse Glassfish 5.1
[Interceptors] (https://jakarta.ee/specifications/interceptors/1.2/) 1.2 JSR-318 Eclipse GlassFish 5.1
[Contexts and Dependency Injection] (https://jakarta.ee/specifications/cdi/2.0/) 2.0 JSR-365 Weld 3.1.1.Final
[Dependency Injection] (https://jakarta.ee/specifications/dependency-injection/1.0/) 1.0 JSR-330 Weld 2.3.8.Final/Weld 3.1.1.Final
[Security] (https://jakarta.ee/specifications/security/1.0/) 1.0 JSR-375 Eclipse Soteria 1.0.1
[Authentication] (https://jakarta.ee/specifications/authentication/1.1/) 1.1 JSR-196 Eclipse GlassFish 5.1

インストール

インストールはダウンロードしたアーカイブファイルを展開するだけです。
この記事ではインストールディレクトリを下記にしました。以降はこのディレクトリを%GLASSFISH_HOME%と表記します。

%GLASSFISH_HOME%
D:\dev\glassfish-web-5.1.0

環境変数pathにbinディレクトリを追加します。

%GLASSFISH_HOME%\bin

JDKについて

現時点ではJDK 9以降のバージョンでは正常に動作しないためJDK 1.8を利用します。なお、環境変数JAVA_HOMEがJDK 1.8を指していれば下記の対応は不要です。

%GLASSFISH_HOME%\glassfish\config\asenv.batの最後に下記を追加します。

set AS_JAVA=C:\Program Files\Java\jdk1.8.0_231

起動と停止

起動と停止は%GLASSFISH_HOME%\binディレクトリにあるasadminコマンド (asadmin.bat)で行います。

起動

起動時に<ドメイン名>を指定しますが、作成されているドメインが1つだけの場合はドメイン名を省略できます。
インストール直後はデフォルトでdomain1というドメインしか登録されていないので省略できます(ドメインについては後述します)。

> asadmin start-domain <ドメイン名>

下記はサーバ起動時のログです。起動に成功すると管理コンソールとサンプルのアプリケーションにアクセスできるようになります。Admin Port: 4848と表示されているように管理コンソールはポート4848でアクセスできます。

> asadmin start-domain
Waiting for domain1 to start ...............................
Successfully started the domain : domain1
domain  Location: D:\dev\glassfish-web-5.1.0\glassfish\domains\domain1
Log File: D:\dev\glassfish-5.1.0\glassfish\domains\domain1\logs\server.log
Admin Port: 4848
Command start-domain executed successfully.

管理コンソール

GlassFishサーバの管理タスクを実行するブラウザベースのツールです。(管理コンソールの使い方はその都度説明します。)

http://localhost:4848

Web Profile版
gg2.png
参考: Full Profile版 (管理できるリソースの種類が増えています)
gg0.png

サンプルのアプリケーション

以下のURLにアクセスするとサンプルアプリケーション(Welcomeページのようなもの)にアクセスできます。
このアプリケーションは8080(HTTP)、8081(HTTPS)の2つのポートをリッスンしています。

http://localhost:8080

gg1.png

停止

停止、再起動もドメイン名の指定は起動時と同じルールです。

> asadmin stop-domain <ドメイン名>

再起動

> asadmin restart-domain <ドメイン名>

以上がインストールから起動までの説明になります。

管理ツール

前述の『起動と停止』の項目で触れた通りGlassFishサーバの管理タスクを実行するツールに、CLIのasadminコマンドとGUIの管理コンソールがあります。どちらもほぼ同じ管理タスクを実行できます。

asadminコマンド

SYNOPSIS
> asadmin [asadmin options] [subcommand [options] [operands]] 

asadmin options

long short default
--host -H localhost
--port -p 4848
--user -u
--passwordfile -W
--terse -t false
--secure -s false
--echo -e false
--interactive -I run from a console window = true / run without a console window = false
--detach false
--help -?

サブコマンド

start-domainstop-domainなどをサブコマンドと呼びます。サブコマンドの使い方は--helpオプションで確認できます。
たとえばstart-domainサブコマンドの使い方は下記の方法で調べられます。

> asadmin start-domain --help

ローカル/リモート

サブコマンドにはローカルサブコマンドとリモートサブコマンドがあります。その違いは以下に引用した通りです。

ローカルサブコマンド

A local subcommand can be run without a running domain administration server (DAS).
However, to run the subcommand and have access to the installation directory and the domain directory, the user must be logged in to the machine that hosts the domain.
---------------------------------------------------------------------------------------------
ローカルサブコマンドは、実行中のドメイン管理サーバー(DAS)なしで実行できます。
ただし、サブコマンドを実行してインストールディレクトリとドメインディレクトリにアクセスするには、ドメインをホストするマシンにログインする必要があります。

リモートサブコマンド

A remote subcommand is always run by connecting to a DAS and running the subcommand there.
A running DAS is required.
---------------------------------------------------------------------------------------------
リモートサブコマンドは、DASに接続し、そこでサブコマンドを実行することにより常に実行されます。
実行中のDASが必要です。

マルチコマンドモード

asadminコマンドにはマルチコマンドモードもあります。サブコマンドを指定せずにasadminとだけ実行するとマルチコマンドモードに入ります。

> asadmin
Use "exit" to exit and "help" for online help.
asadmin>

管理者ユーザのパスワードを設定

デフォルトの管理者ユーザはadminです。パスワードが設定されていないので管理コンソールには誰でもアクセスできますが、パスワードを設定することでログイン認証を行うことができます。

管理コンソールで設定

Configurationsserver-configSecurityRealmsadmin-realmManage Usersをクリックします。
au1.png

User IDのadminをクリックします。
au2.png

新しいパスワードを入力してSAVEをクリックします。
au3.png

asadminコマンドで設定

change-admin-password
> asadmin change-admin-password
Enter admin user name [default: admin]> (enter)
Enter the admin password> (enter)
Enter the new admin password> ********* (new password)
Enter the new admin password again> ********* (new password)
Command change-admin-password executed successfully.

パスワードを設定すると未ログインで管理コンソールへアクセスするとログイン画面が表示されます。
a1.png

またasadminコマンドの実行時にも同様にログインが求められます。

> asadmin list-commands
Enter admin user name>  admin
Enter admin password for user "admin"> **********

一度loginサブコマンドでログインを行えば、以降はログインは不要になります。

> asadmin login
Enter admin user name [Enter to accept default]> admin
Enter admin password> **********
Login information relevant to admin user name [admin] for host [localhost] and admin port [4848] stored at [C:\Users\USERNAME\.gfclient\pass] successfully.
Make sure that this file remains protected. Information stored in this file will be used by administration commands to manage associated domain.
Command login executed successfully.

ドメイン

ドメインとは

GlassFishサーバインスタンスやそのコンフィグレーション、各種リソース、ライブラリ、アプリケーションのデプロイ領域のまとまりをドメインと呼びます。
インストールした1つのGlassFishサーバで複数のドメインを管理することができます。ドメインは互いに独立しているので、あるドメインの変更が他のドメインに影響を与えることはありません。

なお、README.txtに記載されている通りGlassFishサーバのインストール直後はデフォルトでdomain1というドメインが作成されています。

The default domain called 'domain1' is installed and preconfigured.
--------------------------------------------------------------------------
「domain1」というデフォルトのドメインがインストールされ、事前設定されています。

ドメインの作成

create-domainサブコマンドで新しいドメインを作成できます。この例ではdomain2というドメインを作成します。
なお、開発用途であれば新しくドメインを作成する必要はありません。

> asadmin create-domain --adminport 4849 domain2
Enter admin user name [Enter to accept default "admin" / no password]>
Using port 4849 for Admin.
Default port 8080 for HTTP Instance is in use. Using 51549
Using default port 7676 for JMS.
Default port 3700 for IIOP is in use. Using 51550
Default port 8181 for HTTP_SSL is in use. Using 51551
Using default port 3820 for IIOP_SSL.
Using default port 3920 for IIOP_MUTUALAUTH.
Default port 8686 for JMX_ADMIN is in use. Using 51552
Using default port 6666 for OSGI_SHELL.
Using default port 9009 for JAVA_DEBUGGER.
Distinguished Name of the self-signed X.509 Server Certificate is:
[CN=home-dev-pc.mshome.net,OU=GlassFish,O=Oracle Corporation,L=Santa Clara,ST=California,C=US]
Distinguished Name of the self-signed X.509 Server Certificate is:
[CN=home-dev-pc.mshome.net-instance,OU=GlassFish,O=Oracle Corporation,L=Santa Clara,ST=California,C=US]
Domain domain2 created.
Domain domain2 admin port is 4849.
Domain domain2 allows admin login as user "admin" with no password.
Command create-domain executed successfully.

ドメインの一覧はlist-domainsサブコマンドで確認できます。この通り作成直後のドメインは起動していません。

> asadmin list-domains
domain1 running
domain2 not running
Command list-domains executed successfully.

ドメイン名を指定してdomain2を起動します。

> asadmin start-domain domain2
Waiting for domain2 to start .......................
Successfully started the domain : domain2
domain  Location: D:\dev\glassfish-web-5.1.0\glassfish\domains\domain2
Log File: D:\dev\glassfish-web-5.1.0\glassfish\domains\domain2\logs\server.log
Admin Port: 4849
Command start-domain executed successfully.

jpsで確認するとドメイン毎にJVMプロセスが起動していることがわかります。

> jps -l
17056 jdk.jcmd/sun.tools.jps.Jps
15112 com.sun.enterprise.glassfish.bootstrap.ASMain
5208 com.sun.enterprise.glassfish.bootstrap.ASMain

ドメイン管理サーバ (DAS)

1つのドメインにデフォルトでserverという名前のGlassFishサーバインスタンスが作成・実行されています。このサーバインスタンスでドメインの管理を行うので、ドメイン管理サーバ (DAS / Domain Administration Server)と呼びます。
下図はサーバインスタンスの情報を確認するページです。このページ上部のボタンでインスタンスの停止や再起動、ログの確認が行えます。
das.png
下線を引いたConfiguration:のserver-configとは、このサーバインスタンスのコンフィグレーションで、下図のような設定項目があります。ちなみにdefault-configという名前のConfigurationはデフォルト設定値(テンプレート)で、新しくConfigurationを作成するときのコピー元として使えます。
sc.png

仮想サーバ

A virtual server, sometimes called a virtual host, is an object that allows the same physical server to host multiple Internet domain names.
----------------------------------------------------------------------------
仮想サーバーは、仮想ホストとも呼ばれ、同じ物理サーバーが複数のインターネットドメイン名をホストできるようにするオブジェクトです。

GlassFishサーバインスタンス内に、さらに仮想サーバを作成することができます。
デフォルトでは__asadminserverという名前の仮想サーバが作成されていて、管理コンソールは__asadminで実行されています。
serverは開発環境でアプリケーションの開発、テストで必要となる仮想サーバです。
vs.png

仮想サーバの作成

Configurationsserver-configVirtual ServersNew...をクリックします。
vs0.png
Idにserver2と入力し、その他の項目はこのままでOKをクリックして登録します。
vs1.png
下図は登録後です。
vs2.png
次にNetwork Listenerを追加します。
Configurationsserver-configNetwork ConfigNetwork ListenersNew...をクリックします。
nl0.png
NameとProtocolにhttp-listener-3と入力、Default Virtual Serverに先ほど登録したserver2を選択します。
Port、Address、Thread Poolは図の通りとしてOKをクリックして登録します。
nl1.png
下図は登録後です。
nl2.png
登録した仮想サーバserver2の編集画面を開き、下線を引いた項目を編集して更新します。
vs3.png

ドキュメントルート(Docroot)に指定したD:\var\www\server2にindex.htmlを作成し、下記のアドレスにアクセスすると作成したページが表示されます。

http://localhost:8082

list-virtual-serversサブコマンドで仮想サーバの一覧を取得することができます。

> asadmin list-virtual-servers
server
__asadmin
server2
Command list-virtual-servers executed successfully.

デフォルトではポート4848のドメインの結果を返すので、他のドメインの仮想サーバを調べたいときは下記のように--portオプションでポートを指定します。

> asadmin --port 4849 list-virtual-servers
server
__asadmin
Command list-virtual-servers executed successfully.

現時点での状態

この時点でのドメインと仮想サーバは下記の通りです。

ドメイン

> asadmin list-domains -l
DOMAIN   ADMIN_HOST  ADMIN_PORT  RUNNING  RESTART_REQUIRED
domain1  localhost   4848        true     false
domain2  localhost   4849        true     false
Command list-domains executed successfully.

仮想サーバ

> asadmin list-virtual-servers
server
__asadmin
server2
Command list-virtual-servers executed successfully.

> asadmin --port 4849 list-virtual-servers
server
__asadmin
Command list-virtual-servers executed successfully.

リソースの管理

GlassFishサーバが管理するリソースにJDBC Resourceがあります。

JDBC Resourceの登録

JDBC Resourceを登録するには、Database Vendorが提供するJDBC Driverの登録と、JDBC Connection Poolの登録を行う必要があります。
以下は、MySQL Database用のJDBC Resourceの登録手順になります。

MySQL Connector/Jのダウンロードと配置

[MySQL Community Downloads] (https://dev.mysql.com/downloads/connector/j/)よりMySQL Connector/Jのアーカイブファイルをダウンロードします。2020年1月現在の最新バージョン8.0.18をダウンロードし、適当なディレクトリへ展開しておきます。
jarファイルを登録するには、エクスプローラなどで直接コピーする方法とadd-libraryサブコマンドを使う方法があります。

直接コピーする場合は、サーバーを停止した状態でmysql-connector-java-8.0.18.jar%GLASSFISH_HOME%\glassfish\domains\domain1\libにコピーします。
(なお、%GLASSFISH_HOME%\glassfish\libにコピーすると全ドメインで有効になります。)

add-libraryサブコマンドを使う場合はサーバーを停止する必要はありませんが、再起動が必要です。

> asadmin add-library .\mysql-connector-java-8.0.18.jar
Command add-library executed successfully.

list-librariesサブコマンドで登録したライブラリを確認できます。

> asadmin list-libraries
mysql-connector-java-8.0.18.jar
Command list-libraries executed successfully.

Databaseの準備

データベースにはMySQL 8.0.17を利用します。動作検証用に下記のデータベース、ユーザ、テーブルを作成します。

CREATE DATABASE IF NOT EXISTS test_db
  CHARACTER SET = utf8mb4
  COLLATE = utf8mb4_general_ci;

CREATE USER IF NOT EXISTS 'test_user'@'localhost'
  IDENTIFIED BY 'test_user'
  PASSWORD EXPIRE NEVER;

GRANT ALL ON test_db.* TO 'test_user'@'localhost';
CREATE TABLE IF NOT EXISTS memo (
  id BIGINT AUTO_INCREMENT,
  title VARCHAR(255) NOT NULL,
  description TEXT NOT NULL,
  done BOOLEAN DEFAULT FALSE NOT NULL,
  updated TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL,
  PRIMARY KEY (id)
) CHARACTER SET = utf8mb4, COLLATE utf8mb4_general_ci;

JDBC Connection Poolsの登録

ResourcesJDBCJDBC Connection PoolsNew...をクリックします。
jp1.png
Pool NameにMySQLConnPoolと入力、Resource TypeとDatabase Driver Vendorは図の通り選択します。
この例ではResource Typeにjavax.sql.DataSourceを選択しましたが、java.sql.Driverを選択することもできます。
jp2.png
Additional Propertiesは図の通り入力、Finishをクリックして登録します。なお黄色の下線のDatasource classnameはこの時点では変えられないので登録後に編集します。
jp3.png

Name Value
user test_user
password test_user
databaseName test_db
serverName localhost
portNumber 3306
sslMode disabled
allowPublicKeyRetrieval true
serverTimezone Asia/Tokyo

下図は登録後です。
jp4.png
編集画面を開きDatasource Classnameにcom.mysql.cj.jdbc.MysqlDataSourceと入力します。利用するJDBC Driverがのバージョンが8.0以降の場合はこのクラス名を指定する必要があるためです。
jp5.png
次にAdvancedタブを開き図のConnection validationのRequiredにチェックを入れ、Table Nameにdualと入力してSaveをクリックして登録します。
jp6.png
Generalタブを開き図のPingをクリックして通信できるか確認します。Ping Succeededと表示されれば成功です。
jp7.png

JDBC Resourceの登録

ResourcesJDBCJDBC ResourcesNew...をクリックします。
jr1.png
JNDI Nameにjdbc/MySQLConnPoolと入力、Pool Nameに先ほど登録したMySQLConnPoolを選択しOKをクリックして登録します。
jr2.png
下図は登録後です。
jr3.png

補足:Resource Typeにjavax.sql.Driverを選ぶ場合

Pool NameにMySQLConnPool2と入力、Resource TypeとDatabase Driver Vendorは図の通り選択します。
jp8.png
黄色の下線のDriver Classnameはこの時点では変えられないので登録後に編集します。
jp9.png
プロパティを更新します。この画面でプロパティを追加しようとするとエラーが起きて正常に登録できないので、図の3項目だけ登録します。残りは登録後に追加します。
jp10.png

Name Value
URL jdbc:mysql://localhost:3306/test_db
user test_user
password test_user
sslMode DISABLED
allowPublicKeyRetrieval true
serverTimezone Asia/Tokyo

登録後に再度編集画面を開き、Driver Classsnameをcom.mysql.cj.jdbc.Driverで更新します。
jp11.png
残りのプロパティを追加します。
jp12.png

以上でJDBC Resourceの登録は完了です。

アプリケーションの開発とデプロイ

IDEにIntelliJ IDEAを利用します。
JetBrainsの公式ヘルプページ[Java EEアプリケーションの開発] (https://pleiades.io/help/idea/creating-and-running-your-first-java-ee-application.html)を参考にしました。

Application Serverの登録

アイコンをクリックしてメニューを表示、その中からGlassfish Serverを選択します。
ap1.png
GlassFishのインストールディレクトリを指定します。
ap2.png
OKをクリックして登録します。
ap3.png

プロジェクトの作成

左側の選択項目からJava Enterpriseを選択、右側のProject SDK、Java EE version、Application Serverはそれぞれ図の通り選択します。
ip00.png
その下のAdditional Library And Frameworks:で、使用するLibraryとFrameworkにチェックを入れます。下図の下線を引いたものを使用しますが、黄色の下線を引いたJSON Processingだけは、この場でチェックを入れずにプロジェクト作成後に別途追加します。
ip1.png

Web Application
web_application.png

web/WEB-INF/web.xml
External_Libraries[GlassFish5.1.0]
jakarta.ejb-api.jar
jakarta.servlet.jsp-api.jar
jakarta.servlet-api.jar

Bean Validation
bean_validation.png

External_Libraries[GlassFish5.1.0_Bean_Validation]
bean-validator.jar
bean-validator-cdi.jar

CDI
cdi.png

web/WEB-INF/beans.xml
External_Libraries[GlassFish5.1.0_CDI]
cdi-api.jar
cdi-api-fragment.jar
weld-integration.jar
weld-integration-fragment.jar
weld-osgi-bundle.jar

Hiberante
hibernate.png

Configure...をクリック、Downloading Optionsjavax.persistence-api-2.2.jarのチェックを外します。
hibernate_2.png
これはJavaEE Persistencejavax.persistence.jarが組み込まれるためです。

Library
lib/antlr-2.7.7.jar
lib/byte-buddy-1.10.2.jar
lib/classmate-1.5.1.jar
lib/dom4j-2.1.1.jar
lib/FastInfoset-1.2.15.jar
lib/hibernate-commons-annotations-5.1.0.Final.jar
lib/hibernate-core-5.4.9.Final.jar
lib/istack-commons-runtime-3.0.7.jar
lib/jandex-2.1.1.Final.jar
lib/javassist-3.24.0-GA.jar
lib/javax.activation-api-1.2.0.jar
lib/jaxb-api-2.3.1.jar
lib/jaxb-runtime-2.3.1.jar
lib/jboss-logging-3.3.2.Final.jar
lib/jboss-transaction-api_1.2_spec-1.1.1.Final.jar
lib/stax-ex-1.8.jar
lib/txw2-2.3.1.jar

JavaEE Persistence
ProviderにHibernateを選択します。
jpa.png

src/META-INF/persistence.xml
Library
lib/javax.persistence.jar

JSON Binding
json_b.png

Library
lib/javax.json.bind-api.jar

JSON Processing
ここでは組み込まず、プロジェクト作成後に追加します。
json_p.png

プロジェクト作成後の設定

プロジェクト作成直後のディレクトリです。

<PROJECT_ROOT>
  |
  +--- /lib
  |      |
  |      +--- antlr-2.7.7.jar
  |      +--- byte-buddy-1.10.2.jar
  |      +--- classmate-1.5.1.jar
  |      +--- dom4j-2.1.1.jar
  |      +--- FastInfoset-1.2.15.jar
  |      +--- hibernate-commons-annotations-5.1.0.Final.jar
  |      +--- hibernate-core-5.4.9.Final.jar
  |      +--- istack-commons-runtime-3.0.7.jar
  |      +--- jandex-2.1.1.Final.jar
  |      +--- javassist-3.24.0-GA.jar
  |      +--- javax.activation-api-1.2.0.jar
  |      +--- javax.json.bind-api.jar
  |      +--- javax.persistence.jar
  |      +--- jaxb-api-2.3.1.jar
  |      +--- jaxb-runtime-2.3.1.jar
  |      +--- jboss-logging-3.3.2.Final.jar
  |      +--- jboss-transaction-api_1.2_spec-1.1.1.Final.jar
  |      +--- stax-ex-1.8.jar
  |      +--- txw2-2.3.1.jar
  |
  +--- /src
  |      |
  |      +--- /META-INF
  |             |
  |             +--- persistence.xml
  |
  +--- /web
         |
         +--- /WEB-INF
         |      |
         |      +--- beans.xml
         |      |
         |      +--- web.xml
         |
         +--- index.jsp

<EXTERNAL LIBRARIES>
  |
  +--- GlassFish 5.1.0
  |     |
  |     +--- jakarta.ejb-api.jar
  |     +--- jakarta.servlet.jsp-api.jar
  |     +--- jakarta.servlet-api.jar
  |
  +--- GlassFish 5.1.0 - Bean Validation
  |     |
  |     +--- bean-validator.jar
  |     +--- bean-validator-cdi.jar
  |
  +--- GlassFish 5.1.0 - CDI : Contextes and Dependency Inejction
        |
        +--- cdi-api.jar
        +--- cdi-api-fragment.jar
        +--- weld-integration.jar
        +--- weld-integration-fragment.jar
        +--- weld-osgi-bundle.jar

パッケージの作成

srcディレクトリにcom.example.demoというパッケージを作成、さらにその下にcontroller、dto、ejb、entity、serviceパッケージを作成します。

/src
  |
  +--- /com
         |
         +--- /example
                |
                +--- /demo
                       |
                       +--- /controller
                       |
                       +--- /dto
                       |
                       +--- /ejb
                       |
                       +--- /entity
                       |
                       +--- /service

index.jspの修正とJSTLの追加

下図のようにjspでJSTLを使おうとするとtaglibのuriが解決できないというエラーが表示されます。
jstl0.png

index.jsp
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
    <title>Welcome</title>
</head>
<body>
    <div>
        <h1>Welcome</h1>
        <div>
            <ul>
                <li><a href="${pageContext.request.contextPath}/memo">memo</a></li>
            </ul>
        </div>
    </div>
</body>
</html>

JSPでjstlを利用できるようにライブラリを追加します。メニューバー → FileProject StructureProject SettingsLibrariesアイコン → From Maven...をクリックします。
jstl1.png
検索欄にjavax.servlet:jstlと入力して虫眼鏡アイコンをクリックして検索実行 → 検索結果からjavax.servlet:jstl:1.2をクリック → OKボタンをクリックしてライブラリに追加します。
jstl2.png

JSON Processing

jakarta.json-apiの追加

JSON Processingが利用できるようにライブラリを追加します。メニューバー → Project StructureProject SettingsLibrariesアイコン → From Maven...をクリックします。
json-api1.png
検索欄にjakarta.json:jakarta.json-apiと入力して虫眼鏡アイコンをクリックして検索実行 → 検査結果からjakarta.json:jakarta.json-api:1.1.5をクリック → OKボタンをクリックしてライブラリに追加します。
json-api2.png

サンプルコード

package com.example.demo.dto;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;

public class Phone {
  private String home;
  private String mobile;

  public Phone() {
  }

  public Phone(String home, String mobile) {
    this.home = home;
    this.mobile = mobile;
  }

  public String getHome() {
    return home;
  }

  public void setHome(String home) {
    this.home = home;
  }

  public String getMobile() {
    return mobile;
  }

  public void setMobile(String mobile) {
    this.mobile = mobile;
  }

  public JsonObject toJsonObject() {
    JsonObjectBuilder builder = Json.createObjectBuilder();
    if (home != null) {
      builder.add("home", home);
    }
    if (mobile != null) {
      builder.add("mobile", mobile);
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "Phone{" +
        "home='" + home + '\'' +
        ", mobile='" + mobile + '\'' +
        '}';
  }

}
package com.example.demo.dto;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.bind.annotation.JsonbProperty;

public class Person {
  private String name;
  private String gender;
  private Integer age;
  @JsonbProperty("phones")
  private Phone phone;

  public Person() {
  }

  public Person(String name, String gender, Integer age, Phone phone) {
    this.name = name;
    this.gender = gender;
    this.age = age;
    this.phone = phone;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getGender() {
    return gender;
  }

  public void setGender(String gender) {
    this.gender = gender;
  }

  public Integer getAge() {
    return age;
  }

  public void setAge(Integer age) {
    this.age = age;
  }

  public Phone getPhone() {
    return phone;
  }

  public void setPhone(Phone phone) {
    this.phone = phone;
  }

  public JsonObject toJsonObject() {
    JsonObjectBuilder builder = Json.createObjectBuilder();
    builder
        .add("name", name)
        .add("gender", gender)
        .add("age", age);
    if (phone != null) {
      builder.add("phones", phone.toJsonObject());
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "Person{" +
        "name='" + name + '\'' +
        ", gender='" + gender + '\'' +
        ", age=" + age +
        ", phone=" + phone +
        '}';
  }

}
package com.example.demo.ejb;

import com.example.demo.dto.Person;

import javax.ejb.Stateless;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonPointer;
import javax.json.JsonValue;
import javax.json.stream.JsonCollectors;
import java.util.List;

@Stateless
public class PersonProcessor {

  public JsonArray toJsonArray(List<Person> persons) {
    return persons.stream()
        .map(Person::toJsonObject)
        .collect(JsonCollectors.toJsonArray());
  }

  public JsonArray replace(JsonArray jsonArray, String pointer, String replaceValue) {
    JsonPointer p = Json.createPointer(pointer);
    JsonArray newArray = p.replace(jsonArray, Json.createValue(replaceValue));
    return newArray;
  }

  public JsonValue pickup(JsonArray jsonArray, String pointer) {
    JsonPointer p = Json.createPointer(pointer);
    if (p.containsValue(jsonArray)) {
      JsonValue value = p.getValue(jsonArray);
      return value;
    }
    return JsonValue.NULL;
  }

}

JSON Binding

サンプルコード

package com.example.demo.service;

import com.example.demo.dto.Person;

import javax.enterprise.context.Dependent;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;

@Dependent
public class PersonService {

  public List<Person> toList(String json) {
    Jsonb jsonb = JsonbBuilder.create();
    List<Person> persons = jsonb.fromJson(json, new ArrayList<Person>(){}.getClass().getGenericSuperclass());
    return persons;
  }

  public List<Person> toList(Reader reader) {
    Jsonb jsonb = JsonbBuilder.create();
    List<Person> persons = jsonb.fromJson(reader, new ArrayList<Person>(){}.getClass().getGenericSuperclass());
    return persons;
  }

}
package com.example.demo.controller;

import com.example.demo.dto.Person;
import com.example.demo.service.PersonService;
import com.example.demo.ejb.PersonProcessor;

import javax.inject.Inject;
import javax.json.JsonArray;
import javax.json.JsonValue;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;

@WebServlet(urlPatterns = {"/person"})
public class PersonController extends HttpServlet {
  private static final Logger log = Logger.getLogger(PersonController.class.getName());

  @Inject
  private PersonService personService;
  @Inject
  private PersonProcessor personProcessor;

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    req.getRequestDispatcher("/WEB-INF/views/person/list.jsp").forward(req, resp);
  }

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    List<Person> persons;
    try (BufferedReader reader = req.getReader()) {
      // JSON Binding
      persons = personService.toList(reader);
      req.setAttribute("persons", persons);
    }

    // JSON Processing
    JsonArray jsonArray = personProcessor.toJsonArray(persons);
    JsonValue mobile = personProcessor.pickup(jsonArray, "/0/phones/mobile");
    JsonValue home = personProcessor.pickup(jsonArray, "/1/phones/home");
    req.setAttribute("mobile", mobile.toString());
    req.setAttribute("home", home.toString());

    req.getRequestDispatcher("/WEB-INF/views/person/list.jsp").forward(req, resp);
  }

}

Inject

javax.injectの追加

@Inectアノテーションが利用できるようにライブラリを追加します。メニューバー → Project StructureProject SettingsLibrariesアイコン → From Maven...をクリックします。
ij0.png
検索欄にjavax.inject:javax.injectと入力して虫眼鏡アイコンをクリックして検索実行 → 検査結果からjavax.inject:javax.inject:1をクリック → OKボタンをクリックしてライブラリに追加します。
ij1.png

web.xmlの修正とエラーページの追加

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
    <error-page>
        <error-code>404</error-code>
        <location>/WEB-INF/views/errors/404.jsp</location>
    </error-page>
</web-app>

HTTPステータスコード404のときのエラーページを下記の場所に作成します。

/web
  |
  +--- /WEB-INF
         |
         +--- /views
                |
                +--- /errors
                       |
                       +--- 404.jsp
404.jsp
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head>
    <title>404 Not Found</title>
</head>
<body>
    <div>
        <h1>404 Not Found</h1>
    </div>
</body>
</html>

静的ファイルの追加

静的ファイルはwebディレクトリの直下に配置します。

/web
  |
  +--- /public
         |
         +--- /css
                |
                +--- base.css

inex.jspのheadタグ内に下記の行を追加します。

index.jsp
<link rel="stylesheet" href="public/css/base.css">

アプリケーションの動作確認

この時点の状態でアプリケーションが起動するか確認します。
画面右上のRun GlassFish 5.1.0アイコンをクリックしてGlassFishサーバを起動します。
run.png

GlassFishサーバが起動すると自動的にブラウザが立ち上がり指定(デフォルトではトップページ、この例ではindex.jsp)のページが表示されます。

Application Serverのコンフィグレーション

URLで表示するページのURLを指定します。また任意のドメインや仮想サーバでアプリケーションを起動したい場合はServer DomainVirtual Serverで指定します。
run_config.png

warファイルのビルド

ビルドの設定は初回だけです。

ビルドの設定

メニューバーのFileProject StructureProject SettingsArtifactsアイコン → Web Application: ArchiveFor 'demo-javaee8:war explodedをクリックします。
bw1.png
画面右側Output Layoutdemo-javaee8_war.warをクリックすると画面下にCreate Manifest...ボタンが表示されるのでクリックします。
bw2.png
Manifestファイルの場所を/web/META-INF/MANIFEST.MFに指定し、OKボタンをクリックして画面を閉じます。
bw3.png

ビルドの実行

メニューバーのBuildBuild Artifacts...をクリックします。
bw4.png
demo-javaee8:warBuildをクリックするとビルドが始まります。
bw5.png
下図の場所にwarファイルが出力されます。
bw6.png

warファイルをデプロイする

IDE上で実行するGlassFishサーバではなく、ローカル上で実行中のGlassFishサーバへwarファイルをデプロイします。
管理コンソールにもデプロイ機能がありますが2020年1月現在、デプロイしようとするとエラーが発生してデプロイできないため(Issue : [Can´t deploy applications from Web Console [deploy origPath is NULL]] (https://github.com/eclipse-ee4j/glassfish/issues/22739))、asadminコマンドでデプロイします。
なお、IntellJ IDEA上からもデプロイすることができますが、この記事では割愛します。

> asadmin deploy --contextroot=demo-javaee8 --name=demo-javaee8 /path/to/demo-javaee8_war.war
Application deployed with name demo-javaee8.
Command deploy executed successfully.

デプロイ時にドメインと仮想サーバを指定しなかった場合、ポート4848で実行中のドメインの仮想サーバすべてにアプリケーションがデプロイされます。
この記事の例でいえばdomain1で仮想サーバserverserver2を実行しているのでこの2つにデプロイされます。

> asadmin list-virtual-servers
server
__asadmin
server2
Command list-virtual-servers executed successfully.

デプロイ後に下記のURLにアクセスすると同じアプリケーションが実行されていることが分かります。

# server
http://localhost:8080/demo-javaee8/
# server2
http://localhost:8082/demo-javaee8/

ポート4849で実行中のdomain2に対してデプロイする場合は--portを指定します。

> asadmin --port 4849 deploy --contextroot=demo-javaee8 --name=demo-javaee8 /path/to/demo-javaee8_war.war

管理コンソールでデプロイされているアプリケーションを確認する

管理コンソールでデプロイされているアプリケーションを確認することができます。
この画面でUndeploy、Enable/Disableなどの操作が行えます。DisableにしたアプリケーションにアクセスするとHTTPステータスコード404が返ります。
app.png

アプリケーションをアンデプロイする

アンデプロイは管理コンソールおよびasadminコマンドから行えます。アンデプロイではアプリケーション名を指定します。またデプロイ時と同様に、ポートと仮想サーバを指定しなければ仮想サーバにデプロイされているすべてのアプリケーションをアンデプロイします。

> asadmin undeploy demo-javaee8
Command undeploy executed successfully.

ポート4849で実行中のdomain2に対するアンデプロイは--portを指定します。

> asadmin --port 4849 undeploy demo-javaee8
Command undeploy executed successfully.

データベースアクセス処理の実装

次にJPAを使ったデータベースアクセス処理を実装します。

persistence.xmlの修正

jta-data-sourceには管理コンソールで登録したJDBC Resourceのjdbd/MySQLConnPoolを指定します。

persistence.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
             version="2.2">

    <persistence-unit name="default" transaction-type="JTA">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <jta-data-source>jdbc/MySQLConnPool</jta-data-source>
        <properties>
            <property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.SunOneJtaPlatform" />
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL8Dialect"/>
        </properties>
    </persistence-unit>
</persistence>

Entity

package com.example.demo.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.LocalDateTime;

@Table(name = "memo")
@Entity
public class Memo {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  @Column(name = "title", nullable = false)
  private String title;
  @Column(name = "description", nullable = false)
  private String description;
  @Column(name = "done", nullable = false)
  private Boolean done;
  @Column(name = "updated", nullable = false)
  private LocalDateTime updated;

  // ...アクセサ省略...

}

図の通りテーブル名やカラム名に赤い波線が表示されます。
entity_warning.png

アプリケーションのビルドや実行には影響はありませんが、これを解決する場合はメニューバー → ViewTool WindowsDatabaseを開きDataSourceを追加します。
Database Tool Windowのアイコン → Data SourceMySQLを選択します。
database_tool_window.png
Databaseの接続に必要な項目を入力したら、Test Connectionボタンをクリックして確認します。
database_tool_window_2.png
メニューバー → ViewTool WindowsPersistencedemo-javaee8を右クリック → Assign Data Sources... → 上記で追加したData Sourceを紐づけます。
database_tool_window_3.png

Service

package com.example.demo.service;

import com.example.demo.entity.Memo;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.transaction.Transactional;
import java.util.List;
import java.util.logging.Logger;

@Transactional
@Dependent
public class MemoService {
  private final static Logger log = Logger.getLogger(MemoService.class.getName());

  @Inject
  private EntityManager em;

  @PostConstruct
  public void init() {
    System.out.println("MemoService#init");
  }

  @PreDestroy
  public void destroy() {
    System.out.println("MemoService#destroy");
  }

  public Memo findById(Long id) {
    log.info("MemoService#findById(" + id + ")");
    TypedQuery<Memo> query = em.createQuery("SELECT m FROM Memo m WHERE m.id = :id", Memo.class)
        .setParameter("id", id);
    Memo memo = query.getSingleResult();
    return memo;
  }

  public List<Memo> findAll() {
    log.info("MemoService#findAll");
    TypedQuery<Memo> query = em.createQuery("SELECT m FROM Memo m", Memo.class);
    List<Memo> list = query.getResultList();
    return list;
  }

  public void save(Memo memo) {
    log.info("MemoService#save(" + memo.toString() + ")");
    em.persist(memo);
    em.flush();
   }

}

EntityManagerを@InjectアノテーションでインジェクトできるようにResourceクラスを作成します。

package com.example.demo;

import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;

public class Resource {
  @PersistenceUnit(unitName = "default")
  private EntityManagerFactory emf;

  @Produces
  public EntityManager getEntityManager() {
    return emf.createEntityManager();
  }

  public void closeEntityManager(@Disposes EntityManager em) {
    em.close();
  }
}

Servlet

package com.example.demo.controller;

import com.example.demo.service.MemoService;
import com.example.demo.entity.Memo;

import javax.inject.Inject;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.logging.Logger;

@WebServlet(urlPatterns = {"/memo"})
public class MemoController extends HttpServlet {
  private static final Logger log = Logger.getLogger(MemoController.class.getName());

  @Inject
  private MemoService memoService;

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    List<Memo> memos = memoService.findAll();
    req.setAttribute("memos", memos);
    req.getRequestDispatcher("/WEB-INF/views/memo/list.jsp").forward(req, resp);
  }

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    try (BufferedReader reader = req.getReader()) {
      Memo memo = toMemo(reader);
      memoService.save(memo);
      req.setAttribute("memo", memo);
    }
    req.getRequestDispatcher("/WEB-INF/views/memo/list.jsp").forward(req, resp);
  }

  private static final JsonbConfig config = new JsonbConfig()
      .withEncoding("UTF-8")
      .withNullValues(Boolean.FALSE)
      .withDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());

  private Memo toMemo(BufferedReader reader) {
    Jsonb jsonb = JsonbBuilder.create(config);
    Memo memo = jsonb.fromJson(reader, Memo.class);
    return memo;
  }


JSP

/web
  |
  +--- /WEB-INF
         |
         +--- /views
                |
                +--- /memo
                       |
                       +--- list.jsp
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>メモ一覧</title>
    <link rel="stylesheet" href="public/css/base.css">
</head>
<body>
    <div>
        <h1>メモ一覧</h1>
        <div>
            <table>
                <thead>
                <tr>
                    <th>No</th>
                    <th>ID</th>
                    <th>タイトル</th>
                    <th>説明</th>
                    <th>完了</th>
                    <th>更新日</th>
                </tr>
                </thead>
                <tbody>
                <c:forEach items="${memos}" var="memo" varStatus="index">
                    <tr>
                        <td>${index}</td>
                        <td>${memo.id}</td>
                        <td>${memo.title}</td>
                        <td>${memo.description}</td>
                        <td>${memo.done}</td>
                        <td>${memo.updated}</td>
                    </tr>
                </c:forEach>
                </tbody>
            </table>
        </div>
    </div>
</body>
</html>

補足

[Specifications] (https://jakarta.ee/specifications/)

Servlet

Server Pages (JSP)

Expression Language (EL)

[Jakarta Expression Language 3.0] (https://jakarta.ee/specifications/expression-language/3.0/)

  • Maven
    • jakarta.el : jakarta.el-api : 3.0.3

Debugging Support for Other Languages

Standard Tag Library (JSTL)

Server Faces (JSF)

RESTful Web Services (JAX-RS)

WebSocket

JSON Processing (JSON-P)

JSON Binding (JSON-B)

Annotations

Enterprise Beans (EJB)

Transactions (JTA)

Persistence (JPA)

Bean Validation

Managed Beans

Interceptors

Contexts and Dependency Injection

Dependency Injection

Security

Authentication

8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?