Ajax を使用した Spring Boot とのサンプルプログラム

ウェブページの一部をAjaxで非同期通信をして、JavaScriptで書き換えるサンプルプログラムを作成してみました。書き換えるデータは Spring Boot が稼働するサーバーから取得します。

画面の動きを示しておきます。初回アクセス時の画面です。

IDを “1” にすると「私の名前は太郎です。こんにちは」と表示されます。

このときJavaScriptが入力欄のイベントを検知して Spring Boot のアプリケーションと通信をしています。Spring Boot からのデータはJSON形式で受け取り、画面を部分的に書き換えます。

IDを “2” にすると「私の名前は花子です。はじめまして」と表示されます。

画面遷移しているわけではなく、部分的な書き換えです。

アプリケーションの構成を示しておきます。

Web ServerにあるHTMLファイルを取得したブラウザはJavaScriptを実行して Spring Boot と通信をします。ここでAjaxという技術が使われます。Spring Boot はデータベースからデータを取得してJSON形式でブラウザに返します。ブラウザは取得したデータを画面表示します。

論理的には上記の図になるのですが、実際の物理環境は1台のPC( ubuntu24.04 LTS )に、Nginx も Spring Boot も MySQL も全部載せています。

サンプルプログラムを示す前に、データベースに格納してあるデータを説明しておきます。PERSONテーブルからレコードを取得します。PERSONテーブルのcreate文です。


create table PERSON (
Id MEDIUMINT PRIMARY KEY AUTO_INCREMENT,
Name varchar(20) NOT NULL,
Memo text );

PERSONテーブルには3件のレコードが登録されています。

Spring Boot のサンプルプログラムの説明です。

Spring Boot のプロジェクトは Spring Initializr で生成します。Spring Initializr のURLは https://start.spring.io/ です。

Group、Artifact、Name は以下としました。

Group : com.example
Artifact : demo10
Name : demo10

ビルドは Gradle です。Dependencies には Spring Web、Spring Data JDBC、MySQL Driver を選択します。プロジェクトをダウンロードします。

今回の Spring Boot のサンプルプログラムでは、コントローラー、および、リポジトリを作成します。

コントローラー
JsonController.java

リポジトリ
PersonRepository.java
PersonRepositoryImpl.java

また、データベースへの接続情報を application.properties に追記をします。

プロジェクト内のファイルの構成は、このようになります。赤字が追加・修正をするファイルです。

application.properties の記載から説明します。

application.properties にはデータベースに接続する際のデータベース名、ユーザー名、パスワード、ドライバー名を記載します。以下が記載内容です。環境にあわせて接続情報は読み替えてください。


spring.application.name=demo10

# ===============================
# MySQL 接続設定
# ===============================
spring.datasource.url=jdbc:mysql://localhost/testdb
spring.datasource.username=testuser
spring.datasource.password=testuser
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

リポジトリの説明です。

リポジトリはインターフェースの PersonRepository.java と、それを実装した PersonRepositoryImpl.java を用意します。リポジトリのパッケージは別にしているため repository フォルダを作成して、その中にこれらのファイルを格納します。

PersonRepository.java のソースです(テキストファイルの画像です)。コピペ用のソースは最後に載せておきます。

searchById() というメソッドを定義しています。引数のidは画面から引き渡されるものです。

コントローラーがデータベースにアクセスする際にリポジトリを介してアクセスをするのですが、リポジトリをDIして使うようにしているためインターフェイスを用意しています。

PersonRepositoryImpl.java のソースです。PersonRepository の実装をします。

Repository アノテーションを付けています。これにより PersonRepositoryImpl がDIコンテナに格納されます。

データベースへのアクセスは Spring Data JDBC で行います。JdbcTemplate クラスをコンストラクタインジェクションでDIしています。検索は JdbcTemplate の queryForMap() メソッドを使うことにしました。queryForMap() はレコードがない場合は EmptyResultDataAccessException をスローします。データベースからの取得結果はMapに格納して、それをOptionalクラスでラップして呼び出し元に返しています。レコードがない場合は空のOptionalを返します。

コントローラーの説明です。

コントローラーは RestController です。リポジトリをコンストラクタインジェクションでDIしています。画面からのリクエストURLの一部にidを含ませて RequestMapping でメソッドに紐付けています。

