1
0

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 1 year has passed since last update.

「はじめての Django アプリ作成」をSpring WebFluxでやってみた、その 2

Last updated at Posted at 2022-02-21

はじめに

この記事はDjangoの公式チュートリアル「はじめての Django アプリ作成」と同じものをSpring WebFluxで作成することでDjangoとSpring WebFluxの違いを理解することを目的としています。

違い一覧

DjangoとSpringで類似している要素を纏めてみました。

Django Spring 備考
Model(モデル) Model(モデル)
Template(テンプレート) View(ビュー)
View(ビュー) Controller(コントローラ)
SQLite3 H2 Database 基本のデータベース
Django Admin 無し CMS支援

はじめてのアプリ作成、その 2

Database の設定

Django

mysite/settings.pyENGINEdjango.db.backends.sqlite3と定義されている通り、デフォルトでSQLite3を使います。データはdb.sqlite3というファイルに保存されます。

mysite/settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

Spring Webflux

SpringではH2 DatabaseというJavaプラットフォーム上で動作するRDBSをDjangoのsqlite3のような感じで使うみたいです。一般的にDBへの連結はJPA(Java Persistence API)というモジュールが役を担います。
しかし、JPAはRDBSの性質上ブロッキングIO方式なのでリアクティブなWebFluxの長所を生かしきれません。そこで登場するのがR2DBC(Reactive Relational Database Connectivity)です。まだ新しいプロジェクトですがリアクティブ向けのRDBSサポートを目標としています。
H2 DatabaseR2DBCと使うためには下記のようにbuild.gradleに依存関係を記述します。

build.gradle
...

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
	implementation 'org.springframework.boot:spring-boot-starter-webflux'
	runtimeOnly 'com.h2database:h2'
    runtimeOnly 'io.r2dbc:r2dbc-h2'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'io.projectreactor:reactor-test'
}

...

H2データベースは基本的にインメモリーDBとして動作しますが、設定でデータをファイルとして永続化できます。今回は設定なしでインメモリーDBとして使用します。

モデルの作成

Django

Djangoではアプリのmodels.pymodels.Modelのサブクラスを定義しmigrateすることでデータモデルを作成することができます。

1. models.pyにモデルを定義する。

polls/models.py
from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

2. (もし追加していなかったら)mysite/settings.pyにアプリを登録する。

mysite/settings.py
...

