Spring Boot

MyBatis + SpringでWebアプリ(CRUD)を作成する

はじめに

MyBatisとSpring Bootを利用して「検索画面」「登録画面」「編集画面」の作成方法を紹介します。

本記事で作成する画面は次のとおりです。

■検索画面

検索画面のイメージ

■新規登録画面

新規登録画面のイメージ

■編集画面

編集画面のイメージ

開発環境

開発環境 名称 説明
開発言語 Java 人気の開発言語
開発ツール Eclipse Javaでの定番開発ツール
フレームワーク Spring Boot 人気のSpringフレームワークをベースとしたフレームワーク
テンプレートエンジン Thymeleaf Spring Bootと相性が良いテンプレートエンジン
データベース MySQL フリーで利用できる人気データベース
ORM(オブジェクト関係マッピング) MyBatis DBとJavaオブジェクトのマッピングを行ってくれる人気のORM
画面デザイン Bootstrap 作られた雛形を使うことで簡単にWebのデザインができるWebフレームワーク

スポンサーリンク

本記事で使用するテーブル定義は次のとおり。

物理名 論理名 データ型 NOT NULL 説明
id ID BIGINT 主キー(AUTO_INCREMENT)
name 名前 VARCHAR(100) ユーザーの名前
address 住所 VARCHAR(255) ユーザーの住所
phone 電話番号 VARCHAR(50) ユーザーの電話番号
update_date 更新日時 DATETIME 最終更新日時
create_date 作成日時 DATETIME 登録日時
delete_date 削除日時 DATETIME 論理削除した日時

■CREATE文

CREATE TABLE `sampledb`.`userinfo` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(100) NOT NULL,
  `address` VARCHAR(255) NULL,
  `phone` VARCHAR(50) NULL,
  `update_date` DATETIME NOT NULL,
  `create_date` DATETIME NOT NULL,
  `delete_date` DATETIME NULL,
  PRIMARY KEY (`id`));

ディレクトリ構成

MybatisSample
  |
  |___src.main.java
       |
       |___com.example.demo
       |    |
       |    |___controller
       |    |    |
       |    |    |__UserInfoController.java
       |    |
       |    |____dto
       |    |    |
       |    |    |__UserAddRequest.java
       |    |    |
       |    |    |__UserSearchRequest.java
       |    |    |
       |    |    |__UserUpdateRequest.java
       |    |
       |    |___entity
       |    |    |
       |    |    |___UserInfo.java
       |    |
       |    |___dao
       |    |    |
       |    |    |___UserInfoMapper.java
       |    |
       |    |___service
       |         |
       |         |___UserInfoService.java
       |
       |___src.main.resources
            |
            |___com.example.demo
            |    |
            |    |___dao
            |         |
            |         |__UserInfoMapper.xml
            |
            |___templates
            |    |
            |    |___common
            |    |    |
            |    |    |___head.html 
            |    |
            |    |___user
            |         |
            |         |___add.html
            |         |
            |         |___edit.html
            |         |
            |         |___search.html
            |
            |___application.properties

設定ファイル

使用しているライブライとその依存関係は次のとおり。

■pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.2</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
</dependencies>

データベースの接続情報とmybatisの設定は次のとおり。(mybatisで自動的にキャメル→スネークに変換してくれる設定をtrueにしています)

■application.properties

spring.datasource.url=jdbc:mysql://localhost/sampledb
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.configuration.map-underscore-to-camel-case=true

バックエンド(サーバ)側のソースコード

コントローラークラス

画面とビジネスロジックを繋ぐコントローラークラスは次のとおり。

package com.example.demo.controller;

import java.util.ArrayList;
import java.util.List;

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.ObjectError;
import org.springframework.validation.annotation.Validated;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.example.demo.dto.UserAddRequest;
import com.example.demo.dto.UserSearchRequest;
import com.example.demo.dto.UserUpdateRequest;
import com.example.demo.entity.UserInfo;
import com.example.demo.service.UserInfoService;

/**
 * ユーザー情報 Controller
 */
@Controller
public class UserInfoController {

    /**
     * ユーザー情報 Service
     */
    @Autowired
    private UserInfoService userInfoService;

    /**
     * ユーザー情報一覧画面を表示
     * @param model Model
     * @return ユーザー情報一覧画面
     */
    @GetMapping(value = "/user/list")
    public String displayList(Model model) {
        List<UserInfo> userList = userInfoService.findAll();
        model.addAttribute("userlist", userList);
        model.addAttribute("userSearchRequest", new UserSearchRequest());
        return "user/search";
    }