CrossOrigin アノテーションを付けているのですが、これは Spring Boot が動くオリジンとJavaScriptが動くオリジンが異なる場合でもデータのやり取りができるようにするためです。

searchById() の戻り値 Optionalを確認して、中身があればそれを取り出しJSONの形式に変換して返します。中身がある場合のJSONデータは以下のような形式です。

{"name":"太郎", "memo":"こんにちは"}

中身がない(レコードがない)場合は、固定の文言をJSONに埋め込んで返しています。

{"name":"不明", "memo":"なし"}

なお、プロジェクトを生成したときに Demo10Application.java のソースコードが自動生成されていますが、こちらはそのまま(修正は不要)です。

Web Serverに配置するHTMLファイルの説明です。

ファイル名は ajax.html とします。Nginxの設定は割愛します。HTMLファイルの配置先は環境に合わせてください。

JavaScriptの showJsonText() という関数の中でAjaxの通信をしています。fetch() で Spring Boot のアプリケーションにアクセスをして結果を取得します。アクセスするURLには入力欄のidを付与しています。結果は response オブジェクトの json() で取り出します。textContent を使ってHTMLの一部を取り出したデータに書き換えています。

addEventListener を設定して、イベント検知した際に showJsonText() 関数を呼び出すようになっています。

それでは実行をします。Spring Boot の起動には gradlew を使用します。引数に bootRun を付与することで実行ができます。以下は Spring Boot を起動させたときのものです。

$ ./gradlew bootRun

Spring Boot を起動させたらHTMLファイルにアクセスをします。URLについてはNginxの設定によります。入力欄の数値を変えると画面要素が書き換えられます。なお Spring Boot が起動していない状態だとAjaxの通信が失敗するので、エラー表示となります。

ソースコードを載せておきます。

リポジトリ


package com.example.demo10.repository;

import java.util.Optional;
import java.util.Map;

public interface PersonRepository {

	Optional< Map<String, Object> > searchById( int id );

}

package com.example.demo10.repository;

import org.springframework.stereotype.Repository;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.dao.EmptyResultDataAccessException;
import java.util.Optional;
import java.util.Map;

@Repository
public class PersonRepositoryImpl implements PersonRepository {

	private final JdbcTemplate jdbcTemplate;

	public PersonRepositoryImpl( JdbcTemplate jdbcTemplate ) {
		this.jdbcTemplate = jdbcTemplate;
	}

	public Optional< Map<String, Object> > searchById( int id ) {

		String sql = "select Name, Memo from PERSON where Id = ?";

		try {
			Map<String, Object> map = jdbcTemplate.queryForMap( sql, id );
			return Optional.of( map );
		} catch ( EmptyResultDataAccessException e ) {
			return Optional.empty();
		}

	}
}

コントローラー


package com.example.demo10;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
import com.example.demo10.repository.PersonRepository;
import java.util.Optional;
import java.util.Map;

@RestController
public class JsonController {

	private final PersonRepository repository;

	public JsonController( PersonRepository repository ) {
		this.repository = repository;
	}

	@RequestMapping("/sample/{id}")
	@CrossOrigin
	public String createJson( @PathVariable int id ) {

		Optional< Map<String, Object> > opt = repository.searchById( id );
    
		if ( !opt.isEmpty() ) {    
			Map<String, Object> map = opt.get();
			return "{\"name\":\"" + (String)map.get("Name") + "\", \"memo\":\"" + (String)map.get("Memo") + "\"}";
		} else {
			return "{\"name\":\"不明\", \"memo\":\"なし\"}";
		}

	}
}

HTMLファイル


<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Ajax Demo</title>
</head>
<body>
<p>Ajaxのサンプルプログラム</p>
<div>
<label>ID:</label>
<input type="number" id="id1" size="10" />
</div>
<div id="result1"></div>

<script>
const numberBox = document.getElementById('id1');
const resultBox = document.getElementById('result1');

async function showJsonText() {
	resultBox.textContent = '';
	try {
		const response = await fetch('http://localhost:8080/sample/' + numberBox.value);
		if (!response.ok) {
			throw new Error(`HTTPエラー: ${res.status}`);
		}
		const json = await response.json();
		resultBox.textContent = '私の名前は' + json.name + 'です。' + json.memo;
	} catch (err) {
		resultBox.textContent = '通信エラーです。' + err.message;
	}
}

numberBox.addEventListener('change', showJsonText);
</script>