INSTALLED_APPS = [
    'polls.apps.PollsConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

...

3. モデルの変更を読み取り差分をマイグレーションファイルにする

python manage.py makemigrations polls

4. DBに反映する

python manage.py migrate

Spring Webflux

現在のR2DBCには自動的にスキーマを定義してくれる機能はなく、データモデルのクラスとスキーマ両方を定義する必要があります。

1. スキーマファイルを作る

データベーススキーマを初期化するためsrc/main/resources/schema.sqlにスキーマを定義します。spring-bootはresourcesの最上段に位置するschema.sqldata.sqlファイルを発見し自動に初期化します1

src/main/resources/schema.sql
BEGIN;
--
-- Create model Question
--
CREATE TABLE question (
    id serial NOT NULL PRIMARY KEY,
    question_text varchar(200) NOT NULL,
    pub_date timestamp with time zone NOT NULL
);
--
-- Create model Choice
--
CREATE TABLE choice (
    id serial NOT NULL PRIMARY KEY,
    choice_text varchar(200) NOT NULL,
    votes integer NOT NULL,
    question_id integer NOT NULL
);
--
-- Add constraint
--
ALTER TABLE choice
  ADD CONSTRAINT choice_question_id_c5b4b260_fk_polls_question_id
    FOREIGN KEY (question_id)
    REFERENCES question (id);

CREATE INDEX choice_question_id_c5b4b260 ON choice (question_id);

COMMIT;

今回はDjangoのSQLをそのまま流用します。

2. エンティティを定義する

Djangoプロジェクトと一貫性を取るためsrc/main/java/com/example/mysite/pollsの下にmodelsというディレクトリーを作り、QuestionChoiceのエンティティ定義します。

polls/models/Question.java
package com.example.mysite.polls.models;

import java.time.ZonedDateTime;
import org.springframework.data.annotation.Id;

public class Question {

    @Id
    private Long id;

    private final String question_text;

    private final ZonedDateTime pub_date;

    public Question(String question_text, ZonedDateTime pub_date) {
        this.question_text = question_text;
        this.pub_date = pub_date;
    }

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getQuestionText() {
        return this.question_text;
    }

    public ZonedDateTime getPubDate() {
        return this.pub_date;
    }

    @Override
    public String toString() {
        return String.format(
            "Question[id=%d, question_text='%s', pub_date='%s']",
            id, question_text, pub_date);
    }
}
polls/models/Choice.java
package com.example.mysite.polls.models;

import org.springframework.data.annotation.Id;

public class Choice {

    @Id
    private Long id;

    private final String choice_text;

    private final Integer votes;

    private final Long question_id;

    public Choice(String choice_text, Integer votes, Long question_id) {
        this.choice_text = choice_text;
        this.votes = votes;
        this.question_id = question_id;
    }

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getChoiceText() {
        return this.choice_text;
    }

    public Integer getVotes() {
        return this.votes;
    }

    public Long getQuestionId() {
        return this.question_id;
    }

    @Override
    public String toString() {
        return String.format(
            "Choice[id=%d, choice_text='%s', votes='%s', question_id='%s']",
            id, choice_text, votes, question_id);
    }
}

3. エンティティを操作する

R2DBCではSpring Dataの抽象化であるReactiveCrudRepositoryを使うかR2DBCのR2dbcEntityTemplateを使うことができます2。下記の例はR2dbcEntityTemplateを使った場合です。

MysiteApplication.java
package com.example.mysite;

import com.example.mysite.polls.models.Question;
import io.r2dbc.spi.ConnectionFactory;
import java.time.ZonedDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.r2dbc.connectionfactory.init.ConnectionFactoryInitializer;
import org.springframework.data.r2dbc.connectionfactory.init.ResourceDatabasePopulator;
import reactor.core.publisher.Mono;


@SpringBootApplication
public class MysiteApplication {

	private static final Logger log = LoggerFactory.getLogger(MysiteApplication.class);

	public static void main(String[] args) {
		SpringApplication.run(MysiteApplication.class, args);
	}

    @Bean
    public CommandLineRunner demo(ConnectionFactory connectionFactory) {

        return (args) -> {
        	R2dbcEntityTemplate template = new R2dbcEntityTemplate(connectionFactory);

            log.info("--------------1-----------------");

		    Mono<Question> saved = template.insert(Question.class)
		      						.using(new Question("What's new?", ZonedDateTime.now()));

		    saved.subscribe(obj -> log.info(obj.toString()));	 

			log.info("--------------2-----------------");

		    template.select(Question.class)
		      .first()
		      .doOnNext(it -> log.info(it.toString()))
		      .subscribe();

            log.info("--------------3-----------------");
        };
    }
}

./gradlew bootrunで実行すると以下のようなログが出力されます。

output
INFO 82641 --- [           main] com.example.mysite.MysiteApplication     : Starting MysiteApplication using Java 17.0.2
INFO 82641 --- [           main] com.example.mysite.MysiteApplication     : No active profile set, falling back to default profiles: default
INFO 82641 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode.
INFO 82641 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 5 ms. Found 0 R2DBC repository interfaces.
INFO 82641 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port 8080
INFO 82641 --- [           main] com.example.mysite.MysiteApplication     : Started MysiteApplication in 2.0 seconds (JVM running for 2.324)
INFO 82641 --- [           main] com.example.mysite.MysiteApplication     : --------------1-----------------
INFO 82641 --- [           main] com.example.mysite.MysiteApplication     : Question[id=1, question_text='What's new?', pub_date='2022-02-22T01:20:24.411035+09:00[Asia/Tokyo]']
INFO 82641 --- [           main] com.example.mysite.MysiteApplication     : --------------2-----------------
INFO 82641 --- [           main] com.example.mysite.MysiteApplication     : Question[id=1, question_text='What's new?', pub_date='2022-02-22T01:20:24.411035+09:00']
INFO 82641 --- [           main] com.example.mysite.MysiteApplication     : --------------3-----------------

Adminページ

DjangoではCMSに該当するDjango Adminというモデル管理インタフェースを提供します。しかし、これは一般的な仕様ではないのでSpringでは自分で一からAdminページ構成しなければいけません。

  1. Initialize a Database Using Basic SQL Scripts, https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-initialization.using-basic-sql-scripts

  2. R2DBC support, https://docs.spring.io/spring-data/r2dbc/docs/current/reference/html/#r2dbc.core

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?