<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>やってみた | SMS DataTech</title>
	<atom:link href="https://www.sms-datatech.co.jp/category/try/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.sms-datatech.co.jp</link>
	<description></description>
	<lastBuildDate>Wed, 27 May 2026 03:58:19 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://www.sms-datatech.co.jp/wp-content/uploads/2022/09/cropped-cropped-favicon-180x180-1-32x32.png</url>
	<title>やってみた | SMS DataTech</title>
	<link>https://www.sms-datatech.co.jp</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>【はじめてのJava】ダッシュボードAPIを作成しよう！⑦試験結果分布グラフ編【完結】</title>
		<link>https://www.sms-datatech.co.jp/column/try_java-7/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=try_java-7</link>
		
		<dc:creator><![CDATA[後藤]]></dc:creator>
		<pubDate>Fri, 29 May 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[やってみた]]></category>
		<guid isPermaLink="false">https://www.sms-datatech.co.jp/?p=35825</guid>

					<description><![CDATA[<p>マーケターからエンジニアへ転身した社員による、Javaを用いたダッシュボードAPI作成の実践録。サーバーサイド初心者に向けて、試験結果分布グラフの設計から実装ステップ、開発を通じて得たリアルな気づきまでをわかりやすく解説します。</p>
<p>The post <a href="https://www.sms-datatech.co.jp/column/try_java-7/">【はじめてのJava】ダッシュボードAPIを作成しよう！⑦試験結果分布グラフ編【完結】</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">はじめに</h2>



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



<h2 class="wp-block-heading">本記事の対象の方</h2>



<ul class="wp-block-list">
<li>サーバーサイド開発をこれから学びたい方</li>



<li>JavaでのAPI開発に興味がある方</li>



<li>ダッシュボードの作成を通じて、実践的な技術を学びたい方</li>
</ul>



<h2 class="wp-block-heading">Javaとは？</h2>



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



<p>詳しい説明については、過去のブログ記事をご参照ください。<br><a href="https://www.sms-datatech.co.jp/category/try/" title=""><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">⇒関連記事はこちら</span></mark></strong></a></p>



<h2 class="wp-block-heading">環境構築</h2>



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



<h2 class="wp-block-heading">ダッシュボードAPIの作成</h2>



<p>今回作成したいのは、こんなダッシュボード画面です。<strong><mark style="background-color:#fdff84" class="has-inline-color">ユーザーがコースを選択することで、自分の学習状況を一目で確認できるようにすることを目指しています。</mark></strong></p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="465" src="https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1024x465.png" alt="" class="wp-image-34969" srcset="https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1024x465.png 1024w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-300x136.png 300w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-768x349.png 768w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1536x698.png 1536w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3.png 1736w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



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



<ol class="wp-block-list">
<li>ユーザー情報を取得</li>



<li>コース情報を取得</li>



<li>各グラフ描画に必要なデータを取得</li>
</ol>



<p>今回は、3.各グラフ描画に必要なデータを取得するAPIの実装について詳しく触れていきたいと思います。5つグラフがありますが、右下の同資格受験者との比較グラフを作成します。</p>



<p>1. ユーザー情報の取得については、過去のブログ記事をご参考ください。<br><a href="https://www.sms-datatech.co.jp/column/try_java-1/" title=""><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">⇒関連記事はこちら</span></mark></strong></a></p>



<p>2. コース情報の取得については、過去のブログ記事をご参考ください。<br><a href="https://www.sms-datatech.co.jp/column/try_java-2/" title=""><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">⇒関連記事はこちら</span></mark></strong></a></p>



<h2 class="wp-block-heading">試験情報取得APIの設計</h2>



<h3 class="wp-block-heading">エンドポイント</h3>



<pre style="background-color:#000;color:#fff;padding:16px;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,'Courier New',monospace;font-size:14px;line-height:1.5;margin:0 0 16px 0;"><code style="background-color:transparent;color:#fff;">/top/getScoreDistribution</code></pre>



<ul class="wp-block-list">
<li>HTTPメソッド: POST</li>



<li>リクエストボディ: ユーザーID、コースID</li>



<li>レスポンス: 模擬試験のスコアリスト</li>
</ul>



<h3 class="wp-block-heading">リクエスト例</h3>



<pre style="background-color:#000;color:#fff;padding:16px;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,'Courier New',monospace;font-size:14px;line-height:1.5;margin:0 0 16px 0;"><code style="background-color:transparent;color:#fff;">{
    "courseId":"7",
    "userId":"175"
}</code></pre>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回はパスパラメータではなく、リクエストボディからユーザーIDとコースIDを受け取る設計にしています。</mark></strong>これにより、柔軟なデータの受け渡しが可能となります。</p>



<h3 class="wp-block-heading">レスポンス例</h3>



<pre style="background-color:#000;color:#fff;padding:16px;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,'Courier New',monospace;font-size:14px;line-height:1.5;margin:0 0 16px 0;"><code style="background-color:transparent;color:#fff;">{
    "scoreDistribution": {
        "A": 5,
        "B": 3,
        "C": 2,
        "D": 10
    },
    "errorMessageList": null
}</code></pre>



<h2 class="wp-block-heading">試験情報取得APIの実装</h2>



<h3 class="wp-block-heading">モデルクラスの作成</h3>



<p>模擬試験履歴を表すエンティティクラスを作成します。このクラスはデータベースの trial_exam_history テーブルに対応し、各試験の履歴情報を管理します。</p>



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">TrialExamHistoryEntity.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#569cd6">package</span> <span style="color:#9cdcfe">jp</span>.<span style="color:#9cdcfe">co</span>.<span style="color:#9cdcfe">smsdatatech</span>.<span style="color:#9cdcfe">questiongenerate</span>.<span style="color:#9cdcfe">entity</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">java</span>.<span style="color:#9cdcfe">io</span>.<span style="color:#4ec9b0">Serializable</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">lombok</span>.<span style="color:#4ec9b0">Data</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span><span style="color:#6a9955">/**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span><span style="color:#6a9955"> * TrialExamHistoryテーブルEntity</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span><span style="color:#6a9955"> */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span><span style="color:#dcdcaa">@Data</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span><span style="color:#569cd6">public</span> <span style="color:#569cd6">class</span> <span style="color:#4ec9b0">TrialExamHistoryEntity</span> <span style="color:#569cd6">implements</span> <span style="color:#4ec9b0">Serializable</span> {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span><span style="color:#6a9955">    /** シリアルバージョンUID */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span>    <span style="color:#569cd6">private</span> <span style="color:#569cd6">static</span> <span style="color:#569cd6">final</span> <span style="color:#9cdcfe">long</span> <span style="color:#9cdcfe">serialVersionUID</span> = <span style="color:#b5cea8">1L</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span><span style="color:#6a9955">    /** 試験履歴ID */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">trialExamHistoryId</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span><span style="color:#6a9955">    /** 試験ID */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">trialExamId</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span><span style="color:#6a9955">    /** 試験回数 */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">trialExamTimes</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span><span style="color:#6a9955">    /** 試験スコア */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">trialScore</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span><span style="color:#6a9955">    /** ユーザーID */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">userId</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span>}</pre></div>



<h3 class="wp-block-heading">MyBatisのMapper設定</h3>



<p>このクエリでは、<strong><mark style="background-color:#fdff84" class="has-inline-color">RANK() 関数を用いて、各ユーザーの試験履歴をスコアの降順でランク付けし、最も高いスコアの試験のみを取得しています。</mark></strong></p>



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">TrialExamHistoryMapper.xml</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#569cd6">&lt;mapper</span> <span style="color:#9cdcfe">namespace</span>=<span style="color:#ce9178">&quot;jp.co.smsdatatech.questiongenerate.repository.TrialExamHistoryMapper&quot;</span><span style="color:#569cd6">&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span>    <span style="color:#569cd6">&lt;select</span> <span style="color:#9cdcfe">id</span>=<span style="color:#ce9178">&quot;selectLatestTrialExamHistoryList&quot;</span> <span style="color:#9cdcfe">resultMap</span>=<span style="color:#ce9178">&quot;TrialExamHistoryResultMap&quot;</span> <span style="color:#9cdcfe">resultType</span>=<span style="color:#ce9178">&quot;jp.co.smsdatatech.questiongenerate.entity.TrialExamHistoryEntity&quot;</span><span style="color:#569cd6">&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span>        <span style="color:#569cd6">SELECT</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span>            <span style="color:#d4d4d4">highest_score_exam.user_id</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span>            <span style="color:#d4d4d4">highest_score_exam.trial_exam_id</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span>            <span style="color:#d4d4d4">highest_score_exam.trial_exam_score</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span>            <span style="color:#d4d4d4">highest_score_exam.trial_exam_times</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span>        <span style="color:#569cd6">FROM</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span>            (<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span>                <span style="color:#569cd6">SELECT</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span>                    <span style="color:#d4d4d4">teh.user_id</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span>                    <span style="color:#d4d4d4">teh.trial_exam_id</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span>                    <span style="color:#d4d4d4">teh.trial_exam_score</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span>                    <span style="color:#d4d4d4">teh.trial_exam_times</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span>                    <span style="color:#569cd6">RANK</span>() <span style="color:#569cd6">OVER</span> (<span style="color:#569cd6">PARTITION</span> <span style="color:#569cd6">BY</span> <span style="color:#d4d4d4">teh.user_id</span> <span style="color:#569cd6">ORDER</span> <span style="color:#569cd6">BY</span> <span style="color:#d4d4d4">teh.trial_exam_score</span> <span style="color:#569cd6">DESC</span>, <span style="color:#d4d4d4">teh.trial_exam_times</span> <span style="color:#569cd6">DESC</span>) <span style="color:#569cd6">AS</span> <span style="color:#d4d4d4">rank_num</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span>                <span style="color:#569cd6">FROM</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span>                    <span style="color:#d4d4d4">trial_exam_history</span> <span style="color:#d4d4d4">teh</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span>                <span style="color:#569cd6">JOIN</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span>                    <span style="color:#d4d4d4">trial_exam</span> <span style="color:#569cd6">AS</span> <span style="color:#d4d4d4">exam</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span>                    <span style="color:#569cd6">ON</span> <span style="color:#d4d4d4">teh.trial_exam_id</span> = <span style="color:#d4d4d4">exam.trial_exam_id</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span>                <span style="color:#569cd6">WHERE</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>                    <span style="color:#d4d4d4">exam.course_id</span> = <span style="color:#9cdcfe">#{courseId}</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span>            ) <span style="color:#569cd6">AS</span> <span style="color:#d4d4d4">highest_score_exam</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span>        <span style="color:#569cd6">WHERE</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span>            <span style="color:#d4d4d4">highest_score_exam.rank_num</span> = <span style="color:#b5cea8">1</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span>    <span style="color:#569cd6">&lt;/select</span><span style="color:#569cd6">&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span><span style="color:#569cd6">&lt;/mapper</span><span style="color:#569cd6">&gt;</span></pre></div>



<h3 class="wp-block-heading">サービスクラスの作成</h3>



<p>trialExamHistoryMapper.selectLatestTrialExamHistoryList(courseId) を呼び出し、試験履歴を取得します。</p>



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">TrialExamHistoryService.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#6a9955">/**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span><span style="color:#6a9955"> * 指定したコースIDに基づいて、複数ユーザーの最新試験履歴を取得する。</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#6a9955"> *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span><span style="color:#6a9955"> * @param courseId コースID</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span><span style="color:#6a9955"> * @return 最新の模擬試験履歴リスト</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span><span style="color:#6a9955"> */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span><span style="color:#dcdcaa">@Transactional</span>(<span style="color:#9cdcfe">readOnly</span> = <span style="color:#569cd6">true</span>)<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span><span style="color:#569cd6">public</span> <span style="color:#4ec9b0">List</span>&lt;<span style="color:#4ec9b0">TrialExamHistoryEntity</span>&gt; <span style="color:#dcdcaa">selectLatestTrialExamHistoryList</span>(<span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">courseId</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span>    <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;コースID {} の最新の模擬試験履歴を取得します。&quot;</span>, <span style="color:#9cdcfe">courseId</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span><span style="color:#6a9955">    // データベースから複数ユーザーの最新試験履歴を取得</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span>    <span style="color:#4ec9b0">List</span>&lt;<span style="color:#4ec9b0">TrialExamHistoryEntity</span>&gt; <span style="color:#9cdcfe">latestTrialExamHistoryList</span> = <span style="color:#9cdcfe">trialExamHistoryMapper</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span>            .<span style="color:#dcdcaa">selectLatestTrialExamHistoryList</span>(<span style="color:#9cdcfe">courseId</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span>    <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;取得した複数ユーザーの最新模擬試験履歴リスト: {}&quot;</span>, <span style="color:#9cdcfe">latestTrialExamHistoryList</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span>    <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">latestTrialExamHistoryList</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span>}</pre></div>



<h3 class="wp-block-heading">コントローラークラスの作成</h3>



<p>selectLatestTrialExamHistoryList(courseId) で最新の試験履歴を取得。取得した履歴データをもとに、スコア範囲ごとの人数をカウントし、レスポンスとして返却します。</p>



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">TopRest.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#6a9955">/**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span><span style="color:#6a9955"> * 最新の模擬試験履歴を取得（スコア範囲ごとの人数分布のみ返却）。</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#6a9955"> *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span><span style="color:#6a9955"> * &lt;h3&gt;機能概要&lt;/h3&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span><span style="color:#6a9955"> * コースIDに基づいて、スコア範囲ごとの人数分布を取得する。</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span><span style="color:#6a9955"> *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span><span style="color:#6a9955"> * &lt;h3&gt;処理フロー&lt;/h3&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span><span style="color:#6a9955"> * 1. リクエストデータのバリデーション&lt;br&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span><span style="color:#6a9955"> * 2. サービスクラスを呼び出して、スコア範囲ごとの人数分布を取得&lt;br&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span><span style="color:#6a9955"> * 3. スコア範囲ごとの人数分布が見つからない場合はエラーメッセージを設定&lt;br&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span><span style="color:#6a9955"> * 4. スコア範囲ごとの人数分布を返却</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span><span style="color:#6a9955"> *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span><span style="color:#6a9955"> * @param form    UserIdForm</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span><span style="color:#6a9955"> * @param result  バリデーション結果やエラーを格納</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span><span style="color:#6a9955"> * @return スコア範囲ごとの人数分布 ScoreDistributionResponseForm</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span><span style="color:#6a9955"> */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span><span style="color:#dcdcaa">@PostMapping</span>(<span style="color:#ce9178">&quot;/top/getScoreDistribution&quot;</span>)<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span><span style="color:#569cd6">public</span> <span style="color:#4ec9b0">ScoreDistributionResponseForm</span> <span style="color:#dcdcaa">getScoreDistribution</span>(<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span>        <span style="color:#dcdcaa">@RequestBody</span> <span style="color:#dcdcaa">@Validated</span>({ <span style="color:#4ec9b0">ValidationGroups</span>.<span style="color:#4ec9b0">Step1</span>.<span style="color:#569cd6">class</span>, <span style="color:#4ec9b0">ValidationGroups</span>.<span style="color:#4ec9b0">Step2</span>.<span style="color:#569cd6">class</span> }) <span style="color:#4ec9b0">UserIdForm</span> <span style="color:#9cdcfe">form</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span>        <span style="color:#4ec9b0">BindingResult</span> <span style="color:#9cdcfe">result</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span>    <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;TopRest (getScoreDistribution) 呼び出し開始&quot;</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>    <span style="color:#4ec9b0">ScoreDistributionResponseForm</span> <span style="color:#9cdcfe">responseForm</span> = <span style="color:#569cd6">new</span> <span style="color:#4ec9b0">ScoreDistributionResponseForm</span>();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span>    <span style="color:#4ec9b0">List</span>&lt;<span style="color:#4ec9b0">String</span>&gt; <span style="color:#9cdcfe">errorMessageList</span> = <span style="color:#569cd6">new</span> <span style="color:#4ec9b0">ArrayList</span>&lt;&gt;();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span><span style="color:#6a9955">    // バリデーションエラーがある場合、詳細なエラーメッセージを追加</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span>    <span style="color:#569cd6">if</span> (<span style="color:#9cdcfe">result</span>.<span style="color:#dcdcaa">hasErrors</span>()) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span>        <span style="color:#569cd6">for</span> (<span style="color:#4ec9b0">FieldError</span> <span style="color:#9cdcfe">fieldError</span> : <span style="color:#9cdcfe">result</span>.<span style="color:#dcdcaa">getFieldErrors</span>()) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span>            <span style="color:#4ec9b0">String</span> <span style="color:#9cdcfe">errorMessage</span> = <span style="color:#9cdcfe">messageSource</span>.<span style="color:#dcdcaa">getMessage</span>(<span style="color:#9cdcfe">fieldError</span>, <span style="color:#4ec9b0">Locale</span>.<span style="color:#dcdcaa">getDefault</span>());<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">30</span>            <span style="color:#9cdcfe">errorMessageList</span>.<span style="color:#dcdcaa">add</span>(<span style="color:#9cdcfe">errorMessage</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">31</span>        }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">32</span>        <span style="color:#9cdcfe">responseForm</span>.<span style="color:#dcdcaa">setErrorMessageList</span>(<span style="color:#9cdcfe">errorMessageList</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">33</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">responseForm</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">34</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">35</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">36</span>    <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">courseId</span> = <span style="color:#9cdcfe">form</span>.<span style="color:#dcdcaa">getCourseId</span>();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">37</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">38</span><span style="color:#6a9955">    // 最新の模擬試験履歴リストを取得</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">39</span>    <span style="color:#4ec9b0">List</span>&lt;<span style="color:#4ec9b0">TrialExamHistoryEntity</span>&gt; <span style="color:#9cdcfe">latestTrialExamHistoryList</span> = <span style="color:#9cdcfe">trialExamHistoryService</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">40</span>            .<span style="color:#dcdcaa">selectLatestTrialExamHistoryList</span>(<span style="color:#9cdcfe">courseId</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">41</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">42</span><span style="color:#6a9955">    // リストが取得できなかった場合</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">43</span>    <span style="color:#569cd6">if</span> (<span style="color:#4ec9b0">CollectionUtils</span>.<span style="color:#dcdcaa">isEmpty</span>(<span style="color:#9cdcfe">latestTrialExamHistoryList</span>)) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">44</span>        <span style="color:#9cdcfe">errorMessageList</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">45</span>                .<span style="color:#dcdcaa">add</span>(<span style="color:#9cdcfe">messageSource</span>.<span style="color:#dcdcaa">getMessage</span>(<span style="color:#4ec9b0">ErrorMessages</span>.<span style="color:#4fc1ff">TRIAL_EXAM_HISTORY_NOT_FOUND</span>, <span style="color:#569cd6">null</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">46</span>                        <span style="color:#4ec9b0">Locale</span>.<span style="color:#dcdcaa">getDefault</span>()));<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">47</span>        <span style="color:#9cdcfe">responseForm</span>.<span style="color:#dcdcaa">setErrorMessageList</span>(<span style="color:#9cdcfe">errorMessageList</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">48</span>        <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;最新の模擬試験履歴が見つかりませんでした&quot;</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">49</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">responseForm</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">50</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">51</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">52</span><span style="color:#6a9955">    // スコア範囲ごとの人数をカウントする</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">53</span>    <span style="color:#4ec9b0">Map</span>&lt;<span style="color:#4ec9b0">String</span>, <span style="color:#4ec9b0">Integer</span>&gt; <span style="color:#9cdcfe">scoreDistribution</span> = <span style="color:#569cd6">new</span> <span style="color:#4ec9b0">HashMap</span>&lt;&gt;();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">54</span>    <span style="color:#569cd6">for</span> (<span style="color:#4ec9b0">TrialExamHistoryEntity</span> <span style="color:#9cdcfe">history</span> : <span style="color:#9cdcfe">latestTrialExamHistoryList</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">55</span>        <span style="color:#9cdcfe">int</span> <span style="color:#9cdcfe">score</span> = <span style="color:#9cdcfe">history</span>.<span style="color:#dcdcaa">getTrialScore</span>();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">56</span>        <span style="color:#4ec9b0">ScoreEnum</span> <span style="color:#9cdcfe">range</span> = <span style="color:#4ec9b0">ScoreEnum</span>.<span style="color:#dcdcaa">getRange</span>(<span style="color:#9cdcfe">score</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">57</span>        <span style="color:#569cd6">if</span> (<span style="color:#9cdcfe">range</span> != <span style="color:#569cd6">null</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">58</span><span style="color:#6a9955">            // スコア範囲に該当する人数を取り出し、1を加算する</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">59</span>            <span style="color:#9cdcfe">int</span> <span style="color:#9cdcfe">currentCount</span> = <span style="color:#9cdcfe">scoreDistribution</span>.<span style="color:#dcdcaa">getOrDefault</span>(<span style="color:#9cdcfe">range</span>.<span style="color:#dcdcaa">name</span>(), <span style="color:#b5cea8">0</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">60</span>            <span style="color:#9cdcfe">scoreDistribution</span>.<span style="color:#dcdcaa">put</span>(<span style="color:#9cdcfe">range</span>.<span style="color:#dcdcaa">name</span>(), <span style="color:#9cdcfe">currentCount</span> + <span style="color:#b5cea8">1</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">61</span>        }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">62</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">63</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">64</span><span style="color:#6a9955">    // スコア分布をResponseFormにセット</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">65</span>    <span style="color:#9cdcfe">responseForm</span>.<span style="color:#dcdcaa">setScoreDistribution</span>(<span style="color:#9cdcfe">scoreDistribution</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">66</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">67</span>    <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;TopRest (getScoreDistribution) 呼び出し終了&quot;</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">68</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">69</span>    <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">responseForm</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">70</span>}</pre></div>



<h3 class="wp-block-heading">Enumクラスの作成</h3>



<p>今回は、試験のスコア範囲を管理するために ScoreEnum を作成しました。スコアがどのランク（A, B, C, D）に属するかを判定するために使います。</p>



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">ScoreEnum.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#569cd6">package</span> <span style="color:#9cdcfe">jp</span>.<span style="color:#9cdcfe">co</span>.<span style="color:#9cdcfe">smsdatatech</span>.<span style="color:#9cdcfe">questiongenerate</span>.<span style="color:#9cdcfe">enums</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">lombok</span>.<span style="color:#4ec9b0">Getter</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span><span style="color:#6a9955">/**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span><span style="color:#6a9955"> * スコア範囲を表す列挙型クラス。</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span><span style="color:#6a9955"> *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span><span style="color:#6a9955"> * &lt;p&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span><span style="color:#6a9955"> * 各範囲はスコアの最小値と最大値によって定義されます。</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span><span style="color:#6a9955"> * スコアを基に適切なスコア範囲を判定する機能を提供します。</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span><span style="color:#6a9955"> * &lt;/p&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span><span style="color:#6a9955"> */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span><span style="color:#dcdcaa">@Getter</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span><span style="color:#569cd6">public</span> <span style="color:#569cd6">enum</span> <span style="color:#4ec9b0">ScoreEnum</span> {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span>    <span style="color:#4ec9b0">A</span>(<span style="color:#b5cea8">90</span>, <span style="color:#b5cea8">100</span>),  // <span style="color:#4fc1ff">A範囲</span>：<span style="color:#b5cea8">90</span>〜<span style="color:#b5cea8">100</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span>    <span style="color:#4ec9b0">B</span>(<span style="color:#b5cea8">80</span>, <span style="color:#b5cea8">89</span>),   // <span style="color:#4fc1ff">B範囲</span>：<span style="color:#b5cea8">80</span>〜<span style="color:#b5cea8">89</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span>    <span style="color:#4ec9b0">C</span>(<span style="color:#b5cea8">70</span>, <span style="color:#b5cea8">79</span>),   // <span style="color:#4fc1ff">C範囲</span>：<span style="color:#b5cea8">70</span>〜<span style="color:#b5cea8">79</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span>    <span style="color:#4ec9b0">D</span>(<span style="color:#b5cea8">0</span>, <span style="color:#b5cea8">69</span>);    // <span style="color:#4fc1ff">D範囲</span>：<span style="color:#b5cea8">0</span>〜<span style="color:#b5cea8">69</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span>    <span style="color:#569cd6">private</span> <span style="color:#569cd6">final</span> <span style="color:#9cdcfe">int</span> <span style="color:#9cdcfe">minScore</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span>    <span style="color:#569cd6">private</span> <span style="color:#569cd6">final</span> <span style="color:#9cdcfe">int</span> <span style="color:#9cdcfe">maxScore</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span><span style="color:#6a9955">    // コンストラクタ</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span>    <span style="color:#4ec9b0">ScoreEnum</span>(<span style="color:#9cdcfe">int</span> <span style="color:#9cdcfe">minScore</span>, <span style="color:#9cdcfe">int</span> <span style="color:#9cdcfe">maxScore</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span>        <span style="color:#569cd6">this</span>.<span style="color:#9cdcfe">minScore</span> = <span style="color:#9cdcfe">minScore</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span>        <span style="color:#569cd6">this</span>.<span style="color:#9cdcfe">maxScore</span> = <span style="color:#9cdcfe">maxScore</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span><span style="color:#6a9955">    // スコアがこの範囲に含まれるかどうかをチェック</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">30</span>    <span style="color:#569cd6">public</span> <span style="color:#9cdcfe">boolean</span> <span style="color:#dcdcaa">isInRange</span>(<span style="color:#9cdcfe">int</span> <span style="color:#9cdcfe">score</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">31</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">score</span> &gt;= <span style="color:#9cdcfe">minScore</span> &amp;&amp; <span style="color:#9cdcfe">score</span> &lt;= <span style="color:#9cdcfe">maxScore</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">32</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">33</span>}</pre></div>



<h2 class="wp-block-heading">実装内容の振り返り</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回の実装では、SQLのRANK()関数とENUMクラスを活用して、ダッシュボード画面のデータ集計を効率的に行いました。</mark></strong></p>



<h3 class="wp-block-heading">1. SQLの RANK() 関数について</h3>



<p>RANK()関数は、特定の条件でデータを順位付けするために使用しました。<strong><mark style="background-color:#fdff84" class="has-inline-color">RANK()を活用することで、プログラム側でループを回して順位を計算する必要がなくなり、SQLだけで効率的にデータを取得できるようになりました。</mark></strong></p>



<h3 class="wp-block-heading">2. ENUMクラス ScoreEnum について</h3>



<p>ScoreEnumは、スコアをランク（A, B, C, D）に分類するために作成しました。<strong><mark style="background-color:#fdff84" class="has-inline-color">ENUMを使うことで、スコアの評価ロジックを統一し、可読性を向上させることができました。</mark></strong>また、メソッドを定義することで、スコアがどのランクに属するかを簡単に判定できるようになり、コードの保守性も向上しました。</p>



<h2 class="wp-block-heading">まとめ</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回のブログ記事を通して、ダッシュボード画面のすべての実装が完了しました。</mark></strong>まだまだ改善の余地はあるかと思いますが、より良い書き方を模索しながら、さらに保守性や拡張性の高い実装を目指していきたいと思います。<br>長らく見ていただいてありがとうございました。次回は、テストやデータベース操作について、自身の学びをまとめていきたいと思います。</p><p>The post <a href="https://www.sms-datatech.co.jp/column/try_java-7/">【はじめてのJava】ダッシュボードAPIを作成しよう！⑦試験結果分布グラフ編【完結】</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【はじめてのJava】ダッシュボードAPIを作成しよう！⑥試験回数遷移グラフ編</title>
		<link>https://www.sms-datatech.co.jp/column/try_java-6/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=try_java-6</link>
		
		<dc:creator><![CDATA[後藤]]></dc:creator>
		<pubDate>Thu, 28 May 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[やってみた]]></category>
		<guid isPermaLink="false">https://www.sms-datatech.co.jp/?p=35823</guid>

					<description><![CDATA[<p>マーケターからエンジニアへ転身した社員による、Javaを用いたダッシュボードAPI作成の実践録。サーバーサイド初心者に向けて、試験回数遷移グラフの設計から実装ステップ、開発を通じて得たリアルな気づきまでをわかりやすく解説します。</p>
<p>The post <a href="https://www.sms-datatech.co.jp/column/try_java-6/">【はじめてのJava】ダッシュボードAPIを作成しよう！⑥試験回数遷移グラフ編</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">はじめに</h2>



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



<h2 class="wp-block-heading">本記事の対象の方</h2>



<ul class="wp-block-list">
<li>サーバーサイド開発をこれから学びたい方</li>



<li>JavaでのAPI開発に興味がある方</li>



<li>ダッシュボードの作成を通じて、実践的な技術を学びたい方</li>
</ul>



<h2 class="wp-block-heading">Javaとは？</h2>



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



<p>詳しい説明については、過去のブログ記事をご参照ください。<br><a href="https://www.sms-datatech.co.jp/category/try/" title=""><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">⇒関連記事はこちら</span></mark></strong></a></p>



<h2 class="wp-block-heading">環境構築</h2>



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



<h2 class="wp-block-heading">ダッシュボードAPIの作成</h2>



<p>今回作成したいのは、こんなダッシュボード画面です。<strong><mark style="background-color:#fdff84" class="has-inline-color">ユーザーがコースを選択することで、自分の学習状況を一目で確認できるようにすることを目指しています。</mark></strong></p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="465" src="https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1024x465.png" alt="" class="wp-image-34969" srcset="https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1024x465.png 1024w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-300x136.png 300w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-768x349.png 768w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1536x698.png 1536w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3.png 1736w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



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



<ol class="wp-block-list">
<li>ユーザー情報を取得</li>



<li>コース情報を取得</li>



<li>各グラフ描画に必要なデータを取得</li>
</ol>



<p>今回は、3.各グラフ描画に必要なデータを取得するAPIの実装について詳しく触れていきたいと思います。5つグラフがありますが、左下のスコア遷移表の制作に取り掛かりたいと思います。</p>



<p>1. ユーザー情報の取得については、過去のブログ記事をご参考ください。<br><a href="https://www.sms-datatech.co.jp/column/try_java-1/" title=""><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">⇒関連記事はこちら</span></mark></strong></a></p>



<p>2. コース情報の取得については、過去のブログ記事をご参考ください。<br><a href="https://www.sms-datatech.co.jp/column/try_java-2/" title=""><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">⇒関連記事はこちら</span></mark></strong></a></p>



<h2 class="wp-block-heading">試験情報取得APIの設計</h2>



<h3 class="wp-block-heading">エンドポイント</h3>



<pre style="background-color:#000;color:#fff;padding:16px;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,'Courier New',monospace;font-size:14px;line-height:1.5;margin:0 0 16px 0;"><code style="background-color:transparent;color:#fff;">/top/getTrialExamHistory</code></pre>



<ul class="wp-block-list">
<li>HTTPメソッド: POST</li>



<li>リクエストボディ: ユーザーID、コースID</li>



<li>レスポンス: 模擬試験のスコアリスト</li>
</ul>



<h3 class="wp-block-heading">リクエスト例</h3>



<pre style="background-color:#000;color:#fff;padding:16px;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,'Courier New',monospace;font-size:14px;line-height:1.5;margin:0 0 16px 0;"><code style="background-color:transparent;color:#fff;">{
    "courseId":"7",
    "userId":"175"
}</code></pre>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回はパスパラメータではなく、リクエストボディからユーザーIDとコースIDを受け取る設計にしています。</mark></strong>これにより、柔軟なデータの受け渡しが可能となります。</p>



<h3 class="wp-block-heading">レスポンス例</h3>



<pre style="background-color:#000;color:#fff;padding:16px;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,'Courier New',monospace;font-size:14px;line-height:1.5;margin:0 0 16px 0;"><code style="background-color:transparent;color:#fff;">{
    "trialExamHistoryList": [
        {
            "trialExamHistoryId": 63,
            "trialExamId": 1,
            "trialExamTimes": 5,
            "trialScore": 90,
            "userId": null
        },
        {
            "trialExamHistoryId": 69,
            "trialExamId": 1,
            "trialExamTimes": 2,
            "trialScore": 75,
            "userId": null
        },
        {
            "trialExamHistoryId": 72,
            "trialExamId": 1,
            "trialExamTimes": 1,
            "trialScore": 70,
            "userId": null
        }
    ],
    "errorMessageList": null
}</code></pre>



<h2 class="wp-block-heading">試験情報取得APIの実装</h2>



<h3 class="wp-block-heading">モデルクラスの作成</h3>



<p>模擬試験履歴を表すエンティティクラスを作成します。このクラスはデータベースの trial_exam_history テーブルに対応し、各試験の履歴情報を管理します。</p>



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">TrialExamHistoryEntity.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#569cd6">package</span> <span style="color:#9cdcfe">jp</span>.<span style="color:#9cdcfe">co</span>.<span style="color:#9cdcfe">smsdatatech</span>.<span style="color:#9cdcfe">questiongenerate</span>.<span style="color:#9cdcfe">entity</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">java</span>.<span style="color:#9cdcfe">io</span>.<span style="color:#4ec9b0">Serializable</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">lombok</span>.<span style="color:#4ec9b0">Data</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span><span style="color:#6a9955">/**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span><span style="color:#6a9955"> * TrialExamHistoryテーブルEntity</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span><span style="color:#6a9955"> */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span><span style="color:#dcdcaa">@Data</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span><span style="color:#569cd6">public</span> <span style="color:#569cd6">class</span> <span style="color:#4ec9b0">TrialExamHistoryEntity</span> <span style="color:#569cd6">implements</span> <span style="color:#4ec9b0">Serializable</span> {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span><span style="color:#6a9955">    /** シリアルバージョンUID */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span>    <span style="color:#569cd6">private</span> <span style="color:#569cd6">static</span> <span style="color:#569cd6">final</span> <span style="color:#9cdcfe">long</span> <span style="color:#9cdcfe">serialVersionUID</span> = <span style="color:#b5cea8">1L</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span><span style="color:#6a9955">    /** 試験履歴ID */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">trialExamHistoryId</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span><span style="color:#6a9955">    /** 試験ID */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">trialExamId</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span><span style="color:#6a9955">    /** 試験回数 */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">trialExamTimes</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span><span style="color:#6a9955">    /** 試験スコア */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">trialScore</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span><span style="color:#6a9955">    /** ユーザーID */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">userId</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span>}</pre></div>



<h3 class="wp-block-heading">MyBatisのMapper設定</h3>



<p>MyBatisのMapperインターフェースを定義し、模擬試験履歴を取得するメソッドを宣言します。指定されたユーザーIDとコースIDに基づいて直近5回分の模擬試験履歴を取得しています。</p>



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">TrialExamHistoryMapper.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#569cd6">package</span> <span style="color:#9cdcfe">jp</span>.<span style="color:#9cdcfe">co</span>.<span style="color:#9cdcfe">smsdatatech</span>.<span style="color:#9cdcfe">questiongenerate</span>.<span style="color:#9cdcfe">repository</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">java</span>.<span style="color:#9cdcfe">util</span>.<span style="color:#4ec9b0">List</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">org</span>.<span style="color:#9cdcfe">apache</span>.<span style="color:#9cdcfe">ibatis</span>.<span style="color:#9cdcfe">annotations</span>.<span style="color:#4ec9b0">Mapper</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">org</span>.<span style="color:#9cdcfe">apache</span>.<span style="color:#9cdcfe">ibatis</span>.<span style="color:#9cdcfe">annotations</span>.<span style="color:#4ec9b0">Param</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">jp</span>.<span style="color:#9cdcfe">co</span>.<span style="color:#9cdcfe">smsdatatech</span>.<span style="color:#9cdcfe">questiongenerate</span>.<span style="color:#9cdcfe">entity</span>.<span style="color:#4ec9b0">TrialExamHistoryEntity</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span><span style="color:#6a9955">/**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span><span style="color:#6a9955"> * TrialExamHistoryテーブルMapper</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span><span style="color:#6a9955"> */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span><span style="color:#dcdcaa">@Mapper</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span><span style="color:#569cd6">public</span> <span style="color:#569cd6">interface</span> <span style="color:#4ec9b0">TrialExamHistoryMapper</span> {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span><span style="color:#6a9955">    /**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span><span style="color:#6a9955">     * 指定されたユーザーID、コースIDに基づいて直近5回分の模擬試験履歴を取得する。</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span><span style="color:#6a9955">     *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span><span style="color:#6a9955">     * @param userId   ユーザーID</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span><span style="color:#6a9955">     * @param courseId コースID</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span><span style="color:#6a9955">     * @return 模擬試験スコアリスト</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span><span style="color:#6a9955">     */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span>    <span style="color:#4ec9b0">List</span>&lt;<span style="color:#4ec9b0">TrialExamHistoryEntity</span>&gt; <span style="color:#dcdcaa">selectTrialExamHistoryList</span>(<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>            <span style="color:#dcdcaa">@Param</span>(<span style="color:#ce9178">&quot;userId&quot;</span>) <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">userId</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span>            <span style="color:#dcdcaa">@Param</span>(<span style="color:#ce9178">&quot;courseId&quot;</span>) <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">courseId</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span><span style="color:#6a9955">    /**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span><span style="color:#6a9955">     * 指定したコースIDに基づいて、複数ユーザーの最新試験履歴を取得します。</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span><span style="color:#6a9955">     *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span><span style="color:#6a9955">     * @param courseId コースID</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">30</span><span style="color:#6a9955">     * @return 最新の試験履歴リスト（各ユーザー1件）</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">31</span><span style="color:#6a9955">     */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">32</span>    <span style="color:#4ec9b0">List</span>&lt;<span style="color:#4ec9b0">TrialExamHistoryEntity</span>&gt; <span style="color:#dcdcaa">selectLatestTrialExamHistoryList</span>(<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">33</span>            <span style="color:#dcdcaa">@Param</span>(<span style="color:#ce9178">&quot;courseId&quot;</span>) <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">courseId</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">34</span>}</pre></div>



<h3 class="wp-block-heading">サービスクラスの作成</h3>



<p>次に、サービス層でデータベースから直近5回分の模擬試験履歴を取得するメソッドを実装します。このメソッドは、ユーザーIDとコースIDをもとに、模擬試験履歴をデータベースから取得します。</p>



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">TrialExamHistoryService.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#6a9955">/**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span><span style="color:#6a9955"> * 模擬試験履歴リスト作成Service</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#6a9955"> */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span><span style="color:#dcdcaa">@Slf4j</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span><span style="color:#dcdcaa">@Service</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span><span style="color:#569cd6">public</span> <span style="color:#569cd6">class</span> <span style="color:#4ec9b0">TrialExamHistoryService</span> {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span>    <span style="color:#569cd6">private</span> <span style="color:#569cd6">final</span> <span style="color:#4ec9b0">TrialExamHistoryMapper</span> <span style="color:#9cdcfe">trialExamHistoryMapper</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span><span style="color:#6a9955">    // コンストラクタインジェクションを使用</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span>    <span style="color:#dcdcaa">@Autowired</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span>    <span style="color:#569cd6">public</span> <span style="color:#4ec9b0">TrialExamHistoryService</span>(<span style="color:#4ec9b0">TrialExamHistoryMapper</span> <span style="color:#9cdcfe">trialExamHistoryMapper</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span>        <span style="color:#569cd6">this</span>.<span style="color:#9cdcfe">trialExamHistoryMapper</span> = <span style="color:#9cdcfe">trialExamHistoryMapper</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span><span style="color:#6a9955">    /**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span><span style="color:#6a9955">     * 指定されたユーザーID、コースIDに基づいて直近5回分の模擬試験履歴を取得する。</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span><span style="color:#6a9955">     *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span><span style="color:#6a9955">     * @param userId   ユーザーID</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span><span style="color:#6a9955">     * @param courseId コースID</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span><span style="color:#6a9955">     * @return 模擬試験履歴リスト</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span><span style="color:#6a9955">     */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>    <span style="color:#dcdcaa">@Transactional</span>(<span style="color:#9cdcfe">readOnly</span> = <span style="color:#569cd6">true</span>)<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span>    <span style="color:#569cd6">public</span> <span style="color:#4ec9b0">List</span>&lt;<span style="color:#4ec9b0">TrialExamHistoryEntity</span>&gt; <span style="color:#dcdcaa">selectTrialExamHistoryList</span>(<span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">userId</span>, <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">courseId</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span>        <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;ユーザーID {} のコースID {} の模擬試験履歴を取得します。&quot;</span>, <span style="color:#9cdcfe">userId</span>, <span style="color:#9cdcfe">courseId</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span><span style="color:#6a9955">        // データベースから模擬試験履歴を取得</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span>        <span style="color:#4ec9b0">List</span>&lt;<span style="color:#4ec9b0">TrialExamHistoryEntity</span>&gt; <span style="color:#9cdcfe">trialExamHistoryList</span> = <span style="color:#9cdcfe">trialExamHistoryMapper</span>.<span style="color:#dcdcaa">selectTrialExamHistoryList</span>(<span style="color:#9cdcfe">userId</span>, <span style="color:#9cdcfe">courseId</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">30</span>        <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;取得した模擬試験履歴リスト: {}&quot;</span>, <span style="color:#9cdcfe">trialExamHistoryList</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">31</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">32</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">trialExamHistoryList</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">33</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">34</span>}</pre></div>



<h3 class="wp-block-heading">コントローラークラスの作成</h3>



<p>@PostMapping アノテーションを使って、/top/getTrialExamHistory というエンドポイントを定義します。このエンドポイントは、ユーザーIDとコースIDを含むフォームを受け取り、バリデーションエラーがあればエラーメッセージを返します。</p>



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">TopRest.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#6a9955">/**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span><span style="color:#6a9955"> * 模擬試験履歴を取得.</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#6a9955"> *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span><span style="color:#6a9955"> * &lt;h3&gt;機能概要&lt;/h3&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span><span style="color:#6a9955"> * ユーザーIDおよびコースIDに基づいて、模擬試験履歴を取得する。</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span><span style="color:#6a9955"> *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span><span style="color:#6a9955"> * &lt;h3&gt;処理フロー&lt;/h3&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span><span style="color:#6a9955"> * 1. リクエストデータのバリデーション&lt;br&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span><span style="color:#6a9955"> * 2. サービスクラスを呼び出して、模擬試験履歴を取得&lt;br&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span><span style="color:#6a9955"> * 3. 履歴が見つからない場合はエラーメッセージを設定&lt;br&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span><span style="color:#6a9955"> * 4. 模擬試験履歴またはエラーメッセージリストを返却</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span><span style="color:#6a9955"> *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span><span style="color:#6a9955"> * @param form    ユーザーIDとコースIDのリクエストフォーム</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span><span style="color:#6a9955"> * @param result  バリデーション結果やエラーを格納</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span><span style="color:#6a9955"> * @return 模擬試験履歴リスト TrialExamHistoryResponseForm</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span><span style="color:#6a9955"> */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span><span style="color:#dcdcaa">@PostMapping</span>(<span style="color:#ce9178">&quot;/top/getTrialExamHistory&quot;</span>)<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span><span style="color:#569cd6">public</span> <span style="color:#4ec9b0">TrialExamHistoryResponseForm</span> <span style="color:#dcdcaa">getTrialExamHistory</span>(<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span>        <span style="color:#dcdcaa">@RequestBody</span> <span style="color:#dcdcaa">@Validated</span>({ <span style="color:#4ec9b0">ValidationGroups</span>.<span style="color:#4ec9b0">Step1</span>.<span style="color:#569cd6">class</span>, <span style="color:#4ec9b0">ValidationGroups</span>.<span style="color:#4ec9b0">Step2</span>.<span style="color:#569cd6">class</span> }) <span style="color:#4ec9b0">UserIdForm</span> <span style="color:#9cdcfe">form</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span>        <span style="color:#4ec9b0">BindingResult</span> <span style="color:#9cdcfe">result</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span>    <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;TrialExamHistoryRest (getTrialExamHistory) 呼び出し開始&quot;</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>    <span style="color:#4ec9b0">TrialExamHistoryResponseForm</span> <span style="color:#9cdcfe">responseForm</span> = <span style="color:#569cd6">new</span> <span style="color:#4ec9b0">TrialExamHistoryResponseForm</span>();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span>    <span style="color:#4ec9b0">List</span>&lt;<span style="color:#4ec9b0">String</span>&gt; <span style="color:#9cdcfe">errorMessageList</span> = <span style="color:#569cd6">new</span> <span style="color:#4ec9b0">ArrayList</span>&lt;&gt;();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span><span style="color:#6a9955">    // バリデーションエラーがある場合、詳細なエラーメッセージを追加</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span>    <span style="color:#569cd6">if</span> (<span style="color:#9cdcfe">result</span>.<span style="color:#dcdcaa">hasErrors</span>()) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span>        <span style="color:#569cd6">for</span> (<span style="color:#4ec9b0">FieldError</span> <span style="color:#9cdcfe">fieldError</span> : <span style="color:#9cdcfe">result</span>.<span style="color:#dcdcaa">getFieldErrors</span>()) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span>            <span style="color:#4ec9b0">String</span> <span style="color:#9cdcfe">errorMessage</span> = <span style="color:#9cdcfe">messageSource</span>.<span style="color:#dcdcaa">getMessage</span>(<span style="color:#9cdcfe">fieldError</span>, <span style="color:#4ec9b0">Locale</span>.<span style="color:#dcdcaa">getDefault</span>()); // <span style="color:#4ec9b0">Localeを指定</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">30</span>            <span style="color:#9cdcfe">errorMessageList</span>.<span style="color:#dcdcaa">add</span>(<span style="color:#9cdcfe">errorMessage</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">31</span>        }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">32</span>        <span style="color:#9cdcfe">responseForm</span>.<span style="color:#dcdcaa">setErrorMessageList</span>(<span style="color:#9cdcfe">errorMessageList</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">33</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">responseForm</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">34</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">35</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">36</span>    <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">userId</span> = <span style="color:#4ec9b0">Integer</span>.<span style="color:#dcdcaa">parseInt</span>(<span style="color:#9cdcfe">form</span>.<span style="color:#dcdcaa">getUserId</span>());<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">37</span>    <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">courseId</span> = <span style="color:#9cdcfe">form</span>.<span style="color:#dcdcaa">getCourseId</span>();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">38</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">39</span><span style="color:#6a9955">    // 模擬試験履歴リストを取得</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">40</span>    <span style="color:#4ec9b0">List</span>&lt;<span style="color:#4ec9b0">TrialExamHistoryEntity</span>&gt; <span style="color:#9cdcfe">trialExamHistoryList</span> = <span style="color:#9cdcfe">trialExamHistoryService</span>.<span style="color:#dcdcaa">selectTrialExamHistoryList</span>(<span style="color:#9cdcfe">userId</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">41</span>            <span style="color:#9cdcfe">courseId</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">42</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">43</span><span style="color:#6a9955">    // リストが取得できなかった場合</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">44</span>    <span style="color:#569cd6">if</span> (<span style="color:#4ec9b0">CollectionUtils</span>.<span style="color:#dcdcaa">isEmpty</span>(<span style="color:#9cdcfe">trialExamHistoryList</span>)) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">45</span>        <span style="color:#9cdcfe">errorMessageList</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">46</span>                .<span style="color:#dcdcaa">add</span>(<span style="color:#9cdcfe">messageSource</span>.<span style="color:#dcdcaa">getMessage</span>(<span style="color:#4ec9b0">ErrorMessages</span>.<span style="color:#4fc1ff">TRIAL_EXAM_HISTORY_NOT_FOUND</span>, <span style="color:#569cd6">null</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">47</span>                        <span style="color:#4ec9b0">Locale</span>.<span style="color:#dcdcaa">getDefault</span>()));<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">48</span>        <span style="color:#9cdcfe">responseForm</span>.<span style="color:#dcdcaa">setErrorMessageList</span>(<span style="color:#9cdcfe">errorMessageList</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">49</span>        <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;模擬試験履歴が見つかりませんでした&quot;</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">50</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">responseForm</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">51</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">52</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">53</span>    <span style="color:#9cdcfe">responseForm</span>.<span style="color:#dcdcaa">setTrialExamHistoryList</span>(<span style="color:#9cdcfe">trialExamHistoryList</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">54</span>    <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;TrialExamHistoryRest (getTrialExamHistory) 呼び出し終了&quot;</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">55</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">56</span>    <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">responseForm</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">57</span>}</pre></div>



<h2 class="wp-block-heading">実装内容の振り返り</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回はシンプルに直近5回分の試験履歴を取得する実装でしたが、今後はキャッシュの活用や、SQLクエリ内で全件取得後に絞り込むのではなく、最初から直近5回分だけを取得するように修正することで、データベースへの負荷を軽減でき、より実用的に利用できるようになると感じました。</mark></strong></p>



<h2 class="wp-block-heading">まとめ</h2>



<p>シンプルな実装ではありましたが、SQLの書き方やキャッシュの導入余地など改善点も見えてきました。今後はパフォーマンスを意識した設計を取り入れ、より実用的なAPIに仕上げていきたいと思います。</p><p>The post <a href="https://www.sms-datatech.co.jp/column/try_java-6/">【はじめてのJava】ダッシュボードAPIを作成しよう！⑥試験回数遷移グラフ編</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【はじめてのJava】ダッシュボードAPIを作成しよう！⑤分野別進捗データ取得編</title>
		<link>https://www.sms-datatech.co.jp/column/try_java-5/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=try_java-5</link>
		
		<dc:creator><![CDATA[後藤]]></dc:creator>
		<pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[やってみた]]></category>
		<guid isPermaLink="false">https://www.sms-datatech.co.jp/?p=35821</guid>

					<description><![CDATA[<p>マーケターからエンジニアへ転身した社員による、Javaを用いたダッシュボードAPI作成の実践録。サーバーサイド初心者に向けて、分野別進捗データ取得APIの設計から実装ステップ、開発を通じて得たリアルな気づきまでをわかりやすく解説します。</p>
<p>The post <a href="https://www.sms-datatech.co.jp/column/try_java-5/">【はじめてのJava】ダッシュボードAPIを作成しよう！⑤分野別進捗データ取得編</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">はじめに</h2>



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



<h2 class="wp-block-heading">本記事の対象の方</h2>



<ul class="wp-block-list">
<li>サーバーサイド開発をこれから学びたい方</li>



<li>JavaでのAPI開発に興味がある方</li>



<li>ダッシュボードの作成を通じて、実践的な技術を学びたい方</li>
</ul>



<h2 class="wp-block-heading">Javaとは？</h2>



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



<p>詳しい説明については、過去のブログ記事をご参照ください。<br><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;"><a href="https://www.sms-datatech.co.jp/category/try/" title="">⇒関連記事はこちら</a></span></mark></strong></p>



<h2 class="wp-block-heading">環境構築</h2>



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



<h2 class="wp-block-heading">ダッシュボードAPIの作成</h2>



<p>今回作成したいのは、こんなダッシュボード画面です。<strong><mark style="background-color:#fdff84" class="has-inline-color">ユーザーがコースを選択することで、自分の学習状況を一目で確認できるようにすることを目指しています。</mark></strong></p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="465" src="https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1024x465.png" alt="" class="wp-image-34969" srcset="https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1024x465.png 1024w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-300x136.png 300w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-768x349.png 768w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1536x698.png 1536w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3.png 1736w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



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



<ol class="wp-block-list">
<li>ユーザー情報を取得</li>



<li>コース情報を取得</li>



<li>各グラフ描画に必要なデータを取得</li>
</ol>



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



<p>1. ユーザー情報の取得については、過去のブログ記事をご参考ください。<br><a href="https://www.sms-datatech.co.jp/column/try_java-1/" title=""><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">⇒関連記事はこちら</span></mark></strong></a></p>



<p>2. コース情報の取得については、過去のブログ記事をご参考ください。<br><a href="https://www.sms-datatech.co.jp/column/try_java-2/" title=""><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">⇒関連記事はこちら</span></mark></strong></a></p>



<h2 class="wp-block-heading">分野別進捗データ取得APIの設計</h2>



<h3 class="wp-block-heading">エンドポイント</h3>



<pre style="background-color:#000;color:#fff;padding:16px;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,'Courier New',monospace;font-size:14px;line-height:1.5;margin:0 0 16px 0;"><code style="background-color:transparent;color:#fff;">/top/getViewHistory</code></pre>



<ul class="wp-block-list">
<li>HTTPメソッド: POST</li>



<li>リクエストボディ: ユーザーID、コースID</li>



<li>レスポンス: 分野別の進捗率のリスト</li>
</ul>



<h3 class="wp-block-heading">リクエスト例</h3>



<pre style="background-color:#000;color:#fff;padding:16px;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,'Courier New',monospace;font-size:14px;line-height:1.5;margin:0 0 16px 0;"><code style="background-color:transparent;color:#fff;">{
    "courseId":"7",
    "userId":"175"
}</code></pre>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回はパスパラメータではなく、リクエストボディからユーザーIDとコースIDを受け取る設計にしています。</mark></strong>これにより、柔軟なデータの受け渡しが可能となります。</p>



<h3 class="wp-block-heading">レスポンス例</h3>



<pre style="background-color:#000;color:#fff;padding:16px;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,'Courier New',monospace;font-size:14px;line-height:1.5;margin:0 0 16px 0;"><code style="background-color:transparent;color:#fff;">{
    "viewHistoryList": [
        {
            "fieldId": 1,
            "fieldName": "Javaの基礎",
            "fieldMaxProgress": 30.00
        },
        {
            "fieldId": 2,
            "fieldName": "Javaオブジェクト指向",
            "fieldMaxProgress": 40.00
        }
    ],
    "errorMessageList": null
}</code></pre>



<h2 class="wp-block-heading">分野別進捗データAPIの実装</h2>



<h3 class="wp-block-heading">モデルクラスの作成</h3>



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



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">ViewHistoryEntity.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#569cd6">package</span> <span style="color:#9cdcfe">jp</span>.<span style="color:#9cdcfe">co</span>.<span style="color:#9cdcfe">smsdatatech</span>.<span style="color:#9cdcfe">questiongenerate</span>.<span style="color:#9cdcfe">entity</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">java</span>.<span style="color:#9cdcfe">io</span>.<span style="color:#4ec9b0">Serializable</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">java</span>.<span style="color:#9cdcfe">math</span>.<span style="color:#4ec9b0">BigDecimal</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">lombok</span>.<span style="color:#4ec9b0">Data</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span><span style="color:#6a9955">/**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span><span style="color:#6a9955"> *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span><span style="color:#6a9955"> * ViewHistoryテーブルEntity</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span><span style="color:#6a9955"> */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span><span style="color:#dcdcaa">@Data</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span><span style="color:#569cd6">public</span> <span style="color:#569cd6">class</span> <span style="color:#4ec9b0">ViewHistoryEntity</span> <span style="color:#569cd6">implements</span> <span style="color:#4ec9b0">Serializable</span> {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span><span style="color:#6a9955">    /** シリアルバージョンUID */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span>    <span style="color:#569cd6">private</span> <span style="color:#569cd6">static</span> <span style="color:#569cd6">final</span> <span style="color:#9cdcfe">long</span> <span style="color:#9cdcfe">serialVersionUID</span> = <span style="color:#b5cea8">1L</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span><span style="color:#6a9955">    /** 分野ID */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">fieldId</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span><span style="color:#6a9955">    /** 分野名 */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">String</span> <span style="color:#9cdcfe">fieldName</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span><span style="color:#6a9955">    /** 最大進捗率（集計結果） */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">BigDecimal</span> <span style="color:#9cdcfe">fieldMaxProgress</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span>}</pre></div>



<h3 class="wp-block-heading">MyBatisのMapper設定</h3>



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



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">ViewHistoryMapper.xml</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#569cd6">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span><span style="color:#569cd6">&lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#569cd6">&lt;mapper</span> <span style="color:#9cdcfe">namespace</span>=<span style="color:#ce9178">&quot;jp.co.smsdatatech.questiongenerate.repository.ViewHistoryMapper&quot;</span><span style="color:#569cd6">&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span>    <span style="color:#569cd6">&lt;select</span> <span style="color:#9cdcfe">id</span>=<span style="color:#ce9178">&quot;getViewHistoryWithMaxProgress&quot;</span> <span style="color:#9cdcfe">resultMap</span>=<span style="color:#ce9178">&quot;ViewHistoryResultMap&quot;</span><span style="color:#569cd6">&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span>        <span style="color:#569cd6">SELECT</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span>            <span style="color:#d4d4d4">subquery.course_id</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span>            <span style="color:#d4d4d4">subquery.field_id</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span>            <span style="color:#d4d4d4">subquery.fieldMaxProgress</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span>            <span style="color:#d4d4d4">f.field_name</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span>        <span style="color:#569cd6">FROM</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span>            (<span style="color:#569cd6">SELECT</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span>                <span style="color:#d4d4d4">vh.course_id</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span>                <span style="color:#d4d4d4">vh.field_id</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span>                <span style="color:#569cd6">MAX</span>(<span style="color:#d4d4d4">vh.progress_percentage</span>) <span style="color:#569cd6">AS</span> <span style="color:#d4d4d4">fieldMaxProgress</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span>            <span style="color:#569cd6">FROM</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span>                <span style="color:#d4d4d4">view_history</span> <span style="color:#d4d4d4">vh</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span>            <span style="color:#569cd6">WHERE</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span>                <span style="color:#d4d4d4">vh.user_id</span> = <span style="color:#9cdcfe">#{userId}</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span>                <span style="color:#569cd6">AND</span> <span style="color:#d4d4d4">vh.course_id</span> = <span style="color:#9cdcfe">#{courseId}</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span>                <span style="color:#569cd6">AND</span> <span style="color:#d4d4d4">vh.viewed_at</span> <span style="color:#569cd6">BETWEEN</span> <span style="color:#9cdcfe">#{startOfWeek}</span> <span style="color:#569cd6">AND</span> <span style="color:#9cdcfe">#{endOfWeek}</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span>            <span style="color:#569cd6">GROUP</span> <span style="color:#569cd6">BY</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>                <span style="color:#d4d4d4">vh.course_id</span>, <span style="color:#d4d4d4">vh.field_id</span>) <span style="color:#569cd6">AS</span> <span style="color:#d4d4d4">subquery</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span>        <span style="color:#569cd6">INNER</span> <span style="color:#569cd6">JOIN</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span>            <span style="color:#d4d4d4">fields</span> <span style="color:#d4d4d4">f</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span>        <span style="color:#569cd6">ON</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span>            <span style="color:#d4d4d4">subquery.field_id</span> = <span style="color:#d4d4d4">f.field_id</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span>    <span style="color:#569cd6">&lt;/select</span><span style="color:#569cd6">&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span><span style="color:#569cd6">&lt;/mapper</span><span style="color:#569cd6">&gt;</span></pre></div>



<h3 class="wp-block-heading">サービスクラスの作成</h3>



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



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">ViewHistoryService.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#6a9955">/**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span><span style="color:#6a9955"> * 学習履歴リスト作成Service</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#6a9955"> */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span><span style="color:#dcdcaa">@Slf4j</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span><span style="color:#dcdcaa">@Service</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span><span style="color:#569cd6">public</span> <span style="color:#569cd6">class</span> <span style="color:#4ec9b0">ViewHistoryService</span> {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span>    <span style="color:#569cd6">private</span> <span style="color:#569cd6">final</span> <span style="color:#4ec9b0">ViewHistoryMapper</span> <span style="color:#9cdcfe">viewHistoryMapper</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span><span style="color:#6a9955">    // コンストラクタインジェクションを使用</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span>    <span style="color:#dcdcaa">@Autowired</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span>    <span style="color:#569cd6">public</span> <span style="color:#4ec9b0">ViewHistoryService</span>(<span style="color:#4ec9b0">ViewHistoryMapper</span> <span style="color:#9cdcfe">viewHistoryMapper</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span>        <span style="color:#569cd6">this</span>.<span style="color:#9cdcfe">viewHistoryMapper</span> = <span style="color:#9cdcfe">viewHistoryMapper</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span><span style="color:#6a9955">    /**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span><span style="color:#6a9955">     * 指定されたユーザーID、コースID、期間に基づいて分野別に最大進捗率を取得する。</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span><span style="color:#6a9955">     *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span><span style="color:#6a9955">     * @param userId   ユーザーID</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span><span style="color:#6a9955">     * @param courseId コースID</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span><span style="color:#6a9955">     * @return 学習履歴（分野別に最大進捗率）のリスト</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span><span style="color:#6a9955">     */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>    <span style="color:#dcdcaa">@Transactional</span>(<span style="color:#9cdcfe">readOnly</span> = <span style="color:#569cd6">true</span>)<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span>    <span style="color:#569cd6">public</span> <span style="color:#4ec9b0">List</span>&lt;<span style="color:#4ec9b0">ViewHistoryEntity</span>&gt; <span style="color:#dcdcaa">getViewHistoryWithMaxProgress</span>(<span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">userId</span>, <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">courseId</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span><span style="color:#6a9955">        // 週の開始日と終了日を計算</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span>        <span style="color:#4ec9b0">LocalDateTime</span> <span style="color:#9cdcfe">startOfWeek</span> = <span style="color:#4ec9b0">DateUtils</span>.<span style="color:#dcdcaa">getStartOfWeek</span>();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span>        <span style="color:#4ec9b0">LocalDateTime</span> <span style="color:#9cdcfe">endOfWeek</span> = <span style="color:#4ec9b0">DateUtils</span>.<span style="color:#dcdcaa">getEndOfWeek</span>();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span>        <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;ユーザーID {} のコースID {} の学習履歴と最大進捗率を週の範囲 {} - {} で取得します。&quot;</span>, <span style="color:#9cdcfe">userId</span>, <span style="color:#9cdcfe">courseId</span>, <span style="color:#9cdcfe">startOfWeek</span>, <span style="color:#9cdcfe">endOfWeek</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">30</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">31</span><span style="color:#6a9955">        // データベースから分野別の最大進捗率を直接返す</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">32</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">viewHistoryMapper</span>.<span style="color:#dcdcaa">getViewHistoryWithMaxProgress</span>(<span style="color:#9cdcfe">userId</span>, <span style="color:#9cdcfe">courseId</span>, <span style="color:#9cdcfe">startOfWeek</span>, <span style="color:#9cdcfe">endOfWeek</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">33</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">34</span>}</pre></div>



<h3 class="wp-block-heading">コントローラークラスの作成</h3>



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



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">TopRest.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#6a9955">/**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span><span style="color:#6a9955"> * 分野別の最大進捗率を取得.</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#6a9955"> * &lt;h3&gt;機能概要&lt;/h3&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span><span style="color:#6a9955"> * ユーザーIDおよびコースIDに基づいて分野別の最大進捗率を取得し、ViewHistoryResponseFormに詰めて返却する。</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span><span style="color:#6a9955"> *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span><span style="color:#6a9955"> * &lt;h3&gt;処理フロー&lt;/h3&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span><span style="color:#6a9955"> * 1. リクエストデータのバリデーション&lt;br&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span><span style="color:#6a9955"> * 2. サービスクラスを呼び出して、最大進捗率を取得&lt;br&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span><span style="color:#6a9955"> * 3. 進捗率が見つからない場合はエラーメッセージを設定&lt;br&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span><span style="color:#6a9955"> * 4. 最大進捗率またはエラーメッセージリストを返却</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span><span style="color:#6a9955"> *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span><span style="color:#6a9955"> * @param form    UserIdForm</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span><span style="color:#6a9955"> * @param result  バリデーション結果やエラーを格納</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span><span style="color:#6a9955"> * @return 最大進捗率リスト ViewHistoryResponseForm</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span><span style="color:#6a9955"> */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span><span style="color:#dcdcaa">@PostMapping</span>(<span style="color:#ce9178">&quot;/top/getViewHistory&quot;</span>)<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span><span style="color:#569cd6">public</span> <span style="color:#4ec9b0">ViewHistoryResponseForm</span> <span style="color:#dcdcaa">getViewHistory</span>(<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span>        <span style="color:#dcdcaa">@RequestBody</span> <span style="color:#dcdcaa">@Validated</span>({ <span style="color:#4ec9b0">ValidationGroups</span>.<span style="color:#4ec9b0">Step1</span>.<span style="color:#569cd6">class</span>, <span style="color:#4ec9b0">ValidationGroups</span>.<span style="color:#4ec9b0">Step2</span>.<span style="color:#569cd6">class</span> }) <span style="color:#4ec9b0">UserIdForm</span> <span style="color:#9cdcfe">form</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span>        <span style="color:#4ec9b0">BindingResult</span> <span style="color:#9cdcfe">result</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span>    <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;TopRest (getViewHistory) 呼び出し開始&quot;</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span>    <span style="color:#4ec9b0">ViewHistoryResponseForm</span> <span style="color:#9cdcfe">responseForm</span> = <span style="color:#569cd6">new</span> <span style="color:#4ec9b0">ViewHistoryResponseForm</span>();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>    <span style="color:#4ec9b0">List</span>&lt;<span style="color:#4ec9b0">String</span>&gt; <span style="color:#9cdcfe">errorMessageList</span> = <span style="color:#569cd6">new</span> <span style="color:#4ec9b0">ArrayList</span>&lt;&gt;();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span><span style="color:#6a9955">    // バリデーションエラーがある場合、詳細なエラーメッセージを追加</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span>    <span style="color:#569cd6">if</span> (<span style="color:#9cdcfe">result</span>.<span style="color:#dcdcaa">hasErrors</span>()) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span>        <span style="color:#569cd6">for</span> (<span style="color:#4ec9b0">FieldError</span> <span style="color:#9cdcfe">fieldError</span> : <span style="color:#9cdcfe">result</span>.<span style="color:#dcdcaa">getFieldErrors</span>()) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span>            <span style="color:#4ec9b0">String</span> <span style="color:#9cdcfe">errorMessage</span> = <span style="color:#9cdcfe">messageSource</span>.<span style="color:#dcdcaa">getMessage</span>(<span style="color:#9cdcfe">fieldError</span>, <span style="color:#4ec9b0">Locale</span>.<span style="color:#dcdcaa">getDefault</span>());<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span>            <span style="color:#9cdcfe">errorMessageList</span>.<span style="color:#dcdcaa">add</span>(<span style="color:#9cdcfe">errorMessage</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">30</span>        }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">31</span>        <span style="color:#9cdcfe">responseForm</span>.<span style="color:#dcdcaa">setErrorMessageList</span>(<span style="color:#9cdcfe">errorMessageList</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">32</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">responseForm</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">33</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">34</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">35</span>    <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">userId</span> = <span style="color:#4ec9b0">Integer</span>.<span style="color:#dcdcaa">parseInt</span>(<span style="color:#9cdcfe">form</span>.<span style="color:#dcdcaa">getUserId</span>());<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">36</span>    <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">courseId</span> = <span style="color:#9cdcfe">form</span>.<span style="color:#dcdcaa">getCourseId</span>();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">37</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">38</span><span style="color:#6a9955">    // 最大進捗率のリストを取得</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">39</span>    <span style="color:#4ec9b0">List</span>&lt;<span style="color:#4ec9b0">ViewHistoryEntity</span>&gt; <span style="color:#9cdcfe">viewHistoryList</span> = <span style="color:#9cdcfe">viewHistoryService</span>.<span style="color:#dcdcaa">getViewHistoryWithMaxProgress</span>(<span style="color:#9cdcfe">userId</span>, <span style="color:#9cdcfe">courseId</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">40</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">41</span><span style="color:#6a9955">    // リストが取得できなかった場合</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">42</span>    <span style="color:#569cd6">if</span> (<span style="color:#4ec9b0">CollectionUtils</span>.<span style="color:#dcdcaa">isEmpty</span>(<span style="color:#9cdcfe">viewHistoryList</span>)) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">43</span>        <span style="color:#9cdcfe">errorMessageList</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">44</span>                .<span style="color:#dcdcaa">add</span>(<span style="color:#9cdcfe">messageSource</span>.<span style="color:#dcdcaa">getMessage</span>(<span style="color:#4ec9b0">ErrorMessages</span>.<span style="color:#4fc1ff">VIEW_HISTORY_NOT_FOUND</span>, <span style="color:#569cd6">null</span>, <span style="color:#4ec9b0">Locale</span>.<span style="color:#dcdcaa">getDefault</span>()));<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">45</span>        <span style="color:#9cdcfe">responseForm</span>.<span style="color:#dcdcaa">setErrorMessageList</span>(<span style="color:#9cdcfe">errorMessageList</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">46</span>        <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;ViewHistoryList (viewHistoryList) 学習履歴が見つかりません: &quot;</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">47</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">responseForm</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">48</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">49</span>    <span style="color:#9cdcfe">responseForm</span>.<span style="color:#dcdcaa">setViewHistoryList</span>(<span style="color:#9cdcfe">viewHistoryList</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">50</span>    <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;TopRest (getViewHistory) 呼び出し終了&quot;</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">51</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">52</span>    <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">responseForm</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">53</span>}</pre></div>



<h2 class="wp-block-heading">実装内容の振り返り</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">バリデーショングループを作成した点は新たな学びがありました。</mark></strong>これまで単一のバリデーションルールを適用していましたが、バリデーショングループを導入することで、処理ごとに異なるバリデーションルールを適用できるようになりました。たとえば、新規登録時と更新時で必須項目を変える必要があるケースに対応しやすくなりました。</p>



<p>さらに、<strong><mark style="background-color:#fdff84" class="has-inline-color">SQL の部分では INNER JOIN を活用し、効率的にデータを取得できるようにしました。</mark></strong>view_history テーブルから最新の進捗データを取得し、fields テーブルと結合することで、分野名を含めた結果を一度のクエリで取得できるようになりました。</p>



<h2 class="wp-block-heading">まとめ</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回の実装を通じて、バリデーションの柔軟な適用方法や共通部品の活用によるコードの整理、SQL の最適化といった学びを得ることができました。</mark></strong>今後も、より効率的なコード設計を意識しながら開発を進めていきたいと思います。</p><p>The post <a href="https://www.sms-datatech.co.jp/column/try_java-5/">【はじめてのJava】ダッシュボードAPIを作成しよう！⑤分野別進捗データ取得編</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【はじめてのJava】ダッシュボードAPIを作成しよう！④総学習時間API編</title>
		<link>https://www.sms-datatech.co.jp/column/try_java-4/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=try_java-4</link>
		
		<dc:creator><![CDATA[後藤]]></dc:creator>
		<pubDate>Tue, 26 May 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[やってみた]]></category>
		<guid isPermaLink="false">https://www.sms-datatech.co.jp/?p=35813</guid>

					<description><![CDATA[<p>マーケターからエンジニアへ転身した社員による、Javaを用いたダッシュボードAPI作成の実践録。サーバーサイド初心者に向けて、総学習時間APIの設計から実装ステップ、開発を通じて得たリアルな気づきまでをわかりやすく解説します。</p>
<p>The post <a href="https://www.sms-datatech.co.jp/column/try_java-4/">【はじめてのJava】ダッシュボードAPIを作成しよう！④総学習時間API編</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">はじめに</h2>



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



<h2 class="wp-block-heading">本記事の対象の方</h2>



<ul class="wp-block-list">
<li>サーバーサイド開発をこれから学びたい方</li>



<li>JavaでのAPI開発に興味がある方</li>



<li>ダッシュボードの作成を通じて、実践的な技術を学びたい方</li>
</ul>



<h2 class="wp-block-heading">Javaとは？</h2>



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



<p>詳しい説明については、過去のブログ記事をご参照ください。<br><a href="https://www.sms-datatech.co.jp/category/try/" title=""><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;"><a href="https://www.sms-datatech.co.jp/category/try/"><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">⇒過去のブログ一覧</span></mark></strong></a></span></mark></strong></a></p>



<h2 class="wp-block-heading">環境構築</h2>



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



<h2 class="wp-block-heading">ダッシュボードAPIの作成</h2>



<p>今回作成したいのは、こんなダッシュボード画面です。<strong><mark style="background-color:#fdff84" class="has-inline-color">ユーザーがコースを選択することで、自分の学習状況を一目で確認できるようにすることを目指しています。</mark></strong></p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="465" src="https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1024x465.png" alt="" class="wp-image-34969" srcset="https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1024x465.png 1024w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-300x136.png 300w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-768x349.png 768w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1536x698.png 1536w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3.png 1736w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



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



<ol class="wp-block-list">
<li>ユーザー情報を取得</li>



<li>コース情報を取得</li>



<li>各グラフ描画に必要なデータを取得</li>
</ol>



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



<p>1. ユーザー情報の取得については、過去のブログ記事をご参考ください。<br><a href="https://www.sms-datatech.co.jp/column/try_java-1/" title=""><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">⇒関連記事はこちら</span></mark></strong></a></p>



<p>2. コース情報の取得については、過去のブログ記事をご参考ください。<br><a href="https://www.sms-datatech.co.jp/column/try_java-2/" title=""><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">⇒関連記事はこちら</span></mark></strong></a></p>



<h2 class="wp-block-heading">総学習時間取得APIの設計</h2>



<h3 class="wp-block-heading">エンドポイント</h3>



<pre class="wp-block-code"><code>/top/getLoginHistoryByUserId</code></pre>



<ul class="wp-block-list">
<li>HTTPメソッド: POST</li>



<li>リクエストボディ: ユーザーID、コースID</li>



<li>レスポンス: 総学習時間</li>
</ul>



<h3 class="wp-block-heading">リクエスト例</h3>



<pre style="background-color:#000;color:#fff;padding:16px;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,'Courier New',monospace;font-size:14px;line-height:1.5;margin:0 0 16px 0;"><code style="background-color:transparent;color:#fff;">{
    "courseId":"7",
    "userId":"175"
}</code></pre>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回はパスパラメータではなく、リクエストボディからユーザーIDとコースIDを受け取る設計にしています。</mark></strong>これにより、柔軟なデータの受け渡しが可能となります。</p>



<h3 class="wp-block-heading">レスポンス例</h3>



<pre style="background-color:#000;color:#fff;padding:16px;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,'Courier New',monospace;font-size:14px;line-height:1.5;margin:0 0 16px 0;"><code style="background-color:transparent;color:#fff;">{
  "totalLearningTime": 0,
  "errorMessageList": null
}</code></pre>



<h2 class="wp-block-heading">総学習時間APIの実装</h2>



<h3 class="wp-block-heading">モデルクラスの作成</h3>



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



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">ExerciseRecordsEntity.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#569cd6">package</span> <span style="color:#9cdcfe">jp</span>.<span style="color:#9cdcfe">co</span>.<span style="color:#9cdcfe">smsdatatech</span>.<span style="color:#9cdcfe">questiongenerate</span>.<span style="color:#9cdcfe">entity</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">java</span>.<span style="color:#9cdcfe">io</span>.<span style="color:#4ec9b0">Serializable</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">java</span>.<span style="color:#9cdcfe">sql</span>.<span style="color:#4ec9b0">Timestamp</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">lombok</span>.<span style="color:#4ec9b0">Data</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span><span style="color:#6a9955">/**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span><span style="color:#6a9955"> *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span><span style="color:#6a9955"> * ExerciseRecordsテーブルEntity</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span><span style="color:#6a9955"> */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span><span style="color:#dcdcaa">@Data</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span><span style="color:#569cd6">public</span> <span style="color:#569cd6">class</span> <span style="color:#4ec9b0">ExerciseRecordsEntity</span> <span style="color:#569cd6">implements</span> <span style="color:#4ec9b0">Serializable</span> {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span><span style="color:#6a9955">    /** シリアルバージョンUID */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span>    <span style="color:#569cd6">private</span> <span style="color:#569cd6">static</span> <span style="color:#569cd6">final</span> <span style="color:#9cdcfe">long</span> <span style="color:#9cdcfe">serialVersionUID</span> = <span style="color:#b5cea8">1L</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span><span style="color:#6a9955">    /** ユーザーID */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">userId</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span><span style="color:#6a9955">    /** コースID */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">courseId</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span><span style="color:#6a9955">    /** 演習開始日時 */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Timestamp</span> <span style="color:#9cdcfe">startDate</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span><span style="color:#6a9955">    /** 作成日 */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Timestamp</span> <span style="color:#9cdcfe">createDate</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span><span style="color:#6a9955">    /** 更新日 */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Timestamp</span> <span style="color:#9cdcfe">updateDate</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">30</span>}</pre></div>



<h3 class="wp-block-heading">MyBatisのMapper設定</h3>



<p>ExerciseRecordsMapper では、SQLを定義して、学習時間を合計します。EXTRACT(EPOCH FROM (end_time &#8211; start_time)) を使って学習時間を計算し、その結果を合計して返します。</p>



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">ExerciseRecordsMapper.xml</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#569cd6">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span><span style="color:#569cd6">&lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#569cd6">&lt;mapper</span> <span style="color:#9cdcfe">namespace</span>=<span style="color:#ce9178">&quot;jp.co.smsdatatech.questiongenerate.repository.ExerciseRecordsMapper&quot;</span><span style="color:#569cd6">&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span>    <span style="color:#569cd6">&lt;select</span> <span style="color:#9cdcfe">id</span>=<span style="color:#ce9178">&quot;getTotalLearning&quot;</span> <span style="color:#9cdcfe">resultType</span>=<span style="color:#ce9178">&quot;java.math.BigDecimal&quot;</span><span style="color:#569cd6">&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span>        <span style="color:#569cd6">SELECT</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span>            <span style="color:#569cd6">SUM</span>(<span style="color:#569cd6">EXTRACT</span>(<span style="color:#569cd6">EPOCH</span> <span style="color:#569cd6">FROM</span> (<span style="color:#d4d4d4">end_time</span> - <span style="color:#d4d4d4">start_time</span>)) / <span style="color:#b5cea8">3600</span>) <span style="color:#569cd6">AS</span> <span style="color:#d4d4d4">totalLearningTime</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span>        <span style="color:#569cd6">FROM</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span>            <span style="color:#d4d4d4">exercise_records</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span>        <span style="color:#569cd6">WHERE</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span>            <span style="color:#d4d4d4">user_id</span> = <span style="color:#9cdcfe">#{userId}</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span>            <span style="color:#569cd6">AND</span> <span style="color:#d4d4d4">course_id</span> = <span style="color:#9cdcfe">#{courseId}</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span>            <span style="color:#569cd6">AND</span> <span style="color:#d4d4d4">start_time</span> <span style="color:#569cd6">BETWEEN</span> <span style="color:#9cdcfe">#{startOfWeek}</span> <span style="color:#569cd6">AND</span> <span style="color:#9cdcfe">#{endOfWeek}</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span>    <span style="color:#569cd6">&lt;/select</span><span style="color:#569cd6">&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span><span style="color:#569cd6">&lt;/mapper</span><span style="color:#569cd6">&gt;</span></pre></div>



<h3 class="wp-block-heading">サービスクラスの作成</h3>



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



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">ExerciseRecordsService.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">lombok</span>.<span style="color:#9cdcfe">extern</span>.<span style="color:#9cdcfe">slf4j</span>.<span style="color:#4ec9b0">Slf4j</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#6a9955">/**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span><span style="color:#6a9955"> * 演習履歴テーブルService</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span><span style="color:#6a9955"> */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span><span style="color:#dcdcaa">@Slf4j</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span><span style="color:#dcdcaa">@Service</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span><span style="color:#569cd6">public</span> <span style="color:#569cd6">class</span> <span style="color:#4ec9b0">ExerciseRecordsService</span> {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span>    <span style="color:#569cd6">private</span> <span style="color:#569cd6">final</span> <span style="color:#4ec9b0">ExerciseRecordsMapper</span> <span style="color:#9cdcfe">exerciseRecordsMapper</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span><span style="color:#6a9955">    // コンストラクタインジェクションを使用</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span>    <span style="color:#dcdcaa">@Autowired</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span>    <span style="color:#569cd6">public</span> <span style="color:#4ec9b0">ExerciseRecordsService</span>(<span style="color:#4ec9b0">ExerciseRecordsMapper</span> <span style="color:#9cdcfe">exerciseRecordsMapper</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span>        <span style="color:#569cd6">this</span>.<span style="color:#9cdcfe">exerciseRecordsMapper</span> = <span style="color:#9cdcfe">exerciseRecordsMapper</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span><span style="color:#6a9955">    /**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span><span style="color:#6a9955">     * ユーザーIDとコースIDを基に、週の範囲内で合計学習時間を取得する。</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span><span style="color:#6a9955">     *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span><span style="color:#6a9955">     * @param userId   ユーザーID</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span><span style="color:#6a9955">     * @param courseId コースID</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span><span style="color:#6a9955">     * @return 総学習時間</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span><span style="color:#6a9955">     */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span>    <span style="color:#dcdcaa">@Transactional</span>(<span style="color:#9cdcfe">readOnly</span> = <span style="color:#569cd6">true</span>)<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span>    <span style="color:#569cd6">public</span> <span style="color:#4ec9b0">BigDecimal</span> <span style="color:#dcdcaa">getTotalLearningTime</span>(<span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">userId</span>, <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">courseId</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span><span style="color:#6a9955">        // 週の開始日と終了日を計算</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span>        <span style="color:#4ec9b0">LocalDateTime</span> <span style="color:#9cdcfe">startOfWeek</span> = <span style="color:#4ec9b0">DateUtils</span>.<span style="color:#dcdcaa">getStartOfWeek</span>();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">30</span>        <span style="color:#4ec9b0">LocalDateTime</span> <span style="color:#9cdcfe">endOfWeek</span> = <span style="color:#4ec9b0">DateUtils</span>.<span style="color:#dcdcaa">getEndOfWeek</span>();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">31</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">32</span>        <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;ユーザーID {} とコースID {} の合計学習時間を週の範囲 {} - {} で取得します。&quot;</span>, <span style="color:#9cdcfe">userId</span>, <span style="color:#9cdcfe">courseId</span>, <span style="color:#9cdcfe">startOfWeek</span>, <span style="color:#9cdcfe">endOfWeek</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">33</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">exerciseRecordsMapper</span>.<span style="color:#dcdcaa">getTotalLearning</span>(<span style="color:#9cdcfe">userId</span>, <span style="color:#9cdcfe">courseId</span>, <span style="color:#9cdcfe">startOfWeek</span>, <span style="color:#9cdcfe">endOfWeek</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">34</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">35</span>}</pre></div>



<h3 class="wp-block-heading">コントローラークラスの作成</h3>



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



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">TopRest.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#6a9955">/**</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span><span style="color:#6a9955"> * 総学習時間を取得.</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#6a9955"> * &lt;h3&gt;機能概要&lt;/h3&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span><span style="color:#6a9955"> * ユーザーIDおよびコースIDに基づいて総学習時間を取得し、TotalLearningTimeResponseFormに詰めて返却する。</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span><span style="color:#6a9955"> *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span><span style="color:#6a9955"> * &lt;h3&gt;処理フロー&lt;/h3&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span><span style="color:#6a9955"> * 1. リクエストデータのバリデーション&lt;br&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span><span style="color:#6a9955"> * 2. サービスクラスを呼び出して、総学習時間を取得&lt;br&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span><span style="color:#6a9955"> * 3. 学習時間が見つからない場合はエラーメッセージを設定&lt;br&gt;</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span><span style="color:#6a9955"> * 4. 総学習時間またはエラーメッセージリストを返却</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span><span style="color:#6a9955"> *</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span><span style="color:#6a9955"> * @param form    UserIdForm</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span><span style="color:#6a9955"> * @param result  バリデーション結果やエラーを格納</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span><span style="color:#6a9955"> * @return 総学習時間リスト TotalLearningTimeResponseForm</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span><span style="color:#6a9955"> */</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span><span style="color:#dcdcaa">@PostMapping</span>(<span style="color:#ce9178">&quot;/top/getTotalLearningTime&quot;</span>)<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span><span style="color:#569cd6">public</span> <span style="color:#4ec9b0">TotalLearningTimeResponseForm</span> <span style="color:#dcdcaa">getTotalLearningTime</span>(<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span>        <span style="color:#dcdcaa">@RequestBody</span> <span style="color:#dcdcaa">@Validated</span>({ <span style="color:#4ec9b0">ValidationGroups</span>.<span style="color:#4ec9b0">Step1</span>.<span style="color:#569cd6">class</span>, <span style="color:#4ec9b0">ValidationGroups</span>.<span style="color:#4ec9b0">Step2</span>.<span style="color:#569cd6">class</span> }) <span style="color:#4ec9b0">UserIdForm</span> <span style="color:#9cdcfe">form</span>,<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span>        <span style="color:#4ec9b0">BindingResult</span> <span style="color:#9cdcfe">result</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span>    <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;TopRest (getTotalLearningTime) 呼び出し開始&quot;</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span>    <span style="color:#4ec9b0">TotalLearningTimeResponseForm</span> <span style="color:#9cdcfe">responseForm</span> = <span style="color:#569cd6">new</span> <span style="color:#4ec9b0">TotalLearningTimeResponseForm</span>();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>    <span style="color:#4ec9b0">List</span>&lt;<span style="color:#4ec9b0">String</span>&gt; <span style="color:#9cdcfe">errorMessageList</span> = <span style="color:#569cd6">new</span> <span style="color:#4ec9b0">ArrayList</span>&lt;&gt;();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span><span style="color:#6a9955">    // バリデーションエラーがある場合、詳細なエラーメッセージを追加</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span>    <span style="color:#569cd6">if</span> (<span style="color:#9cdcfe">result</span>.<span style="color:#dcdcaa">hasErrors</span>()) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span>        <span style="color:#569cd6">for</span> (<span style="color:#4ec9b0">FieldError</span> <span style="color:#9cdcfe">fieldError</span> : <span style="color:#9cdcfe">result</span>.<span style="color:#dcdcaa">getFieldErrors</span>()) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span>            <span style="color:#4ec9b0">String</span> <span style="color:#9cdcfe">errorMessage</span> = <span style="color:#9cdcfe">messageSource</span>.<span style="color:#dcdcaa">getMessage</span>(<span style="color:#9cdcfe">fieldError</span>, <span style="color:#4ec9b0">Locale</span>.<span style="color:#dcdcaa">getDefault</span>());<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span>            <span style="color:#9cdcfe">errorMessageList</span>.<span style="color:#dcdcaa">add</span>(<span style="color:#9cdcfe">errorMessage</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">30</span>        }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">31</span>        <span style="color:#9cdcfe">responseForm</span>.<span style="color:#dcdcaa">setErrorMessageList</span>(<span style="color:#9cdcfe">errorMessageList</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">32</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">responseForm</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">33</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">34</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">35</span>    <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">userId</span> = <span style="color:#4ec9b0">Integer</span>.<span style="color:#dcdcaa">parseInt</span>(<span style="color:#9cdcfe">form</span>.<span style="color:#dcdcaa">getUserId</span>());<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">36</span>    <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">courseId</span> = <span style="color:#9cdcfe">form</span>.<span style="color:#dcdcaa">getCourseId</span>();<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">37</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">38</span><span style="color:#6a9955">    // サービスで合計学習時間を取得（学習時間がない場合はデフォルト値0.0)</span><br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">39</span>    <span style="color:#4ec9b0">BigDecimal</span> <span style="color:#9cdcfe">totalLearningTime</span> = <span style="color:#9cdcfe">exerciseRecordsService</span>.<span style="color:#dcdcaa">getTotalLearningTime</span>(<span style="color:#9cdcfe">userId</span>, <span style="color:#9cdcfe">courseId</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">40</span>    <span style="color:#569cd6">if</span> (<span style="color:#9cdcfe">totalLearningTime</span> == <span style="color:#569cd6">null</span>) {<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">41</span>        <span style="color:#9cdcfe">totalLearningTime</span> = <span style="color:#4ec9b0">BigDecimal</span>.<span style="color:#4fc1ff">ZERO</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">42</span>    }<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">43</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">44</span>    <span style="color:#9cdcfe">responseForm</span>.<span style="color:#dcdcaa">setTotalLearningTime</span>(<span style="color:#9cdcfe">totalLearningTime</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">45</span>    <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;TopRest (getTotalLearningTime) 呼び出し終了&quot;</span>);<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">46</span>&nbsp;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">47</span>    <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">responseForm</span>;<br>
<span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">48</span>}</pre></div>



<h2 class="wp-block-heading">実装内容の振り返り</h2>



<h3 class="wp-block-heading">1. ユーティリティクラスの活用</h3>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">以前作成した DateUtils クラスを活用し、週の開始日と終了日を簡単に取得できました。</mark></strong>このおかげで、日付操作をシンプルに保ち、コードの重複を避けることができました。</p>



<h3 class="wp-block-heading">2. BigDecimalの利用</h3>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">学習時間の合計を計算するために BigDecimal を使用しました。</mark></strong>BigDecimal は精度の高い数値計算が可能で、浮動小数点誤差を防ぐために最適です。学習時間がない場合は BigDecimal.ZERO を使用してデフォルト値を設定しました。</p>



<h2 class="wp-block-heading">まとめ</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回の実装では、ユーティリティクラスの再利用と BigDecimal の活用で、精度高くシンプルなコードが書けました。</mark></strong>これにより、学習時間計算が正確で信頼性のあるものとなり、保守性が向上しました。<br>次回も、各グラフの作成方法について記載しますのでお楽しみに！</p><p>The post <a href="https://www.sms-datatech.co.jp/column/try_java-4/">【はじめてのJava】ダッシュボードAPIを作成しよう！④総学習時間API編</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【はじめてのJava】ダッシュボードAPIを作成しよう！③ログイン状況可視化編</title>
		<link>https://www.sms-datatech.co.jp/column/try_java-3/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=try_java-3</link>
		
		<dc:creator><![CDATA[後藤]]></dc:creator>
		<pubDate>Mon, 25 May 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[やってみた]]></category>
		<guid isPermaLink="false">https://www.sms-datatech.co.jp/?p=35806</guid>

					<description><![CDATA[<p>マーケターからエンジニアへ転身した社員による、Javaを用いたダッシュボードAPI作成の実践録。サーバーサイド初心者に向けて、ログイン情報取得APIの設計から実装ステップ、開発を通じて得たリアルな気づきまでをわかりやすく解説します。</p>
<p>The post <a href="https://www.sms-datatech.co.jp/column/try_java-3/">【はじめてのJava】ダッシュボードAPIを作成しよう！③ログイン状況可視化編</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">はじめに</h2>



<p>こんにちは！マーケターから未経験のエンジニアに転身した社員Tです。</p>



<p>開発を始めて1年が経ち、サーバーサイド開発にも触れるようになりました。</p>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回は、Javaを使用したダッシュボードAPIの作成についてご紹介します。</mark></strong></p>



<p>これまでフロントエンド中心の開発を行っていた私が、サーバーサイドのAPI開発に挑戦していく様子をお届けします。</p>



<h2 class="wp-block-heading">本記事の対象の方</h2>



<ul class="wp-block-list">
<li>サーバーサイド開発をこれから学びたい方</li>



<li>JavaでのAPI開発に興味がある方</li>



<li>ダッシュボードの作成を通じて、実践的な技術を学びたい方</li>
</ul>



<h2 class="wp-block-heading">Javaとは？</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">Javaは、非常に人気のあるオブジェクト指向プログラミング言語で、さまざまなアプリケーションの開発に広く使用されています。</mark></strong></p>



<p>詳しい説明については、過去のブログ記事をご参照ください。<br><a href="https://www.sms-datatech.co.jp/category/try/"><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">⇒過去のブログ一覧</span></mark></strong></a></p>



<h2 class="wp-block-heading">環境構築</h2>



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



<h2 class="wp-block-heading">ダッシュボードAPIの作成</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回作成したいのは、こんなダッシュボード画面です。</mark></strong>ユーザーがコースを選択することで、自分の学習状況を一目で確認できるようにすることを目指しています。</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="465" src="https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1024x465.png" alt="" class="wp-image-34969" srcset="https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1024x465.png 1024w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-300x136.png 300w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-768x349.png 768w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1536x698.png 1536w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3.png 1736w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



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



<ol class="wp-block-list">
<li>ユーザー情報を取得</li>



<li>コース情報を取得</li>



<li>各グラフ描画に必要なデータを取得</li>
</ol>



<p>今回は、3.各グラフ描画に必要なデータを取得するAPIの実装について詳しく触れていきたいと思います。左上のログイン状況の表の制作に取り掛かりたいと思います。</p>



<p>1. ユーザー情報の取得については、過去のブログ記事をご参考ください。<br><a href="https://www.sms-datatech.co.jp/column/try_java-1/"><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">⇒【はじめてのJava】ダッシュボードAPIを作成しよう！① ユーザー情報取得編</span></mark></strong></a></p>



<p>2. コース情報の取得については、過去のブログ記事をご参考ください。<br><a href="https://www.sms-datatech.co.jp/column/try_java-2/"><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">⇒【はじめてのJava】ダッシュボードAPIを作成しよう！②コース情報取得編</span></mark></strong></a></p>



<h2 class="wp-block-heading">ログイン情報取得APIの設計</h2>



<p>取得したユーザーIDからログイン情報を取得し返却する。</p>



<h3 class="wp-block-heading">エンドポイント</h3>



<p>/top/getLoginHistoryByUserId</p>



<ul class="wp-block-list">
<li>HTTPメソッド: POST</li>



<li>リクエストボディ: ユーザーID</li>



<li>レスポンス: ログイン履歴のマップ</li>
</ul>



<h3 class="wp-block-heading">リクエスト例</h3>



<pre style="background-color:#000;color:#fff;padding:16px;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,'Courier New',monospace;font-size:14px;line-height:1.5;margin:0 0 16px 0;"><code style="background-color:transparent;color:#fff;">{
  "userId":"175"
}</code></pre>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回はパスパラメータではなく、リクエストボディからユーザーIDを受け取る設計にしています。</mark></strong>これにより、柔軟なデータの受け渡しが可能となります。</p>



<h3 class="wp-block-heading">レスポンス例</h3>



<pre style="background-color:#000;color:#fff;padding:16px;border-radius:6px;overflow-x:auto;font-family:Consolas,Monaco,'Courier New',monospace;font-size:14px;line-height:1.5;margin:0 0 16px 0;"><code style="background-color:transparent;color:#fff;">{
  "dailyLoginHistoryMap": {
    "monday": false,
    "tuesday": false,
    "wednesday": false,
    "thursday": false,
    "friday": false,
    "saturday": false,
    "sunday": false
  },
  "errorMessageList": null
}</code></pre>



<h2 class="wp-block-heading">ログイン情報取得APIの実装</h2>



<h3 class="wp-block-heading">ログイン情報を管理するモデルクラスの作成</h3>



<p>まず、ログイン履歴を保持するエンティティクラスを作成します。</p>



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">LoginHistoryEntity.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#569cd6">package</span> <span style="color:#9cdcfe">jp</span>.<span style="color:#9cdcfe">co</span>.<span style="color:#9cdcfe">smsdatatech</span>.<span style="color:#9cdcfe">questiongenerate</span>.<span style="color:#9cdcfe">entity</span>;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">java</span>.<span style="color:#9cdcfe">io</span>.<span style="color:#4ec9b0">Serializable</span>;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">java</span>.<span style="color:#9cdcfe">sql</span>.<span style="color:#4ec9b0">Timestamp</span>;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span><span style="color:#569cd6">import</span> <span style="color:#9cdcfe">lombok</span>.<span style="color:#4ec9b0">Data</span>;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span><span style="color:#6a9955">/**</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span> <span style="color:#6a9955">* ログイン履歴テーブルEntity</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span> <span style="color:#6a9955">*/</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span><span style="color:#dcdcaa">@Data</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span><span style="color:#569cd6">public</span> <span style="color:#569cd6">class</span> <span style="color:#4ec9b0">LoginHistoryEntity</span> <span style="color:#569cd6">implements</span> <span style="color:#4ec9b0">Serializable</span> {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span>    <span style="color:#6a9955">/** シリアルバージョンUID */</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span>    <span style="color:#569cd6">private</span> <span style="color:#569cd6">static</span> <span style="color:#569cd6">final</span> <span style="color:#569cd6">long</span> <span style="color:#9cdcfe">serialVersionUID</span> = <span style="color:#b5cea8">1L</span>;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span>    <span style="color:#6a9955">/** ユーザーID */</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">userId</span>;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span>    <span style="color:#6a9955">/** 法人ID */</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">corporateId</span>;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>    <span style="color:#6a9955">/** ログイン日時 */</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">Timestamp</span> <span style="color:#9cdcfe">loginDate</span>;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span>}</pre></div>



<h3 class="wp-block-heading">MyBatisのMapper設定</h3>



<p>次に、MyBatisのMapperを使用してデータベースからログイン履歴を取得します。getLoginHistoryByUserIdAndDateRange メソッドは、指定されたユーザーIDと日付範囲に基づいてログイン履歴を取得します。</p>



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">LoginHistoryMapper.xml</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#d4d4d4">&lt;</span><span style="color:#569cd6">mapper</span> <span style="color:#9cdcfe">namespace</span><span style="color:#d4d4d4">=</span><span style="color:#ce9178">&quot;jp.co.smsdatatech.questiongenerate.repository.LoginHistoryMapper&quot;</span><span style="color:#d4d4d4">&gt;</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span><span style="color:#d4d4d4">    </span><span style="color:#d4d4d4">&lt;</span><span style="color:#569cd6">select</span> <span style="color:#9cdcfe">id</span><span style="color:#d4d4d4">=</span><span style="color:#ce9178">&quot;getLoginHistoryByUserIdAndDateRange&quot;</span> <span style="color:#9cdcfe">parameterType</span><span style="color:#d4d4d4">=</span><span style="color:#ce9178">&quot;map&quot;</span> <span style="color:#9cdcfe">resultType</span><span style="color:#d4d4d4">=</span><span style="color:#ce9178">&quot;java.time.LocalDateTime&quot;</span><span style="color:#d4d4d4">&gt;</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span><span style="color:#d4d4d4">        SELECT DISTINCT</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span><span style="color:#d4d4d4">            login_date</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span><span style="color:#d4d4d4">        FROM</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span><span style="color:#d4d4d4">            login_history</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span><span style="color:#d4d4d4">        WHERE</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span><span style="color:#d4d4d4">            user_id = #{userId}</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span><span style="color:#d4d4d4">            AND login_date BETWEEN #{startOfWeek} AND #{endOfWeek}</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span><span style="color:#d4d4d4">        ORDER BY</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span><span style="color:#d4d4d4">            login_date DESC</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span><span style="color:#d4d4d4">    </span><span style="color:#d4d4d4">&lt;</span><span style="color:#d4d4d4">/</span><span style="color:#569cd6">select</span><span style="color:#d4d4d4">&gt;</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span><span style="color:#d4d4d4">&lt;</span><span style="color:#d4d4d4">/</span><span style="color:#569cd6">mapper</span><span style="color:#d4d4d4">&gt;</span></pre></div>



<h3 class="wp-block-heading">サービスクラスの作成</h3>



<p>サービスクラスで、ログイン履歴を取得するロジックを構築します。LoginHistoryService クラスでは、ユーザーIDを受け取り、ログイン履歴を取得します。</p>



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">LoginHistoryService.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#569cd6">public</span> <span style="color:#569cd6">class</span> <span style="color:#4ec9b0">LoginHistoryService</span> {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span>    <span style="color:#6a9955">/**</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span>     <span style="color:#6a9955">* 指定されたユーザーIDに基づいてログイン履歴を取得する。</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span>     <span style="color:#6a9955">*</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span>     <span style="color:#6a9955">* @param userId ユーザーID</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span>     <span style="color:#6a9955">* @return ログイン履歴（曜日ごとの情報）</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span>     <span style="color:#6a9955">*/</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span>    <span style="color:#dcdcaa">@Transactional</span>(<span style="color:#9cdcfe">readOnly</span> = <span style="color:#569cd6">true</span>)<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span>    <span style="color:#569cd6">public</span> <span style="color:#4ec9b0">Map</span>&lt;<span style="color:#569cd6">String</span>, <span style="color:#4ec9b0">Boolean</span>&gt; <span style="color:#dcdcaa">getLoginHistoryByUserIdAndDateRange</span>(<span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">userId</span>) {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span>        <span style="color:#6a9955">// 週の開始日と終了日を計算</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span>        <span style="color:#4ec9b0">LocalDateTime</span> <span style="color:#9cdcfe">startOfWeek</span> = <span style="color:#4ec9b0">DateUtils</span>.<span style="color:#dcdcaa">getStartOfWeek</span>();<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span>        <span style="color:#4ec9b0">LocalDateTime</span> <span style="color:#9cdcfe">endOfWeek</span> = <span style="color:#4ec9b0">DateUtils</span>.<span style="color:#dcdcaa">getEndOfWeek</span>();<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span>        <span style="color:#9cdcfe">log</span>.<span style="color:#dcdcaa">debug</span>(<span style="color:#ce9178">&quot;ユーザーID {} のログイン履歴を週の範囲 {} - {} で取得します。&quot;</span>, <span style="color:#9cdcfe">userId</span>, <span style="color:#9cdcfe">startOfWeek</span>, <span style="color:#9cdcfe">endOfWeek</span>);<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span>        <span style="color:#6a9955">// データベースからログイン履歴を取得</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span>        <span style="color:#4ec9b0">List</span>&lt;<span style="color:#4ec9b0">LocalDateTime</span>&gt; <span style="color:#9cdcfe">loginHistory</span> = <span style="color:#9cdcfe">loginHistoryMapper</span>.<span style="color:#dcdcaa">getLoginHistoryByUserIdAndDateRange</span>(<span style="color:#9cdcfe">userId</span>, <span style="color:#9cdcfe">startOfWeek</span>, <span style="color:#9cdcfe">endOfWeek</span>);<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span>        <span style="color:#4ec9b0">Map</span>&lt;<span style="color:#569cd6">String</span>, <span style="color:#4ec9b0">Boolean</span>&gt; <span style="color:#9cdcfe">dailyLoginHistoryMap</span> = <span style="color:#4ec9b0">DateUtils</span>.<span style="color:#dcdcaa">initializeWeekMap</span>();<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span>        <span style="color:#4ec9b0">DateTimeFormatter</span> <span style="color:#9cdcfe">dayFormatter</span> = <span style="color:#4ec9b0">DateTimeFormatter</span>.<span style="color:#dcdcaa">ofPattern</span>(<span style="color:#ce9178">&quot;EEEE&quot;</span>, <span style="color:#4ec9b0">Locale</span>.<span style="color:#4fc1ff">ENGLISH</span>);<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>        <span style="color:#569cd6">for</span> (<span style="color:#4ec9b0">LocalDateTime</span> <span style="color:#9cdcfe">login</span> : <span style="color:#9cdcfe">loginHistory</span>) {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span>            <span style="color:#569cd6">if</span> (<span style="color:#9cdcfe">login</span> != <span style="color:#569cd6">null</span>) {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span>                <span style="color:#569cd6">String</span> <span style="color:#9cdcfe">dayOfWeek</span> = <span style="color:#9cdcfe">login</span>.<span style="color:#dcdcaa">format</span>(<span style="color:#9cdcfe">dayFormatter</span>).<span style="color:#dcdcaa">toLowerCase</span>(<span style="color:#4ec9b0">Locale</span>.<span style="color:#4fc1ff">ROOT</span>);<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span>                <span style="color:#9cdcfe">dailyLoginHistoryMap</span>.<span style="color:#dcdcaa">put</span>(<span style="color:#9cdcfe">dayOfWeek</span>, <span style="color:#569cd6">true</span>);<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span>            }<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span>        }<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">dailyLoginHistoryMap</span>;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">30</span>    }<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">31</span>}</pre></div>



<h3 class="wp-block-heading">コントローラークラスの作成</h3>



<p>コントローラークラスでは、APIのエンドポイントを定義し、サービス層を呼び出してレスポンスを返します。</p>



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">TopRest.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#569cd6">public</span> <span style="color:#569cd6">class</span> <span style="color:#4ec9b0">TopRest</span> {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span>    <span style="color:#6a9955">/**</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span>     <span style="color:#6a9955">* ログイン状況取得.</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span>     <span style="color:#6a9955">*</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span>     <span style="color:#6a9955">* login_historyテーブルからユーザーIDに紐づくログイン状況を取得し、LoginHistoryResponseFormに詰めて返却する。</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span>     <span style="color:#6a9955">*</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span>     <span style="color:#6a9955">* &lt;h3&gt;処理フロー&lt;/h3&gt;</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span>     <span style="color:#6a9955">* 1. ログイン履歴を取得&lt;br&gt;</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span>     <span style="color:#6a9955">* 2. 取得データを返却</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span>     <span style="color:#6a9955">*</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span>     <span style="color:#6a9955">* &lt;h3&gt;レスポンス構成&lt;/h3&gt;</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span>     <span style="color:#6a9955">* - **loginHistoryByDay**: 曜日ごとのログイン状況（キー: 曜日、値: true/false）&lt;br&gt;</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span>     <span style="color:#6a9955">* - **error**: エラー発生時のメッセージ</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span>     <span style="color:#6a9955">*</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span>     <span style="color:#6a9955">* @param form    Top画面UserId用Form</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span>     <span style="color:#6a9955">* @param result  バリデーション結果</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span>     <span style="color:#6a9955">* @return ログイン履歴情報 LoginHistoryResponseForm</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span>     <span style="color:#6a9955">*/</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span>    <span style="color:#dcdcaa">@PostMapping</span>(<span style="color:#ce9178">&quot;/top/getLoginHistoryByUserId&quot;</span>)<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span>    <span style="color:#569cd6">public</span> <span style="color:#4ec9b0">LoginHistoryResponseForm</span> <span style="color:#dcdcaa">getLoginHistoryByUserId</span>(<span style="color:#dcdcaa">@RequestBody</span> <span style="color:#dcdcaa">@Valid</span> <span style="color:#4ec9b0">UserIdForm</span> <span style="color:#9cdcfe">form</span>, <span style="color:#4ec9b0">BindingResult</span> <span style="color:#9cdcfe">result</span>) {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span>        <span style="color:#4ec9b0">List</span>&lt;<span style="color:#569cd6">String</span>&gt; <span style="color:#9cdcfe">errorMessageList</span> = <span style="color:#569cd6">new</span> <span style="color:#4ec9b0">ArrayList</span>&lt;&gt;();<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>        <span style="color:#4ec9b0">LoginHistoryResponseForm</span> <span style="color:#9cdcfe">responseForm</span> = <span style="color:#569cd6">new</span> <span style="color:#4ec9b0">LoginHistoryResponseForm</span>();<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span>        <span style="color:#4ec9b0">Set</span>&lt;<span style="color:#569cd6">String</span>&gt; <span style="color:#9cdcfe">processedFields</span> = <span style="color:#569cd6">new</span> <span style="color:#4ec9b0">HashSet</span>&lt;&gt;();<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span>        <span style="color:#6a9955">// バリデーションエラーがある場合、詳細なエラーメッセージを追加</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span>        <span style="color:#569cd6">if</span> (<span style="color:#9cdcfe">result</span>.<span style="color:#dcdcaa">hasErrors</span>()) {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span>            <span style="color:#569cd6">for</span> (<span style="color:#4ec9b0">FieldError</span> <span style="color:#9cdcfe">fieldError</span> : <span style="color:#9cdcfe">result</span>.<span style="color:#dcdcaa">getFieldErrors</span>()) {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">30</span>                <span style="color:#6a9955">// フィールド名を基に重複チェック</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">31</span>                <span style="color:#569cd6">if</span> (!<span style="color:#9cdcfe">processedFields</span>.<span style="color:#dcdcaa">contains</span>(<span style="color:#9cdcfe">fieldError</span>.<span style="color:#dcdcaa">getField</span>())) {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">32</span>                    <span style="color:#569cd6">String</span> <span style="color:#9cdcfe">errorMessage</span> = <span style="color:#9cdcfe">messageSource</span>.<span style="color:#dcdcaa">getMessage</span>(<span style="color:#9cdcfe">fieldError</span>, <span style="color:#4ec9b0">Locale</span>.<span style="color:#dcdcaa">getDefault</span>());<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">33</span>                    <span style="color:#9cdcfe">errorMessageList</span>.<span style="color:#dcdcaa">add</span>(<span style="color:#9cdcfe">errorMessage</span>);<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">34</span>                    <span style="color:#9cdcfe">processedFields</span>.<span style="color:#dcdcaa">add</span>(<span style="color:#9cdcfe">fieldError</span>.<span style="color:#dcdcaa">getField</span>());<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">35</span>                }<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">36</span>            }<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">37</span>            <span style="color:#9cdcfe">responseForm</span>.<span style="color:#dcdcaa">setErrorMessageList</span>(<span style="color:#9cdcfe">errorMessageList</span>);<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">38</span>            <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">responseForm</span>;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">39</span>        }<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">40</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">41</span>        <span style="color:#6a9955">// ユーザーIDを整数に変換</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">42</span>        <span style="color:#4ec9b0">Integer</span> <span style="color:#9cdcfe">userId</span> = <span style="color:#4ec9b0">Integer</span>.<span style="color:#dcdcaa">parseInt</span>(<span style="color:#9cdcfe">form</span>.<span style="color:#dcdcaa">getUserId</span>());<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">43</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">44</span>        <span style="color:#6a9955">// サービス層で曜日ごとのログイン履歴を取得</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">45</span>        <span style="color:#4ec9b0">Map</span>&lt;<span style="color:#569cd6">String</span>, <span style="color:#4ec9b0">Boolean</span>&gt; <span style="color:#9cdcfe">dailyLoginHistoryMap</span> = <span style="color:#9cdcfe">loginHistoryService</span>.<span style="color:#dcdcaa">getLoginHistoryByUserIdAndDateRange</span>(<span style="color:#9cdcfe">userId</span>);<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">46</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">47</span>        <span style="color:#6a9955">// ログイン履歴が空かどうかをチェック</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">48</span>        <span style="color:#569cd6">if</span> (<span style="color:#9cdcfe">dailyLoginHistoryMap</span>.<span style="color:#dcdcaa">isEmpty</span>()) {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">49</span>            <span style="color:#9cdcfe">errorMessageList</span>.<span style="color:#dcdcaa">add</span>(<span style="color:#9cdcfe">messageSource</span>.<span style="color:#dcdcaa">getMessage</span>(<span style="color:#4ec9b0">ErrorMessages</span>.<span style="color:#4fc1ff">NO_LOGIN_HISTORY</span>, <span style="color:#569cd6">null</span>, <span style="color:#4ec9b0">Locale</span>.<span style="color:#dcdcaa">getDefault</span>()));<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">50</span>            <span style="color:#9cdcfe">responseForm</span>.<span style="color:#dcdcaa">setErrorMessageList</span>(<span style="color:#9cdcfe">errorMessageList</span>);<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">51</span>            <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">responseForm</span>;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">52</span>        }<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">53</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">54</span>        <span style="color:#9cdcfe">responseForm</span>.<span style="color:#dcdcaa">setDailyLoginHistoryMap</span>(<span style="color:#9cdcfe">dailyLoginHistoryMap</span>);<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">55</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">responseForm</span>;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">56</span>    }<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">57</span>}</pre></div>



<h3 class="wp-block-heading">日付および曜日に関するユーティリティクラス</h3>



<p>ログイン履歴の処理に役立つユーティリティメソッド（例：曜日の初期化や日付の範囲計算など）を別途 DateUtils クラスとして用意します。</p>



<div style="margin:1.5em 0;border-radius:6px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.15)"><div style="background:#252526;color:#cccccc;padding:6px 12px;font-family:Consolas,'Courier New',monospace;font-size:12px;border-bottom:1px solid #1e1e1e;border-top-left-radius:6px;border-top-right-radius:6px">DateUtils.java</div><pre style="background:#1e1e1e;color:#d4d4d4;font-family:Consolas,'Courier New',monospace;font-size:13px;line-height:1.55;padding:14px 16px;margin:0;overflow-x:auto;white-space:pre;border-bottom-left-radius:6px;border-bottom-right-radius:6px"><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 1</span><span style="color:#569cd6">public</span> <span style="color:#569cd6">class</span> <span style="color:#4ec9b0">DateUtils</span> {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 2</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 3</span>    <span style="color:#6a9955">/** 曜日をまとめた配列 */</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 4</span>    <span style="color:#569cd6">protected</span> <span style="color:#569cd6">static</span> <span style="color:#569cd6">final</span> <span style="color:#569cd6">String</span>[] <span style="color:#4fc1ff">DAYS_OF_WEEK</span> = {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 5</span>            <span style="color:#ce9178">&quot;monday&quot;</span>, <span style="color:#ce9178">&quot;tuesday&quot;</span>, <span style="color:#ce9178">&quot;wednesday&quot;</span>, <span style="color:#ce9178">&quot;thursday&quot;</span>, <span style="color:#ce9178">&quot;friday&quot;</span>, <span style="color:#ce9178">&quot;saturday&quot;</span>, <span style="color:#ce9178">&quot;sunday&quot;</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 6</span>    };<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 7</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 8</span>    <span style="color:#6a9955">/**</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none"> 9</span>     <span style="color:#6a9955">* プライベートコンストラクタでインスタンス化を禁止する</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">10</span>     <span style="color:#6a9955">*/</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">11</span>    <span style="color:#569cd6">private</span> <span style="color:#4ec9b0">DateUtils</span>() {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">12</span>        <span style="color:#569cd6">throw</span> <span style="color:#569cd6">new</span> <span style="color:#4ec9b0">UnsupportedOperationException</span>(<span style="color:#ce9178">&quot;このクラスはインスタンス化できません&quot;</span>);<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">13</span>    }<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">14</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">15</span>    <span style="color:#6a9955">/**</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">16</span>     <span style="color:#6a9955">* 指定された日付の週の開始日（月曜日の00:00:00）を取得.</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">17</span>     <span style="color:#6a9955">*</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">18</span>     <span style="color:#6a9955">* @param date 基準日</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">19</span>     <span style="color:#6a9955">* @return 週の開始日</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">20</span>     <span style="color:#6a9955">*/</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">21</span>    <span style="color:#569cd6">public</span> <span style="color:#569cd6">static</span> <span style="color:#4ec9b0">LocalDateTime</span> <span style="color:#dcdcaa">getStartOfWeek</span>(<span style="color:#4ec9b0">LocalDate</span> <span style="color:#9cdcfe">date</span>) {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">22</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">date</span>.<span style="color:#dcdcaa">with</span>(<span style="color:#4ec9b0">TemporalAdjusters</span>.<span style="color:#dcdcaa">previousOrSame</span>(<span style="color:#4ec9b0">DayOfWeek</span>.<span style="color:#4fc1ff">MONDAY</span>)).<span style="color:#dcdcaa">atStartOfDay</span>();<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">23</span>    }<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">24</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">25</span>    <span style="color:#6a9955">/**</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">26</span>     <span style="color:#6a9955">* 現在週の開始日（月曜日の00:00:00）を取得.</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">27</span>     <span style="color:#6a9955">*</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">28</span>     <span style="color:#6a9955">* @return 現在週の開始日</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">29</span>     <span style="color:#6a9955">*/</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">30</span>    <span style="color:#569cd6">public</span> <span style="color:#569cd6">static</span> <span style="color:#4ec9b0">LocalDateTime</span> <span style="color:#dcdcaa">getStartOfWeek</span>() {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">31</span>        <span style="color:#569cd6">return</span> <span style="color:#dcdcaa">getStartOfWeek</span>(<span style="color:#4ec9b0">LocalDate</span>.<span style="color:#dcdcaa">now</span>());<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">32</span>    }<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">33</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">34</span>    <span style="color:#6a9955">/**</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">35</span>     <span style="color:#6a9955">* 指定された日付の週の終了日（日曜日の23:59:59）を取得.</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">36</span>     <span style="color:#6a9955">*</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">37</span>     <span style="color:#6a9955">* @param date 基準日</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">38</span>     <span style="color:#6a9955">* @return 週の終了日</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">39</span>     <span style="color:#6a9955">*/</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">40</span>    <span style="color:#569cd6">public</span> <span style="color:#569cd6">static</span> <span style="color:#4ec9b0">LocalDateTime</span> <span style="color:#dcdcaa">getEndOfWeek</span>(<span style="color:#4ec9b0">LocalDate</span> <span style="color:#9cdcfe">date</span>) {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">41</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">date</span>.<span style="color:#dcdcaa">with</span>(<span style="color:#4ec9b0">TemporalAdjusters</span>.<span style="color:#dcdcaa">nextOrSame</span>(<span style="color:#4ec9b0">DayOfWeek</span>.<span style="color:#4fc1ff">SUNDAY</span>)).<span style="color:#dcdcaa">atTime</span>(<span style="color:#4ec9b0">LocalTime</span>.<span style="color:#4fc1ff">MAX</span>);<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">42</span>    }<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">43</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">44</span>    <span style="color:#6a9955">/**</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">45</span>     <span style="color:#6a9955">* 現在週の終了日（日曜日の23:59:59）を取得.</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">46</span>     <span style="color:#6a9955">*</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">47</span>     <span style="color:#6a9955">* @return 現在週の終了日</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">48</span>     <span style="color:#6a9955">*/</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">49</span>    <span style="color:#569cd6">public</span> <span style="color:#569cd6">static</span> <span style="color:#4ec9b0">LocalDateTime</span> <span style="color:#dcdcaa">getEndOfWeek</span>() {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">50</span>        <span style="color:#569cd6">return</span> <span style="color:#dcdcaa">getEndOfWeek</span>(<span style="color:#4ec9b0">LocalDate</span>.<span style="color:#dcdcaa">now</span>());<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">51</span>    }<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">52</span>&nbsp;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">53</span>    <span style="color:#6a9955">/**</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">54</span>     <span style="color:#6a9955">* 曜日ごとの初期マップを作成（初期値はすべてfalse）.</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">55</span>     <span style="color:#6a9955">*</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">56</span>     <span style="color:#6a9955">* @return 曜日をキーにしたマップ（値はすべてfalse）</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">57</span>     <span style="color:#6a9955">*/</span><br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">58</span>    <span style="color:#569cd6">public</span> <span style="color:#569cd6">static</span> <span style="color:#4ec9b0">Map</span>&lt;<span style="color:#569cd6">String</span>, <span style="color:#4ec9b0">Boolean</span>&gt; <span style="color:#dcdcaa">initializeWeekMap</span>() {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">59</span>        <span style="color:#4ec9b0">Map</span>&lt;<span style="color:#569cd6">String</span>, <span style="color:#4ec9b0">Boolean</span>&gt; <span style="color:#9cdcfe">weekMap</span> = <span style="color:#569cd6">new</span> <span style="color:#4ec9b0">LinkedHashMap</span>&lt;&gt;();<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">60</span>        <span style="color:#569cd6">for</span> (<span style="color:#569cd6">String</span> <span style="color:#9cdcfe">day</span> : <span style="color:#4fc1ff">DAYS_OF_WEEK</span>) {<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">61</span>            <span style="color:#9cdcfe">weekMap</span>.<span style="color:#dcdcaa">put</span>(<span style="color:#9cdcfe">day</span>, <span style="color:#569cd6">false</span>);<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">62</span>        }<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">63</span>        <span style="color:#569cd6">return</span> <span style="color:#9cdcfe">weekMap</span>;<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">64</span>    }<br><span style="display:inline-block;width:2.5em;color:#858585;text-align:right;padding-right:1em;user-select:none">65</span>}</pre></div>



<h2 class="wp-block-heading">実装内容の振り返り</h2>



<p>今回の実装で、ログイン履歴を週ごとに取得する処理において、複数回使用される処理（例えば、曜日の初期化や日付の範囲計算）をユーティリティクラスにまとめることができました。この部分は非常に勉強になりました。</p>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">ユーティリティクラスを作成することで、以下のようなメリットを実感できました。</mark></strong></p>



<ul class="wp-block-list">
<li>再利用性の向上：一度作成した共通部品を他の部分でも利用できるようになり、コードの重複を減らすことができました。これにより、将来的にメンテナンスがしやすくなります。</li>



<li>可読性の向上：ユーティリティメソッドを使うことで、メインのビジネスロジックがシンプルになり、読みやすくなりました。例えば、曜日の初期化処理や日付範囲の計算などがユーティリティクラスに隠蔽されているため、コントローラークラスがすっきりとしたものになりました。</li>



<li>テストのしやすさ：共通の処理をユーティリティクラスにまとめることで、その部分を個別にテストすることができ、ロジックの正確性を担保しやすくなりました。</li>
</ul>



<p>これらを踏まえて、ユーティリティクラスはシステム全体のコードをより効率的に、そして保守しやすくするための強力なツールだと改めて実感しました。</p>



<h2 class="wp-block-heading">まとめ</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">効率的なコードを書くための方法を一つ引き出しを増やせたことが良かったです。</mark></strong>特に、複数のクラスで使い回す共通の処理や汎用的で一貫性が求められる処理をユーティリティクラスにまとめることが、コードの効率化や保守性向上に大いに役立つと感じました。</p>



<p>次回も、各グラフの作成方法について記載しますのでお楽しみに！</p>



<div class="author__card">
  <div class="author-data">
    <img decoding="async" src="https://www.sms-datatech.co.jp/wp-content/uploads/2024/11/author-3.png" alt="筆者イメージ">
    <div>
      <p>筆者：K・T（20代）</p>
      <p>所属：ハイブリット・マルチクラウド部</p>
    </div>
  </div>
  <div class="author-intro">
    <p>マーケターとしてWEB広告運用やアパレルブランドの運営に携わってきたが、エンジニアとしての新たな挑戦を決意した転職組。<br>好奇心が旺盛で、新しいことに挑戦する姿勢が彼女の魅力。<br>マーケター×エンジニアの強みを生かし、UI/UXの提案から実装まで幅広く対応し、彼女自身がブランドを育てているのだろうと思わせてくれる、そんな20代の乙女。</p>
  </div>
</div>


<div class="block-block-22">
	<div class="Editor-wrap">
					<div class="Editor-wrap__titleMsg">まずはお気軽にご相談ください</div>
			<div class="Editor-wrap__title">お問い合わせフォーム</div>
			</div>
</div>


<script charset="utf-8" type="text/javascript" src="//js.hsforms.net/forms/embed/v2.js"></script>
<script>
  hbspt.forms.create({
    region: "na1",
    portalId: "23171742",
    formId: "b36c7c3b-83a0-43c4-8daa-8e055d6805e9"
  });
</script><p>The post <a href="https://www.sms-datatech.co.jp/column/try_java-3/">【はじめてのJava】ダッシュボードAPIを作成しよう！③ログイン状況可視化編</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【はじめてのJava】ダッシュボードAPIを作成しよう！②コース情報取得編</title>
		<link>https://www.sms-datatech.co.jp/column/try_java-2/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=try_java-2</link>
		
		<dc:creator><![CDATA[後藤]]></dc:creator>
		<pubDate>Mon, 27 Apr 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[やってみた]]></category>
		<guid isPermaLink="false">https://www.sms-datatech.co.jp/?p=34978</guid>

					<description><![CDATA[<p>マーケターからエンジニアへ転身した社員による、Javaを用いたダッシュボードAPI作成の実践録。サーバーサイド初心者に向けて、コース情報取得APIの設計から実装ステップ、開発を通じて得たリアルな気づきまでをわかりやすく解説します。</p>
<p>The post <a href="https://www.sms-datatech.co.jp/column/try_java-2/">【はじめてのJava】ダッシュボードAPIを作成しよう！②コース情報取得編</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">はじめに</h2>



<p>こんにちは！マーケターから未経験のエンジニアに転職した社員Tです。<br>開発を始めて1年が経ち、サーバーサイド開発にも触れるようになりました。</p>



<p>今回は、Javaを使用したダッシュボードAPIの作成についてご紹介します。 <br><strong><mark style="background-color:#fdff84" class="has-inline-color">これまでフロントエンド中心の開発を行っていた私が、サーバーサイドのAPI開発に挑戦していく様子をお届けします。</mark></strong></p>



<h2 class="wp-block-heading">本記事の対象の方</h2>



<ul class="wp-block-list">
<li>サーバーサイド開発をこれから学びたい方</li>



<li>JavaでのAPI開発に興味がある方</li>



<li>ダッシュボードの作成を通じて、実践的な技術を学びたい方</li>
</ul>



<h2 class="wp-block-heading">Javaとは？</h2>



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



<p>詳しい説明については、<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><a href="https://www.sms-datatech.co.jp/column/try_java-1/"><span style="text-decoration: underline;">過去のブログ記事</span></a></mark>をご参照ください。</p>



<h2 class="wp-block-heading">環境構築</h2>



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



<h2 class="wp-block-heading">ダッシュボードAPIの作成</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回作成したいのは、こんなダッシュボード画面です。ユーザーがコースを選択することで、自分の学習状況を一目で確認できるようにすることを目指しています。</mark></strong></p>



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



<ol class="wp-block-list">
<li>ユーザー情報を取得</li>



<li>コース情報を取得</li>



<li>各グラフ描画に必要なデータを取得</li>
</ol>



<p>今回は、2.コース情報を取得するAPIIの実装について詳しく触れていきたいと思います。</p>



<p>1. ユーザー情報の取得については、<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><a href="https://www.sms-datatech.co.jp/column/try_java-1/"><span style="text-decoration: underline;">過去のブログ記事</span></a></mark>をご参考ください。</p>



<h2 class="wp-block-heading">コース情報取得APIの設計</h2>



<p>取得したユーザー情報から法人IDをリクエストに含め、コースリストを取得する</p>



<h3 class="wp-block-heading">エンドポイント</h3>



<div style="background-color:#1e1e1e; border-radius:8px; padding:16px 20px; margin:10px 0; overflow-x:auto;">
<code style="color:#d4d4d4; font-family:'Consolas','Monaco','Courier New',monospace; font-size:14px; line-height:1.6; white-space:pre;">/top/getCourseListByCorporateId</code>
</div>



<ul class="wp-block-list">
<li>HTTPメソッド: POST</li>



<li>リクエストボディ: 法人ID</li>



<li>レスポンス: エラーメッセージリスト、コースリスト</li>
</ul>



<h3 class="wp-block-heading">リクエスト例</h3>



<div style="background-color:#1e1e1e; border-radius:8px; padding:16px 20px; margin:10px 0; overflow-x:auto;">
<pre style="margin:0; color:#d4d4d4; font-family:'Consolas','Monaco','Courier New',monospace; font-size:14px; line-height:1.6;"><span style="color:#9cdcfe;">{</span>
  <span style="color:#9cdcfe;">"corporateId"</span><span style="color:#d4d4d4;">:</span> <span style="color:#b5cea8;">1</span>
<span style="color:#9cdcfe;">}</span></pre>
</div>



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



<h3 class="wp-block-heading">レスポンス例</h3>



<div style="background-color:#1e1e1e; border-radius:8px; padding:16px 20px; margin:10px 0; overflow-x:auto;">
<pre style="margin:0; color:#d4d4d4; font-family:'Consolas','Monaco','Courier New',monospace; font-size:14px; line-height:1.6;"><span style="color:#9cdcfe;">{</span>
    <span style="color:#9cdcfe;">"errorMessageList"</span><span style="color:#d4d4d4;">:</span> <span style="color:#569cd6;">null</span><span style="color:#d4d4d4;">,</span>
    <span style="color:#9cdcfe;">"courseList"</span><span style="color:#d4d4d4;">:</span> <span style="color:#d4d4d4;">[</span>
        <span style="color:#9cdcfe;">{</span>
            <span style="color:#9cdcfe;">"course_id"</span><span style="color:#d4d4d4;">:</span> <span style="color:#b5cea8;">1</span><span style="color:#d4d4d4;">,</span>
            <span style="color:#9cdcfe;">"corporate_id"</span><span style="color:#d4d4d4;">:</span> <span style="color:#b5cea8;">1</span><span style="color:#d4d4d4;">,</span>
            <span style="color:#9cdcfe;">"course_name"</span><span style="color:#d4d4d4;">:</span> <span style="color:#ce9178;">"プログラミング基礎"</span><span style="color:#d4d4d4;">,</span>
            <span style="color:#9cdcfe;">"create_date"</span><span style="color:#d4d4d4;">:</span> <span style="color:#ce9178;">"2024-06-01T01:00:00.000+00:00"</span><span style="color:#d4d4d4;">,</span>
            <span style="color:#9cdcfe;">"update_date"</span><span style="color:#d4d4d4;">:</span> <span style="color:#ce9178;">"2024-06-01T01:00:00.000+00:00"</span>
        <span style="color:#9cdcfe;">}</span><span style="color:#d4d4d4;">,</span>
        <span style="color:#9cdcfe;">{</span>
            <span style="color:#9cdcfe;">"course_id"</span><span style="color:#d4d4d4;">:</span> <span style="color:#b5cea8;">2</span><span style="color:#d4d4d4;">,</span>
            <span style="color:#9cdcfe;">"corporate_id"</span><span style="color:#d4d4d4;">:</span> <span style="color:#b5cea8;">1</span><span style="color:#d4d4d4;">,</span>
            <span style="color:#9cdcfe;">"course_name"</span><span style="color:#d4d4d4;">:</span> <span style="color:#ce9178;">"データベース管理"</span><span style="color:#d4d4d4;">,</span>
            <span style="color:#9cdcfe;">"create_date"</span><span style="color:#d4d4d4;">:</span> <span style="color:#ce9178;">"2024-06-15T00:00:00.000+00:00"</span><span style="color:#d4d4d4;">,</span>
            <span style="color:#9cdcfe;">"update_date"</span><span style="color:#d4d4d4;">:</span> <span style="color:#ce9178;">"2024-06-15T00:00:00.000+00:00"</span>
        <span style="color:#9cdcfe;">}</span>
    <span style="color:#d4d4d4;">]</span>
<span style="color:#9cdcfe;">}</span></pre>
</div>



<p>レスポンスでは、courseList というフィールドにコース情報のリストが返されます。<br>このリストには、各コース情報が含まれます。もしエラーメッセージがあれば、errorMessageListにリストとして含まれますが、今回はエラーがない想定です。</p>



<h2 class="wp-block-heading">コース情報取得APIの実装</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回は、ユーザー情報をもとにコースリストを取得するためのAPIを実装します。このAPIは、法人IDに基づいて、対応するコースの情報を返す役割を果たします。</mark></strong></p>



<h3 class="wp-block-heading"><strong>コース情報を管理するモデルクラスの作成</strong></h3>



<p>まず最初に、ユーザー情報を格納するためのエンティティクラスを作成します。<br>このクラスは、データベースのテーブルとマッピングされ、ユーザー情報を格納する役割を持ちます。ここで定義したクラスを基に、データベース操作を行います。</p>



<p>例えば、以下のような CourseEntity クラスを作成します。</p>



<div style="background-color:#1e1e1e; border-radius:8px; padding:16px 20px; margin:10px 0; overflow-x:auto;">
<pre style="margin:0; color:#d4d4d4; font-family:'Consolas','Monaco','Courier New',monospace; font-size:14px; line-height:1.6;"><span style="color:#c586c0;">package</span> <span style="color:#4ec9b0;">jp.co.smsdatatech.questiongenerate.entity</span><span style="color:#d4d4d4;">;</span>

<span style="color:#c586c0;">import</span> <span style="color:#4ec9b0;">java.io.Serializable</span><span style="color:#d4d4d4;">;</span>
<span style="color:#c586c0;">import</span> <span style="color:#4ec9b0;">java.sql.Timestamp</span><span style="color:#d4d4d4;">;</span>

<span style="color:#c586c0;">import</span> <span style="color:#4ec9b0;">jakarta.persistence.Column</span><span style="color:#d4d4d4;">;</span>
<span style="color:#c586c0;">import</span> <span style="color:#4ec9b0;">lombok.Data</span><span style="color:#d4d4d4;">;</span>

<span style="color:#6a9955;">/**</span>
<span style="color:#6a9955;"> * courseテーブルEntity</span>
<span style="color:#6a9955;"> */</span>
<span style="color:#dcdcaa;">@Data</span>
<span style="color:#569cd6;">public class</span> <span style="color:#4ec9b0;">CourseEntity</span> <span style="color:#569cd6;">implements</span> <span style="color:#4ec9b0;">Serializable</span> <span style="color:#d4d4d4;">{</span>

    <span style="color:#6a9955;">/** シリアルバージョンUID */</span>
    <span style="color:#569cd6;">private static final</span> <span style="color:#569cd6;">long</span> <span style="color:#4fc1ff;">serialVersionUID</span> <span style="color:#d4d4d4;">=</span> <span style="color:#b5cea8;">1L</span><span style="color:#d4d4d4;">;</span>

    <span style="color:#6a9955;">/** コースID */</span>
    <span style="color:#569cd6;">private</span> <span style="color:#4ec9b0;">Integer</span> <span style="color:#9cdcfe;">course_id</span><span style="color:#d4d4d4;">;</span>

    <span style="color:#6a9955;">/** 法人ID */</span>
    <span style="color:#569cd6;">private</span> <span style="color:#4ec9b0;">Integer</span> <span style="color:#9cdcfe;">corporate_id</span><span style="color:#d4d4d4;">;</span>

    <span style="color:#6a9955;">/** コース名 */</span>
    <span style="color:#569cd6;">private</span> <span style="color:#4ec9b0;">String</span> <span style="color:#9cdcfe;">course_name</span><span style="color:#d4d4d4;">;</span>

    <span style="color:#6a9955;">/** 作成日 */</span>
    <span style="color:#569cd6;">private</span> <span style="color:#4ec9b0;">Timestamp</span> <span style="color:#9cdcfe;">create_date</span><span style="color:#d4d4d4;">;</span>

    <span style="color:#6a9955;">/** 更新日 */</span>
    <span style="color:#569cd6;">private</span> <span style="color:#4ec9b0;">Timestamp</span> <span style="color:#9cdcfe;">update_date</span><span style="color:#d4d4d4;">;</span>
<span style="color:#d4d4d4;">}</span></pre>
</div>



<h3 class="wp-block-heading"><strong>MapperXMLの作成</strong></h3>



<p>次に、データベースからコース情報を取得するためのクエリを定義する MapperXML を作成します。このXMLファイルは、MyBatisなどのORMツールと組み合わせてデータベースとのやり取りを行います。</p>



<div style="background-color:#1e1e1e; border-radius:8px; padding:16px 20px; margin:10px 0; overflow-x:auto;">
<pre style="margin:0; color:#d4d4d4; font-family:'Consolas','Monaco','Courier New',monospace; font-size:14px; line-height:1.6;"><span style="color:#808080;">&lt;?</span><span style="color:#569cd6;">xml</span> <span style="color:#9cdcfe;">version</span><span style="color:#d4d4d4;">=</span><span style="color:#ce9178;">"1.0"</span> <span style="color:#9cdcfe;">encoding</span><span style="color:#d4d4d4;">=</span><span style="color:#ce9178;">"UTF-8"</span><span style="color:#808080;">?&gt;</span>
<span style="color:#808080;">&lt;!</span><span style="color:#569cd6;">DOCTYPE</span> <span style="color:#569cd6;">mapper</span> PUBLIC <span style="color:#ce9178;">"-//mybatis.org//DTD Mapper 3.0//EN"</span> <span style="color:#ce9178;">"http://mybatis.org/dtd/mybatis-3-mapper.dtd"</span><span style="color:#808080;">&gt;</span>
<span style="color:#808080;">&lt;</span><span style="color:#569cd6;">mapper</span> <span style="color:#9cdcfe;">namespace</span><span style="color:#d4d4d4;">=</span><span style="color:#ce9178;">"jp.co.smsdatatech.questiongenerate.repository.CourseMapper"</span><span style="color:#808080;">&gt;</span>
    <span style="color:#808080;">&lt;</span><span style="color:#569cd6;">select</span> <span style="color:#9cdcfe;">id</span><span style="color:#d4d4d4;">=</span><span style="color:#ce9178;">"getAllByCorporateId"</span> <span style="color:#9cdcfe;">parameterType</span><span style="color:#d4d4d4;">=</span><span style="color:#ce9178;">"Integer"</span> <span style="color:#9cdcfe;">resultType</span><span style="color:#d4d4d4;">=</span><span style="color:#ce9178;">"jp.co.smsdatatech.questiongenerate.entity.CourseEntity"</span><span style="color:#808080;">&gt;</span>
        <span style="color:#569cd6;">SELECT</span>
            <span style="color:#9cdcfe;">course_id</span>,
            <span style="color:#9cdcfe;">corporate_id</span>,
            <span style="color:#9cdcfe;">course_name</span>,
            <span style="color:#9cdcfe;">create_date</span>,
            <span style="color:#9cdcfe;">update_date</span>
        <span style="color:#569cd6;">FROM</span>
            <span style="color:#4ec9b0;">course</span>
        <span style="color:#569cd6;">WHERE</span>
            <span style="color:#9cdcfe;">corporate_id</span> <span style="color:#d4d4d4;">=</span> <span style="color:#dcdcaa;">#{corporateId}</span>
        <span style="color:#569cd6;">ORDER BY</span>
            <span style="color:#9cdcfe;">course_id</span>
    <span style="color:#808080;">&lt;/</span><span style="color:#569cd6;">select</span><span style="color:#808080;">&gt;</span>
<span style="color:#808080;">&lt;/</span><span style="color:#569cd6;">mapper</span><span style="color:#808080;">&gt;</span></pre>
</div>



<h3 class="wp-block-heading"><strong>サービスクラスの作成</strong></h3>



<p>次に、サービスクラスを作成してコース情報の取得処理を実装します。このクラスでは、リポジトリや Mapper を使ってデータベースから情報を取得し、レスポンスとして変換します。</p>



<div style="background-color:#1e1e1e; border-radius:8px; padding:16px 20px; margin:10px 0; overflow-x:auto;">
<pre style="margin:0; color:#d4d4d4; font-family:'Consolas','Monaco','Courier New',monospace; font-size:14px; line-height:1.6;"><span style="color:#c586c0;">import</span> <span style="color:#4ec9b0;">org.springframework.beans.factory.annotation.Autowired</span><span style="color:#d4d4d4;">;</span>
<span style="color:#c586c0;">import</span> <span style="color:#4ec9b0;">org.springframework.stereotype.Service</span><span style="color:#d4d4d4;">;</span>

<span style="color:#c586c0;">import</span> <span style="color:#4ec9b0;">jp.co.smsdatatech.questiongenerate.entity.CourseEntity</span><span style="color:#d4d4d4;">;</span>
<span style="color:#c586c0;">import</span> <span style="color:#4ec9b0;">jp.co.smsdatatech.questiongenerate.repository.CourseMapper</span><span style="color:#d4d4d4;">;</span>

<span style="color:#6a9955;">/**</span>
<span style="color:#6a9955;"> * コーステーブルService</span>
<span style="color:#6a9955;"> */</span>
<span style="color:#dcdcaa;">@Service</span>
<span style="color:#569cd6;">public class</span> <span style="color:#4ec9b0;">CourseService</span> <span style="color:#d4d4d4;">{</span>

    <span style="color:#6a9955;">/** ログ出力クラス */</span>
    <span style="color:#569cd6;">private static final</span> <span style="color:#4ec9b0;">Logger</span> <span style="color:#9cdcfe;">log</span> <span style="color:#d4d4d4;">=</span> <span style="color:#4ec9b0;">LoggerFactory</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getLogger</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">clazz</span><span style="color:#d4d4d4;">:</span><span style="color:#4ec9b0;">CourseService</span><span style="color:#d4d4d4;">.</span><span style="color:#569cd6;">class</span><span style="color:#d4d4d4;">);</span>

    <span style="color:#6a9955;">/** courseテーブルMapper */</span>
    <span style="color:#dcdcaa;">@Autowired</span>
    <span style="color:#569cd6;">private</span> <span style="color:#4ec9b0;">CourseMapper</span> <span style="color:#9cdcfe;">courseMapper</span><span style="color:#d4d4d4;">;</span>

    <span style="color:#6a9955;">/**</span>
<span style="color:#6a9955;">     * コース検索（法人ID検索）</span>
<span style="color:#6a9955;">     * 1.機能概要</span>
<span style="color:#6a9955;">     * courseテーブルから法人IDに紐づくコースを取得し、Listで返却する。</span>
<span style="color:#6a9955;">     * 2.処理フロー</span>
<span style="color:#6a9955;">     * ①DB接続・抽出</span>
<span style="color:#6a9955;">     * ②返却</span>
<span style="color:#6a9955;">     *</span>
<span style="color:#6a9955;">     * @param corporateId 法人ID</span>
<span style="color:#6a9955;">     * @return {@literal List&lt;CourseEntity&gt; コース一覧}</span>
<span style="color:#6a9955;">     */</span>
    <span style="color:#569cd6;">public</span> <span style="color:#4ec9b0;">List</span><span style="color:#d4d4d4;">&lt;</span><span style="color:#4ec9b0;">CourseEntity</span><span style="color:#d4d4d4;">&gt;</span> <span style="color:#dcdcaa;">getAllByCorporateId</span><span style="color:#d4d4d4;">(</span><span style="color:#4ec9b0;">Integer</span> <span style="color:#9cdcfe;">corporateId</span><span style="color:#d4d4d4;">) {</span>
        <span style="color:#4ec9b0;">List</span><span style="color:#d4d4d4;">&lt;</span><span style="color:#4ec9b0;">CourseEntity</span><span style="color:#d4d4d4;">&gt;</span> <span style="color:#9cdcfe;">courseList</span> <span style="color:#d4d4d4;">=</span> <span style="color:#9cdcfe;">courseMapper</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getAllByCorporateId</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">corporateId</span><span style="color:#d4d4d4;">);</span>
        <span style="color:#9cdcfe;">log</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">info</span><span style="color:#d4d4d4;">(</span><span style="color:#ce9178;">"courseテーブルから法人ごとのレコードを取得しました。"</span><span style="color:#d4d4d4;">);</span>
        <span style="color:#c586c0;">return</span> <span style="color:#9cdcfe;">courseList</span><span style="color:#d4d4d4;">;</span>
    <span style="color:#d4d4d4;">}</span>
<span style="color:#d4d4d4;">}</span></pre>
</div>



<h3 class="wp-block-heading"><strong>コントローラークラスの作成</strong></h3>



<p>最後に、リクエストを処理する TopRest コントローラークラスを作成します。</p>



<div style="background-color:#1e1e1e; border-radius:8px; padding:16px 20px; margin:10px 0; overflow-x:auto;">
<pre style="margin:0; color:#d4d4d4; font-family:'Consolas','Monaco','Courier New',monospace; font-size:14px; line-height:1.6;"><span style="color:#6a9955;">/**</span>
<span style="color:#6a9955;"> * コースリスト取得.</span>
<span style="color:#6a9955;"> *</span>
<span style="color:#6a9955;"> * 法人IDに紐づくコースリストを取得し、CorporateCourseResponseForm に詰めて返却する。</span>
<span style="color:#6a9955;"> *</span>
<span style="color:#6a9955;"> * &lt;h3&gt;処理フロー&lt;/h3&gt;</span>
<span style="color:#6a9955;"> * 1. コース情報（コースIDとコース名）を取得&lt;br&gt;</span>
<span style="color:#6a9955;"> * 2. 取得したリストを返却</span>
<span style="color:#6a9955;"> *</span>
<span style="color:#6a9955;"> * @param form Top画面Form</span>
<span style="color:#6a9955;"> * @param result バリデーション結果</span>
<span style="color:#6a9955;"> * @return コースリスト CorporateCourseResponseForm</span>
<span style="color:#6a9955;"> */</span>
<span style="color:#dcdcaa;">@PostMapping</span><span style="color:#d4d4d4;">(</span><span style="color:#ce9178;">"/top/getCourseListByCorporateId"</span><span style="color:#d4d4d4;">)</span>
<span style="color:#569cd6;">public</span> <span style="color:#4ec9b0;">CorporateCourseResponseForm</span> <span style="color:#dcdcaa;">getCourseListByCorporateId</span><span style="color:#d4d4d4;">(</span><span style="color:#dcdcaa;">@RequestBody</span> <span style="color:#dcdcaa;">@Valid</span> <span style="color:#4ec9b0;">TopForm</span> <span style="color:#9cdcfe;">form</span><span style="color:#d4d4d4;">,</span>
        <span style="color:#4ec9b0;">BindingResult</span> <span style="color:#9cdcfe;">result</span><span style="color:#d4d4d4;">) {</span>
    <span style="color:#9cdcfe;">log</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">debug</span><span style="color:#d4d4d4;">(</span><span style="color:#ce9178;">"TopRest (getCourseListByCorporateId) 呼び出し開始"</span><span style="color:#d4d4d4;">);</span>

    <span style="color:#4ec9b0;">CorporateCourseResponseForm</span> <span style="color:#9cdcfe;">responseForm</span> <span style="color:#d4d4d4;">=</span> <span style="color:#569cd6;">new</span> <span style="color:#4ec9b0;">CorporateCourseResponseForm</span><span style="color:#d4d4d4;">();</span>
    <span style="color:#4ec9b0;">List</span><span style="color:#d4d4d4;">&lt;</span><span style="color:#4ec9b0;">String</span><span style="color:#d4d4d4;">&gt;</span> <span style="color:#9cdcfe;">errorMessageList</span> <span style="color:#d4d4d4;">=</span> <span style="color:#569cd6;">new</span> <span style="color:#4ec9b0;">ArrayList</span><span style="color:#d4d4d4;">&lt;&gt;();</span>

    <span style="color:#4ec9b0;">Set</span><span style="color:#d4d4d4;">&lt;</span><span style="color:#4ec9b0;">String</span><span style="color:#d4d4d4;">&gt;</span> <span style="color:#9cdcfe;">processedFields</span> <span style="color:#d4d4d4;">=</span> <span style="color:#569cd6;">new</span> <span style="color:#4ec9b0;">HashSet</span><span style="color:#d4d4d4;">&lt;&gt;();</span>

    <span style="color:#6a9955;">// バリデーションエラーがある場合、詳細なエラーメッセージを追加</span>
    <span style="color:#c586c0;">if</span> <span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">result</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">hasErrors</span><span style="color:#d4d4d4;">()) {</span>
        <span style="color:#c586c0;">for</span> <span style="color:#d4d4d4;">(</span><span style="color:#4ec9b0;">FieldError</span> <span style="color:#9cdcfe;">fieldError</span> <span style="color:#d4d4d4;">:</span> <span style="color:#9cdcfe;">result</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getFieldErrors</span><span style="color:#d4d4d4;">()) {</span>
            <span style="color:#6a9955;">// フィールド名を基に重複チェック</span>
            <span style="color:#c586c0;">if</span> <span style="color:#d4d4d4;">(!</span><span style="color:#9cdcfe;">processedFields</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">contains</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">fieldError</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getField</span><span style="color:#d4d4d4;">())) {</span>
                <span style="color:#4ec9b0;">String</span> <span style="color:#9cdcfe;">errorMessage</span> <span style="color:#d4d4d4;">=</span> <span style="color:#9cdcfe;">messageSource</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getMessage</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">fieldError</span><span style="color:#d4d4d4;">,</span> <span style="color:#4ec9b0;">Locale</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getDefault</span><span style="color:#d4d4d4;">());</span>
                <span style="color:#9cdcfe;">errorMessageList</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">add</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">errorMessage</span><span style="color:#d4d4d4;">);</span>
                <span style="color:#9cdcfe;">processedFields</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">add</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">fieldError</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getField</span><span style="color:#d4d4d4;">());</span>
            <span style="color:#d4d4d4;">}</span>
        <span style="color:#d4d4d4;">}</span>
        <span style="color:#9cdcfe;">responseForm</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">setErrorMessageList</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">errorMessageList</span><span style="color:#d4d4d4;">);</span>
        <span style="color:#c586c0;">return</span> <span style="color:#9cdcfe;">responseForm</span><span style="color:#d4d4d4;">;</span>
    <span style="color:#d4d4d4;">}</span>

    <span style="color:#6a9955;">// 法人IDを整数に変換</span>
    <span style="color:#4ec9b0;">Integer</span> <span style="color:#9cdcfe;">corporateId</span> <span style="color:#d4d4d4;">=</span> <span style="color:#4ec9b0;">Integer</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">parseInt</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">form</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getCorporateId</span><span style="color:#d4d4d4;">());</span>

    <span style="color:#6a9955;">// 法人IDを基にコースリストを取得</span>
    <span style="color:#4ec9b0;">List</span><span style="color:#d4d4d4;">&lt;</span><span style="color:#4ec9b0;">CourseEntity</span><span style="color:#d4d4d4;">&gt;</span> <span style="color:#9cdcfe;">courseList</span> <span style="color:#d4d4d4;">=</span> <span style="color:#9cdcfe;">courseService</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getAllByCorporateId</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">corporateId</span><span style="color:#d4d4d4;">);</span>

    <span style="color:#c586c0;">if</span> <span style="color:#d4d4d4;">(</span><span style="color:#4ec9b0;">CollectionUtils</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">isEmpty</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">courseList</span><span style="color:#d4d4d4;">)) {</span>
        <span style="color:#6a9955;">// コースが見つからなかった場合</span>
        <span style="color:#9cdcfe;">errorMessageList</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">add</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">messageSource</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getMessage</span><span style="color:#d4d4d4;">(</span><span style="color:#4ec9b0;">ErrorMessages</span><span style="color:#d4d4d4;">.</span><span style="color:#4fc1ff;">COURSE_NOT_FOUND</span><span style="color:#d4d4d4;">,</span> <span style="color:#9cdcfe;">args</span><span style="color:#d4d4d4;">:</span><span style="color:#569cd6;">null</span><span style="color:#d4d4d4;">,</span> <span style="color:#4ec9b0;">Locale</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getDefault</span><span style="color:#d4d4d4;">()));</span>
        <span style="color:#9cdcfe;">responseForm</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">setErrorMessageList</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">errorMessageList</span><span style="color:#d4d4d4;">);</span>
        <span style="color:#9cdcfe;">log</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">debug</span><span style="color:#d4d4d4;">(</span><span style="color:#ce9178;">"CourseRest (getCourseList) コースが見つかりません: "</span><span style="color:#d4d4d4;">);</span>
        <span style="color:#c586c0;">return</span> <span style="color:#9cdcfe;">responseForm</span><span style="color:#d4d4d4;">;</span>
    <span style="color:#d4d4d4;">}</span>

    <span style="color:#6a9955;">// コースリストをレスポンスに設定</span>
    <span style="color:#9cdcfe;">responseForm</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">setCourseList</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">courseList</span><span style="color:#d4d4d4;">);</span>
    <span style="color:#c586c0;">return</span> <span style="color:#9cdcfe;">responseForm</span><span style="color:#d4d4d4;">;</span>
<span style="color:#d4d4d4;">}</span></pre>
</div>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回の実装では、リクエストボディのバリデーション に @NotBlank と @Pattern のアノテーションを適用することで、不正なデータによる例外発生を未然に防ぐ設計 になっています。</mark></strong></p>



<h2 class="wp-block-heading">実装内容の振り返り</h2>



<p>今回のコース情報取得に関して、情報が頻繁に変更されることはないため、キャッシュの活用が有効であることに実装後に気付きました。<br>現状ではリクエストごとにDBへのアクセスを行っていますが、キャッシュを導入することで、レスポンス速度の向上やDB負荷の軽減が期待できると考えています。</p>



<p>これについては、今後の改善点として検討していく予定です。</p>



<h2 class="wp-block-heading">まとめ</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回の実装を通じて、実装前に視野を広げ、さまざまな手段を自分の引き出しに入れておく重要性を実感しました。</mark></strong>そのためには、「もっと良い方法はないか」と常にベストを探し続けることが大切だと感じました。経験を積むことと同時に、探究心を持ち続けることで、自然と「より良いものを作りたい」という気持ちが湧いてきます。今後もこの姿勢を大切にし、より良い設計と実装を目指して学び続けていきたいと思います。</p>



<div class="author__card">
  <div class="author-data">
    <img decoding="async" src="https://www.sms-datatech.co.jp/wp-content/uploads/2024/11/author-3.png" alt="筆者イメージ">
    <div>
      <p>筆者：K・T（20代）</p>
      <p>所属：ハイブリット・マルチクラウド部</p>
    </div>
  </div>
  <div class="author-intro">
    <p>マーケターとしてWEB広告運用やアパレルブランドの運営に携わってきたが、エンジニアとしての新たな挑戦を決意した転職組。<br>好奇心が旺盛で、新しいことに挑戦する姿勢が彼女の魅力。<br>マーケター×エンジニアの強みを生かし、UI/UXの提案から実装まで幅広く対応し、彼女自身がブランドを育てているのだろうと思わせてくれる、そんな20代の乙女。</p>
  </div>
</div>


<div class="block-block-22">
	<div class="Editor-wrap">
					<div class="Editor-wrap__titleMsg">まずはお気軽にご相談ください</div>
			<div class="Editor-wrap__title">お問い合わせフォーム</div>
			</div>
</div>


<script charset="utf-8" type="text/javascript" src="//js.hsforms.net/forms/embed/v2.js"></script>
<script>
  hbspt.forms.create({
    region: "na1",
    portalId: "23171742",
    formId: "b36c7c3b-83a0-43c4-8daa-8e055d6805e9"
  });
</script><p>The post <a href="https://www.sms-datatech.co.jp/column/try_java-2/">【はじめてのJava】ダッシュボードAPIを作成しよう！②コース情報取得編</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【はじめてのJava】ダッシュボードAPIを作成しよう！① ユーザー情報取得編</title>
		<link>https://www.sms-datatech.co.jp/column/try_java-1/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=try_java-1</link>
		
		<dc:creator><![CDATA[後藤]]></dc:creator>
		<pubDate>Thu, 26 Mar 2026 01:52:04 +0000</pubDate>
				<category><![CDATA[やってみた]]></category>
		<category><![CDATA[システム開発]]></category>
		<guid isPermaLink="false">https://www.sms-datatech.co.jp/?p=34967</guid>

					<description><![CDATA[<p>マーケターからエンジニアへ転身した社員による、Java（Spring Boot × MyBatis）を用いたダッシュボードAPI作成の実践録。サーバーサイド初心者に向けて、ユーザー情報取得APIの設計から実装ステップ、開発を通じて得たセキュリティ面でのリアルな気づきまでをわかりやすく解説します。</p>
<p>The post <a href="https://www.sms-datatech.co.jp/column/try_java-1/">【はじめてのJava】ダッシュボードAPIを作成しよう！① ユーザー情報取得編</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">はじめに</h2>



<p>こんにちは！マーケターから未経験のエンジニアに転身した社員Tです。<br>開発を始めて1年が経ち、サーバーサイド開発にも触れるようになりました。</p>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回は、Javaを使用したダッシュボードAPIの作成についてご紹介します。</mark></strong><br>これまでクライアントサイド中心の開発を行っていた私が、サーバーサイドのAPI開発に挑戦していく様子をお届けします。</p>



<h3 class="wp-block-heading">本記事の対象の方</h3>



<ul class="wp-block-list">
<li>サーバーサイド開発をこれから学びたい方</li>



<li>JavaでのAPI開発に興味がある方</li>



<li>ダッシュボードの作成を通じて、実践的な技術を学びたい方</li>
</ul>



<h2 class="wp-block-heading">Javaとは？</h2>



<p>クライアントサイドしか触れてこなかった私は、サーバーサイドについては全くの初心者でした。そこで、まずは「Javaとは何か？」というところからお話ししたいと思います。</p>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">Javaは、非常に人気のあるオブジェクト指向プログラミング言語で、さまざまなアプリケーションの開発に広く使用されています。</mark></strong>特に、サーバーサイド開発や大規模システム開発において強力なツールとなっており、以下のような特徴があります。</p>



<h3 class="wp-block-heading">プラットフォームに依存しない</h3>



<p>Javaで開発されたアプリケーションは、どのOSでも実行可能で、クロスプラットフォームで動作します。これにより、Javaで書かれたコードは、Windows、Mac、Linuxなど、異なる環境でも問題なく実行できます。</p>



<h3 class="wp-block-heading">オブジェクト指向</h3>



<p>Javaはオブジェクト指向プログラミングに基づいているため、コードが整理されていて、再利用性が高く、保守性に優れています。これにより、アプリケーションの規模が大きくなっても、管理がしやすくなります。</p>



<h3 class="wp-block-heading">豊富なライブラリとフレームワーク</h3>



<p>Javaには、開発を効率化するための豊富なライブラリやフレームワークが存在します。特に、Spring BootやHibernateなどは、サーバーサイド開発において非常に便利で、効率的に開発を進めることができます。</p>



<p>このように、Javaは非常に強力で多用途な言語です。これからサーバーサイド開発に取り組む際に、大きな武器となってくれること間違いなしです。</p>



<h2 class="wp-block-heading">環境構築</h2>



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



<h2 class="wp-block-heading">ダッシュボードAPIの作成</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回作成したいのは、こんなダッシュボード画面です。</mark></strong>ユーザーがコースを選択することで、自分の学習状況を一目で確認できるようにすることを目指しています。</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="465" src="https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1024x465.png" alt="" class="wp-image-34969" srcset="https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1024x465.png 1024w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-300x136.png 300w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-768x349.png 768w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3-1536x698.png 1536w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-3.png 1736w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



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



<ol class="wp-block-list">
<li>ユーザー情報を取得</li>



<li>コース情報を取得</li>



<li>各グラフ描画に必要なデータを取得</li>
</ol>



<p>今回は、その中でも最初のステップとして、<br>「1.ユーザー情報を取得」するAPIの実装について詳しく触れていきたいと思います。</p>



<h2 class="wp-block-heading">ユーザー情報取得APIの設計</h2>



<p>まず最初に、ユーザー情報を取得するAPIの設計方針を考えます。</p>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">今回のAPIでは、指定されたメールアドレスを基に、ユーザーIDと法人IDを取得し、画面に返すことが目的です。</mark></strong>従って、POSTメソッドを使用し、リクエストボディからメールアドレスを受け取るシンプルなAPI設計にします。</p>



<h3 class="wp-block-heading">エンドポイント</h3>



<div style="background-color:#1e1e1e; border-radius:8px; padding:16px 20px; margin:10px 0; overflow-x:auto;">
<code style="color:#d4d4d4; font-family:'Consolas','Monaco','Courier New',monospace; font-size:14px; line-height:1.6; white-space:pre;">/top/getUserDetailsByEmail</code>
</div>



<ul class="wp-block-list">
<li>HTTPメソッド： POST</li>



<li>リクエストボディ： メールアドレス</li>



<li>レスポンス： ユーザーIDと法人ID（JSON形式）</li>
</ul>



<h4 class="wp-block-heading">リクエスト例</h4>



<div style="background-color:#1e1e1e; border-radius:8px; padding:16px 20px; margin:10px 0; overflow-x:auto;">
<pre style="margin:0; color:#d4d4d4; font-family:'Consolas','Monaco','Courier New',monospace; font-size:14px; line-height:1.6;"><span style="color:#9cdcfe;">{</span>
  <span style="color:#9cdcfe;">"mailAddress"</span><span style="color:#d4d4d4;">:</span> <span style="color:#ce9178;">"[email protected]"</span>
<span style="color:#9cdcfe;">}</span></pre>
</div>



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



<h4 class="wp-block-heading">レスポンス例</h4>



<div style="background-color:#1e1e1e; border-radius:8px; padding:16px 20px; margin:10px 0; overflow-x:auto;">
<pre style="margin:0; color:#d4d4d4; font-family:'Consolas','Monaco','Courier New',monospace; font-size:14px; line-height:1.6;"><span style="color:#9cdcfe;">{</span>
  <span style="color:#9cdcfe;">"userId"</span><span style="color:#d4d4d4;">:</span> <span style="color:#b5cea8;">175</span><span style="color:#d4d4d4;">,</span>
  <span style="color:#9cdcfe;">"corporateId"</span><span style="color:#d4d4d4;">:</span> <span style="color:#b5cea8;">1</span><span style="color:#d4d4d4;">,</span>
  <span style="color:#9cdcfe;">"errorMessageList"</span><span style="color:#d4d4d4;">:</span> <span style="color:#569cd6;">null</span>
<span style="color:#9cdcfe;">}</span></pre>
</div>



<p>このレスポンスでは、指定されたメールアドレスに対応するユーザーの情報（ユーザーIDと法人ID）をJSON形式で返します。もしエラーが発生した場合は、errorMessageListにエラーメッセージを追加することで、エラー対応を行います。</p>



<h2 class="wp-block-heading">ユーザー情報取得APIの実装</h2>



<p>次に、このAPIを実際に実装していきます。Spring Bootを使って実装を行い、リクエストを受け取った後にデータベースからユーザー情報を取得し、レスポンスを返す流れになります。</p>



<h3 class="wp-block-heading">ユーザー情報を管理するモデルクラスの作成</h3>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">まず最初に、ユーザー情報を格納するためのエンティティクラスを作成します。</mark></strong>このクラスは、データベースのテーブルとマッピングされ、ユーザー情報を格納する役割を持ちます。ここで定義したクラスを基に、データベース操作を行います。</p>



<p>例として、LoginHistoryEntityエンティティクラスを作成します。</p>



<div style="background-color:#1e1e1e; border-radius:8px; padding:16px 20px; margin:10px 0; overflow-x:auto;">
<pre style="margin:0; color:#d4d4d4; font-family:'Consolas','Monaco','Courier New',monospace; font-size:14px; line-height:1.6;"><span style="color:#c586c0;">package</span> <span style="color:#4ec9b0;">jp.co.smsdatatech.questiongenerate.entity</span><span style="color:#d4d4d4;">;</span>

<span style="color:#c586c0;">import</span> <span style="color:#4ec9b0;">java.io.Serializable</span><span style="color:#d4d4d4;">;</span>
<span style="color:#c586c0;">import</span> <span style="color:#4ec9b0;">java.sql.Timestamp</span><span style="color:#d4d4d4;">;</span>
<span style="color:#c586c0;">import</span> <span style="color:#4ec9b0;">lombok.Data</span><span style="color:#d4d4d4;">;</span>

<span style="color:#6a9955;">/**
 * ログイン履歴テーブルEntity
 */</span>
<span style="color:#dcdcaa;">@Data</span>
<span style="color:#569cd6;">public class</span> <span style="color:#4ec9b0;">LoginHistoryEntity</span> <span style="color:#569cd6;">implements</span> <span style="color:#4ec9b0;">Serializable</span> <span style="color:#d4d4d4;">{</span>

    <span style="color:#6a9955;">/** シリアルバージョンUID */</span>
    <span style="color:#569cd6;">private static final long</span> <span style="color:#4fc1ff;">serialVersionUID</span> <span style="color:#d4d4d4;">=</span> <span style="color:#b5cea8;">1L</span><span style="color:#d4d4d4;">;</span>

    <span style="color:#6a9955;">/** ユーザーID */</span>
    <span style="color:#569cd6;">private</span> <span style="color:#4ec9b0;">Integer</span> <span style="color:#9cdcfe;">userId</span><span style="color:#d4d4d4;">;</span>

    <span style="color:#6a9955;">/** 法人ID */</span>
    <span style="color:#569cd6;">private</span> <span style="color:#4ec9b0;">Integer</span> <span style="color:#9cdcfe;">corporateID</span><span style="color:#d4d4d4;">;</span>

    <span style="color:#6a9955;">/** ログイン日時 */</span>
    <span style="color:#569cd6;">private</span> <span style="color:#4ec9b0;">Timestamp</span> <span style="color:#9cdcfe;">loginDate</span><span style="color:#d4d4d4;">;</span>
<span style="color:#d4d4d4;">}</span></pre>
</div>



<p>Serializable インターフェースを実装することで、オブジェクトのシリアライズが可能となり、永続化や通信に利用できるようになっています。</p>



<h3 class="wp-block-heading">MyBatisのMapper設定</h3>



<p>次に、データベースからユーザー情報を取得するためのクエリを定義するXMLファイルを作成します。このXMLファイルは、MyBatisなどのORMツールと組み合わせてデータベースとのやり取りを行います。</p>



<div style="background-color:#1e1e1e; border-radius:8px; padding:16px 20px; margin:10px 0; overflow-x:auto;">
<pre style="margin:0; color:#d4d4d4; font-family:'Consolas','Monaco','Courier New',monospace; font-size:14px; line-height:1.6;"><span style="color:#808080;">&lt;?</span><span style="color:#569cd6;">xml</span> <span style="color:#9cdcfe;">version</span><span style="color:#d4d4d4;">=</span><span style="color:#ce9178;">"1.0"</span> <span style="color:#9cdcfe;">encoding</span><span style="color:#d4d4d4;">=</span><span style="color:#ce9178;">"UTF-8"</span><span style="color:#808080;">?&gt;</span>
<span style="color:#808080;">&lt;!</span><span style="color:#569cd6;">DOCTYPE</span> <span style="color:#4ec9b0;">mapper</span> <span style="color:#9cdcfe;">PUBLIC</span> <span style="color:#ce9178;">"-//mybatis.org//DTD Mapper 3.0//EN"</span>
  <span style="color:#ce9178;">"http://mybatis.org/dtd/mybatis-3-mapper.dtd"</span><span style="color:#808080;">&gt;</span>

<span style="color:#808080;">&lt;</span><span style="color:#569cd6;">mapper</span> <span style="color:#9cdcfe;">namespace</span><span style="color:#d4d4d4;">=</span><span style="color:#ce9178;">"jp.co.smsdatatech.questiongenerate.repository.CustomerMapper"</span><span style="color:#808080;">&gt;</span>

  <span style="color:#808080;">&lt;</span><span style="color:#569cd6;">select</span> <span style="color:#9cdcfe;">id</span><span style="color:#d4d4d4;">=</span><span style="color:#ce9178;">"getByMailAddress"</span>
          <span style="color:#9cdcfe;">parameterType</span><span style="color:#d4d4d4;">=</span><span style="color:#ce9178;">"String"</span>
          <span style="color:#9cdcfe;">resultType</span><span style="color:#d4d4d4;">=</span><span style="color:#ce9178;">"jp.co.smsdatatech.questiongenerate.entity.CustomerEntity"</span><span style="color:#808080;">&gt;</span>
    <span style="color:#569cd6;">SELECT</span>
      <span style="color:#9cdcfe;">user_id</span>,
      <span style="color:#9cdcfe;">user_name</span>,
      <span style="color:#9cdcfe;">mail_address</span>,
      <span style="color:#9cdcfe;">address_prefecture</span>,
      <span style="color:#9cdcfe;">address_municipality</span>,
      <span style="color:#9cdcfe;">birthday</span>,
      <span style="color:#9cdcfe;">gender</span>,
      <span style="color:#9cdcfe;">create_date</span>,
      <span style="color:#9cdcfe;">update_date</span>
    <span style="color:#569cd6;">FROM</span>
      <span style="color:#4ec9b0;">customer</span>
    <span style="color:#569cd6;">WHERE</span>
      <span style="color:#9cdcfe;">mail_address</span> <span style="color:#d4d4d4;">=</span> <span style="color:#dcdcaa;">#{mailAddress}</span>
  <span style="color:#808080;">&lt;/</span><span style="color:#569cd6;">select</span><span style="color:#808080;">&gt;</span>

<span style="color:#808080;">&lt;/</span><span style="color:#569cd6;">mapper</span><span style="color:#808080;">&gt;</span></pre>
</div>



<p>#{mailAddress}： この部分が、実際に呼び出し元から渡された email パラメータの値に置き換えられます。</p>



<h3 class="wp-block-heading">サービスクラスの作成</h3>



<p>次に、サービスクラスを作成してユーザー情報の取得処理を実装します。このクラスでは、リポジトリやMapperを使ってデータベースから情報を取得し、必要に応じてレスポンスに変換します。</p>



<div style="background-color:#1e1e1e; border-radius:8px; padding:16px 20px; margin:10px 0; overflow-x:auto;">
<pre style="margin:0; color:#d4d4d4; font-family:'Consolas','Monaco','Courier New',monospace; font-size:14px; line-height:1.6;"><span style="color:#c586c0;">import</span> <span style="color:#4ec9b0;">lombok.extern.slf4j.Slf4j</span><span style="color:#d4d4d4;">;</span>
<span style="color:#c586c0;">import</span> <span style="color:#4ec9b0;">jp.co.smsdatatech.questiongenerate.util.DateUtils</span><span style="color:#d4d4d4;">;</span>

<span style="color:#6a9955;">/**
 * ログイン履歴テーブルサービス
 */</span>
<span style="color:#dcdcaa;">@Slf4j</span>
<span style="color:#dcdcaa;">@Service</span>
<span style="color:#569cd6;">public class</span> <span style="color:#4ec9b0;">LoginHistoryService</span> <span style="color:#d4d4d4;">{</span>

    <span style="color:#569cd6;">private final</span> <span style="color:#4ec9b0;">LoginHistoryMapper</span> <span style="color:#9cdcfe;">loginHistoryMapper</span><span style="color:#d4d4d4;">;</span>

    <span style="color:#6a9955;">// コンストラインジェクションを使用</span>
    <span style="color:#dcdcaa;">@Autowired</span>
    <span style="color:#569cd6;">public</span> <span style="color:#dcdcaa;">LoginHistoryService</span><span style="color:#d4d4d4;">(</span><span style="color:#4ec9b0;">LoginHistoryMapper</span> <span style="color:#9cdcfe;">loginHistoryMapper</span><span style="color:#d4d4d4;">)</span> <span style="color:#d4d4d4;">{</span>
        <span style="color:#569cd6;">this</span><span style="color:#d4d4d4;">.</span><span style="color:#9cdcfe;">loginHistoryMapper</span> <span style="color:#d4d4d4;">=</span> <span style="color:#9cdcfe;">loginHistoryMapper</span><span style="color:#d4d4d4;">;</span>
    <span style="color:#d4d4d4;">}</span>

    <span style="color:#6a9955;">/**
     * 指定されたメールアドレスに基づいてユーザー情報を取得する。
     *
     * @param email メールアドレス
     * @return ユーザーIDと法人IDを含むリスト。
     */</span>
    <span style="color:#dcdcaa;">@Transactional</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">readOnly</span> <span style="color:#d4d4d4;">=</span> <span style="color:#569cd6;">true</span><span style="color:#d4d4d4;">)</span>
    <span style="color:#569cd6;">public</span> <span style="color:#4ec9b0;">Map</span><span style="color:#d4d4d4;">&lt;</span><span style="color:#4ec9b0;">String</span><span style="color:#d4d4d4;">,</span> <span style="color:#4ec9b0;">Object</span><span style="color:#d4d4d4;">&gt;</span> <span style="color:#dcdcaa;">getUserDetailsByEmail</span><span style="color:#d4d4d4;">(</span><span style="color:#4ec9b0;">String</span> <span style="color:#9cdcfe;">email</span><span style="color:#d4d4d4;">)</span> <span style="color:#d4d4d4;">{</span>
        <span style="color:#9cdcfe;">log</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">debug</span><span style="color:#d4d4d4;">(</span><span style="color:#ce9178;">"format: メールアドレス {} に基づくユーザー情報を取得します。"</span><span style="color:#d4d4d4;">,</span> <span style="color:#9cdcfe;">email</span><span style="color:#d4d4d4;">);</span>
        <span style="color:#c586c0;">return</span> <span style="color:#9cdcfe;">loginHistoryMapper</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getUserDetailsByEmail</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">email</span><span style="color:#d4d4d4;">);</span>
    <span style="color:#d4d4d4;">}</span></pre>
</div>



<p>getUserDetailsByEmail メソッドは、@Transactional アノテーションを使用してトランザクションを管理し、指定されたメールアドレスに基づいてユーザー情報を取得します。</p>



<h3 class="wp-block-heading">コントローラークラスの作成</h3>



<p>最後に、リクエストを処理するコントローラークラスを作成します。このクラスで、リクエストボディからメールアドレスを受け取り、サービスクラスを呼び出して情報を取得し、その情報をレスポンスとして返します。</p>



<p>例えば、TopRestクラスを以下のように作成します。</p>



<div style="background-color:#1e1e1e; border-radius:8px; padding:16px 20px; margin:10px 0; overflow-x:auto;">
<pre style="margin:0; color:#d4d4d4; font-family:'Consolas','Monaco','Courier New',monospace; font-size:14px; line-height:1.6;"><span style="color:#569cd6;">public class</span> <span style="color:#4ec9b0;">TopRest</span> <span style="color:#d4d4d4;">{</span>

    <span style="color:#6a9955;">/**
     * ログイン状況取得。
     *
     *&lt;p&gt;login_historyテーブルからユーザーIDに基づくログイン状況を取得し、
     * LoginHistoryResponseFormに詰めて返却する。
     *
     *&lt;h3&gt;処理フロー&lt;/h3&gt;
     * 1. ログイン履歴を取得
     * 2. 取得データを返却
     *
     *&lt;h3&gt;レスポンス構成&lt;/h3&gt;
     * "loginHistorysByDay" -&gt; 曜日ごとのログイン状況 (キー：曜日, 値：true/false)
     * "error" -&gt; エラー発生時のメッセージ
     *
     *@param form Top画面UserId用form
     *@param result バリデーション結果
     *@return ログイン履歴情報 LoginHistoryResponseform
     */</span>
    <span style="color:#dcdcaa;">@PostMapping</span><span style="color:#d4d4d4;">(</span><span style="color:#ce9178;">"/getLoginHistoryByUserId"</span><span style="color:#d4d4d4;">)</span>
    <span style="color:#569cd6;">public</span> <span style="color:#4ec9b0;">LoginHistoryResponseForm</span> <span style="color:#dcdcaa;">getLoginHistoryByUserId</span><span style="color:#d4d4d4;">(</span>
            <span style="color:#dcdcaa;">@RequestBody</span> <span style="color:#dcdcaa;">@Valid</span> <span style="color:#4ec9b0;">UserIdForm</span> <span style="color:#9cdcfe;">form</span><span style="color:#d4d4d4;">,</span>
            <span style="color:#4ec9b0;">BindingResult</span> <span style="color:#9cdcfe;">result</span><span style="color:#d4d4d4;">)</span> <span style="color:#d4d4d4;">{</span>

        <span style="color:#4ec9b0;">List</span><span style="color:#d4d4d4;">&lt;</span><span style="color:#4ec9b0;">String</span><span style="color:#d4d4d4;">&gt;</span> <span style="color:#9cdcfe;">errorMessageList</span> <span style="color:#d4d4d4;">=</span> <span style="color:#569cd6;">new</span> <span style="color:#4ec9b0;">ArrayList</span><span style="color:#d4d4d4;">&lt;&gt;();</span>
        <span style="color:#4ec9b0;">LoginHistoryResponseForm</span> <span style="color:#9cdcfe;">responseForm</span> <span style="color:#d4d4d4;">=</span> <span style="color:#569cd6;">new</span> <span style="color:#4ec9b0;">LoginHistoryResponseForm</span><span style="color:#d4d4d4;">();</span>
        <span style="color:#4ec9b0;">Set</span><span style="color:#d4d4d4;">&lt;</span><span style="color:#4ec9b0;">String</span><span style="color:#d4d4d4;">&gt;</span> <span style="color:#9cdcfe;">processedFields</span> <span style="color:#d4d4d4;">=</span> <span style="color:#569cd6;">new</span> <span style="color:#4ec9b0;">HashSet</span><span style="color:#d4d4d4;">&lt;&gt;();</span>

        <span style="color:#6a9955;">// バリデーションエラーがある場合、詳細なエラーメッセージを追加</span>
        <span style="color:#c586c0;">if</span> <span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">result</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">hasErrors</span><span style="color:#d4d4d4;">())</span> <span style="color:#d4d4d4;">{</span>
            <span style="color:#c586c0;">for</span> <span style="color:#d4d4d4;">(</span><span style="color:#4ec9b0;">FieldError</span> <span style="color:#9cdcfe;">fieldError</span> <span style="color:#d4d4d4;">:</span> <span style="color:#9cdcfe;">result</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getFieldErrors</span><span style="color:#d4d4d4;">())</span> <span style="color:#d4d4d4;">{</span>
                <span style="color:#6a9955;">// フィールド名を基に重複チェック</span>
                <span style="color:#c586c0;">if</span> <span style="color:#d4d4d4;">(!</span><span style="color:#9cdcfe;">processedFields</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">contains</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">fieldError</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getField</span><span style="color:#d4d4d4;">()))</span> <span style="color:#d4d4d4;">{</span>
                    <span style="color:#4ec9b0;">String</span> <span style="color:#9cdcfe;">errorMessage</span> <span style="color:#d4d4d4;">=</span> <span style="color:#9cdcfe;">messageSource</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getMessage</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">fieldError</span><span style="color:#d4d4d4;">,</span> <span style="color:#4ec9b0;">Locale</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getDefault</span><span style="color:#d4d4d4;">());</span>
                    <span style="color:#9cdcfe;">errorMessageList</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">add</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">errorMessage</span><span style="color:#d4d4d4;">);</span>
                    <span style="color:#9cdcfe;">processedFields</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">add</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">fieldError</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getField</span><span style="color:#d4d4d4;">());</span>
                <span style="color:#d4d4d4;">}</span>
            <span style="color:#d4d4d4;">}</span>
            <span style="color:#9cdcfe;">responseForm</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">setErrorMessageList</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">errorMessageList</span><span style="color:#d4d4d4;">);</span>
            <span style="color:#c586c0;">return</span> <span style="color:#9cdcfe;">responseForm</span><span style="color:#d4d4d4;">;</span>
        <span style="color:#d4d4d4;">}</span>

        <span style="color:#6a9955;">// ユーザーIDを整数に変換</span>
        <span style="color:#4ec9b0;">Integer</span> <span style="color:#9cdcfe;">userId</span> <span style="color:#d4d4d4;">=</span> <span style="color:#4ec9b0;">Integer</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">parseInt</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">form</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getUserId</span><span style="color:#d4d4d4;">());</span>

        <span style="color:#6a9955;">// サービス層で曜日ごとのログイン履歴を取得</span>
        <span style="color:#4ec9b0;">Map</span><span style="color:#d4d4d4;">&lt;</span><span style="color:#4ec9b0;">String</span><span style="color:#d4d4d4;">,</span> <span style="color:#4ec9b0;">Boolean</span><span style="color:#d4d4d4;">&gt;</span> <span style="color:#9cdcfe;">dailyLoginHistoryMap</span> <span style="color:#d4d4d4;">=</span>
            <span style="color:#9cdcfe;">loginHistoryService</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getLoginHistoryByUserIdAndDateRange</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">userId</span><span style="color:#d4d4d4;">);</span>

        <span style="color:#6a9955;">// ログイン履歴が空かどうかをチェック</span>
        <span style="color:#c586c0;">if</span> <span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">dailyLoginHistoryMap</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">isEmpty</span><span style="color:#d4d4d4;">())</span> <span style="color:#d4d4d4;">{</span>
            <span style="color:#9cdcfe;">errorMessageList</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">add</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">messageSource</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getMessage</span><span style="color:#d4d4d4;">(</span>
                <span style="color:#4ec9b0;">ErrorMessages</span><span style="color:#d4d4d4;">.</span><span style="color:#4fc1ff;">NO_LOGIN_HISTORY</span><span style="color:#d4d4d4;">,</span> <span style="color:#569cd6;">null</span><span style="color:#d4d4d4;">,</span> <span style="color:#4ec9b0;">Locale</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">getDefault</span><span style="color:#d4d4d4;">()));</span>
            <span style="color:#9cdcfe;">responseForm</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">setErrorMessageList</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">errorMessageList</span><span style="color:#d4d4d4;">);</span>
            <span style="color:#c586c0;">return</span> <span style="color:#9cdcfe;">responseForm</span><span style="color:#d4d4d4;">;</span>
        <span style="color:#d4d4d4;">}</span>

        <span style="color:#9cdcfe;">responseForm</span><span style="color:#d4d4d4;">.</span><span style="color:#dcdcaa;">setDailyLoginHistoryMap</span><span style="color:#d4d4d4;">(</span><span style="color:#9cdcfe;">dailyLoginHistoryMap</span><span style="color:#d4d4d4;">);</span>
        <span style="color:#c586c0;">return</span> <span style="color:#9cdcfe;">responseForm</span><span style="color:#d4d4d4;">;</span>
    <span style="color:#d4d4d4;">}</span></pre>
</div>



<p>このメソッドは、リクエストボディで受け取ったユーザーID（form）に対してバリデーションを実行します。<br>もしバリデーションエラーがあれば、そのエラーメッセージをリストに追加し、LoginHistoryResponseForm にセットしてエラーレスポンスとして返却します。</p>



<p>バリデーションエラーがなければ、ユーザーIDを整数に変換し、loginHistoryService.getLoginHistoryByUserIdAndDateRange を使用して、指定されたユーザーの曜日ごとのログイン履歴を取得します。</p>



<p>もしログイン履歴が存在しない場合は、エラーメッセージをリストに追加し、早期リターンします。<br>ログイン履歴が取得できた場合は、その情報をレスポンスフォームに設定し、正常なレスポンスとして返却します。</p>



<h2 class="wp-block-heading">実装内容の振り返り</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">Mapの便利さゆえに使用しがちですが、型安全性の欠如やコードの可読性を考慮すると、DTOやEnumを利用した実装に統一していく方が望ましいと感じました。</mark></strong></p>



<p>また、作成中に感じた点として、サーバーサイドに情報を送信する際にリクエストが改ざんされる可能性があるというセキュリティ上の懸念が浮き彫りになりました。特に、クライアントサイドから送信されたデータがそのままサーバーで利用されるため、不正な操作を防ぐためにセキュリティ対策を強化する必要があると痛感しました。この点については、Javaのフィルターや認証・認可の仕組みを活用し、より安全なシステムを構築するという課題が残りました。次回制作時には、このセキュリティ部分を改良していきたいと思います。</p>



<h2 class="wp-block-heading">まとめ</h2>



<p>まだ知識が浅く、試行錯誤しながら進めている部分も多いですが、実装を通じて多くのことを学びました。<strong><mark style="background-color:#fdff84" class="has-inline-color">今後は、より洗練された設計を目指し、コードの可読性や保守性を高めるための改善に取り組んでいきたいと思います。</mark></strong>また、セキュリティやパフォーマンスに関する課題も意識し、これらの分野についてさらに学び、システム全体をより堅牢で効率的なものにしていきたいと考えています。</p>



<div class="author__card">
  <div class="author-data">
    <img decoding="async" src="https://www.sms-datatech.co.jp/wp-content/uploads/2024/11/author-3.png" alt="筆者イメージ">
    <div>
      <p>筆者：K・T（20代）</p>
      <p>所属：ハイブリット・マルチクラウド部</p>
    </div>
  </div>
  <div class="author-intro">
    <p>マーケターとしてWEB広告運用やアパレルブランドの運営に携わってきたが、エンジニアとしての新たな挑戦を決意した転職組。<br>好奇心が旺盛で、新しいことに挑戦する姿勢が彼女の魅力。<br>マーケター×エンジニアの強みを生かし、UI/UXの提案から実装まで幅広く対応し、彼女自身がブランドを育てているのだろうと思わせてくれる、そんな20代の乙女。</p>
  </div>
</div>


<div class="block-block-22">
	<div class="Editor-wrap">
					<div class="Editor-wrap__titleMsg">まずはお気軽にご相談ください</div>
			<div class="Editor-wrap__title">お問い合わせフォーム</div>
			</div>
</div>


<script charset="utf-8" type="text/javascript" src="//js.hsforms.net/forms/embed/v2.js"></script>
<script>
  hbspt.forms.create({
    region: "na1",
    portalId: "23171742",
    formId: "b36c7c3b-83a0-43c4-8daa-8e055d6805e9"
  });
</script>



<p></p><p>The post <a href="https://www.sms-datatech.co.jp/column/try_java-1/">【はじめてのJava】ダッシュボードAPIを作成しよう！① ユーザー情報取得編</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【学習アプリ開発記】Vue.jsとChart.jsを使ってグラフ描画してみた。</title>
		<link>https://www.sms-datatech.co.jp/column/try_chart-js/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=try_chart-js</link>
		
		<dc:creator><![CDATA[後藤]]></dc:creator>
		<pubDate>Fri, 20 Feb 2026 06:18:58 +0000</pubDate>
				<category><![CDATA[やってみた]]></category>
		<category><![CDATA[システム開発]]></category>
		<guid isPermaLink="false">https://www.sms-datatech.co.jp/?p=34947</guid>

					<description><![CDATA[<p>このブログ記事は、JavaScriptのフレームワークであるVue.jsを学びたい初学者や、実際にアプリケーションを開発したい方を対象にしています。<br />
また、グラフやチャートを使ってデータを視覚化したい方に向けて、Chart.jsの魅力や具体的な使い方をご紹介します。</p>
<p>The post <a href="https://www.sms-datatech.co.jp/column/try_chart-js/">【学習アプリ開発記】Vue.jsとChart.jsを使ってグラフ描画してみた。</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">はじめに</h2>



<p>こんにちは！マーケターから未経験のエンジニアに転職した社員Tです。再びブログを執筆できる機会をいただき、とても嬉しく思います。<br><a href="https://www.sms-datatech.co.jp/column/try_vue-js/" title=""><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">前回のブログ</span></mark></strong></a>から半年が経ちましたが、その間に学んだことや身につけた技術を引き続き皆さんと共有できたらと思っています。</p>



<p>今回は、Vue.jsと、Chart.jsを組み合わせて視覚的にデータを表現する方法にチャレンジしてみました！</p>



<h2 class="wp-block-heading">本記事の対象の方</h2>



<p>このブログ記事は、JavaScriptのフレームワークであるVue.jsを学びたい初学者や、実際にアプリケーションを開発したい方を対象にしています。<br>また、グラフやチャートを使ってデータを視覚化したい方に向けて、Chart.jsの魅力や具体的な使い方をご紹介します。</p>



<h2 class="wp-block-heading">vue.jsとは？</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">Vue.jsは、JavaScriptのフレームワークのひとつで、HTMLとJavaScriptの間でデータの連携を行う役割を果たします。</mark></strong><br>詳しい説明については、<a href="https://www.sms-datatech.co.jp/column/try_vue-js/" title=""><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">過去のブログ記事</span></mark></strong></a>をご参照ください。</p>



<h2 class="wp-block-heading">vue.js環境構築</h2>



<p>Vue.jsを使うための環境構築についても、<a href="https://www.sms-datatech.co.jp/column/try_vue-js/" title=""><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">過去のブログ</span></mark></strong></a>を参考にしてください。<br>具体的な手順を詳しく解説していますので、導入方法や初期設定の詳細を確認できます。</p>



<h2 class="wp-block-heading">メリット</h2>



<p>Chart.jsを使用することで、データを視覚的に表現することが可能になります。<br>そのメリットをいくつかご紹介します。</p>



<h3 class="wp-block-heading">1. 簡単な導入と使いやすさ</h3>



<p>シンプルなAPIで、初心者でもすぐに利用できるのが魅力です。</p>



<h3 class="wp-block-heading">2. 豊富なチャートタイプ</h3>



<p>折れ線グラフや円グラフなど、多彩なグラフを簡単に作成できます。</p>



<h3 class="wp-block-heading">3. カスタマイズ性</h3>



<p>デザインを統一しやすく、自由にカスタマイズが可能です。<br>そして、Vue.jsと組み合わせることで、さらに次のような利点があります。</p>



<h3 class="wp-block-heading">4. リアクティブデータの自動反映</h3>



<p>Vue.jsのリアクティブシステムを活用することで、データが更新されるたびにグラフも自動で再描画されます。<br>これにより、動的なデータ表示が簡単に実現します。</p>



<h3 class="wp-block-heading">5. コンポーネントベースでの再利用性</h3>



<p>Vue.jsでは、グラフをコンポーネントとして再利用できるため、プロジェクト全体で効率的にグラフ描画が行えます。</p>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">これらの組み合わせによって、ユーザーにとっては視覚的にわかりやすいデータ表示が可能になり、開発者にとっては効率的な実装が実現します！</mark></strong>
</p>



<h2 class="wp-block-heading">Chart.js環境構築</h2>



<p>Chart.jsを使用するには、以下の方法があります。</p>



<ul class="wp-block-list">
<li>scriptタグを埋め込む</li>



<li>NPMを利用してインストール</li>



<li>CLIを利用してインストール(単一ファイルコンポーネントが利用可)</li>
</ul>



<p>今回は、&lt;body&gt;終了タグの直後にscriptタグを挿入しました！</p>



<div
    style="background-color: #1d2020; border-radius: 8px; padding: 16px 20px; margin: 16px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.6;">
    <code><span style="color: #b7b7b7;">&lt;</span><span style="color: #ff6b6b;">script</span> <span style="color: #ebd247;">src</span><span style="color: #b7b7b7;">=</span><span style="color: #00ff00;">"https://cdn.jsdelivr.net/npm/chart.js@4.2.1"</span><span style="color: #b7b7b7;">&gt;&lt;/</span><span style="color: #ff6b6b;">script</span><span style="color: #b7b7b7;">&gt;</span></code>
</div>



<p>この方法なら、すぐに使い始められるので、手軽にグラフを表示したい場合に便利です。</p>



<h2 class="wp-block-heading">描画領域の準備</h2>



<p>Chart.jsは、HTMLの&lt;canvas&gt;要素にグラフを描画します。まずは&lt;canvas&gt;要素をHTMLに追加します。ここでは、idとして&#8221;learningTimeChart&#8221;を指定しています。</p>



<div
    style="background-color: #1d2020; border-radius: 8px; padding: 16px 20px; margin: 16px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.6;">
    <code><span style="color: #b7b7b7;">&lt;</span><span style="color: #ff6b6b;">canvas</span> <span style="color: #ebd247;">id</span><span style="color: #b7b7b7;">=</span><span style="color: #00ff00;">"learningTimeChart"</span> <span style="color: #ebd247;">class</span><span style="color: #b7b7b7;">=</span><span style="color: #00ff00;">"chart"</span><span style="color: #b7b7b7;">&gt;&lt;/</span><span style="color: #ff6b6b;">canvas</span><span style="color: #b7b7b7;">&gt;</span></code>
</div>



<p>次に、canvas要素のコンテキストを取得して、描画のための準備をセットしていきます。</p>



<div
    style="background-color: #1d2020; border-radius: 8px; padding: 16px 20px; margin: 16px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.6;">
    <code><span style="color: #4a86e8;">let</span> <span style="color: #ffffff;">canvas</span> <span style="color: #ebd247;">=</span> <span style="color: #ffffff;">document</span><span style="color: #b7b7b7;">.</span><span style="color: #ebd247;">getElementById</span><span style="color: #b7b7b7;">(</span><span style="color: #00ff00;">'learningTimeChart'</span><span style="color: #b7b7b7;">);</span></code>
</div>



<h2 class="wp-block-heading">グラフ描画エリア設定</h2>



<p>Vue.jsでChart.jsを使用して、チャートを作成します。サンプルコードは以下の通りです。</p>



<div
    style="background-color: #1d2020; border-radius: 8px; padding: 16px 20px; margin: 16px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.6;">
    <pre style="margin: 0; color: #b7b7b7;"><code><span style="color: #4a86e8;">var</span> <span style="color: #ffffff;">myLineChart</span> <span style="color: #ebd247;">=</span> <span style="color: #4a86e8;">new</span> <span style="color: #ff6b6b;">Chart</span><span style="color: #ffffff;">(ctx, {</span>
  <span style="color: #ff6b6b;">type</span><span style="color: #ffffff;">:</span> <span style="color: #00ff00;">'doughnut'</span><span style="color: #ebd247;">,</span> <span style="color: #b7b7b7;">// グラフの種類</span>
  <span style="color: #ff6b6b;">data</span><span style="color: #ffffff;">: data</span><span style="color: #ebd247;">,</span>       <span style="color: #b7b7b7;">// データ</span>
  <span style="color: #ff6b6b;">options</span><span style="color: #ebd247;">:</span> <span style="color: #ffffff;">options</span> <span style="color: #b7b7b7;">// オプション設定</span>
<span style="color: #ffffff;">});</span></code></pre>
</div>



<p>それぞれの設定項目の詳細を表形式でまとめました。</p>



<table style="border-collapse: collapse; border: 1px solid #ccc;" width="1040">
    <thead>
        <tr>
            <th style="width: 33%;">設定項目</th>
            <th style="width: 67%;">設定内容</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>type</td>
            <td>描画するグラフの種類</td>
        </tr>
        <tr>
            <td>data</td>
            <td>ラベルとデータセット</td>
        </tr>
        <tr>
            <td>options</td>
            <td>オプション設定</td>
        </tr>
    </tbody>
</table>



<p>先ほど紹介したHTMLをもとに、scriptを記述してグラフを描いていきます。</p>



<h2 class="wp-block-heading">グラフ描画の実践</h2>



<p>円グラフを表示するサンプルを示します。</p>



<div
    style="background-color: #1d2020; border-radius: 8px; padding: 16px 20px; margin: 16px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.6;">
    <pre style="margin: 0; color: #b7b7b7;"><code><span style="color: #ebd247;">createChart</span><span style="color: #ffffff;">(labels, data) {</span>
  <span style="color: #4a86e8;">let</span> <span style="color: #ffffff;">canvas</span> <span style="color: #ebd247;">=</span> <span style="color: #ffffff;">document</span><span style="color: #b7b7b7;">.</span><span style="color: #ebd247;">getElementById</span><span style="color: #b7b7b7;">(</span><span style="color: #00ff00;">'learningTimeChart'</span><span style="color: #b7b7b7;">);</span>
  <span style="color: #4a86e8;">if</span> <span style="color: #ffffff;">(canvas) {</span>
    <span style="color: #4a86e8;">let</span> <span style="color: #ffffff;">ctx2</span> <span style="color: #ebd247;">=</span> <span style="color: #ffffff;">canvas</span><span style="color: #b7b7b7;">.</span><span style="color: #ebd247;">getContext</span><span style="color: #b7b7b7;">(</span><span style="color: #00ff00;">'2d'</span><span style="color: #b7b7b7;">);</span>
    <span style="color: #4a86e8;">if</span> <span style="color: #ffffff;">(ctx2) {</span>
      <span style="color: #4a86e8;">if</span> <span style="color: #ffffff;">(</span><span style="color: #4a86e8;">this</span><span style="color: #b7b7b7;">.</span><span style="color: #ffffff;">learningTimeChart) {</span>
        <span style="color: #4a86e8;">this</span><span style="color: #b7b7b7;">.</span><span style="color: #ffffff;">learningTimeChart</span><span style="color: #b7b7b7;">.</span><span style="color: #ebd247;">destroy</span><span style="color: #b7b7b7;">();</span>
      <span style="color: #ffffff;">}</span>

      <span style="color: #4a86e8;">this</span><span style="color: #b7b7b7;">.</span><span style="color: #ffffff;">learningTimeChart</span> <span style="color: #ebd247;">=</span> <span style="color: #4a86e8;">new</span> <span style="color: #ff6b6b;">Chart</span><span style="color: #ffffff;">(ctx2, {</span>
        <span style="color: #ff6b6b;">type</span><span style="color: #ffffff;">:</span> <span style="color: #00ff00;">'doughnut'</span><span style="color: #ffffff;">,</span>
        <span style="color: #ff6b6b;">data</span><span style="color: #ffffff;">: {</span>
          <span style="color: #ff6b6b;">labels</span><span style="color: #ffffff;">: labels,</span>
          <span style="color: #ff6b6b;">datasets</span><span style="color: #ffffff;">: [{</span>
            <span style="color: #ff6b6b;">data</span><span style="color: #ffffff;">: data,</span>
            <span style="color: #ff6b6b;">backgroundColor</span><span style="color: #ffffff;">: [</span><span style="color: #00ff00;">'#36A2EB'</span><span style="color: #ffffff;">,</span> <span style="color: #00ff00;">'#FF6384'</span><span style="color: #ffffff;">],</span>
            <span style="color: #ff6b6b;">hoverBackgroundColor</span><span style="color: #ffffff;">: [</span><span style="color: #00ff00;">'#36A2EB'</span><span style="color: #ffffff;">,</span> <span style="color: #00ff00;">'#FF6384'</span><span style="color: #ffffff;">]</span>
          <span style="color: #ffffff;">}]</span>
        <span style="color: #ffffff;">},</span>
        <span style="color: #ff6b6b;">options</span><span style="color: #ffffff;">: {</span>
          <span style="color: #ff6b6b;">cutout</span><span style="color: #ffffff;">:</span> <span style="color: #00ff00;">'0%'</span><span style="color: #ffffff;">,</span>
          <span style="color: #ff6b6b;">plugins</span><span style="color: #ffffff;">: {</span>
            <span style="color: #ff6b6b;">legend</span><span style="color: #ffffff;">: {</span>
              <span style="color: #ff6b6b;">display</span><span style="color: #ffffff;">:</span> <span style="color: #4a86e8;">false</span>
            <span style="color: #ffffff;">},</span>
            <span style="color: #ff6b6b;">tooltip</span><span style="color: #ffffff;">: {</span>
              <span style="color: #ff6b6b;">enabled</span><span style="color: #ffffff;">:</span> <span style="color: #4a86e8;">false</span>
            <span style="color: #ffffff;">},</span>
            <span style="color: #ff6b6b;">datalabels</span><span style="color: #ffffff;">: {</span>
              <span style="color: #ff6b6b;">display</span><span style="color: #ffffff;">:</span> <span style="color: #4a86e8;">false</span>
            <span style="color: #ffffff;">}</span>
          <span style="color: #ffffff;">}</span>
        <span style="color: #ffffff;">}</span>
      <span style="color: #ffffff;">});</span>

      <span style="color: #4a86e8;">this</span><span style="color: #b7b7b7;">.</span><span style="color: #ffffff;">isLoading</span> <span style="color: #ebd247;">=</span> <span style="color: #4a86e8;">false</span><span style="color: #ffffff;">;</span>
    <span style="color: #ffffff;">}</span>
  <span style="color: #ffffff;">}</span>
<span style="color: #ffffff;">},</span></code></pre>
</div>



<p>以下のようなグラフが描画されます。</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="505" height="378" src="https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-1.png" alt="" class="wp-image-34957" srcset="https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-1.png 505w, https://www.sms-datatech.co.jp/wp-content/uploads/2026/02/image1-1-300x225.png 300w" sizes="(max-width: 505px) 100vw, 505px" /></figure>
</div>


<p>今回は、円グラフを使って目標時間と総学習時間を表示しています。</p>



<h2 class="wp-block-heading">グラフのタイプとデータ設定</h2>



<p>上記の表でもご説明した通り、グラフのタイプは、typeプロパティで指定します。今回の円グラフを描く際は、&#8217;doughnut&#8217;を指定します。<br>データはdataプロパティで設定し、Vue.jsのコンポーネントとしてグラフを作成することで、データが変更されるたびに自動的にグラフも再描画されます。<br><br>既存のグラフがある場合は、destroyメソッドを呼び出して破棄し、Chartコンストラクタを使用して新しいグラフを生成します。<br><br>今回の実装では、Axiosを使用してJSONファイルからデータを取得し、モックデータとして活用しています。</p>



<div
    style="background-color: #1d2020; border-radius: 8px; padding: 16px 20px; margin: 16px 0; overflow-x: auto; font-family: 'Courier New', Consolas, monospace; font-size: 14px; line-height: 1.6;">
    <pre style="margin: 0; color: #b7b7b7;"><code><span style="color: #00ff00;">"learning_time_data"</span><span style="color: #ffffff;">: {</span>
  <span style="color: #00ff00;">"labels"</span><span style="color: #ffffff;">: [</span><span style="color: #00ff00;">"学習時間"</span><span style="color: #ffffff;">,</span> <span style="color: #00ff00;">"残りの時間"</span><span style="color: #ffffff;">],</span>
  <span style="color: #00ff00;">"data"</span><span style="color: #ffffff;">: [200, 120]</span>
<span style="color: #ffffff;">},</span></code></pre>
</div>



<p>この方法により、リアルなAPIレスポンスを模倣でき、データがない状態でもスムーズに開発やテストが可能です。</p>



<p>実際の開発時には、学習時間を表示するグラフをコンポーネント化することで、異なるページで再利用が容易になり、プロパティを通じて異なるデータを渡すことで開発効率が向上します！</p>



<h2 class="wp-block-heading">オプションの設定</h2>



<p><span style="background-color: #f3f3f3;">options</span> オブジェクトを使うことで、グラフの見た目や動作をカスタマイズできます。</p>



<ul class="wp-block-list">
<li>cutout: 円グラフの中心の空間のサイズを指定</li>



<li>legend: 凡例の表示</li>



<li>tooltip: ツールチップの表示</li>



<li>datalabels: データラベルの表示</li>
</ul>



<p>そのほかにも、スタイルの設定で背景色を変更したり、アニメーションを加えたりと、色々と楽しむことができます。</p>



<h2 class="wp-block-heading">まとめ</h2>



<p>プロジェクトで使う機会があったので、Chart.jsの使い方をまとめてみました。<br><strong><mark style="background-color:#fdff84" class="has-inline-color">これ一つで色々なグラフが生成できるのは本当に便利ですね！</mark></strong></p>



<p>今後もさまざまなグラフを作成していきたいと思います。<br>最後までお読みいただき、ありがとうございました！</p>



<h2 class="wp-block-heading">参考サイト(公式)</h2>



<ul class="wp-block-list">
<li><a href="https://ja.vuejs.org/guide/quick-start.html"><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">クイックスタート |
                        Vue.js</span></mark></strong></a></li>



<li><a href="https://www.chartjs.org/"><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">Chart.js</span></mark></strong></a></li>
</ul>



<div class="author__card">
  <div class="author-data">
    <img decoding="async" src="https://www.sms-datatech.co.jp/wp-content/uploads/2024/11/author-3.png" alt="筆者イメージ">
    <div>
      <p>筆者：K・T（20代）</p>
      <p>所属：ハイブリット・マルチクラウド部</p>
    </div>
  </div>
  <div class="author-intro">
    <p>マーケターとしてWEB広告運用やアパレルブランドの運営に携わってきたが、エンジニアとしての新たな挑戦を決意した転職組。<br>好奇心が旺盛で、新しいことに挑戦する姿勢が彼女の魅力。<br>マーケター×エンジニアの強みを生かし、UI/UXの提案から実装まで幅広く対応し、彼女自身がブランドを育てているのだろうと思わせてくれる、そんな20代の乙女。</p>
  </div>
</div>


<div class="block-block-22">
	<div class="Editor-wrap">
					<div class="Editor-wrap__titleMsg">まずはお気軽にご相談ください</div>
			<div class="Editor-wrap__title">お問い合わせフォーム</div>
			</div>
</div>


<script charset="utf-8" type="text/javascript" src="//js.hsforms.net/forms/embed/v2.js"></script>
<script>
  hbspt.forms.create({
    region: "na1",
    portalId: "23171742",
    formId: "b36c7c3b-83a0-43c4-8daa-8e055d6805e9"
  });
</script>



<p></p><p>The post <a href="https://www.sms-datatech.co.jp/column/try_chart-js/">【学習アプリ開発記】Vue.jsとChart.jsを使ってグラフ描画してみた。</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>【学習アプリ開発記：はじめてのVue.js】問題解答画面を作成してみた！</title>
		<link>https://www.sms-datatech.co.jp/column/try_vue-js/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=try_vue-js</link>
					<comments>https://www.sms-datatech.co.jp/column/try_vue-js/#respond</comments>
		
		<dc:creator><![CDATA[後藤]]></dc:creator>
		<pubDate>Thu, 26 Sep 2024 04:12:42 +0000</pubDate>
				<category><![CDATA[やってみた]]></category>
		<category><![CDATA[システム開発]]></category>
		<category><![CDATA[自動化]]></category>
		<guid isPermaLink="false">https://betaarena.local.sdt-autolabo.com/?p=22111</guid>

					<description><![CDATA[<p>Vue.jsとは、JavaScriptのフレームワークの1つであり、HTMLとJavaScriptの間でデータの連携を行う役割を果たします。このデータの連携には、Vue特有の機能であるディレクティブが使用されます。「ディレクティブ」とは、HTML内で特定の属性や要素に指示を記述することで、Vue.jsがその部分を監視し、データの変更に応じて動的にHTMLを更新します。<br />
本記事では、Vue.jsの基本的な機能やメリットから、環境構築方法や外部APIからのデータ取得、ディレクティブの活用方法などを解説しています。</p>
<p>The post <a href="https://www.sms-datatech.co.jp/column/try_vue-js/">【学習アプリ開発記：はじめてのVue.js】問題解答画面を作成してみた！</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">はじめに</h2>



<p>こんにちは！マーケターから未経験のエンジニア職に転身した社員Tです。<br>この度は、初めてのブログ記事を執筆する機会を得て、とても嬉しく思います。<br>この記事では、自身の学びと成長を振り返りながら、過去に実施した vue.js について書いていきたいと思います。</p>



<h2 class="wp-block-heading">本記事の対象の方</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">本記事は、JavaScriptのフレームワークであるVue.jsを学びたい初学者や、Vue.jsを実際に使用してアプリケーションを開発したい方が対象です。</mark></strong>記事では、Vue.jsの基本的な機能やメリットから始めて、環境構築方法や外部APIからのデータ取得、ディレクティブの活用方法などを解説しています。</p>



<h2 class="wp-block-heading">vue.jsとは？</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">まずVue.jsとは、JavaScriptのフレームワークの1つであり、HTMLとJavaScriptの間でデータの連携を行う役割を果たします。</mark></strong>このデータの連携には、Vue特有の機能であるディレクティブが使用されます。「ディレクティブ」とは、HTML内で特定の属性や要素に指示を記述することで、Vue.jsがその部分を監視し、データの変更に応じて動的にHTMLを更新します。</p>



<h2 class="wp-block-heading">メリット</h2>



<p>Vue.jsを扱うメリットは、<strong><mark style="background-color:#fdff84" class="has-inline-color">JQueryよりも記述が簡単であり、初心者でも扱いやすいことです。</mark></strong><br>さらに、Vue.jsは※双方向のデータバインディングが可能です。<br>つまり、<strong><mark style="background-color:#fdff84" class="has-inline-color">UIとデータのいずれか一方が更新されれば、もう一方も自動的に更新される仕組みになっています。</mark></strong>これにより、データとUIが常に同期されるため、開発効率が向上します。<br>初学者の私には、理想的な学習機会となりました。<br>※データモデルとUI（ユーザーインターフェース）の間でデータが自動的に同期される仕組み</p>



<h2 class="wp-block-heading">環境構築の手順</h2>



<h3 class="wp-block-heading">Vue.jsを導入する方法</h3>



<p>Vue.jsは、いくつかの方法で使用することができます！</p>



<ul class="wp-block-list">
<li>scriptタグを埋め込む</li>



<li>NPMを利用してインストール</li>



<li>CLIを利用してインストール(単一ファイルコンポーネントが利用可)</li>
</ul>



<p>今回はbody終了タグの直後にscriptタグを挿入しました！</p>



<figure class="wp-block-image"><img decoding="async" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXed-70DqeIZZbUA9f9Ic818qECoD4zxTOPLMCMIwbOD6F1e_B6v9-nLKyWDxza9BSVUiwshl92ZCKFitrHbE_3wSsLkAn_fyIDDJJKbkiflpS8o0Tar0SQuLxFdmx-0omOjcgkwwptuM1wJa3pjpyOogru6?key=ATiY26x7e9T2g768UR-ZKg" alt=""/></figure>



<h3 class="wp-block-heading">Vue.jsの拡張機能をインストール</h3>



<p>事前にお使いのブラウザにVue Devtoolsをインストールしておくことをおすすめします。<br><strong><mark style="background-color:#fdff84" class="has-inline-color">Vueコンポーネントの階層構造や状態を視覚化できます。</mark></strong><br>私はchromeを使うため、拡張機能<strong><a href="https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=ja&amp;pli=1" target="_blank" rel="noopener" title=""><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">vuejs-devtools</span></mark></a></strong>を入れました。</p>



<h2 class="wp-block-heading"><br>問題取得とディレクティブの活用</h2>



<p>タイトルに記載のとおり、学習アプリの制作に挑戦しました。<br>このアプリは、ユーザーに問題を提供し、解答を受け付け、解説を表示する、一問一答のサービスです。<br><strong><mark style="background-color:#fdff84" class="has-inline-color">ここからは、特に問題の取得とVue.jsのディレクティブの活用にフォーカスしてみたいと思います。</mark></strong></p>



<h3 class="wp-block-heading">問題の取得と表示</h3>



<p>問題の取得や回答の解説表示にはChatGPTのAPIを利用しています。<br>具体的には、JAVA側でAPIエンドポイントを指定し、axiosを使用してHTTPリクエストを送信しています。<br>これにより、アプリが動的に問題を取得し、表示することが可能になります。</p>



<figure class="wp-block-image"><img decoding="async" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcZk5C4i4ubIiCky6wAGER6XkdmlJL4q1RI_wEqMTcN39MkYbDgMTzcanvUKh8xVsjC0EJLJxueEg3O5j1TsDKnRnjzAqnSR0_of0OVoeZMShx5aDkBIs4tBB1c4x0FWzWpRnyDm8PDpjD1-MkzFXsGr-HH?key=ATiY26x7e9T2g768UR-ZKg" alt=""/></figure>



<h3 class="wp-block-heading">デザイン（ディレクティブの活用）</h3>



<p>Vue.jsのディレクティブを活用して、動的な表示を実現します。<br>具体的には、v-ifやv-cloakなどのディレクティブを利用しました。</p>



<h4 class="wp-block-heading">エラーテキストの表示</h4>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">条件に応じて要素を表示または非表示にしたい場合には、「v-if 」ディレクティブを利用しています。</mark></strong><br>今回はシステム上、なにかしらの問題が発生した際にはエラーを表示させたいが、通常時は表示したくない場合に「v-if 」ディレクティブを活用しました。</p>



<figure class="wp-block-image"><img decoding="async" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXecq4vSZ_b_aCdcajSzD_BiPOj_hruCkVQFQUImySeTSvmW3OlJ25JPnAgFOUSs4mFNmHE8BO6DBmRvZZ7Oz5bAeaVbz1MrZaDjIp5IGKTFCn4qNhP19SL6yQRfoHsz1luCEomMFVkpnBORh-jGCgrlVsmi?key=ATiY26x7e9T2g768UR-ZKg" alt=""/></figure>



<h4 class="wp-block-heading">解説の非表示</h4>



<p>演習を行うアプリでは、問題を解いている際に回答が見えてしまったら使いものになりません。<br>そのため、ユーザーが回答の閲覧を希望するまでは解説を非表示にする必要があります。<br>さらに、次の問題に進む際には、新しい問題と回答が再取得されるため、<strong><mark style="background-color:#fdff84" class="has-inline-color">一時的にデータが表示されることを防ぐために今回は、v-cloak ディレクティブを使用しました。</mark></strong></p>



<p>v-cloak ディレクティブは、Vue.jsの初期化が完了するまで要素を隠すためのディレクティブです。初期化が完了する前にデータが表示されてしまうことを防ぎ、見栄えを向上させます。</p>



<figure class="wp-block-image"><img decoding="async" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdpriB_yQqS2i51OIoLEVNteLhNfBS2uLpBnT5NZmKQ38wVajP_3sZYm2zO4EePjyy22vChaEYs7xx3xF1B_Oe2f8hzrghBwJwnaqVjB8kAISzssxfb63Q0OXZVoiJiEGfnTciOJoEQjmAuKRw1BvrOTQM?key=ATiY26x7e9T2g768UR-ZKg" alt=""/></figure>



<p>これらのディレクティブを活用することで、アプリの見た目や動作をスムーズに調整することができます。</p>



<h2 class="wp-block-heading">おわりに</h2>



<p>以上、Vue.jsを使用して問題の取得からディレクティブの活用までをご紹介させていただきました！<br>次回も、Vue.jsについてさらに深く掘り下げていきたいと思います。<br>皆さんもぜひ、Vue.jsを使った開発に挑戦してみてください！</p>



<div class="author__card">
  <div class="author-data">
    <img decoding="async" src="https://www.sms-datatech.co.jp/wp-content/uploads/2024/11/author-3.png" alt="筆者イメージ">
    <div>
      <p>筆者：K・T（20代）</p>
      <p>所属：ハイブリット・マルチクラウド部</p>
    </div>
  </div>
  <div class="author-intro">
    <p>マーケターとしてWEB広告運用やアパレルブランドの運営に携わってきたが、エンジニアとしての新たな挑戦を決意した転職組。<br>好奇心が旺盛で、新しいことに挑戦する姿勢が彼女の魅力。<br>マーケター×エンジニアの強みを生かし、UI/UXの提案から実装まで幅広く対応し、彼女自身がブランドを育てているのだろうと思わせてくれる、そんな20代の乙女。</p>
  </div>
</div>


<div class="block-block-22">
	<div class="Editor-wrap">
					<div class="Editor-wrap__titleMsg">まずはお気軽にご相談ください</div>
			<div class="Editor-wrap__title">お問い合わせフォーム</div>
			</div>
</div>


<script charset="utf-8" type="text/javascript" src="//js.hsforms.net/forms/embed/v2.js"></script>
<script>
  hbspt.forms.create({
    region: "na1",
    portalId: "23171742",
    formId: "b36c7c3b-83a0-43c4-8daa-8e055d6805e9"
  });
</script>



<p></p><p>The post <a href="https://www.sms-datatech.co.jp/column/try_vue-js/">【学習アプリ開発記：はじめてのVue.js】問題解答画面を作成してみた！</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://www.sms-datatech.co.jp/column/try_vue-js/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>RedmineのSSO設定をAWSで行う</title>
		<link>https://www.sms-datatech.co.jp/column/try_redmine/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=try_redmine</link>
					<comments>https://www.sms-datatech.co.jp/column/try_redmine/#respond</comments>
		
		<dc:creator><![CDATA[後藤]]></dc:creator>
		<pubDate>Thu, 15 Aug 2024 02:05:00 +0000</pubDate>
				<category><![CDATA[やってみた]]></category>
		<category><![CDATA[業務効率化]]></category>
		<category><![CDATA[ITコンサルティング]]></category>
		<category><![CDATA[DX]]></category>
		<guid isPermaLink="false">http://192.168.10.58/?p=19671</guid>

					<description><![CDATA[<p>こんにちは。社員Mです。<br />
前回の記事ではECSでRedmineを構築する方法について解説しました。<br />
ECSでRedmineを構築することで、サーバーの管理・運用業務を効率化することができました。<br />
本記事では、次のステップとして、AWS SSOの設定をやってみたいと思います。AWSでRedmineを構築しており、シングルサインオン(SSO)設定をしたいという方を対象としております。<br />
※現在、omniauth samlのpluginsに脆弱性が発見されております。<br />
導入する際は気を付けて下さい。</p>
<p>The post <a href="https://www.sms-datatech.co.jp/column/try_redmine/">RedmineのSSO設定をAWSで行う</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">はじめに</h2>



<p>こんにちは、株式会社SMSデータテックの社員Mです。<br>前回の記事ではECSで<strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;"><a href="https://www.sms-datatech.co.jp/column/try_ecs-redmine/" title="">Redmineを構築する方法</a></span></mark></strong>について解説しました。<br>ECSでRedmineを構築することで、サーバーの管理・運用業務を効率化することができました。<br><strong><mark style="background-color:#fdff84" class="has-inline-color">本記事では、次のステップとして、AWS SSOの設定をやってみたいと思います。</mark></strong></p>



<h2 class="wp-block-heading">本記事対象の方</h2>



<p>AWSでRedmineを構築しており、シングルサインオン(SSO)設定をしたいという方を対象としております。<br>※現在、omniauth samlのpluginsに脆弱性が発見されております。<br>導入する際は気を付けて下さい。</p>



<h2 class="wp-block-heading">SSO設定をするメリット</h2>



<p><strong><mark style="background-color:#fdff84" class="has-inline-color">AWSコンソールにログインすることができれば、ワンクリックでRedmineにアクセスすることができます。</mark></strong><br>また、初アクセスの際にアカウントが作成されるため、アカウント作成も必要ありません。</p>



<h2 class="wp-block-heading">使用技術について</h2>



<h3 class="wp-block-heading">SSOとは</h3>



<p>SSOとは、Single Sign-Onとして知られる認証の仕組みのことです。SSOを使用すると、ユーザーは1度の認証で複数の関連システムやアプリケーションにアクセスできます。これは、ユーザーが複数のアカウントやアプリケーションに対して別々にログインする必要がなくなるため、利便性が向上し、セキュリティが向上します。</p>



<h3 class="wp-block-heading">IAM Identity Centerとは</h3>



<p>AWS IAM アイデンティティセンターは、AWS アプリケーションもしくは複数の AWS アカウントに対するワークフォースのアクセスを管理するために推奨されるサービスです。</p>



<h2 class="wp-block-heading">RedmineにSSO設定を導入する方法</h2>



<p>手順は以下の通りになります。</p>



<ol class="wp-block-list">
<li>IAM Identity Centerでアプリケーションの設定</li>



<li>omniauth samlファイルインストール</li>



<li>samlファイルを編集</li>
</ol>



<p>それぞれ説明していきます。</p>



<!--HubSpot Call-to-Action Code --><span class="hs-cta-wrapper" id="hs-cta-wrapper-bab8f25f-71bf-498b-b9bf-dbc6f6233b49"><span class="hs-cta-node hs-cta-bab8f25f-71bf-498b-b9bf-dbc6f6233b49" id="hs-cta-bab8f25f-71bf-498b-b9bf-dbc6f6233b49"><!--[if lte IE 8]><div id="hs-cta-ie-element"></div><![endif]--><a href="https://cta-redirect.hubspot.com/cta/redirect/23171742/bab8f25f-71bf-498b-b9bf-dbc6f6233b49" target="_blank" rel="noopener"><img decoding="async" class="hs-cta-img" id="hs-cta-img-bab8f25f-71bf-498b-b9bf-dbc6f6233b49" style="border-width:0px;" src="https://no-cache.hubspot.com/cta/default/23171742/bab8f25f-71bf-498b-b9bf-dbc6f6233b49.png" alt="新規CTA"></a></span><script charset="utf-8" src="https://js.hscta.net/cta/current.js"></script><script type="text/javascript"> hbspt.cta.load(23171742, 'bab8f25f-71bf-498b-b9bf-dbc6f6233b49', {"useNewLoader":"true","region":"na1"}); </script></span><!-- end HubSpot Call-to-Action Code -->



<h2 class="wp-block-heading">IAM Identity Center</h2>



<p>IAM Identity Centerのアプリケーションから、アプリケーションの追加を行います。</p>



<h3 class="wp-block-heading">アプリケーションの追加</h3>



<p>写真</p>



<p>こちらの情報は、後述するsamlファイルに記述するので、メモしておいてください。</p>



<p>アプリケーションメタデータ</p>



<p><img decoding="async" width="637" height="271" src="https://lh7-us.googleusercontent.com/docsz/AD_4nXcg3cYSMOq84te2A2Wsj-nxcrSYUMs03Q7eKULMdN7tuVt4OUsBbDukjQAtFdwxCTEi5IAWYHNnf45FXWuiC9Ce5TFN-pwDnDFg8JX4hXn28n9-Ehdr0O-qKlfnwWI3YhYnxZwH9FyJh94FjWk8A2B3Mfgy?key=Jwhf6CorFZ4aqYz3VopuJg"></p>



<p>「メタデータ値をマニュアルで入力する」を選択</p>



<p>入力項目は下記内容を記述<br>アプリケーションACS URL：URL/auth/saml/callback<br>アプリケーションSAML対象者：IAM Identity Center SAML 発行者 URL</p>



<figure class="wp-block-image size-large is-resized"><img decoding="async" width="1024" height="245" src="https://www.sms-datatech.co.jp/wp-content/uploads/2024/07/image-1024x245.png" alt="" class="wp-image-19674" style="width:682px;height:163px" srcset="https://www.sms-datatech.co.jp/wp-content/uploads/2024/07/image-1024x245.png 1024w, https://www.sms-datatech.co.jp/wp-content/uploads/2024/07/image-300x72.png 300w, https://www.sms-datatech.co.jp/wp-content/uploads/2024/07/image-768x184.png 768w, https://www.sms-datatech.co.jp/wp-content/uploads/2024/07/image.png 1036w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h3 class="wp-block-heading">属性マッピングの編集</h3>



<p>属性マッピングについては、AWS公式ドキュメントを参照しています。</p>



<p>下記表の通り記述</p>



<figure class="wp-block-table"><table><tbody><tr><td>アプリケーションのユーザー属性</td><td>ユーザー属性マッピング</td><td>形式</td></tr><tr><td>Subject</td><td>${user:AD_GUID}</td><td>Persistent</td></tr><tr><td>name</td><td>${user:subject}</td><td>basic</td></tr><tr><td>firstname</td><td>${user:givenName}</td><td>basic</td></tr><tr><td>email</td><td>${user:email}</td><td>basic</td></tr><tr><td>lastname</td><td>${user:familyName}</td><td>basic</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">omniauth samlファイル編集</h2>



<p>→<a href="https://github.com/chrodriguez/redmine_omniauth_saml" target="_blank" rel="noopener" title=""><strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-blue-2-color"><span style="text-decoration: underline;">omniauth saml</span></mark></strong></a></p>



<p>AWSからSSOログインが可能になるpluginです。</p>



<p>Start画面からワンクリックでredmineにアクセスできるようになります。</p>



<p>※version5.1.1でも導入できますが、脆弱性が検知されているため、注意が必要です。</p>



<h3 class="wp-block-heading">インストール方法</h3>



<p>dockerfileが存在するディレクトリにpluginsというディレクトリを作成します。<br>そのディレクトリに移動し、下記コマンドを実行します。</p>



<figure class="wp-block-table"><table><tbody><tr><td>git clone https://github.com/chrodriguez/redmine_omniauth_saml.git</td></tr></tbody></table></figure>



<h3 class="wp-block-heading">2saml.rbの編集</h3>



<p>samlファイルを編集します。<br>sample-saml-initializers.rbをコピーし、編集していきます。</p>



<p><img decoding="async" width="632" height="399" src="https://lh7-us.googleusercontent.com/docsz/AD_4nXe26vJ8p3bwskGLqlBL3GoUeSyjri4JKmoyyPDd_Ma4Ux6w0t96PJGB5lvw35crju-6uCzm9iXIhGHhjdx1__oZZL8VgNiHoAj2RoVe0n7UdgffMZFQmkd_y2bL9MY8MMh_WJufd_lkVOhDuKcyjX2T_WX9?key=Jwhf6CorFZ4aqYz3VopuJg"></p>



<p>assertion_consumer_service_url =&gt; ”URL/auth/saml/callback”<br>issue &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; =&gt; ”URL/”<br>idp_sso_target_url&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; =&gt; “IAM Identity Center SAML 発行者 URL”<br>idp_cert &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; =&gt; “IAM Identity Center証明書”<br>name_identifier_format &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; =&gt; &#8220;urn:oasis:names:tc:SAML:2.0:nameid-format:persistent&#8221;<br>idp_slo_target_url &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; =&gt; &#8220;URL&#8221;<br>login&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; =&gt; &#8216;extra.raw_info.name&#8217;,<br>mail &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; =&gt; &#8216;extra.raw_info.email&#8217;,<br>firstname &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; =&gt; &#8216;extra.raw_info.firstname&#8217;,<br>lastname &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; =&gt; &#8216;extra.raw_info.lastname&#8217;</p>



<p>※Redmineのversion4以下だとこのままで良いのですが、version5以上になるとエラーが発生してしまいます。<br>そのため、version5以降は下記設定が必須となります。</p>



<h3 class="wp-block-heading">Redmine version5以降の追加設定</h3>



<p>このsamlファイルの一行目に</p>



<figure class="wp-block-table"><table><tbody><tr><td>require File.expand_path(&#8216;../../../plugins/redmine_omniauth_saml/lib/redmine_omniauth_saml&#8217;, __FILE__)</td></tr></tbody></table></figure>



<p>と入力します。(自身が設定しているディレクトリから/redmine_omniauth_saml/lib/redmine_omniauth_samlを見に行ってください。デフォルトのディレクトリはusr/src/redmineです。)<br>このディレクトリにはSSOログインに関係するファイルがあります。</p>



<p>以上で設定完了です。</p>



<p>AWSコンソール画面のアプリケーションから、直接Redmineにアクセスできるようになります。</p>



<p>写真追加</p>



<h2 class="wp-block-heading">おわり</h2>



<p>今回は、ECSで構築したRedmineにシングルサインオン(SSO)設定を導入してみました。AWS SSOを活用してRedmineに接続する方法についての情報が少なく、設定にはかなり苦労しました。特に、SAMLファイルの編集やIAM Identity Centerの登録に関する部分が難しかったです。<br>しかし、設定後は面倒なアカウント作成が不要になり、ワンクリックで接続できるようになったため、非常に便利になりました。ただ、一部の脆弱性がまだ解消されておらず、その点が気がかりです。<br>試行錯誤の末に作り上げたAWS SSO認証が、他の方々のお役に立てることを願っています。</p>



<div class="author__card">
  <div class="author-data">
    <img decoding="async" src="https://www.sms-datatech.co.jp/wp-content/uploads/2024/11/author-2.png" alt="筆者イメージ">
    <div>
      <p>筆者：R・M（20代）</p>
      <p>所属：ハイブリット・マルチクラウド部</p>
    </div>
  </div>
  <div class="author-intro">
    <p>まったくの未経験でこの業界に飛び込んできた若手。<br>パッと見はビジュアル系？<br>でもプレゼンしだすと、少し胡散臭さもあわせもったセールスように華麗なプレゼンを見せる。<br>読者と一緒にAWSのプロになりたい、そんな20代。</p>
  </div>
</div>



<!--HubSpot Call-to-Action Code --><span class="hs-cta-wrapper" id="hs-cta-wrapper-0a5030d3-78e1-46b3-94b3-b77077a201ba"><span class="hs-cta-node hs-cta-0a5030d3-78e1-46b3-94b3-b77077a201ba" id="hs-cta-0a5030d3-78e1-46b3-94b3-b77077a201ba"><!--[if lte IE 8]><div id="hs-cta-ie-element"></div><![endif]--><a href="https://cta-redirect.hubspot.com/cta/redirect/23171742/0a5030d3-78e1-46b3-94b3-b77077a201ba" target="_blank" rel="noopener"><img decoding="async" class="hs-cta-img" id="hs-cta-img-0a5030d3-78e1-46b3-94b3-b77077a201ba" style="border-width:0px;" src="https://no-cache.hubspot.com/cta/default/23171742/0a5030d3-78e1-46b3-94b3-b77077a201ba.png" alt="新規CTA"></a></span><script charset="utf-8" src="https://js.hscta.net/cta/current.js"></script><script type="text/javascript"> hbspt.cta.load(23171742, '0a5030d3-78e1-46b3-94b3-b77077a201ba', {"useNewLoader":"true","region":"na1"}); </script></span><!-- end HubSpot Call-to-Action Code -->


<div class="block-block-22">
	<div class="Editor-wrap">
					<div class="Editor-wrap__titleMsg">まずはお気軽にご相談ください</div>
			<div class="Editor-wrap__title">お問い合わせフォーム</div>
			</div>
</div>


<script charset="utf-8" type="text/javascript" src="//js.hsforms.net/forms/embed/v2.js"></script> <script> hbspt.forms.create({ region: "na1", portalId: "23171742", formId: "b36c7c3b-83a0-43c4-8daa-8e055d6805e9" }); </script><p>The post <a href="https://www.sms-datatech.co.jp/column/try_redmine/">RedmineのSSO設定をAWSで行う</a> first appeared on <a href="https://www.sms-datatech.co.jp">SMS DataTech</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://www.sms-datatech.co.jp/column/try_redmine/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
