【はじめてのJava】ダッシュボードAPIを作成しよう!⑤分野別進捗データ取得編

はじめに

こんにちは!マーケターから未経験のエンジニアに転身した社員Tです。
開発を始めて1年が経ち、サーバーサイド開発にも触れるようになりました。
今回は、Javaを使用したダッシュボードAPIの作成についてご紹介します。
これまでフロントエンド中心の開発を行っていた私が、サーバーサイドのAPI開発に挑戦していく様子をお届けします。

本記事の対象の方

  • サーバーサイド開発をこれから学びたい方
  • JavaでのAPI開発に興味がある方
  • ダッシュボードの作成を通じて、実践的な技術を学びたい方

Javaとは?

Javaは、非常に人気のあるオブジェクト指向プログラミング言語で、さまざまなアプリケーションの開発に広く使用されています。

詳しい説明については、過去のブログ記事をご参照ください。
⇒関連記事はこちら

環境構築

環境構築に関しては、内容が広範囲にわたるため、別の記事で詳細に解説する予定です。

ダッシュボードAPIの作成

今回作成したいのは、こんなダッシュボード画面です。ユーザーがコースを選択することで、自分の学習状況を一目で確認できるようにすることを目指しています。

全体の作成の流れとしては、下記の通りとなります。

  1. ユーザー情報を取得
  2. コース情報を取得
  3. 各グラフ描画に必要なデータを取得

今回は、3.各グラフ描画に必要なデータを取得するAPIの実装について詳しく触れていきたいと思います。5つグラフがありますが、右上の各分野別の習得状況の表の制作に取り掛かりたいと思います。

1. ユーザー情報の取得については、過去のブログ記事をご参考ください。
⇒関連記事はこちら

2. コース情報の取得については、過去のブログ記事をご参考ください。
⇒関連記事はこちら

分野別進捗データ取得APIの設計

エンドポイント

/top/getViewHistory
  • HTTPメソッド: POST
  • リクエストボディ: ユーザーID、コースID
  • レスポンス: 分野別の進捗率のリスト

リクエスト例

{
    "courseId":"7",
    "userId":"175"
}

今回はパスパラメータではなく、リクエストボディからユーザーIDとコースIDを受け取る設計にしています。これにより、柔軟なデータの受け渡しが可能となります。

レスポンス例

{
    "viewHistoryList": [
        {
            "fieldId": 1,
            "fieldName": "Javaの基礎",
            "fieldMaxProgress": 30.00
        },
        {
            "fieldId": 2,
            "fieldName": "Javaオブジェクト指向",
            "fieldMaxProgress": 40.00
        }
    ],
    "errorMessageList": null
}

分野別進捗データAPIの実装

モデルクラスの作成

ViewHistoryテーブルに対応するEntityクラスを作成します。Lombokを利用しており、getter、setter、toString 等を自動生成します。

ViewHistoryEntity.java
 1package jp.co.smsdatatech.questiongenerate.entity;
2 
3import java.io.Serializable;
4import java.math.BigDecimal;
5 
6import lombok.Data;
7 
8/**
9 *
10 * ViewHistoryテーブルEntity
11 */
12@Data
13public class ViewHistoryEntity implements Serializable {
14 
15 /** シリアルバージョンUID */
16 private static final long serialVersionUID = 1L;
17 
18 /** 分野ID */
19 private Integer fieldId;
20 
21 /** 分野名 */
22 private String fieldName;
23 
24 /** 最大進捗率(集計結果) */
25 private BigDecimal fieldMaxProgress;
26}

MyBatisのMapper設定

MyBatis を使用して view_history テーブルからデータを取得する SQL マッピングを定義します。

ViewHistoryMapper.xml
 1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3<mapper namespace="jp.co.smsdatatech.questiongenerate.repository.ViewHistoryMapper">
4 
5 <select id="getViewHistoryWithMaxProgress" resultMap="ViewHistoryResultMap">
6 SELECT
7 subquery.course_id,
8 subquery.field_id,
9 subquery.fieldMaxProgress,
10 f.field_name
11 FROM
12 (SELECT
13 vh.course_id,
14 vh.field_id,
15 MAX(vh.progress_percentage) AS fieldMaxProgress
16 FROM
17 view_history vh
18 WHERE
19 vh.user_id = #{userId}
20 AND vh.course_id = #{courseId}
21 AND vh.viewed_at BETWEEN #{startOfWeek} AND #{endOfWeek}
22 GROUP BY
23 vh.course_id, vh.field_id) AS subquery
24 INNER JOIN
25 fields f
26 ON
27 subquery.field_id = f.field_id;
28 </select>
29</mapper>

サービスクラスの作成

Service層でDBからデータを取得する処理を実装します。