    /**
     * ユーザー新規登録画面を表示
     * @param model Model
     * @return ユーザー情報一覧画面
     */
    @GetMapping(value = "/user/add")
    public String displayAdd(Model model) {
        model.addAttribute("userAddRequest", new UserAddRequest());
        return "user/add";
    }

    /**
     * ユーザー編集画面を表示
     * @param id ユーザーID
     * @param model Model
     * @return ユーザー編集画面
     */
    @GetMapping("/user/{id}/edit")
    public String displayEdit(@PathVariable Long id, Model model) {
        UserInfo user = userInfoService.findById(id);
        UserUpdateRequest userUpdateRequest = new UserUpdateRequest();
        userUpdateRequest.setId(user.getId());
        userUpdateRequest.setId(user.getId());
        userUpdateRequest.setName(user.getName());
        userUpdateRequest.setPhone(user.getPhone());
        userUpdateRequest.setAddress(user.getAddress());
        model.addAttribute("userUpdateRequest", userUpdateRequest);
        return "user/edit";
    }

    /**
     * ユーザー情報検索
     * @param userSearchRequest リクエストデータ
     * @param model Model
     * @return ユーザー情報一覧画面
     */
    @RequestMapping(value = "/user/search", method = RequestMethod.POST)
    public String search(@ModelAttribute UserSearchRequest userSearchRequest, Model model) {
        List<UserInfo> userList = userInfoService.search(userSearchRequest);
        model.addAttribute("userlist", userList);
        return "user/search";
    }

    /**
     * ユーザー情報削除(論理削除)
     * @param id ユーザーID
     * @param model Model
     * @return ユーザー情報一覧画面
     */
    @GetMapping("/user/{id}/delete")
    public String delete(@PathVariable Long id, Model model) {
        // ユーザー情報の削除
        userInfoService.delete(id);
        return "redirect:/user/list";
    }

    /**
     * ユーザー新規登録
     * @param userRequest リクエストデータ
     * @param model Model
     * @return ユーザー情報一覧画面
     */
    @RequestMapping(value = "/user/create", method = RequestMethod.POST)
    public String create(@Validated @ModelAttribute UserAddRequest userRequest, BindingResult result, Model model) {
        if (result.hasErrors()) {
            // 入力チェックエラーの場合
            List<String> errorList = new ArrayList<String>();
            for (ObjectError error : result.getAllErrors()) {
                errorList.add(error.getDefaultMessage());
            }
            model.addAttribute("validationError", errorList);
            return "user/add";
        }
        // ユーザー情報の登録
        userInfoService.save(userRequest);
        return "redirect:/user/list";
    }

    /**
     * ユーザー更新
     * @param userRequest リクエストデータ
     * @param model Model
     * @return ユーザー情報詳細画面
     */
    @RequestMapping(value = "/user/update", method = RequestMethod.POST)
    public String update(@Validated @ModelAttribute UserUpdateRequest userUpdateRequest, BindingResult result, Model model) {
        if (result.hasErrors()) {
            List<String> errorList = new ArrayList<String>();
            for (ObjectError error : result.getAllErrors()) {
                errorList.add(error.getDefaultMessage());
            }
            model.addAttribute("validationError", errorList);
            return "user/edit";
        }
        // ユーザー情報の更新
        userInfoService.update(userUpdateRequest);
        return "redirect:/user/list";
    }
}

サービスクラス

ビジネスロジックを記述するサービスクラスは次のとおり。

[com.example.demo.service.UserInfoService.java]

package com.example.demo.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.dao.UserInfoMapper;
import com.example.demo.dto.UserAddRequest;
import com.example.demo.dto.UserSearchRequest;
import com.example.demo.dto.UserUpdateRequest;
import com.example.demo.entity.UserInfo;

/**
 * ユーザー情報 Service
 */
@Service
public class UserInfoService {

    /**
     * ユーザー情報 Mapper
     */
    @Autowired
    private UserInfoMapper userInfoMapper;

    /**
     * ユーザー情報全件検索
     * @return 検索結果
     */
    public List<UserInfo> findAll() {
        return userInfoMapper.findAll();
    }

