【はじめてのJava】ダッシュボードAPIを作成しよう!④総学習時間API編

はじめに

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

本記事の対象の方

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

Javaとは?

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

詳しい説明については、過去のブログ記事をご参照ください。
⇒過去のブログ一覧

環境構築

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

ダッシュボードAPIの作成

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

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

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

今回は、3.各グラフ描画に必要なデータを取得するAPIの実装について詳しく触れていきたいと思います。5つグラフがありますが、上段真ん中の総学習時間円グラフの制作に取り掛かりたいと思います。

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

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

総学習時間取得APIの設計

エンドポイント

/top/getLoginHistoryByUserId
  • HTTPメソッド: POST
  • リクエストボディ: ユーザーID、コースID
  • レスポンス: 総学習時間

リクエスト例

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

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

レスポンス例

{
  "totalLearningTime": 0,
  "errorMessageList": null
}

総学習時間APIの実装

モデルクラスの作成

まず、ExerciseRecords テーブルに対応するエンティティクラスを定義します。このクラスは、データベースから取得した学習記録の情報を保持します。

ExerciseRecordsEntity.java
 1package jp.co.smsdatatech.questiongenerate.entity;
2 
3import java.io.Serializable;
4import java.sql.Timestamp;
5import lombok.Data;
6 
7/**
8 *
9 * ExerciseRecordsテーブルEntity
10 */
11@Data
12public class ExerciseRecordsEntity implements Serializable {
13 /** シリアルバージョンUID */
14 private static final long serialVersionUID = 1L;
15 
16 /** ユーザーID */
17 private Integer userId;
18 
19 /** コースID */
20 private Integer courseId;
21 
22 /** 演習開始日時 */
23 private Timestamp startDate;
24 
25 /** 作成日 */
26 private Timestamp createDate;
27 
28 /** 更新日 */
29 private Timestamp updateDate;
30}

MyBatisのMapper設定

ExerciseRecordsMapper では、SQLを定義して、学習時間を合計します。EXTRACT(EPOCH FROM (end_time – start_time)) を使って学習時間を計算し、その結果を合計して返します。

ExerciseRecordsMapper.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.ExerciseRecordsMapper">
4 <select id="getTotalLearning" resultType="java.math.BigDecimal">
5 SELECT
6 SUM(EXTRACT(EPOCH FROM (end_time - start_time)) / 3600) AS totalLearningTime
7 FROM
8 exercise_records
9 WHERE
10 user_id = #{userId}
11 AND course_id = #{courseId}
12 AND start_time BETWEEN #{startOfWeek} AND #{endOfWeek}
13 </select>
14</mapper>

サービスクラスの作成

次に、学習時間を取得するためのサービスクラスを作成します。ここでは、getTotalLearningTime メソッドを定義して、週の開始日と終了日を計算し、その範囲内での学習時間を集計します。

ExerciseRecordsService.java
 1import lombok.extern.slf4j.Slf4j;
2 
3/**
4 * 演習履歴テーブルService
5 */
6@Slf4j
7@Service
8public class ExerciseRecordsService {
9 
10 private final ExerciseRecordsMapper exerciseRecordsMapper;
11 
12 // コンストラクタインジェクションを使用
13 @Autowired
14 public ExerciseRecordsService(ExerciseRecordsMapper exerciseRecordsMapper) {
15 this.exerciseRecordsMapper = exerciseRecordsMapper;
16 }
17 
18 /**
19 * ユーザーIDとコースIDを基に、週の範囲内で合計学習時間を取得する。
20 *
21 * @param userId ユーザーID
22 * @param courseId コースID
23 * @return 総学習時間
24 */
25 @Transactional(readOnly = true)
26 public BigDecimal getTotalLearningTime(Integer userId, Integer courseId) {
27 
28 // 週の開始日と終了日を計算
29 LocalDateTime startOfWeek = DateUtils.getStartOfWeek();
30 LocalDateTime endOfWeek = DateUtils.getEndOfWeek();
31 
32 log.debug("ユーザーID {} とコースID {} の合計学習時間を週の範囲 {} - {} で取得します。", userId, courseId, startOfWeek, endOfWeek);
33 return exerciseRecordsMapper.getTotalLearning(userId, courseId, startOfWeek, endOfWeek);
34 }
35}

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

次に、Spring BootのRESTコントローラを作成し、getTotalLearningTime メソッドを呼び出して、クライアントに学習時間を返却します。

TopRest.java
 1/**
2 * 総学習時間を取得.
3 * <h3>機能概要</h3>
4 * ユーザーIDおよびコースIDに基づいて総学習時間を取得し、TotalLearningTimeResponseFormに詰めて返却する。
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 総学習時間リスト TotalLearningTimeResponseForm
15 */
16@PostMapping("/top/getTotalLearningTime")
17public TotalLearningTimeResponseForm getTotalLearningTime(
18 @RequestBody @Validated({ ValidationGroups.Step1.class, ValidationGroups.Step2.class }) UserIdForm form,
19 BindingResult result) {
20 log.debug("TopRest (getTotalLearningTime) 呼び出し開始");
21 
22 TotalLearningTimeResponseForm responseForm = new TotalLearningTimeResponseForm();
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 // サービスで合計学習時間を取得(学習時間がない場合はデフォルト値0.0)
39 BigDecimal totalLearningTime = exerciseRecordsService.getTotalLearningTime(userId, courseId);
40 if (totalLearningTime == null) {
41 totalLearningTime = BigDecimal.ZERO;
42 }
43 
44 responseForm.setTotalLearningTime(totalLearningTime);
45 log.debug("TopRest (getTotalLearningTime) 呼び出し終了");
46 
47 return responseForm;
48}

実装内容の振り返り

1. ユーティリティクラスの活用

以前作成した DateUtils クラスを活用し、週の開始日と終了日を簡単に取得できました。このおかげで、日付操作をシンプルに保ち、コードの重複を避けることができました。

2. BigDecimalの利用

学習時間の合計を計算するために BigDecimal を使用しました。BigDecimal は精度の高い数値計算が可能で、浮動小数点誤差を防ぐために最適です。学習時間がない場合は BigDecimal.ZERO を使用してデフォルト値を設定しました。

まとめ

今回の実装では、ユーティリティクラスの再利用と BigDecimal の活用で、精度高くシンプルなコードが書けました。これにより、学習時間計算が正確で信頼性のあるものとなり、保守性が向上しました。
次回も、各グラフの作成方法について記載しますのでお楽しみに!

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