読者です 読者をやめる 読者になる 読者になる

かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その42 )( 貸出承認画面の作成2 )

概要

Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その41 )( 貸出承認画面の作成 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 貸出承認画面の作成
      • パラメータで指定されている貸出申請 ID の「申請する」が選択された書籍と申請理由の一覧を DB から取得して表示する処理の実装

参照したサイト・書籍

目次

  1. LendingapprovalParamForm, ApplyingBookForm, LendingapprovalForm クラスの作成
  2. LendingAppDao クラスの変更
  3. LendingBookDao クラスの変更
  4. messages_ja_JP.properties にエラーメッセージを定義する
  5. LendingBookApprovalResultValues クラスの作成
  6. LendingapprovalService クラスの作成
  7. LendingapprovalController クラスの変更
  8. lendingapproval.html の変更
  9. 動作確認
  10. 次回は。。。

手順

LendingapprovalParamForm, ApplyingBookForm, LendingapprovalForm クラスの作成

貸出申請画面では一覧内の各書籍の入力データ用のクラスを Dto クラスで定義したのですが、Form クラスの方が良さそうに思えたので、今回は全て Form クラスで作成します。

  1. src/main/java/ksbysample/webapp/lending/web/lendingapp の下の LendingappParamForm.java を src/main/java/ksbysample/webapp/lending/web/lendingapproval の下へ LendingapprovalParamForm.java というファイル名でコピーします。リンク先の内容 になります。

  2. src/main/java/ksbysample/webapp/lending/web/lendingapproval の下に ApplyingBookForm.java を作成します。作成後、リンク先の内容 に変更します。

  3. src/main/java/ksbysample/webapp/lending/web/lendingapproval の下に LendingapprovalForm.java を作成します。作成後、リンク先の内容 に変更します。

LendingAppDao クラスの変更

  1. lending_app_id だけではなく status も指定してデータを取得するメソッドを追加します。src/main/java/ksbysample/webapp/lending/dao の下の LendingAppDao.javaリンク先の内容 に変更します。

  2. src/main/resources/META-INF/ksbysample/webapp/lending/dao/LendingAppDao の下に selectByIdAndStatus.sql を作成します。作成後、リンク先の内容 に変更します。

LendingBookDao クラスの変更

  1. 「申請する」が選択された書籍だけを取得するメソッドを追加します。src/main/java/ksbysample/webapp/lending/dao の下の LendingBookDao.javaリンク先の内容 に変更します。

  2. src/main/resources/META-INF/ksbysample/webapp/lending/dao/LendingBookDao の下に selectByLendingAppIdAndLendingAppFlg.sql を作成します。作成後、リンク先の内容 に変更します。

messages_ja_JP.properties にエラーメッセージを定義する

  1. エラーメッセージを定義します。src/main/resources の下の messages_ja_JP.properties を リンク先の内容 に変更します。

LendingBookApprovalResultValues クラスの作成

  1. lending_book.approval_result にセットする「承認」「却下」の値を定義する Values 列挙型を定義します。src/main/java/ksbysample/webapp/lending/values の下に LendingBookApprovalResultValues.java を作成します。作成後、リンク先の内容 に変更します。

LendingapprovalService クラスの作成

  1. src/main/java/ksbysample/webapp/lending/web/lendingapproval の下に LendingapprovalService.java を作成します。作成後、リンク先の内容 に変更します。

LendingapprovalController クラスの変更

  1. src/main/java/ksbysample/webapp/lending/web/lendingapproval の下の LendingapprovalController.javaリンク先の内容 に変更します。

lendingapproval.html の変更

  1. src/main/resources/templates/lendingapproval の下の lendingapproval.html を リンク先の内容 に変更します。

動作確認

  1. 動作確認します。データは以下の状態です。

    f:id:ksby:20160110205034p:plain

  2. Gradle projects View から bootRun タスクを実行して Tomcat を起動します。

  3. ブラウザを起動し http://localhost:8080/lendingapproval へアクセスします ( lendingAppId パラメータが指定されていない )。最初はログイン画面が表示されますので ID に "tanaka.taro@sample.com"、Password に "taro" を入力して、「次回から自動的にログインする」をチェックせずに「ログイン」ボタンをクリックします。

    共通エラー画面が表示され「貸出申請IDが指定されていません。」のエラーメッセージが表示されることが確認できます。

    f:id:ksby:20160110205450p:plain

  4. http://localhost:8080/lendingapproval?lendingAppId=a にアクセスします ( lendingAppId パラメータに数値以外が指定されている )。

    こちらも共通エラー画面が表示され「貸出申請IDが指定されていません。」のエラーメッセージが表示されることが確認できます。

    f:id:ksby:20160110210105p:plain

  5. http://localhost:8080/lendingapproval?lendingAppId=106 にアクセスします ( lendingAppId パラメータに指定された貸出申請ID のデータが存在しない )。

    貸出承認画面が表示されますが、「指定された貸出申請IDでは貸出申請されておりません。」のエラーメッセージが表示されることが確認できます。

    f:id:ksby:20160110210204p:plain

  6. http://localhost:8080/lendingapproval?lendingAppId=105 にアクセスします。

    貸出承認画面が表示され、貸出申請ID、ステータス、申請者、「申請する」が選択された書籍一覧が表示されることが確認できます。

    f:id:ksby:20160110210500p:plain

  7. Ctrl+F2 を押して Tomcat を停止します。

  8. 一旦 commit します。

次回は。。。

「確定」ボタンをクリックした時の処理を実装します。

ソースコード

LendingapprovalParamForm.java

package ksbysample.webapp.lending.web.lendingapproval;

import lombok.Data;

import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;

@Data
public class LendingapprovalParamForm {

    @NotNull
    @DecimalMin(value = "1")
    @DecimalMax(value = "9223372036854775807")
    private Long lendingAppId;

}

ApplyingBookForm.java

package ksbysample.webapp.lending.web.lendingapproval;

import ksbysample.webapp.lending.entity.LendingBook;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.BeanUtils;

@Data
@NoArgsConstructor
public class ApplyingBookForm {

    private Long lendingBookId;

    private String isbn;

    private String bookName;

    private String lendingAppReason;

    private String approvalResult;

    private String approvalReason;

    private Long version;

    public ApplyingBookForm(LendingBook lendingBook) {
        BeanUtils.copyProperties(lendingBook, this);
    }

}

LendingapprovalForm.java

package ksbysample.webapp.lending.web.lendingapproval;

import ksbysample.webapp.lending.entity.LendingApp;
import ksbysample.webapp.lending.entity.LendingBook;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;
import java.util.stream.Collectors;

@Data
@NoArgsConstructor
public class LendingapprovalForm {

    private LendingApp lendingApp;

    private String username;
    
    private List<ApplyingBookForm> applyingBookFormList;

    public void setApplyingBookFormList(List<LendingBook> lendingBookList) {
        this.applyingBookFormList = lendingBookList.stream()
                .map(ApplyingBookForm::new)
                .collect(Collectors.toList());
    }
    
}

LendingAppDao.java

package ksbysample.webapp.lending.dao;

import ksbysample.webapp.lending.entity.LendingApp;
import ksbysample.webapp.lending.util.doma.ComponentAndAutowiredDomaConfig;
import org.seasar.doma.Dao;
import org.seasar.doma.Delete;
import org.seasar.doma.Insert;
import org.seasar.doma.Select;
import org.seasar.doma.Update;
import org.seasar.doma.jdbc.SelectOptions;

import java.util.List;

/**
 */
@Dao
@ComponentAndAutowiredDomaConfig
public interface LendingAppDao {

    /**
     * @param lendingAppId
     * @return the LendingApp entity
     */
    @Select
    LendingApp selectById(Long lendingAppId);
    @Select(ensureResult = true)
    LendingApp selectByIdAndVersion(Long lendingAppId, Long version);
    @Select
    LendingApp selectById(Long lendingAppId, SelectOptions options);
    @Select(ensureResult = true)
    LendingApp selectByIdAndVersion(Long lendingAppId, Long version, SelectOptions options);
    @Select
    LendingApp selectByIdAndStatus(Long lendingAppId, List<String> statusList);
    @Select
    LendingApp selectByIdAndStatus(Long lendingAppId, List<String> statusList, SelectOptions options);

    /**
     * @param entity
     * @return affected rows
     */
    @Insert
    int insert(LendingApp entity);

    /**
     * @param entity
     * @return affected rows
     */
    @Update
    int update(LendingApp entity);

    /**
     * @param entity
     * @return affected rows
     */
    @Delete
    int delete(LendingApp entity);
}

selectByIdAndStatus.sql

select
  /*%expand*/*
from
  lending_app
where
  lending_app_id = /* lendingAppId */105
  /*%if statusList != null */
    and status in /* statusList */('3', '4')
  /*%end*/

LendingBookDao.java

package ksbysample.webapp.lending.dao;

import ksbysample.webapp.lending.entity.LendingBook;
import ksbysample.webapp.lending.util.doma.ComponentAndAutowiredDomaConfig;
import org.seasar.doma.Dao;
import org.seasar.doma.Delete;
import org.seasar.doma.Insert;
import org.seasar.doma.Select;
import org.seasar.doma.Update;
import org.seasar.doma.jdbc.SelectOptions;

import java.util.List;

/**
 */
@Dao
@ComponentAndAutowiredDomaConfig
public interface LendingBookDao {

    /**
     * @param lendingBookId
     * @return the LendingBook entity
     */
    @Select
    LendingBook selectById(Long lendingBookId);
    @Select(ensureResult = true)
    LendingBook selectByIdAndVersion(Long lendingBookId, Long version);
    @Select
    List<LendingBook> selectByLendingAppId(Long lendingAppId);
    @Select
    List<LendingBook> selectByLendingAppId(Long lendingAppId, SelectOptions options);
    @Select
    List<LendingBook> selectByLendingAppIdAndLendingAppFlg(Long lendingAppId, String lendingAppFlg);
    @Select
    List<LendingBook> selectByLendingAppIdAndLendingAppFlg(Long lendingAppId, String lendingAppFlg, SelectOptions options);
    
    /**
     * @param entity
     * @return affected rows
     */
    @Insert
    int insert(LendingBook entity);

    /**
     * @param entity
     * @return affected rows
     */
    @Update
    int update(LendingBook entity);
    @Update(include = {"lendingState"})
    int updateLendingState(LendingBook entity);
    @Update(include = {"lendingAppFlg", "lendingAppReason"})
    int updateLendingAppFlgAndReason(LendingBook entity);
    
    /**
     * @param entity
     * @return affected rows
     */
    @Delete
    int delete(LendingBook entity);
}
  • selectByLendingAppIdAndLendingAppFlg メソッドを追加します。

selectByLendingAppIdAndLendingAppFlg.sql

select
  /*%expand*/*
from
  lending_book
where
  lending_app_id = /* lendingAppId */105
  /*%if lendingAppFlg != null */
    and lending_app_flg = /* lendingAppFlg */'1'
  /*%end*/
order by
  lending_book_id

messages_ja_JP.properties

LendingappForm.lendingAppId.emptyerr=貸出申請IDが指定されていません。
LendingappForm.lendingApp.nodataerr=指定された貸出申請IDのデータが登録されておりません。
LendingappForm.lendingBookDtoList.notExistApply=最低1冊「申請する」を選択してください。
LendingappForm.lendingBookDtoList.emptyReason=「申請する」を選択している書籍の申請理由を入力してください。

LendingapprovalParamForm.lendingAppId.emptyerr=貸出申請IDが指定されていません。
LendingapprovalForm.lendingApp.nodataerr=指定された貸出申請IDでは貸出申請されておりません。
  • LendingapprovalParamForm.lendingAppId.emptyerr, LendingapprovalForm.lendingApp.nodataerr を追加します。

LendingBookApprovalResultValues.java

package ksbysample.webapp.lending.values;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum LendingBookApprovalResultValues implements Values {

    APPROVAL("1", "承認")
    , REJECT("2", "却下");

    private final String value;
    private final String text;

}

LendingapprovalService.java

package ksbysample.webapp.lending.web.lendingapproval;

import ksbysample.webapp.lending.dao.LendingAppDao;
import ksbysample.webapp.lending.dao.LendingBookDao;
import ksbysample.webapp.lending.dao.UserInfoDao;
import ksbysample.webapp.lending.entity.LendingApp;
import ksbysample.webapp.lending.entity.LendingBook;
import ksbysample.webapp.lending.entity.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;

import static ksbysample.webapp.lending.values.LendingAppStatusValues.APPLOVED;
import static ksbysample.webapp.lending.values.LendingAppStatusValues.PENDING;
import static ksbysample.webapp.lending.values.LendingBookLendingAppFlgValues.APPLY;

@Service
public class LendingapprovalService {

    @Autowired
    private LendingAppDao lendingAppDao;

    @Autowired
    private UserInfoDao userInfoDao;

    @Autowired
    private LendingBookDao lendingBookDao;

    public void setDispData(Long lendingAppId, LendingapprovalForm lendingapprovalForm) {
        LendingApp lendingApp = lendingAppDao.selectByIdAndStatus(lendingAppId
                , Arrays.asList(PENDING.getValue(), APPLOVED.getValue()));
        String username = "";
        if (lendingApp != null) {
            UserInfo userInfo = userInfoDao.selectById(lendingApp.getLendingUserId());
            username = userInfo.getUsername();
        }
        List<LendingBook> lendingBookList
                = lendingBookDao.selectByLendingAppIdAndLendingAppFlg(lendingAppId, APPLY.getValue());

        lendingapprovalForm.setLendingApp(lendingApp);
        lendingapprovalForm.setUsername(username);
        lendingapprovalForm.setApplyingBookFormList(lendingBookList);
    }

}

LendingapprovalController.java

package ksbysample.webapp.lending.web.lendingapproval;

import ksbysample.webapp.lending.exception.WebApplicationRuntimeException;
import ksbysample.webapp.lending.helper.message.MessagesPropertiesHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/lendingapproval")
public class LendingapprovalController {

    @Autowired
    private LendingapprovalService lendingapprovalService;

    @Autowired
    private MessagesPropertiesHelper messagesPropertiesHelper;

    @RequestMapping
    public String index(@Validated LendingapprovalParamForm lendingapprovalParamForm
            , BindingResult bindingResult
            , LendingapprovalForm lendingapprovalForm
            , BindingResult bindingResultOfLendingapprovalForm) {
        if (bindingResult.hasErrors()) {
            throw new WebApplicationRuntimeException(
                    messagesPropertiesHelper.getMessage("LendingapprovalParamForm.lendingAppId.emptyerr", null));
        }

        // 画面に表示するデータを取得する
        lendingapprovalService.setDispData(lendingapprovalParamForm.getLendingAppId(), lendingapprovalForm);

        // 指定された貸出申請IDで申請中、承認済のデータがない場合には、貸出承認画面上にエラーメッセージを表示する
        if (lendingapprovalForm.getLendingApp() == null) {
            bindingResultOfLendingapprovalForm.reject("LendingapprovalForm.lendingApp.nodataerr");
        }
        
        return "lendingapproval/lendingapproval";        
    }

    @RequestMapping(value = "/complete", method = RequestMethod.POST)
    public String complete() {
        return "lendingapproval/lendingapproval";
    }

}
  • @Autowired private LendingapprovalService lendingapprovalService; を追加します。
  • @Autowired private MessagesPropertiesHelper messagesPropertiesHelper; を追加します。
  • index メソッドの以下の点を変更します。
    • 引数に lendingapprovalParamForm, bindingResult, lendingapprovalForm, bindingResultOfLendingapprovalForm の4つを追加します。
    • 内部の処理を実装します。

lendingapproval.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <title>貸出承認</title>
    <!-- Tell the browser to be responsive to screen width -->
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"/>
    <link th:replace="common/head-cssjs"/>

    <style>
        .content-wrapper {
            background-color: #fffafa;
        }
        .selected-library {
            color: #ffffff !important;
            font-size: 100%;
            font-weight: 700;
        }
        .box-body.no-padding {
            padding-bottom: 10px !important;
        }
        .table>tbody>tr>td
        , .table>tbody>tr>th
        , .table>tfoot>tr>td
        , .table>tfoot>tr>th
        , .table>thead>tr>td
        , .table>thead>tr>th {
            padding: 5px;
            font-size: 90%;
        }
        .jp-gothic {
            font-family: Verdana, "游ゴシック", YuGothic, "Hiragino Kaku Gothic ProN", Meiryo, sans-serif;
        }
        .btn-approval.active {
            background-color: #00a65a;
        }
        .btn-reject.active {
            background-color: #dd4b39;
        }
    </style>
</head>

<!-- ADD THE CLASS layout-top-nav TO REMOVE THE SIDEBAR. -->
<body class="skin-blue layout-top-nav">
<div class="wrapper">

    <!-- Main Header -->
    <div th:replace="common/mainparts :: main-header"></div>

    <!-- Full Width Column -->
    <div class="content-wrapper">
        <div class="container">
            <!-- Content Header (Page header) -->
            <section class="content-header">
                <h1>貸出承認</h1>
            </section>

            <!-- Main content -->
            <section class="content">
                <div class="row">
                    <div class="col-xs-12">
                        <form id="lendingapprovalForm" method="post" action="/lendingapproval/complete"
                              th:action="@{/lendingapproval/complete}" th:object="${lendingapprovalForm}">
                            <div class="alert alert-danger" th:if="${#fields.hasGlobalErrors()}">
                                <p th:each="err : ${#fields.globalErrors()}" th:text="${err}">共通エラーメッセージ表示エリア</p>
                            </div>
                            <div class="box" th:if="*{lendingApp != null}">
                                <div class="box-body no-padding">
                                    <div class="col-xs-6 no-padding">
                                        <table class="table table-bordered">
                                            <colgroup>
                                                <col width="30%"/>
                                                <col width="70%"/>
                                            </colgroup>
                                            <tr>
                                                <th class="bg-purple">貸出申請ID</th>
                                                <td th:text="*{lendingApp.lendingAppId}">1</td>
                                            </tr>
                                            <tr>
                                                <th class="bg-purple">ステータス</th>
                                                <td th:text="${@vh.getText('LendingAppStatusValues', lendingapprovalForm.lendingApp.status)}">申請中</td>
                                            </tr>
                                            <tr>
                                                <th class="bg-purple">申請者</th>
                                                <td th:text="*{username}">田中 太郎</td>
                                            </tr>
                                        </table>
                                    </div>
                                    <br/>
    
                                    <table class="table">
                                        <colgroup>
                                            <col width="5%"/>
                                            <col width="15%"/>
                                            <col width="15%"/>
                                            <col width="20%"/>
                                            <col width="20%"/>
                                            <col width="25%"/>
                                        </colgroup>
                                        <thead class="bg-purple">
                                        <tr>
                                            <th>No.</th>
                                            <th>ISBN</th>
                                            <th>書名</th>
                                            <th>申請理由</th>
                                            <th>承認/却下</th>
                                            <th>却下理由</th>
                                        </tr>
                                        </thead>
                                        <tbody class="jp-gothic">
                                        <tr th:each="applyingBookForm, iterStat : *{applyingBookFormList}">
                                            <td th:text="${iterStat.count}">1</td>
                                            <td th:text="${applyingBookForm.isbn}">978-4-7741-6366-6</td>
                                            <td th:text="${applyingBookForm.bookName}">GitHub実践入門</td>
                                            <td th:text="${applyingBookForm.lendingAppReason}">開発で使用する為</td>
                                            <td>
                                                <div class="btn-group-sm" data-toggle="buttons">
                                                    <label class="btn btn-default btn-approval">
                                                        <input type="radio"
                                                               th:field="*{applyingBookFormList[__${iterStat.index}__].approvalResult}"
                                                               th:value="${@vh.getValue('LendingBookApprovalResultValues', 'APPROVAL')}"/>
                                                        承認
                                                    </label>
                                                    <label class="btn btn-default btn-reject">
                                                        <input type="radio"
                                                               th:field="*{applyingBookFormList[__${iterStat.index}__].approvalResult}"
                                                               th:value="${@vh.getValue('LendingBookApprovalResultValues', 'REJECT')}"/>
                                                        却下
                                                    </label>
                                                </div>
                                            </td>
                                            <td>
                                                <input type="text" class="form-control input-sm"
                                                       th:field="*{applyingBookFormList[__${iterStat.index}__].approvalReason}"/>
                                            </td>
                                            <input type="hidden" th:field="*{applyingBookFormList[__${iterStat.index}__].lendingBookId}"/>
                                            <input type="hidden" th:field="*{applyingBookFormList[__${iterStat.index}__].version}"/>
                                        </tr>
                                        </tbody>
                                    </table>
                                    <div class="text-center">
                                        <button class="btn bg-blue js-btn-complete"><i class="fa fa-check-square-o"></i> 確定</button>
                                    </div>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>
            </section>
            <!-- /.content -->
        </div>
        <!-- /.container -->
    </div>

</div>
<!-- ./wrapper -->

<script th:replace="common/bottom-js"></script>
<script type="text/javascript">
    <!--
    $(document).ready(function() {
        $(".js-btn-complete").click(function(){
            $("#lendingapprovalForm").submit();
            return false;
        });
    });
    -->
</script>
</body>
</html>
  • <form id="lendingapprovalForm" ...><div class="box"> の上へ移動します。</form> も対応する位置へ移動します。
  • <form id="lendingapprovalForm" ...> の下に共通エラーメッセージ表示エリアである <div class="alert alert-danger" th:if="${#fields.hasGlobalErrors()}"> ... </div> を追加します。
  • table タグ内でデータを表示する部分のタグが th タグを使用していたので td タグに変更します。
  • 貸出申請ID, ステータス, 申請者にデータが表示されるよう th:text="..." を追加します。ステータスは ValuesHelper クラスを利用して th:text="${@vh.getText('LendingAppStatusValues', lendingapprovalForm.lendingApp.status)}" でコードに対応する文字列が表示されるようにします。
  • <tbody class="jp-gothic"> の下の <tr><tr th:each="applyingBookForm, iterStat : *{applyingBookFormList}"> へ変更します。貸出申請画面と同様にイテレーション処理中のステータスを取るための変数 iterStat を記述します。
  • 一覧表の各行にデータが表示されるように th:text="..." を追加します。
  • 承認/却下の選択ボタンは以下の点を変更します。
    • th:field="*{applyingBookFormList[__${iterStat.index}__].approvalResult}" を記述して Form オブジェクトと関連付けます。
    • th:value="${@vh.getValue('LendingBookApprovalResultValues', '...')}" を追加します。
  • 却下理由の入力フィールドに th:field="..." を記述して Form オブジェクトと関連付けます。
  • 更新時にキー項目が必要になるので、<input type="hidden" th:field="*{lendingBookList[__${iterStat.index}__].lendingBookId}"/>, <input type="hidden" th:field="*{applyingBookFormList[__${iterStat.index}__].version}"/> を追加します。

履歴

2016/01/10
初版発行。