ViewHistoryService.java
 1/**
2 * 学習履歴リスト作成Service
3 */
4@Slf4j
5@Service
6public class ViewHistoryService {
7 
8 private final ViewHistoryMapper viewHistoryMapper;
9 
10 // コンストラクタインジェクションを使用
11 @Autowired
12 public ViewHistoryService(ViewHistoryMapper viewHistoryMapper) {
13 this.viewHistoryMapper = viewHistoryMapper;
14 }
15 
16 /**
17 * 指定されたユーザーID、コースID、期間に基づいて分野別に最大進捗率を取得する。
18 *
19 * @param userId ユーザーID
20 * @param courseId コースID
21 * @return 学習履歴(分野別に最大進捗率)のリスト
22 */
23 @Transactional(readOnly = true)
24 public List<ViewHistoryEntity> getViewHistoryWithMaxProgress(Integer userId, Integer courseId) {
25 // 週の開始日と終了日を計算
26 LocalDateTime startOfWeek = DateUtils.getStartOfWeek();
27 LocalDateTime endOfWeek = DateUtils.getEndOfWeek();
28 
29 log.debug("ユーザーID {} のコースID {} の学習履歴と最大進捗率を週の範囲 {} - {} で取得します。", userId, courseId, startOfWeek, endOfWeek);
30 
31 // データベースから分野別の最大進捗率を直接返す
32 return viewHistoryMapper.getViewHistoryWithMaxProgress(userId, courseId, startOfWeek, endOfWeek);
33 }
34}

コントローラークラスの作成

@PostMapping を使用してエンドポイント /top/getViewHistory を提供します。リクエストのバリデーションを行い、最大進捗率を返します。

TopRest.java
 1/**
2 * 分野別の最大進捗率を取得.
3 * <h3>機能概要</h3>
4 * ユーザーIDおよびコースIDに基づいて分野別の最大進捗率を取得し、ViewHistoryResponseFormに詰めて返却する。
5 *
6 * <h3>処理フロー</h3>
7 * 1. リクエストデータのバリデーション<br>
8 * 2. サービスクラスを呼び出して、最大進捗率を取得<br>
9 * 3. 進捗率が見つからない場合はエラーメッセージを設定<br>
10 * 4. 最大進捗率またはエラーメッセージリストを返却
11 *
12 * @param form UserIdForm
13 * @param result バリデーション結果やエラーを格納
14 * @return 最大進捗率リスト ViewHistoryResponseForm
15 */
16@PostMapping("/top/getViewHistory")
17public ViewHistoryResponseForm getViewHistory(
18 @RequestBody @Validated({ ValidationGroups.Step1.class, ValidationGroups.Step2.class }) UserIdForm form,
19 BindingResult result) {
20 log.debug("TopRest (getViewHistory) 呼び出し開始");
21 
22 ViewHistoryResponseForm responseForm = new ViewHistoryResponseForm();
23 List<String> errorMessageList = new ArrayList<>();
24 
25 // バリデーションエラーがある場合、詳細なエラーメッセージを追加
26 if (result.hasErrors()) {
27 for (FieldError fieldError : result.getFieldErrors()) {
28 String errorMessage = messageSource.getMessage(fieldError, Locale.getDefault());
29 errorMessageList.add(errorMessage);
30 }
31 responseForm.setErrorMessageList(errorMessageList);
32 return responseForm;
33 }
34 
35 Integer userId = Integer.parseInt(form.getUserId());
36 Integer courseId = form.getCourseId();
37 
38 // 最大進捗率のリストを取得
39 List<ViewHistoryEntity> viewHistoryList = viewHistoryService.getViewHistoryWithMaxProgress(userId, courseId);
40 
41 // リストが取得できなかった場合
42 if (CollectionUtils.isEmpty(viewHistoryList)) {
43 errorMessageList
44 .add(messageSource.getMessage(ErrorMessages.VIEW_HISTORY_NOT_FOUND, null, Locale.getDefault()));
45 responseForm.setErrorMessageList(errorMessageList);
46 log.debug("ViewHistoryList (viewHistoryList) 学習履歴が見つかりません: ");
47 return responseForm;
48 }
49 responseForm.setViewHistoryList(viewHistoryList);
50 log.debug("TopRest (getViewHistory) 呼び出し終了");
51 
52 return responseForm;
53}

実装内容の振り返り

バリデーショングループを作成した点は新たな学びがありました。これまで単一のバリデーションルールを適用していましたが、バリデーショングループを導入することで、処理ごとに異なるバリデーションルールを適用できるようになりました。たとえば、新規登録時と更新時で必須項目を変える必要があるケースに対応しやすくなりました。

さらに、SQL の部分では INNER JOIN を活用し、効率的にデータを取得できるようにしました。view_history テーブルから最新の進捗データを取得し、fields テーブルと結合することで、分野名を含めた結果を一度のクエリで取得できるようになりました。

まとめ

今回の実装を通じて、バリデーションの柔軟な適用方法や共通部品の活用によるコードの整理、SQL の最適化といった学びを得ることができました。今後も、より効率的なコード設計を意識しながら開発を進めていきたいと思います。

その他のイベント・セミナー 一覧へ