ウェブページの一部を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のサンプルですが、以下の書籍を参考にさせてもらいました。