本記事はJava, Play Framework(v2.8)で簡単なTodoアプリ作成を通して、Playを学んでいくという記事になります。
まず初めに環境構築を行っていきます。
jdkのイメージにsbtをインストールしたdockerファイルを作成します。
sbtはビルドツールになります。
FROM openjdk:8-jdk
ARG SBT_VERSION
# Envs
ENV SBT_VERSION ${SBT_VERSION:-1.3.7}
# Update
RUN apt-get update && \
apt-get upgrade -y
# Install sbt
RUN curl -L -o sbt-$SBT_VERSION.deb https://dl.bintray.com/sbt/debian/sbt-$SBT_VERSION.deb && \
dpkg -i sbt-$SBT_VERSION.deb && \
rm sbt-$SBT_VERSION.deb && \
apt-get update && \
apt-get install sbt
COPY ./ /server
WORKDIR /server
RUN sbt about
上のdockerイメージをビルドしていきます。
$ mkdir backend
$ cd backend
$ ls
Dockerfile
$ docker build -t some-image-name .
ビルドが終わったらコンテナ内で作業していきます。
$ docker run -it -p 9000:9000 -v `pwd`/:/server sweeep-timestamp_play /bin/bash
Playプロジェクトを作成します。 途中でプロジェクト名を聞かれますので、今回はappを入力しています。
$ sbt new playframework/play-java-seed.g8
プロジェクトの作成が終わったら以下のコマンドを入力して、 ブラウザで確認してみます。
$ cd app
$ sbt run
次にホストの設定とDB接続のための設定を行います。
app/conf/applicaiton.confに以下を追加します。 本来ならdb設定を環境変数に、ホスト設定もしっかりやるべきですが、今回は割愛します。
# This is the main configuration file for the application.
# https://www.playframework.com/documentation/latest/ConfigFile
db {
default.driver = com.mysql.jdbc.Driver
default.url = "jdbc:mysql://db:3306/java-play-sample"
default.username = todo_user
default.password = password
default.logSql = true
default.jndiName = DefaultDS
}
jpa.default=defaultPersistenceUnit
play.filters {
# Allowed hosts filter configuration
hosts {
allowed = ["."]
}
}
基本的にはチュートリアルと同じですが、 今回はdockerに接続するのでurlのhostはlocalhostではなくdocker-composeに記載するサービス名であることに注意が必要です。
default.url = "jdbc:mysql://db:3306/java-play-sample"
依存関係をbuild.sbtに追記していきます。 ormにはHibernate、APIはJPAを使っていきます。
name := """app"""
organization := "com.example"
version := "1.0-SNAPSHOT"
lazy val root = (project in file(".")).enablePlugins(PlayJava)
scalaVersion := "2.13.6"
libraryDependencies ++= Seq(
guice,
javaJpa,
"mysql" % "mysql-connector-java" % "8.0.16",
"org.hibernate" % "hibernate-core" % "5.4.30.Final"
)
Hibernateを使うので
conf/META-INF/persistence.xml
を作成する必要があります。
<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_1.xsd"
version="2.1">
<persistence-unit name="defaultPersistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<non-jta-data-source>DefaultDS</non-jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>
次にMySQLの環境を作っていきます。
初めにDockerファイルを作ります。
FROM mysql:5.7
次にmysql.confを作ります。
[mysqld]
character-set-server=utf8
default-storage-engine=INNODB
explicit-defaults-for-timestamp=true
[mysqldump]
default-character-set=utf8
[mysql]
default-character-set=utf8
[client]
default-character-set=utf8
次にMySQLの環境変数を書いていきます。
$ pwd
~/FastAPI-example-pro/db
$ touch .env
$ vi .env
MYSQL_DATABASE=fastapi-example
MYSQL_USER=todo_user
MYSQL_ROOT_PASSWORD=password
MYSQL_PASSWORD=password
最後にdocker-composeに作成していきます。
version: '3.7'
services:
api:
build:
context: ./backend/
dockerfile: ./Dockerfile
command: >
bash -c 'cd app &&
sbt run'
volumes:
- ./backend:/server
ports:
- 9000:9000
depends_on:
- db
db:
build:
context: ./db/
dockerfile: ./Dockerfile
env_file:
- ./db/.env
ports:
- 3306:3306
volumes:
- ./db/conf.d:/etc/mysql/conf.d
まだbackend側からdbにアクセスする処理は書いていないのですが、 一旦docker-compose up で起動してエラーがないことを確認していきます。
$ docker-compose up
以下のようなメッセージが最後の方に出たら成功です。
db_1 | 2021-05-22T08:53:02.789762Z 0 [Note] mysqld: ready for connections.
db_1 | Version: '5.7.34' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)
以上で環境構築は終了です。
次はアプリを作成していきます。
sbt new
で生成されたapp配下に追加していきます。
今回作成するのはDO, DAO, Service, Controllerになります。
本来はDOを直接渡さずに間にDTOを挟むことが多いですが、今回は割愛します。
以下実装していきます。
app/domain/TodoDO.java
package domain;
import javax.persistence.*;
@Entity
@Table(name = "todo")
public class TodoDO {
@Id
@Column
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@Column
private String title;
@Column
private String text;
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return this.title;
}
public void setTitle(String title) {
this.title = title;
}
public String getText() {
return this.text;
}
public void setText(String text) {
this.text = text;
}
}
app/dao/TodoDao.java
package dao;
import java.util.List;
import com.google.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import play.db.jpa.JPAApi;
import domain.TodoDO;
public class TodoDao {
private static final String ENTITY_MANAGER_NAME = "default";
@Inject
protected JPAApi jpaApi;
public TodoDO create(TodoDO todoDO) {
jpaApi.withTransaction(entityManager -> { entityManager.persist(todoDO); });
return todoDO;
}
public TodoDO find(Integer id) {
return jpaApi.em(ENTITY_MANAGER_NAME).find(TodoDO.class, id);
}
public List<TodoDO> findAll(int limit) {
System.out.println("start findAll");
System.out.println("initialize EntityManager");
EntityManager entityManager = jpaApi.em(ENTITY_MANAGER_NAME);
System.out.println(" EntityManager initialized");
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<TodoDO> criteriaQuery = criteriaBuilder.createQuery(TodoDO.class);
Root<TodoDO> root = criteriaQuery.from(TodoDO.class);
criteriaQuery.select(root);
return entityManager.createQuery(criteriaQuery).setMaxResults(limit).getResultList();
}
public void delete(int id) {
jpaApi.withTransaction(entityManager -> {
TodoDO todoDO = entityManager.find(TodoDO.class, id);
if (todoDO != null) {
entityManager.remove(todoDO);
}
});
}
public TodoDO update(TodoDO todoDO) {
jpaApi.withTransaction(entityManager -> {entityManager.merge(todoDO);});
return todoDO;
}
}
app/services/TodoService.java
package services;
import java.util.List;
import java.util.stream.Collectors;
import com.google.inject.Inject;
import dao.TodoDao;
import domain.TodoDO;
public class TodoService {
@Inject
private TodoDao todoDao;
public List<TodoDO> getAll() {
List<TodoDO> todoDOList = todoDao.findAll(100);
return todoDOList;
}
public TodoDO getById(int id) {
TodoDO todoDO = todoDao.find(id);
if (todoDO == null) {
return null;
}
return todoDO;
}
public TodoDO create(TodoDO todoDO) {
todoDO = todoDao.create(todoDO);
return todoDO;
}
public TodoDO update(TodoDO todoDO, int id) {
TodoDO fromDb = todoDao.find(id);
if (fromDb == null) {
return null;
}
fromDb.setTitle(todoDO.getTitle());
fromDb.setText(todoDO.getText());
fromDb = todoDao.update(fromDb);
return fromDb;
}
public void delete(int id) {
todoDao.delete(id);
}
}
app/controllers/TodoController.java
package controllers;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.inject.Inject;
import play.libs.Json;
import play.mvc.Controller;
import play.mvc.Http;
import play.mvc.Result;
import domain.TodoDO;
import services.TodoService;
public class TodosController extends Controller {
@Inject
private TodoService todoService;
public Result get(Integer id) {
TodoDO todo = todoService.getById(id);
if (todo != null) {
return ok(Json.toJson(todo));
}
return notFound();
}
public Result getAll() {
return ok(Json.toJson(todoService.getAll()));
}
public Result create(Http.Request request) {
JsonNode jsonData = request.body().asJson();
try {
TodoDO todoDO = Json.fromJson(jsonData, TodoDO.class);
todoDO = todoService.create(todoDO);
return ok(Json.toJson(todoDO));
} catch (RuntimeException e) {
// Most probably invalid todo data
return badRequest(request.body().asJson());
}
}
public Result update(Http.Request request, Integer id) {
JsonNode jsonData = request.body().asJson();
try {
TodoDO todoDO = Json.fromJson(jsonData, TodoDO.class);
TodoDO todo = todoService.update(todoDO, id);
if (todo != null) {
return ok(Json.toJson(todo));
}
return notFound();
} catch (RuntimeException e) {
return badRequest(request.body().asJson());
}
}
public Result delete(Integer id) {
todoService.delete(id);
return ok();
}
}
DBのテーブルを作成します。
$ docker-compose up -d
$ mysql -u todo_user -p -h localhost -P 3306 --protocol=tcp
(password)
mysql> use java-play-sample;
mysql> CREATE TABLE IF NOT EXISTS todo(
-> id INT NOT NULL AUTO_INCREMENT,
-> title VARCHAR(255) not null,
-> text TEXT not null,
-> primary key(id)
-> );
以上で完了です。
確認して見ましょう。 今回もPythonインタプリタで確認していきます。
import requests
url = 'http://localhost:8000/todos'
# create
params = {'title': 'test title1', 'text': 'test text1'}
res = requests.post(url, json=params) # キーワード引数のjsonに渡すこと
# update
params = {'title': 'test title1', 'text': 'updated1'}
res = requests.put(f'{url}/1', json=params)
# get(all)
res = requests.get(url)
# get(id=1)
res = requests.get(f'{url}/1')
# delete
res = requests.delete(f'{url}/1')