次の段階をお願いします。コード生成時には可読性のため、不要な改行(空行)はないようにしてください。
承知しました。
以降のコード生成では、以下の方針で統一します。
今回は 第7段階:MyBatis Mapper設計 + DTO設計 + SearchCondition設計 を行います。
第7段階の目的
今回の教材管理システムでは、画面表示は基本的に以下の流れになります。
| Controller
↓
Service
↓
MyBatis Mapper
↓
SQL
↓
DTO
↓
Thymeleaf
|
1. MyBatisの責務
MyBatisでは以下を担当します。
| 機能 |
Mapper |
| 教材一覧 |
ContentMapper |
| 教材詳細 |
ContentMapper |
| 教材検索 |
ContentMapper |
| 利用コース確認 |
ContentUsageMapper |
| コース構成表示 |
CourseMapper |
| 学習進捗 |
LearningMapper |
| 管理者集計 |
DashboardMapper |
2. DTO設計
パッケージ構成
1
2
3
4
5
6
7
8
9
10
11
12 | dto
├ content
│ ├ ContentListDto.java
│ ├ ContentDetailDto.java
│ └ ContentUsageDto.java
│
├ course
│ ├ CourseListDto.java
│ └ CourseTreeDto.java
│
└ learning
└ LearningProgressDto.java
|
3. 教材一覧DTO
教師の教材ライブラリ画面で利用します。
表示例:
ContentListDto.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | package com.example.lms.dto.content;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ContentListDto {
private Long id;
private String title;
private String contentType;
private String authorName;
private String status;
private Integer versionNo;
private Integer usageCount;
private LocalDateTime updatedAt;
}
|
4. 教材詳細DTO
教材詳細画面用です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | package com.example.lms.dto.content;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class ContentDetailDto {
private Long id;
private String title;
private String summary;
private String contentType;
private String authorName;
private String status;
private Integer versionNo;
private LocalDateTime publishedAt;
private String body;
private String videoType;
private String videoId;
private String videoUrl;
private List<AttachmentDto> attachments;
}
|
AttachmentDto.java
1
2
3
4
5
6
7
8
9
10
11
12 | package com.example.lms.dto.content;
import lombok.Data;
@Data
public class AttachmentDto {
private Long id;
private String originalName;
private String mimeType;
private String storageType;
private String storagePath;
}
|
5. 教材検索条件
検索画面ではEntityを使いません。
ContentSearchCondition.java
1
2
3
4
5
6
7
8
9
10
11
12
13 | package com.example.lms.search;
import lombok.Data;
@Data
public class ContentSearchCondition {
private String keyword;
private String contentType;
private String status;
private Long authorId;
private Integer page = 0;
private Integer size = 20;
}
|
MyBatis検索ではページングします。
1
2
3
4
5
6
7
8
9
10
11
12
13 | package com.example.lms.dto.common;
import lombok.Data;
@Data
public class PageRequestDto {
private int page = 0;
private int size = 20;
public int getOffset() {
return page * size;
}
}
|
7. ContentMapper
教材検索用Mapperです。
ContentMapper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | package com.example.lms.mapper;
import com.example.lms.dto.content.ContentDetailDto;
import com.example.lms.dto.content.ContentListDto;
import com.example.lms.search.ContentSearchCondition;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface ContentMapper {
List<ContentListDto> search(ContentSearchCondition condition);
int count(ContentSearchCondition condition);
ContentDetailDto findDetail(Long id);
}
|
8. 教材一覧SQL
MyBatis XML方式を推奨します。
理由:
が多いためです。
ContentMapper.xml
配置:
| resources
└ mapper
└ ContentMapper.xml
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 | <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.lms.mapper.ContentMapper">
<select id="search"
resultType="com.example.lms.dto.content.ContentListDto">
SELECT
c.id,
cv.title,
c.content_type,
u.username AS author_name,
cv.status,
cv.version_no,
COUNT(lc.id) AS usage_count,
cv.updated_at
FROM content c
JOIN content_versions cv
ON c.current_version_id = cv.id
JOIN users u
ON c.author_id = u.id
LEFT JOIN lesson_contents lc
ON c.id = lc.content_id
<where>
<if test="keyword != null and keyword != ''">
AND cv.title LIKE CONCAT('%',#{keyword},'%')
</if>
<if test="contentType != null">
AND c.content_type = #{contentType}
</if>
<if test="status != null">
AND cv.status = #{status}
</if>
<if test="authorId != null">
AND c.author_id = #{authorId}
</if>
</where>
GROUP BY
c.id,
cv.id,
u.username
ORDER BY
cv.updated_at DESC
LIMIT #{size}
OFFSET #{offset}
</select>
</mapper>
|
9. 教材詳細SQL
教材詳細ではタイプ別JOINします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | <select id="findDetail"
resultType="com.example.lms.dto.content.ContentDetailDto">
SELECT
c.id,
cv.title,
cv.summary,
c.content_type,
u.username AS author_name,
cv.status,
cv.version_no,
cv.published_at,
tc.body,
vc.video_type,
vc.video_id,
vc.video_url
FROM content c
JOIN content_versions cv
ON c.current_version_id=cv.id
JOIN users u
ON c.author_id=u.id
LEFT JOIN text_contents tc
ON cv.id=tc.content_version_id
LEFT JOIN video_contents vc
ON cv.id=vc.content_version_id
WHERE c.id=#{id}
</select>
|
10. コースツリーDTO
受講画面用です。
構造:
| Course
└ Section
└ Section
└ Lesson
└ Content
|
CourseTreeDto.java
1
2
3
4
5
6
7
8
9
10
11
12
13 | package com.example.lms.dto.course;
import lombok.Data;
import java.util.List;
@Data
public class CourseTreeDto {
private Long id;
private String title;
private String type;
private List<CourseTreeDto> children;
}
|
11. CourseMapper
| package com.example.lms.mapper;
import com.example.lms.dto.course.CourseTreeDto;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface CourseMapper {
List<CourseTreeDto> findCourseTree(Long courseId);
}
|
12. コースツリー取得SQL
階層構造なのでMySQL8のCTEを利用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | <select id="findCourseTree"
resultType="com.example.lms.dto.course.CourseTreeDto">
WITH RECURSIVE section_tree AS (
SELECT
id,
title,
parent_section_id,
0 AS depth
FROM sections
WHERE course_version_id=#{courseId}
AND parent_section_id IS NULL
UNION ALL
SELECT
s.id,
s.title,
s.parent_section_id,
st.depth+1
FROM sections s
JOIN section_tree st
ON s.parent_section_id=st.id
)
SELECT
id,
title,
'SECTION' AS type
FROM section_tree
</select>
|
13. 教材利用状況DTO
教材編集時に重要です。
例:
| この教材は以下で利用されています
Java基礎
SpringBoot入門
新人研修
|
ContentUsageDto.java
| package com.example.lms.dto.content;
import lombok.Data;
@Data
public class ContentUsageDto {
private Long courseId;
private String courseName;
private String lessonTitle;
}
|
14. ContentUsageMapper
| package com.example.lms.mapper;
import com.example.lms.dto.content.ContentUsageDto;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface ContentUsageMapper {
List<ContentUsageDto> findUsage(Long contentId);
}
|
15. Service利用イメージ
例:
| @Service
@RequiredArgsConstructor
public class ContentQueryService {
private final ContentMapper contentMapper;
public List<ContentListDto> search(ContentSearchCondition condition) {
return contentMapper.search(condition);
}
}
|
この段階で完成したもの
現在の責務分離:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | 登録・更新
Controller
↓
Service
↓
JPA Repository
↓
Entity
検索・表示
Controller
↓
Service
↓
MyBatis Mapper
↓
DTO
↓
Thymeleaf
|
次の第8段階
次は画面と業務フローを実装する段階になります。
内容:
第8段階:Service層設計 + Controller設計
作成予定:
-
ContentService
-
ApprovalService
-
CourseService
-
コース作成
-
セクション管理
-
Lesson登録
-
教材配置
-
Spring Security連携
-
ログインユーザー取得
-
教師のみ編集可能
-
管理者のみ承認可能
-
Thymeleaf画面URL設計
ここから実際のアプリケーション処理になります。