    /**
     * ユーザー情報主キー検索
     * @return 検索結果
     */
    public UserInfo findById(Long id) {
        return userInfoMapper.findById(id);
    }

    /**
     * ユーザー情報検索
     * @param userSearchRequest リクエストデータ
     * @return 検索結果
     */
    public List<UserInfo> search(UserSearchRequest userSearchRequest) {
        return userInfoMapper.search(userSearchRequest);
    }

    /**
     * ユーザ情報登録
     * @param userAddRequest リクエストデータ
     */
    public void save(UserAddRequest userAddRequest) {
        userInfoMapper.save(userAddRequest);
    }

    /**
     * ユーザ情報更新
     * @param userEditRequest リクエストデータ
     */
    public void update(UserUpdateRequest userUpdateRequest) {
        userInfoMapper.update(userUpdateRequest);
    }

    /**
     * ユーザー情報論理削除
     * @param id
     */
    public void delete(Long id) {
        userInfoMapper.delete(id);
    }
}

エンティティクラス

テーブルのエンティティクラスは次のとおり。

[com.example.demo.entity.UserInfo.java]

package com.example.demo.entity;

import java.io.Serializable;
import java.util.Date;

import lombok.Data;

/**
 * ユーザー情報 Entity
 */
@Data
public class UserInfo implements Serializable {

    /**
     * ID
     */
    private Long id;

    /**
     * 名前
     */
    private String name;

    /**
     * 住所
     */
    private String address;

    /**
     * 電話番号
     */
    private String phone;

    /**
     * 更新日時
     */
    private Date updateDate;

    /**
     * 登録日時
     */
    private Date createDate;

    /**
     * 削除日時
     */
    private Date deleteDate;
}

DAO(Data Access Object)

SQL文を呼び出すためのDAOクラスは以下のとおり。

[com.example.demo.dao.UserInfoMapper.java]

package com.example.demo.dao;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import com.example.demo.dto.UserAddRequest;
import com.example.demo.dto.UserSearchRequest;
import com.example.demo.dto.UserUpdateRequest;
import com.example.demo.entity.UserInfo;

/**
 * ユーザー情報 Mapper
 */
@Mapper
public interface UserInfoMapper {

    /**
     * ユーザー情報全件検索
     * @param user 検索用リクエストデータ
     * @return 検索結果
     */
    List<UserInfo> findAll();

    /**
     * ユーザー情報主キー検索
     * @param id 主キー
     * @return 検索結果
     */
    UserInfo findById(Long id);

    /**
     * ユーザー情報検索
     * @param user 検索用リクエストデータ
     * @return 検索結果
     */
    List<UserInfo> search(UserSearchRequest user);

    /**
     * ユーザー情報登録
     * @param userRequest 登録用リクエストデータ
     */
    void save(UserAddRequest userRequest);

    /**
     * ユーザー情報更新
     * @param userUpdateRequest 更新用リクエストデータ
     */
    void update(UserUpdateRequest userUpdateRequest);

    /**
     * ユーザー情報の論理削除
     * @param id ID
     */
    void delete(Long id);
}

SQL文(XMLファイル)

実行するSQL文は次のとおり。

[resources.com.example.demo.dao.UserInfoMapper.xml]

