先日、Java&ScalaのフレームワークであるPlay Framework(以下Play)を使って掲示板サイトを作ったので、それの備忘録を残したいと思います。
この記事ではPlayFrameworkの公式とgithubにある公式サンプルを多数引用します。
PC: Ubuntu20.04
java: 11.0.11
sbt: 1.4.9
Play: 2.8.8
Play! Play Framework
Playとは、MVCでWebサイトを構築するフレームワークです。
MVCとは、Model、View、Controllerの頭文字を取ったものです。
- Model: データの管理、取得などを担当
- View: データの表示を担当
- Controller: ModelとViewの制御を担当
Javaの導入
Playをインストールするには、まずJavaが必要です。
ubuntuであればこちらの記事を参考にしましょう
Javaをインストールする最も簡単なオプションは、Ubuntuに含まれているバージョンを使用することです。デフォルトでは、Ubuntu 20.04には、JREとJDKのオープンソースバージョンであるOpen JDK 11が含まれています。
sudo apt update
java -version
sudo apt install default-jdk
sbtの導入
sbtとは Java版のnpm,composer,pipのようなものです。 Scalaのビルドツールです
sbt is a build tool for Scala, Java, and more. It requires Java 1.6 or later.
こちらの記事を参考に導入しましょう。
Ubuntuであれば、書かれている通りに行えば導入できます。
Ubuntu 及びその他の Debian ベースのディストリビューションは DEB フォーマットを用いるが、 ローカルの DEB ファイルからソフトウェアをインストールすることは稀だ。 これらのディストロは通常コマンドラインや GUI 上から使えるパッケージ・マネージャがあって (例: apt-get、aptitude、Synaptic など)、インストールはそれらから行う。 ターミナル上から以下を実行すると sbt をインストールできる (superuser 権限を必要とするため、sudo を使っている)。
echo "deb https://repo.scala-sbt.org/scalasbt/debian all main" | sudo tee /etc/apt/sources.list.d/sbt.list
echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | sudo tee /etc/apt/sources.list.d/sbt_old.list
curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | sudo apt-key add
sudo apt-get update
sudo apt-get install sbt
ほかの方は下記の記事を参考に頑張ってください!
Playの作成
こちらの記事を参考に、Java版のPlayを構築します。
sbt new playframework/play-java-seed.g8
Playのファイル構造はこのような形になっています。
app → Application sources
└ assets → Compiled asset sources
└ stylesheets → Typically LESS CSS sources
└ javascripts → Typically CoffeeScript sources
└ controllers → Application controllers
└ models → Application business layer
└ views → Templates
build.sbt → Application build script
conf → Configurations files and other non-compiled resources (on classpath)
└ application.conf → Main configuration file
└ routes → Routes definition
dist → Arbitrary files to be included in your projects distribution
public → Public assets
└ stylesheets → CSS files
└ javascripts → Javascript files
└ images → Image files
project → sbt configuration files
└ build.properties → Marker for sbt project
└ plugins.sbt → sbt plugins including the declaration for Play itself
lib → Unmanaged libraries dependencieswot.dannbo@gmail.com
logs → Logs folder
└ application.log → Default log file
target → Generated stuff
└ resolution-cache → Info about dependencies
└ scala-2.13
└ api → Generated API docs
└ classes → Compiled class files
└ routes → Sources generated from routes
└ twirl → Sources generated from templates
└ universal → Application packaging
└ web → Compiled web assets
test → source folder for unit or functional tests
引用
Welcom to Play!
さて、実際にPlayを動かしてみましょう。
Playのプロジェクトを開いてsbt run
します。
cd Playのプロジェクトを作成したディレクトリ
sbt run
しばらく待ってlocalhost:9000をブラウザで開くとPlayのページが開きます。
これで導入成功です!
もろもろのライブラリの導入
今回使うライブラリを導入していきます
- ebean
- evolutions
- guice
ebeanとは、Javaで使えるORマッパーです。
O/Rマッピングとは、オブジェクト指向プログラミング言語におけるオブジェクトとリレーショナルデータベース(RDB)の間でデータ形式の相互変換を行うこと。そのための機能やソフトウェアを「O/Rマッパー」(O/R mapper)という。
evolutionはマイグレーションに使えるライブラリです。
データベースに初回に登録しておきたいデータや、データを移行させたい時にsqlのスクリプトを定義しておき、それが初回起動時に実行されます。
移動、移住、移転」を意味する英語の「migration」が語源。IT分野では、ソフトウェアやハードウェア、システム、データ、開発言語などを別のプラットフォームに移行したり、新しいシステムに切り替えたりすることを意味する。似た言葉に「リプレース」があるが、これは古くなったり破損したりしたハードウェアやソフトウェア、システムなどを、新しいものや同等の機能を持ったものに置き換えることを指す。
guiceはテストで使うライブラリです。アプリケーションに依存性を注入する際などに使用します。
これらを導入するには、sbtのbuildファイルを編集します。
name := """sample"""
organization := "com.example"
version := "1.0-SNAPSHOT"
lazy val root = (project in file(".")).enablePlugins(PlayJava, PlayEbean)
scalaVersion := "2.13.5"
libraryDependencies ++= Seq(
guice,
jdbc,
evolutions,
"javax.xml.bind" % "jaxb-api" % "2.3.1",
"javax.activation" % "activation" % "1.1.1",
"org.glassfish.jaxb" % "jaxb-runtime" % "2.3.2",
"com.h2database" % "h2" % "1.4.192",
)
// The Play plugin
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.8")
addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "5.0.2")
// Defines scaffolding (found under .g8 folder)
// http://www.foundweekends.org/giter8/scaffolding.html
// sbt "g8Scaffold form"
参考にしたコード
build.sbt
plugins.sbt
また、ついでにPlayの設定ファイルも書き換えましょう
# This is the main configuration file for the application.
# https://www.playframework.com/documentation/latest/ConfigFile
# Default database configuration using H2 database engine in an in-memory mode
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play;"
ebean.default="models.*"
play.evolutions.enabled=true
参考
こちらではインメモリーデータベースであるh2の設定とebeanの設定とevolutionの設定を行っています。
Evolutionのスクリプトの作成
# --- First database schema
# --- !Ups
create table user (
id int auto_increment primary key,
name varchar(255) not null,
text varchar(500) not null,
);
insert into user (id,name,text) values (default, '名無し', 'サンプルテキストです。');
insert into user (id,name,text) values (default, '名無し', 'サンプルテキスト2です。');
# --- !Downs
drop table users;
今回作ったのは最低限の設定です。
Userテーブルを一つ作り、id、name、textが入っています。
evolutionのファイルスクリプトの置き場は
conf/evolutions/default/1.sql
になります。フォルダとファイルを作成しましょう。
参考
これでライブラリが使用出来るようになります。
Modelの作成
アプリケーションで使うModelを作成しましょう。
appの中にmodelsディレクトリを作成し、その中に作成していきます。
2つのModelを作っていきます
User.javaがデータベースのデータを参照できます。
今回はModelでデータベースに接続するので、User.javaにebeanのFinderを使用してデータベースの値を取得するメソッドを作りました。
また、Playのバリデーションライブラリを使用して、値を必須にしました。
package models;
import io.ebean.Model;
import io.ebean.Finder;
import play.data.validation.Constraints;
import javax.persistence.Id;
import javax.persistence.Entity;
@Entity
public class User extends Model {
public static Finder<Long,User> finder = new Finder<>(User.class);
@Id
private Long id;
@Constraints.Required
private String name;
@Constraints.Required
private String text;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
参考
UserForm.javaはFormで送れるデータを定義しています
こちらもバリデーションで値を必須、また値を255に制限しています。
idは、sqlの設定でdefaultの値が入るようになっていますので、今回のケースでは必要無いです。
package models;
import play.data.validation.Constraints;
public class UserForm {
@Constraints.Required(message="必須入力です")
@Constraints.MaxLength(value=255, message="255文字以下にしてください。")
private String name;
@Constraints.Required(message="必須入力です")
@Constraints.MaxLength(value=255, message="255文字以下にしてください。")
private String text;
public UserForm() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
参考
Controllerの作成
次にコントローラーを作成します。
行っていることは、FormFactoryでFormを作り、showFormdでViewへ渡しています。
View側でFormがcreateメソッドに送られた時には、Userクラスを用いてデータを定義し、Ebaenでデータベースにデータを保存しています。
package controllers;
import models.User;
import models.UserForm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.data.Form;
import play.data.FormFactory;
import play.i18n.MessagesApi;
import play.mvc.*;
import io.ebean.*;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.List;
@Singleton
public class FormController extends Controller {
private final Form<UserForm> form;
private MessagesApi messagesApi;
private List<User> users;
private final Logger logger = LoggerFactory.getLogger(getClass());
@Inject
public FormController(FormFactory formFactory, MessagesApi messagesApi) {
this.users = User.finder.all();
this.messagesApi = messagesApi;
this.form = formFactory.form(UserForm.class);
}
public Result showForm(Http.Request request) {
this.users = User.finder.all();
return ok(views.html.board.render(users, form, request, messagesApi.preferred(request)));
}
public Result create(Http.Request request) {
final Form<UserForm> boundForm = boardCreateForm.bindFromRequest(request);
if (boundForm.hasErrors()) {
logger.error("errors = {}", boundForm.errors());
return badRequest(views.html.board.render(users, boundForm, request, messagesApi.preferred(request)));
} else {
UserForm data = boundForm.get();
User addUser = new User();
addUser.setName(data.getName());
addUser.setText(data.getText());
Ebean.save(addUser);
return redirect(routes.FormController.showForm()).flashing("info", "書き込みました");
}
}
}
Viewの作成
Viewでは、Scalaの記法を使って、Controllerから送られてくるデータを定義できます。
@
を使うことで、ScalaでifやforなどをHTML内に書くことができます。
formの表示には@helper
を使います。
@import play.mvc.Http.Request
@(users: List[User], form: Form[UserForm])(implicit request: Request, messages: play.i18n.Messages)
@main("掲示板") {
<h1>掲示板</h1>
@request.flash.asScala().data.map { case (name, value) =>
<div class="@name">@value</div>
}
<table>
@for(u <- users) {
<tr><td>@u.name</td><td>@u.text</td></tr>
}
</table>
<hr/>
@if(form.hasGlobalErrors) {
@form.globalErrors.asScala.map { error: play.data.validation.ValidationError =>
<div>
@error.key: @error.message
</div>
}
}
@helper.form(routes.FormController.create) {
@helper.CSRF.formField
@helper.inputText(form("name"))
@helper.inputText(form("text"))
<button type="submit">投稿</button>
}
}
これで完成です!
localhost:9000を見てみると、FormとUserの名前とテキストが表示されていると思います。