Spring Boot 2実践入門:簡単なWEBアプリを一から作成チュートリアル
Spring Frameworkを聞いたことがないWEBシステム開発エンジニアはいないと思いますが、一から作る場合、どこから着手するのかわからない方が多いかもしれません。なぜならば、参加したシステム開発は大体フレームワークが既に構築されて、業務ロジックを設計、実装すれば順調に進められます。本記事はSpring Boot2を利用して簡単なWEBアプリを作ります。詳細説明は少ないかもしれませんが、サンプル通りにアプリを動いたら、システムエンジニアにとって、短期間でも利用されているフレームワークの構造と実現方式がわかるようになると思います。
本記事では下記の知識を紹介していきます。
・ Spring Frameworkの構成
・ thymeleafテンプレート
・ bootstrap簡単設計
・ mybatisデータ持続化
・ 簡単なフォームバリデーション
1. Spring Boot とは
大規模システム開発ではフレームワークの重要性は言わずに当たり前の話です。様々な開発言語に対応する様々なフレームワークがありますが、技術発展により、これらのフレームワークは老朽化になったり、デファクトスタンダードになったりします。Javaの世界では、Spring Frameworkはほとんど採用されています。Spring Frameworkをもとに、更に使いやすいように独自なフレームワークもたくさん作らました。2002年Spring Frameworkはリリースされたときに、単純に(Dependency Injection)を実現した小さいフレームワークでした。今まで何でもできる巨大フレームワークになってきました。下図のSpring Framework製品ファミリーを見ればわかるでしょう。
图1-1. Spring Framework製品ファミリー
それでは、Spring Bootはいったい何でしょうか。簡単に言いますと、Spring BootはSpring開発を簡単化できる開発フレームワークです。わかりにくいですが、言い換えますと、Spring Bootは新しいフレームワークではなく、Spring Frameworkをベースにたくさんのフレームワークを使い方などをまとめて、サーバーも内包されますので、Springの開発をさらに簡単、スピーディにできます!
2. サンプル
今回はSpring Bootを利用して下記簡単なWEBアプリを作ります。基本的なCRUD(新規、検索、更新、削除)を実現して,Spring Webアプリの典型的な構成になります。
図2-1. 商品一覧
図2-2. 商品新規
図2-3. 商品詳細
図2-4. 商品変更
3. 開発環境
まず、開発環境を準備しましょう。本記事は2019/7/2現在の最新バージョンを利用します。なにか調べるときに、旧バージョンの情報もたくさん出ます、新旧バージョン通用しないケースも結構ありますので、掲載情報の前提に特に気をつけましょう。
3.1 JDK
2018年年9月にJava 11のリリースに伴って、OracleはJDKのサポートポリシーも調整しました。JDKメジャーバージョンアップは半年ごと、長期商用サポートなし、長期サポート(LTS)バージョンは3年毎リリースなどなど、ややこしいですので、Java有償化について別途記事をまとめていますので、下記のリンクをご参照ください。
「Java有償化」って、いったいどういうこと?
Java有償化にかかわらず、現時点個人と開発向けの場合、Oracle JDKが無料利用可能です。JDKはまだインストールされていない場合、下記のリンクからダウンロードしてください。
Oracle JDK 12: https://www.oracle.com/technetwork/java/javase/downloads/index.html
Open JDK 12: https://jdk.java.net/12/
3.2 Spring Tool Suite 4
開発効率を上げるために、開発ツールの選定も重要です。SpringはEclipseをベースにSpring Tool SuiteというIDEを提供しています。それ以外、Visual Studio Codeプラグイン、Atomプラグインもあります。ここではSTS4(Spring Tool Suite 4)を利用します。下記のリンクからOSごとのバージョンをダウンロードできます。
STS4.2.2:https://spring.io/tools
3.3 Bootstrap
BootstrapはTwitter社開発したクライアント開発のOSSです、デザイナーのセンスがなくてもプロレベルのレイアウトや画面要素を簡単に作れます。Bootstrapをプロジェクトに導入するにはCDN声明方式、または、CSS、JavaScriptをローカルにダウンロードしてimportする方式があります。ここではローカル方式を採用します。下記のリンクからbootstrap及び依頼されるjQeuryの最新バージョンをダウンロードできます。Bootstrap最新バージョンは4ですが、3より一部のタグを廃止したり、追加したりしていますので、インタネットで掲載しているソースはバージョン3のほうが多いですので、ソースをそのままコピペして、効かなければ、バージョンを確認したほうがよいと思います。
Bootstrap4.3.1: https://getbootstrap.com/
jQuery3.4.1 :https://jquery.com/
4. 実践
これから一緒にサンプルを作りましょう。このサンプルは典型的なSpring MVCを利用するWebアプリになります。H2データベースを利用しています。H2はJavaで実装されたリレーションデータベースであり、データはメモリに保存されます。WEBサーバーが起動するたびにデータがクリアされます。興味がありましたら、MySQLやPostSQLなどのデータベースを入れ替えてみてください。
システム構成は下図に示します。
最終のプロジェクト構成は下記の通りです。
実際のプロジェクトには、エンティティを整理し、属性を抽出してテーブル、画面設計を先に行うことが多いですが、ここでは設計工程を略します。直接MVCモデル順でサンプルWebアプリの構築手順を説明します。
4.1 プロジェクト作成
① Spring Tool Suite 4を起動してワークフォルダを指定します。メニュー「File → New → Spring Starter Project」を選択したら、下記の画面が表示されます。
- Type: Spring BootがMavenまたはGradleをプロジェクト管理ツールとして推薦されています、ここではデフォルトのMavenを利用します。
- Java Version:JDKバージョンを選択します。
- その他の項目はデフォルト値のままします。
② 「Next」ボタンを押下して、下図のように検索ボックスにキーワードを入力して、出てきた依存ライブラリを選択します。サンプルには使っているライブラリは下記の通りです。一つずつ選択してください。
Web、Thymeleaf、MyBatis、Validation、H2、DevTools
③ 「Finish」ボタンを押下して、アプリの雛形は自動的に生成されます。pom.xmlファイルの中身を確認して、上記各依存ライブラリが入っています。開発中必要なライブラリが必要になったら、このファイルを直接修正します。pom.xmlファイルはmavenプロジェクトのコアファイルであることを覚えてください。
4.2 モデル作成(Model)
まず、Itemというクラスを作ります(package:com.example.demo.domain)、商品の各種属性を定義したうえ、バリデーションもアノテーションの形で実装します。Spring Boot開発は一番の特徴はプロジェクト設定、DI注入、バリデーションをアノテーションで定義したら、裏の動作はSpring bootは全部やってくれます。
package com.example.demo.domain;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
public class Item {
private Long id;
@NotBlank(message="商品名を記入してください。")
private String name;
@Min(value=10, message="10以上の数値を入力してください。")
@Max(value=10000, message="10000以下の数値を入力してください。")
private float price;
@Size(max=50, message="ベーダー名は50文字を超えないでください。")
private String vendor;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public String getVendor() {
return vendor;
}
public void setVendor(String vendor) {
this.vendor = vendor;
}
}
4.3 ビュー作成(View)
① 3.3でダウンロードしたBootstrap4及びjQueryを解凍して下記のように関連ファイルをresources/staticに入れます。
└static
└css
└bootstrap.min.css
└js
└bootstrap.min.js
└jquery-3.4.1.min.js
② resources/templatesの配下に4つの画面テンプレート(html)を作成します。テンプレートはThymeleafとBootstrapを利用しています。
- 商品一覧画面:index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/bootstrap.min.css" />
<title>商品一覧</title>
</head>
<body>
<nav class="navbar navbar-inverse">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/items">商品管理デモ</a>
</div>
</div>
</nav>
<div class="container">
<div class="card card-primary mb-3">
<div class="card-header">
<h5 class="card-title">商品リスト<a href="/items/new" class="btn btn-success float-right">新規</a></h5>
</div>
<div class="card-body" th:if="!${items.size()}">
<p>商品がありません。</p>
</div>
<table class="table table-striped" th:if="${items.size()}">
<thead>
<tr>
<th style="width: 10%">ID</th>
<th style="width: 30%">商品名</th>
<th style="width: 10%">価格</th>
<th style="width: 20%">ベンダー</th>
<th style="width: 30%"></th>
</tr>
</thead>
<tbody>
<tr th:each="item:${items}" th:object="${item}">
<td th:text="*{id}"></td>
<td th:text="*{name}"></td>
<td th:text="*{price}"></td>
<td th:text="*{vendor}"></td>
<td class="float-right">
<form th:action="@{/items/{id}(id=*{id})}" th:method="delete">
<a class="btn btn-primary" th:href="@{/items/{id}(id=*{id})}">詳細</a>
<a class="btn btn-primary" th:href="@{/items/{id}/edit(id=*{id})}">変更</a>
<button class="btn btn-primary">削除</button>
</form>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<script src="/js/jquery-3.4.1.min"></script>
<script src="/js/bootstrap.min.js"></script>
</body>
</html>
- 商品新規画面 new.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/bootstrap.min.css" />
<title>商品新規</title>
</head>
<body>
<nav class="navbar navbar-inverse">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/items">商品管理デモ</a>
</div>
</div>
</nav>
<div class="container">
<div class="card card-primary mb-3">
<div class="card-header">
<h5 class="card-title">商品新規</h5>
</div>
<div class="card-body">
<form th:method="post" th:action="@{/items}" th:object="${item}">
<div class="form-group row">
<label class="col-md-2 control-label">商品名</label>
<div class="col-md-10">
<input class="form-control" type="text" name="name" th:value="*{name}" />
<div class="text-danger" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">価格</label>
<div class="col-md-10">
<input class="form-control" type="text" name="price" th:value="*{price}"/>
<div class="text-danger" th:if="${#fields.hasErrors('price')}" th:errors="*{price}"></div>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">ベンダー</label>
<div class="col-md-10">
<input class="form-control" type="text" name="vendor" th:value="*{vendor}" />
<div class="text-danger" th:if="${#fields.hasErrors('vendor')}" th:errors="*{vendor}"></div>
</div>
</div>
<div class="form-group row">
<div class="offset-md-2 col-md-10">
<button class="btn btn-primary">新規</button>
</div>
</div>
</form>
</div>
</div>
</div>
<script src="/js/jquery-3.4.1.min"></script>
<script src="/js/bootstrap.min.js"></script>
</body>
</html>
- 商品詳細画面 show.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/bootstrap.min.css" />
<title>商品詳細</title>
</head>
<body>
<nav class="navbar navbar-inverse">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/items">商品管理デモ</a>
</div>
</div>
</nav>
<div class="container">
<div class="card card-primary mb-3">
<div class="card-header">
<h5 class="card-title">商品詳細</h5>
</div>
<div class="card-body">
<div th:object="${item}">
<div class="form-group row">
<label class="col-md-2">商品名</label>
<div class="col-md-10" th:text="*{name}"></div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">価格</label>
<div class="col-md-10 form-control-static" th:text="*{price}"></div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">ベンダー</label>
<div class="col-md-10 form-control-static" th:text="*{vendor}"></div>
</div>
</div>
</div>
</div>
</div>
<script src="/js/jquery-3.4.1.min"></script>
<script src="/js/bootstrap.min.js"></script>
</body>
</html>
- 商品変更画面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/bootstrap.min.css" />
<title>商品情報変更</title>
</head>
<body>
<nav class="navbar navbar-inverse">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/items">商品管理デモ</a>
</div>
</div>
</nav>
<div class="container">
<div class="card card-primary mb-3">
<div class="card-header">
<h5 class="card-title">商品詳細</h5>
</div>
<div class="card-body">
<form th:action="@{/items/{id}(id=*{id})}" th:method="put" th:object="${item}">
<div class="form-group row">
<label class="col-md-2 control-label">商品名</label>
<div class="col-md-10">
<input class="form-control" type="text" th:field="*{name}" />
<div class="text-danger" th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">価格</label>
<div class="col-md-10">
<input class="form-control" type="text" th:field="*{price}" />
<div class="text-danger" th:if="${#fields.hasErrors('price')}" th:errors="*{price}"></div>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label">ベンダー</label>
<div class="col-md-10">
<input class="form-control" type="text" th:field="*{vendor}" />
<div class="text-danger" th:if="${#fields.hasErrors('vendor')}" th:errors="*{vendor}"></div>
</div>
</div>
<div class="form-group row">
<div class="offset-md-2 col-md-9">
<button class="btn btn-primary">更新</button>
</div>
</div>
</div>
</form>
</div>
</div>
<script src="/js/jquery-3.4.1.min"></script>
<script src="/js/bootstrap.min.js"></script>
</body>
</html>
4.4 DB準備及びMyBatis Mapper作成
① resourcesフォルダ配下にDBテーブル生成ファイル、アプリが起動されると、テーブルが自動的に作成されます。テーブル構成は下記の通りです。
No. | 項目名 | タイプ | 長さ | 説明 |
---|---|---|---|---|
1 | id | bigint | 商品ID | |
2 | name | varchar | 255 | 商品名 |
3 | price | real | 価格 | |
4 | vendor | varchar | 255 | ベンダー |
CREATE TABLE IF NOT EXISTS item (
id bigint(20) NOT NULL AUTO_INCREMENT,
name varchar(255),
price real,
vendor varchar(255),
PRIMARY KEY (id),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
② Spring FrameworkのDB処理はJPAフレームワークもあります、JPAは自動処理機能がありますが、実際のプロジェクト開発の複雑な検索処理を対応できないケースが多いですので、MyBatisを採用するほうが多いです。MyBatisを利用するメリットは、SQL文をそのままJavaとマッピングして、複雑なDB操作も可能です。
まず、ItemMapperというインタフェース(package:com.example.demo.mapper)を作ります, 注意:classではなく、ineterfaceです。5つのメソッドを定義します。下記の処理を行います。インタフェースを定義しましたが、実現クラスはどこに実装しますか。答えはインタフェース定義の前に@Mapperをつければ、それ以外の処理はフレームワークのほうは全部やってくれます。裏の処理は知らなくてもよいです。
No. | メソッド名 | 説明 |
---|---|---|
1 | findAll | すべての商品一覧習得 |
2 | findOne | 商品IDによって商品情報を取得 |
3 | save | 商品情報保存 |
4 | update | 商品情報更新 |
5 | delete | 商品削除 |
package com.example.demo.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.example.demo.domain.Item;
@Mapper
public interface ItemMapper {
List<Item> findAll();
Item findOne(Long id);
void save(Item item);
void update(Item item);
void delete(Long id);
}
③ com.example.demo.mapper配下に対応するSQL定義ファイルItemMapper.xmlを作ります。このファイルは上記各メソッドのSQL文と対応しますので、わかりやすいと思いますので、詳細説明を割愛します。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ItemMapper">
<select id="findAll" resultType="com.example.demo.domain.Item">
select * from item
</select>
<select id="findOne" resultType="com.example.demo.domain.Item">
select * from item where id= #{id}
</select>
<insert id="save" useGeneratedKeys="true" keyProperty="id">
insert into item(name, price, vendor) values(#{name}, #{price}, #{vendor})
</insert>
<update id="update">
update item set name=#{name}, price=#{price}, vendor=#{vendor} where id= #{id}
</update>
<delete id="delete">
delete from item where id = #{id}
</delete>
</mapper>
④ DBアクセスクラスItemService(package:com.example.demo.service)を作ります。serviceクラスはトランザクション処理、関連機能を部品としてまとめます。下記のコードには、2点を注目してほしいです。まず、@Serviceアノテーションの定義です。このアノテーションを追加したら、このクラスが自動的にbeanとして登録されます。ほかのプログラムに直接使えます。上記の@Mapperも同じ機能があります。次は、@Autowiredです、このアノテーションを利用しますと、クラスを初期化しなくても利用できるようになります。
package com.example.demo.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.domain.Item;
import com.example.demo.mapper.ItemMapper;
@Service
public class ItemService {
@Autowired
private ItemMapper itemMapper;
@Transactional
public List<Item> findAll() {
return itemMapper.findAll();
}
@Transactional
public Item findOne(Long id) {
return itemMapper.findOne(id);
}
@Transactional
public void save(Item item) {
itemMapper.save(item);
}
@Transactional
public void update(Item item) {
itemMapper.update(item);
}
@Transactional
public void delete(Long id) {
itemMapper.delete(id);
}
}
4.5 コントロラー(Controller)作成
ItemControllerというクラス(package:com.example.demo.controller)を作成します。controller主な用途はDBからデータを取得してViewクラスに渡してレンダリングします。また、Viewクラスからデータを取得してDBに保存します。クラス定義の前に@Controllerアノテーションを付けたら、各種設定は全部自動的にやってくれます。例えば、REST APIを開発する場合、同じように@RestControllerアノテーションを付けたらOKです。このクラスには@AutoWiredアノテーションがもう一回出てきました。上記のservcieをこのクラスに導入されて、初期化処理不要でも使えるようになります。@RequestMapping、@GetMapping、@PostMapping、@PutMappingなどのアノテーションはHTTPリクエストURLやパラメータを定義します。
package com.example.demo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.demo.domain.Item;
import com.example.demo.service.ItemService;
@Controller
@RequestMapping("/items")
public class ItemController {
@Autowired
private ItemService itemService;
@GetMapping
public String index(Model model) {
model.addAttribute("items", itemService.findAll());
return "index";
}
@GetMapping("{id}")
public String show(@PathVariable Long id, Model model) {
model.addAttribute("item", itemService.findOne(id));
return "show";
}
@GetMapping("new")
public String newItem(@ModelAttribute("item") Item item, Model model) {
return "new";
}
@GetMapping("{id}/edit")
public String edit(@PathVariable Long id, @ModelAttribute("item") Item item, Model model) {
model.addAttribute("item", itemService.findOne(id));
return "edit";
}
@PostMapping
public String create(@ModelAttribute("item") @Validated Item item, BindingResult result, Model model) {
if (result.hasErrors()) {
return "new";
} else {
itemService.save(item);
return "redirect:/items";
}
}
@PutMapping("{id}")
public String update(@PathVariable Long id, @ModelAttribute("item") @Validated Item item, BindingResult result, Model model) {
if (result.hasErrors()) {
model.addAttribute("item", item);
return "edit";
} else {
item.setId(id);
itemService.update(item);
return "redirect:/items";
}
}
@DeleteMapping("{id}")
public String delete(@PathVariable Long id) {
itemService.delete(id);
return "redirect:/items";
}
}
4.6 アプリ起動
① ここまで簡単WEBアプリができました。STSのPackage ExplorerからDemoApplication.javaのファイルを探します、このファイルはプロジェクトのエントリファイルです。下図のように右クリックしてコンテキストメニューから「Spring Boot App」を選択したら、サーバーは起動されます。
②任意のブラウザを開いて、「http://localhost:8080/items」にアクセスすれば、下記の画面が表示されたら、成功です。
5. 後書き
補足ですが、実際のプロジェクトには、上記のサンプルよりはるかはるかに複雑になります。例えば、認可認証処理、セッション処理、DBデータ永続化、ロードバランサーなどなど。基礎がわかれば、複雑な処理でも短期間に作れると思います。
学習中エラーが発生したら、サンプルのソースコードはGithubからダウンロードできます。ご参考ください。
URL:https://github.com/microstone-info/springbootsample
また、記述ミスや間違いがあれば、ご遠慮なくご指摘ください。
素晴らしい記事をありがとうございました。
内容拝見し、Springを使ってWebアプリを開発する際の最初に手を付けるものとしてとてもとっかかりのよいものと感じ、こちらのサイトを友人にどんどん広めていってます。
あちこちで、このサイトから始めましょうと宣伝させていただきたいです
恐れ入りますが質問させて下さい
こちらのサイトに沿って進めてみたところ、商品の更新のところでエラーが出て進められないところがありました
商品詳細メニューで編集して「更新」ボタンを押すと、’HttpRequestMethodNotSupportedException’ エラーが発生しました。
エラー詳細は下記のとおりとなります
“`
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Jun 01 12:47:39 JST 2020
There was an unexpected error (type=Method Not Allowed, status=405).
Request method ‘POST’ not supported
org.springframework.web.HttpRequestMethodNotSupportedException: Request method ‘POST’ not supported
at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:213)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:422)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:367)
at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:110)
at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:59)
at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:395)
at org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1234)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1016)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:830)
“`
コントローラーのedit や update メソッドが疑わしいと考えており、GitHubと見比べておりますが、その他でも、エラー内容から何か疑わしいところなどございましたら、ご教示頂けませんでしょうか
どうぞ宜しくおねがいします
コメントに気づかなかったですので、承認に遅れてすみませんでした。
前日似てるご質問もありましたが、githubのソースを見て、解決されたようです。
調べてますので、お待ち下さい。
横からすみません。
大変、本記事を参考にさせて戴きました。
私も同じ件で、止まりました。
解決方法ですが、下記ではないでしょうか?
https://miyohide.hatenablog.com/entry/2019/11/21/205935
はじめまして!質問があります。
記事を参考にしながら、プログラムを作っております。
4.4 DB準備及びMyBatis Mapper作成の部分ですが、
MySQL等でDBの作成などしておく必要があるのでしょうか?
それともeclipse上ですべて完結しますか?
実行した際のエラーで、CREATE TABLE IF NOT EXISTS item (
id bigint(20) NOT NULL AUTO_INCREMENT,
name varchar(255),
price real,
vendor varchar(255),
PRIMARY KEY (id),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
以上の内容が記載されているエラーが出てくるので、schema.sqlファイルがおかしいのかなとは思うのですが、どうしてよいか分かりません。
schema.sqlというファイルを作ってコピペしただけです。
お忙しい所、おそれいりますが、
もし分かればご回答いただけたら幸いです。
宜しくお願いします。
返信が遅くなり、すみませんでした。
サンプルではH2インメモリDBを使っていますので、特にMYSQLなどのDBインストールは不要ですが、
具体的にどんなエラーが出ますか。可能であれば、エラー内容を教えていただければ、解析します。
はじめまして
私もこちらのサイトを参考にさせていただき、Spring Bootのアプリケーションを作成したところ、こちらの質問者の方と同じようなエラーとなりました。
私の場合、原因はわかりませんが解決できましたので、参考になればと思い共有させていただきます。
解決策: pom.xml 8行目 Spring Bootのバージョンを 2.1.5.RELEASE に設定する
2020年8月時点では、EclipseからSpring Bootの新規プロジェクトを作成する際、Spring Boot Versionが最低でも2.1.16からしか選べません。
そのためpom.xmlを直接書き換えこちらのサイトで扱っているバージョンに指定してあげる必要があるようです。
質問者の方は、pom.xmlのSpring Bootのバージョンを確認してみるといいかもしれません。
はじめまして
質問です
上記の内容を参考に最後までコーディングし終わりましたが。
コンソール画面には以下のように表記されます。
Error starting ApplicationContext. To display the conditions report re-run your application with ‘debug’ enabled.
2020-11-01 16:18:15.272 ERROR 6220 — [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Field itemMapper in com.example.demo.service.ItemService required a bean of type ‘com.exmple.demo.mapper.ItemMapper’ that could not be found.
The injection point has the following annotations:
– @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type ‘com.exmple.demo.mapper.ItemMapper’ in your configuration.
com.exmple.demo.mapper.ItemMapperを明白にしろと書かれてます。
お忙しい中アドバイスよろしくお願いします。
サンプルコードは下記のURLをご参考ください。
https://github.com/microstone-info/springbootsample
はじめまして!
記事を参考にしながらプログラムを作っていますが、
2点質問があります。
1,eclipse(sts)とH2の接続方法
サイトに記載の手順で実行したところ、以下の状況になります。
①「~Started DemoApplication in 2.393 seconds」でいったん止まる。
②「http://localhost:8080/items」にアクセス
③「Cause: org.h2.jdbc.JdbcSQLSyntaxErrorException: テーブル “ITEM” が見つかりません」が出てきて異常終了。
H2データベースのテーブル “ITEM”が見つからないということは、eclipse(sts)とH2の接続に問題があると思います。お手数ですが、eclipse(sts)とH2の接続方法を確認したいので、お教え願います。
2,4.4 DB準備のコマンド
H2 consoleにて「CREATE TABLE IF ~」を入力すると「SQLステートメントに文法エラーがあります 」と異常終了します。できれば最新のコマンドをお教え願います。
※他サイトで検索して有効なコマンドでテーブルを作成しましたが、念のために
はじめまして!
素晴らしい記事をありがとうございます。内容拝見し、Springを使ってWebアプリを開発する際
に最初に手を付けるものとしてとてもとっかかりのよいものと感じました。
当方は
Spring Tool Suite 4
Version: 4.10.0.RELEASE
Build Id: 202103111225
spring-boot-2.4.4.jar
jquery-3.6.0.min.js
bootstrap-5.0.0-beta3-dist
h2-1.4.200.jar
を使用して実践しました。
御多分にもれず、コメントに記載されていた記事を参考に
以下の問題点をパスして無事動作させることに成功いたしました。
これからは、突っ込んで学習していこうと思います。
大変感謝しております。
1.H2DBのバージョンによるスキーマ
の定義文の変更
2.spring-boot-2.4.4.jarのバージョンUPによる
Application.propertiesの定義追加
3.商品変更画面のviewの命名の失敗
素晴らしいです。 とても参考になりました。 もっと勉強してこれからはBootにしようと思います。
下記のサイトというのはこのサイトの事でしょうか?
記事を拝見いたしました。
実際にご説明のように実行してみたんですが、
エラーが出てきてしまっており、共有させていただきます。
何かヒントがございましたらご教授の方を
Error starting ApplicationContext. To display the conditions report re-run your application with ‘debug’ enabled.
[2m2021-06-18 01:06:12.810[0;39m [31mERROR[0;39m [35m9168[0;39m [2m—[0;39m [2m[ restartedMain][0;39m [36mo.s.boot.SpringApplication [0;39m [2m:[0;39m Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘dataSourceScriptDatabaseInitializer’ defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Invocation of init method failed; nested exception is org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement #1 of URL [file:/C:/pleiades/workspace/demo/target/classes/schema.sql]: CREATE TABLE IF NOT EXISTS item ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(255), price real, vendor varchar(255), PRIMARY KEY (id), ) ENGINE=InnoDB DEFAULT CHARSET=utf8; nested exception is org.h2.jdbc.JdbcSQLSyntaxErrorException: SQLステートメントに文法エラーがあります “CREATE TABLE IF NOT EXISTS ITEM ( ID BIGINT(20) NOT NULL AUTO_INCREMENT, NAME VARCHAR(255), PRICE REAL, VENDOR VARCHAR(255), PRIMARY KEY (ID), )[*] ENGINE=INNODB DEFAULT CHARSET=UTF8”; 期待されるステートメント “identifier”
Syntax error in SQL statement “CREATE TABLE IF NOT EXISTS ITEM ( ID BIGINT(20) NOT NULL AUTO_INCREMENT, NAME VARCHAR(255), PRICE REAL, VENDOR VARCHAR(255), PRIMARY KEY (ID), )[*] ENGINE=INNODB DEFAULT CHARSET=UTF8”; expected “identifier”; SQL statement:
CREATE TABLE IF NOT EXISTS item ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(255), price real, vendor varchar(255), PRIMARY KEY (id), ) ENGINE=InnoDB DEFAULT CHARSET=utf8 [42001-200]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1786) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.8.jar:5.3.8]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.8.jar:5.3.8]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.5.1.jar:2.5.1]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-2.5.1.jar:2.5.1]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[spring-boot-2.5.1.jar:2.5.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:338) ~[spring-boot-2.5.1.jar:2.5.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-2.5.1.jar:2.5.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1332) ~[spring-boot-2.5.1.jar:2.5.1]
at com.example.demo.DemoApplication.main(DemoApplication.java:10) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.5.1.jar:2.5.1]
Caused by: org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement #1 of URL [file:/C:/pleiades/workspace/demo/target/classes/schema.sql]: CREATE TABLE IF NOT EXISTS item ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(255), price real, vendor varchar(255), PRIMARY KEY (id), ) ENGINE=InnoDB DEFAULT CHARSET=utf8; nested exception is org.h2.jdbc.JdbcSQLSyntaxErrorException: SQLステートメントに文法エラーがあります “CREATE TABLE IF NOT EXISTS ITEM ( ID BIGINT(20) NOT NULL AUTO_INCREMENT, NAME VARCHAR(255), PRICE REAL, VENDOR VARCHAR(255), PRIMARY KEY (ID), )[*] ENGINE=INNODB DEFAULT CHARSET=UTF8”; 期待されるステートメント “identifier”
Syntax error in SQL statement “CREATE TABLE IF NOT EXISTS ITEM ( ID BIGINT(20) NOT NULL AUTO_INCREMENT, NAME VARCHAR(255), PRICE REAL, VENDOR VARCHAR(255), PRIMARY KEY (ID), )[*] ENGINE=INNODB DEFAULT CHARSET=UTF8”; expected “identifier”; SQL statement:
CREATE TABLE IF NOT EXISTS item ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(255), price real, vendor varchar(255), PRIMARY KEY (id), ) ENGINE=InnoDB DEFAULT CHARSET=utf8 [42001-200]
at org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScript(ScriptUtils.java:282) ~[spring-jdbc-5.3.8.jar:5.3.8]
at org.springframework.jdbc.datasource.init.ResourceDatabasePopulator.populate(ResourceDatabasePopulator.java:254) ~[spring-jdbc-5.3.8.jar:5.3.8]
at org.springframework.jdbc.datasource.init.DatabasePopulatorUtils.execute(DatabasePopulatorUtils.java:49) ~[spring-jdbc-5.3.8.jar:5.3.8]
at org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer.runScripts(DataSourceScriptDatabaseInitializer.java:78) ~[spring-boot-2.5.1.jar:2.5.1]
at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.runScripts(AbstractScriptDatabaseInitializer.java:151) ~[spring-boot-2.5.1.jar:2.5.1]
at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.applyScripts(AbstractScriptDatabaseInitializer.java:111) ~[spring-boot-2.5.1.jar:2.5.1]
at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.applySchemaScripts(AbstractScriptDatabaseInitializer.java:101) ~[spring-boot-2.5.1.jar:2.5.1]
at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.initializeDatabase(AbstractScriptDatabaseInitializer.java:76) ~[spring-boot-2.5.1.jar:2.5.1]
at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.afterPropertiesSet(AbstractScriptDatabaseInitializer.java:65) ~[spring-boot-2.5.1.jar:2.5.1]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1845) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1782) ~[spring-beans-5.3.8.jar:5.3.8]
… 23 common frames omitted
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: SQLステートメントに文法エラーがあります “CREATE TABLE IF NOT EXISTS ITEM ( ID BIGINT(20) NOT NULL AUTO_INCREMENT, NAME VARCHAR(255), PRICE REAL, VENDOR VARCHAR(255), PRIMARY KEY (ID), )[*] ENGINE=INNODB DEFAULT CHARSET=UTF8”; 期待されるステートメント “identifier”
Syntax error in SQL statement “CREATE TABLE IF NOT EXISTS ITEM ( ID BIGINT(20) NOT NULL AUTO_INCREMENT, NAME VARCHAR(255), PRICE REAL, VENDOR VARCHAR(255), PRIMARY KEY (ID), )[*] ENGINE=INNODB DEFAULT CHARSET=UTF8”; expected “identifier”; SQL statement:
CREATE TABLE IF NOT EXISTS item ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(255), price real, vendor varchar(255), PRIMARY KEY (id), ) ENGINE=InnoDB DEFAULT CHARSET=utf8 [42001-200]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:453) ~[h2-1.4.200.jar:1.4.200]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:429) ~[h2-1.4.200.jar:1.4.200]
at org.h2.message.DbException.getSyntaxError(DbException.java:243) ~[h2-1.4.200.jar:1.4.200]
at org.h2.command.Parser.readColumnIdentifier(Parser.java:4976) ~[h2-1.4.200.jar:1.4.200]
at org.h2.command.Parser.parseTableColumnDefinition(Parser.java:8437) ~[h2-1.4.200.jar:1.4.200]
at org.h2.command.Parser.parseCreateTable(Parser.java:8379) ~[h2-1.4.200.jar:1.4.200]
at org.h2.command.Parser.parseCreate(Parser.java:6276) ~[h2-1.4.200.jar:1.4.200]
at org.h2.command.Parser.parsePrepared(Parser.java:903) ~[h2-1.4.200.jar:1.4.200]
at org.h2.command.Parser.parse(Parser.java:843) ~[h2-1.4.200.jar:1.4.200]
at org.h2.command.Parser.parse(Parser.java:815) ~[h2-1.4.200.jar:1.4.200]
at org.h2.command.Parser.prepareCommand(Parser.java:738) ~[h2-1.4.200.jar:1.4.200]
at org.h2.engine.Session.prepareLocal(Session.java:657) ~[h2-1.4.200.jar:1.4.200]
at org.h2.engine.Session.prepareCommand(Session.java:595) ~[h2-1.4.200.jar:1.4.200]
at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1235) ~[h2-1.4.200.jar:1.4.200]
at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:212) ~[h2-1.4.200.jar:1.4.200]
at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:201) ~[h2-1.4.200.jar:1.4.200]
at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:94) ~[HikariCP-4.0.3.jar:na]
at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java) ~[HikariCP-4.0.3.jar:na]
at org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScript(ScriptUtils.java:261) ~[spring-jdbc-5.3.8.jar:5.3.8]
… 33 common frames omitted
文末にもかいてありますが、ソースコードは下記のリンクです。
https://github.com/microstone-info/springbootsample
自分は「ENGINE=InnoDB DEFAULT CHARSET=utf8」を削除で解決しました。
H2のバージョンアップに伴い、SQLのエラーが出力されるようになったようです。こちらを参考にしました。
https://teratail.com/questions/254060
スレ遅ですが、
PRIMARY KEY (id),
の最後に「,」がついているから文法エラーが出ているのでは?
異なる事象かもしれませんが私の場合はschema.sqlを修正したところ解消しました。
元の文では id bigint(20)となっていましたがbigintって桁数指定できなかったような?
そこで以下に変えました。
CREATE
TABLE item(
id INT NOT NULL AUTO_INCREMENT
,name VARCHAR(255)
,price REAL
,vendor VARCHAR(255)
,PRIMARY KEY (id)
);
すみません、正しくは下記でした。
CREATE
TABLE item(
id INT NOT NULL AUTO_INCREMENT
,name VARCHAR(255)
,price REAL
,vendor VARCHAR(255)
,PRIMARY KEY (id)
);