<?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.dao.UserInfoMapper">

    <!-- 全件検索 -->
    <select id="findAll" resultType="com.example.demo.entity.UserInfo">
        SELECT * FROM userinfo WHERE delete_date IS NULL
    </select>

    <!-- 主キー検索 -->
    <select id="findById" resultType="com.example.demo.entity.UserInfo">
        SELECT * FROM userinfo WHERE id=#{id} AND delete_date IS NULL
    </select>

    <!-- 条件指定検索 -->
    <select id="search" resultType="com.example.demo.entity.UserInfo">
        SELECT
            *
        FROM
            userinfo
        WHERE
            delete_date IS NULL
            <if test="id != null and id != ''">
                AND id = #{id}
            </if>
            <if test="name != null and name != ''">
                AND name LIKE CONCAT('%', #{name}, '%')
            </if>
    </select>

    <!-- 新規登録 -->
    <insert id="save">
        INSERT INTO userInfo
            (name, address, phone, update_date, create_date)
        VALUES
            (#{name}, #{address}, #{phone}, CURRENT_TIMESTAMP,  CURRENT_TIMESTAMP)
    </insert>

    <!-- 更新 -->
    <update id="update">
        UPDATE userinfo SET name = #{name}, address = #{address}, phone = #{phone}, update_date = CURRENT_TIMESTAMP WHERE id = #{id}
    </update>

    <!-- 論理削除 -->
    <update id="delete">
        UPDATE userinfo SET delete_date = CURRENT_TIMESTAMP WHERE id = #{id}
    </update>
</mapper>

XMLファイルは、src/main/resources配下にUserInfoMapperクラス(Mapperインタフェース)と同じパスの構成で配置します。同じパスで配置することで、MyBatisが自動でマッピングファイルを読み込んでくれます。

同じパスではなく自由な場所に配置したい場合は、「yaml」もしくは「properties」に「mybatis.mapper-locations」でXMLファイルの格納位置を指定します。

mybatis.mapper-locations=classpath*:sample/*.xml

DTO(Data Transfer Object)

データの受け渡しで利用するDTOクラスは次のとおり。

[com.example.demo.dto.UserAddRequest]

package com.example.demo.dto;

import java.io.Serializable;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import lombok.Data;

/**
 * ユーザー情報登録 リクエストデータ
 */
@Data
public class UserAddRequest implements Serializable {
    /**
     * 名前
     */
    @NotEmpty(message = "名前を入力してください")
    @Size(max = 100, message = "名前は100桁以内で入力してください")
    private String name;
    /**
     * 住所
     */
    @Size(max = 255, message = "住所は255桁以内で入力してください")
    private String address;
    /**
     * 電話番号
     */
    @Pattern(regexp = "0\\d{1,4}-\\d{1,4}-\\d{4}", message = "電話番号の形式で入力してください")
    private String phone;
}

 

[com.example.demo.dto.UserSearchRequest]

package com.example.demo.dto;

import java.io.Serializable;

import lombok.Data;

/**
 * ユーザー情報 検索用リクエストデータ
 */
@Data
public class UserSearchRequest implements Serializable {

    /**
     * ユーザーID
     */
    private String id;

    /**
     * ユーザー名
     */
    private String name;
}

 

[com.example.demo.dto.UserUpdateRequest]

package com.example.demo.dto;

import java.io.Serializable;

import javax.validation.constraints.NotNull;

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * ユーザー情報更新リクエストデータ
 *
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class UserUpdateRequest extends UserAddRequest implements Serializable {

    /**
     * ユーザーID
     */
    @NotNull
    private Long id;
}

スポンサーリンク

フロントエンド(クライアント)側のソースコード

共通ヘッダ(head.html)

共通のヘッダは次のとおり。ヘッダ情報は同じなので共通で管理しています。

[templates.common.head.html]

<head th:fragment="head_fragment(title, scripts, links)">
  <title th:text="${title}"></title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
  <meta charset="utf-8" />
</head>

ユーザー情報一覧画面(search.html)

ユーザー情報一覧画面のHTMLは次のとおり。

[templates.user.search.html]

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head th:replace="common/head :: head_fragment(title = 'ユーザー情報一覧', scripts = ~{::script}, links = ~{::link})"></head>
<body>
    <div class="container">
        <h1>ユーザー情報一覧</h1>
        <div class="float-end">
            <a th:href="@{/user/add}" class="btn btn-primary">新規登録はこちら</a>
        </div>
        <form th:action="@{/user/search}" th:object="${userSearchRequest}" th:method="post">
            <div class="form-group">
                <label for="id">ID</label>
                <input type="text" class="w-25 form-control" th:field="*{id}">
                <label for="name">名前</label>
                <input type="text" class="w-50 form-control" th:field="*{name}">
            </div><br />
            <button type="submit" class="btn btn-primary">検索</button>
        </form>
        <div th:if="${userlist}">
            <table class="table table-striped">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>名前</th>
                        <th>住所</th>
                        <th>電話番号</th>
                        <th>更新日時</th>
                        <th>登録日時</th>
                        <th>削除日時</th>
                        <th></th>
                    </tr>
                </thead>
                <tbody>
                    <tr th:each="user : ${userlist}" th:object="${user}" class="align-middle">
                        <td th:text="*{id}"></td>
                        <td th:text="*{name}"></td>
                        <td th:text="*{address}"></td>
                        <td th:text="*{phone}"></td>
                        <td th:text="${#dates.format(user.updateDate, 'yyyy/MM/dd')}"></td>
                        <td th:text="${#dates.format(user.createDate, 'yyyy/MM/dd')}"></td>
                        <td th:text="${#dates.format(user.deleteDate, 'yyyy/MM/dd')}"></td>
                        <td>
                            <a th:href="@{/user/{id}/edit(id=*{id})}" class="btn btn-primary">編集</a>
                            <a th:href="@{/user/{id}/delete(id=*{id})}" class="btn btn-secondary">削除</a>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</body>
</html>

ユーザー新規登録画面(add.html)

ユーザー新規登録画面のHTMLは次のとおり。

[templates.user.add.html]

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head th:replace="common/head :: head_fragment(title = 'ユーザー新規登録', scripts = ~{::script}, links = ~{::link})"></head>
<body>
    <div class="container">
        <div th:if="${validationError}" th:each="error : ${validationError}">
            <label class="text-danger" th:text="${error}"></label>
        </div>
        <h1>ユーザー新規登録</h1>
        <form th:action="@{/user/create}" th:object="${userAddRequest}" th:method="post">
            <div class="form-group">
                <label>名前:<span class="text-danger">※</span></label>
                <input type="text" th:field="*{name}" class="form-control">
            </div>
            <div class="form-group">
                <label>住所:</label>
                <input type="text" th:field="*{address}" class="form-control">
            </div>
            <div class="form-group">
                <label>電話番号:<span class="text-danger">※</span></label>
                <input type="text" th:field="*{phone}" class="form-control">
            </div>
            <br />
            <div class="text-center">
                <input type="submit" value="登録" class="btn btn-primary">
                <a href="/user/list" class="btn btn-secondary">キャンセル</a>
            </div>
        </form>
    </div>
</body>
</html>

ユーザー情報編集(edit.html)

ユーザー情報編集画面のHTMLは次のとおり。

[templates.user.edit.html]

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head
    th:replace="common/head :: head_fragment(title = 'ユーザー情報編集', scripts = ~{::script}, links = ~{::link})"></head>
<body>
    <div class="container">
        <div th:if="${validationError}" th:each="error : ${validationError}">
            <label class="text-danger" th:text="${error}"></label>
        </div>
        <h1>ユーザー情報編集</h1>
        <form th:action="@{/user/update}" th:object="${userUpdateRequest}"
            th:method="post">
            <input type="hidden" th:field="*{id}" />
            <div>
                <div class="row mx-md-n5">
                    <div class="col-2 pt-3 border bg-light">
                        名前<span class="text-danger">※</span>
                    </div>
                    <div class="col py-2 border">
                        <input type="text" class="form-control" th:field="*{name}">
                    </div>
                </div>
                <div class="row mx-md-n5">
                    <div class="col-2 pt-3 border bg-light">住所</div>
                    <div class="col py-2 border">
                        <input type="text" class="form-control" th:field="*{address}">
                    </div>
                </div>
                <div class="row mx-md-n5">
                    <div class="col-2 pt-3 border bg-light">
                        電話番号<span class="text-danger">※</span>
                    </div>
                    <div class="col py-2 border">
                        <input type="text" class="form-control" th:field="*{phone}">
                    </div>
                </div>
            </div>
            <br />
            <div class="text-center">
                <input type="submit" class="btn btn-primary" value="保存">
                <a href="/user/list" class="btn btn-secondary">キャンセル</a>
            </div>
        </form>
    </div>
</body>
</html>

スポンサーリンク

動作確認

Spring Bootプロジェクトを起動して「http://localhost:8080/user/list」にアクセスします。

一覧画面が表示されればOKです。

検索画面のイメージ

 

「新規登録」ボタンを押下すると「ユーザー新規登録画面」に遷移。

登録画面では入力チェックを実行しているため「登録」ボタン押下時に入力チェックエラーの場合は、画面上部にエラーメッセージを表示しています。

入力チェックエラー

 

ユーザー情報を入力して「登録」ボタンを押下すると、ユーザー情報を登録し一覧画面に戻ります。

レコードの追加

 

検索結果一覧にある「削除」ボタンを押下するとユーザー情報を論理削除、「編集」ボタンを押下すると「ユーザー情報編集画面」に遷移します。

編集画面のイメージ

「保存」ボタンを押下すると、ユーザー情報を更新し一覧画面に戻ります。

YouTubeでも解説しています。チャンネル登録と高評価よろしくお願いします!
ITを分かりやすく解説

チャンネル登録はこちら

フォローはこちら