</body>
</html>

ここに記載したJavaScriptのサンプルですが、以下の書籍を参考にさせてもらいました。

さくらのレンタルサーバーでWordPressのデータベースをアップグレードしてみた。

WordPressの管理画面で、古いデータベースを使っているので改善したほうが良い旨のメッセージが出ていたため、データベースのバージョンをあげることにしました。WordPressはさくらのレンタルサーバーで稼働しているため、さくらのサーバー管理用のコンソール画面から作業をしました。

まずコンソール画面を見てみると、重要な設定が2件とあります。「詳細を見る」を確認しました。

非推奨のDBバージョンを使用しています、の旨のメッセージが出ているため「設定」をクリックします。

ここからデータベースのアップグレードができるようなので「アップグレード設定」をクリックします。

注意書きがあり、対象のデータベースサーバーが表示されています。「データベースのアップグレード」をクリックします。

アップグレードの設定画面になります。どうやらすぐにアップグレードが行われるわけではなくて、指定の時間になったらアップグレードが行われるようです。予定日時の設定のところに希望の日時を設定します。セレクトボックスのためどれかを選ぶことになります。アップグレード後のデータベース名は変更しません。「同意して予約する」をクリックします。

申請完了の画面になります。予約日時とステータスが表示されます。

なお、データベースのアップグレードの予約が完了するとメールが届きます。さくらのレンタルサーバーに登録されているメールアドレスです。

予約した時間になるとアップグレードが行われます。10分後ぐらいに、アップグレードが完了した旨のメールも届きます。

コンソール画面を改めて見てみると、新しいデータベースサーバーが増えています。

このあとWordPressの設定を変更する必要があり、データベースサーバーの名前はメモしておきます。新しいデータベースサーバー名は「mysql80.account.sakura.ne.jp」となります。

WordPressの設定を変更するにあたり、設定ファイルの場所を確認します。コンソール画面の左側のリストから「Webサイト/データ」 → 「インストール済み一覧」をたどります。インストール済みパッケージが表示されます。設定ファイル、および、インストール先パスを見ます。wp-config.php が編集する設定ファイルとなります。

コンソール画面の左側のリストから「Webサイト/データ」 → 「ファイルマネージャー」をたどります。ファイルマネージャーの画面から wp-config.php を選択して、右クリックで編集を選びます。

wp-config.php の編集画面になります。修正するところは、MySQLのホスト名のところです。先ほどメモした mysql80.account.sakura.ne.jp を入力します。今回のデータベースのアップグレードは、MySQL5.7から8.0への変更となります。この場合は、ホスト名の書き換えだけで良いそうです。アップグレード時にデータベース名を意図的に変更した場合は、データベース名の修正も必要になります。

修正ができたら右下の再読込をクリックします。保存のほうが適当だと思うのですが、なぜかわからないですけれど、保存が非活性になっていて押せません。再読込をクリックしましたが、反映はされているようでした。

閉じるボタンをクリックして、編集画面を閉じます。

WordPressの管理画面に戻って、設定の状況を確認してみました。以下はデータベースのアップグレードをする前に出ていた改善の項目です。「古いデータベースサーバー」の表示があります。

こちらがデータベースのアップグレード後の画面です。「古いデータベースサーバー」の表示は消えていました。

WordPressのサイトも問題なく表示されることも確認できました。

ちなみに「REST APIで予期しない結果が発生しました」および「永続オブジェクトキャッシュを使用してください」のメッセージが出ているので、調べてみました。

「REST APIで予期しない結果が発生しました」のほうですが、これはさくらのレンタルサーバーのWAF設定を有効にすると出てしまうものらしいです。無効にすれば解消するかもしれませんが、いったんそのままにしました。

「永続オブジェクトキャッシュを使用してください」のほうですが、こちらはプラグインを入れれば解消するものらしいです。大きな影響はないとのことで、こちらもそのままにしました。

なおデータベースのアップグレードは完了してサイトも問題なく表示されるのですが、さくらのコンソール画面には、重要な設定が2件の表示はそのままです。これは古いデータベースを削除しないと消えないのですかね?

最後に、PHPのバージョンアップについても書いておきます。PHPのバージョンアップはデータベースのアップグレードより簡単で、コンソール画面からPHPのバージョンを選ぶだけです。こちらはすぐに反映されるようです。