Featured image of post [WIP] Spring Bootの入門メモなど

[WIP] Spring Bootの入門メモなど

目次

概要

  • Spring Bootで作られたプロジェクトの管理をする必要がでたのでメモを残す
  • Javaも10年ぶりなのでJavaについてもまとめる
  • なお、AOPとDIについては別の記事にまとめる

Javaの用語

Thymeleaf

MyBatis

Colaアーキテクチャ

MyBatisとは?

  • MyBatis(マイバティス)はJavaのORM
  • MyBatisと他のORMとの違い
    • MyBatisはテーブルではなくSQLの実行結果に対してORマッピングを行う
  • 関連するパッケージ
    • MyBatis Generator
    • MyBatis Plus

MyBatisの主機能

次のような機能がある。

  • データマッピング機能
  • DBスキーママイグレーション機能
  • コードジェネレータ機能(by MyBatis Generator)
  • キャッシュ機能
  • ロギング機能

MyBatisの例

Entityの作成。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.example.demo.mybatis.entity;

import lombok.Data;

/**
 * ユーザー情報を格納するデータ
 */
@Data
public class User {
    /** ID */
    private int userId;
    /** 氏名 */
    private String name;
    /** 年齢 */
    private int age;
}

Mapper Interfaceの作成。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.example.demo.mybatis.mapper;

import java.util.List;
import org.apache.ibatis.annotations.Mapper;

import com.example.demo.mybatis.entity.User;

@Mapper
public interface UserMapper {
    /**
     * 全ユーザーを取得
     * 
     * @return 全ユーザーの情報
     * 
     */
    List<User> selectAll();
}

マッピングファイル作成。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<!-- src/main/resources/com/example/demo/mybatis/mapper/PlaceMapper.xml -->

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mybatis.mapper.UserMapper">
    <resultMap id="userMap"
        type="jp.co.trainocamp.demo.mybatis.entity.User">
        <result column="ID" jdbcType="INTEGER" property="userId" />
        <result column="NAME" jdbcType="VARCHAR" property="name" />
        <result column="AGE" jdbcType="INTEGER" property="age" />
    </resultMap>
    <select id="selectAll" resultMap="userMap">
        SELECT ID, NAME, AGE FROM USER
    </select>
</mapper>

Springのコントローラーから呼び出す。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.example.demo.mybatis.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;

import com.example.demo.mybatis.mapper.UserMapper;

@Controller
public class UserListController {
    @Autowired
    private UserMapper userMapper;
    
    @GetMapping("/")
    public ModelAndView listAll() {
        ModelAndView mav = new ModelAndView("index");

        // すべてのユーザーを取得する
        mav.addObject("users", userMapper.selectAll());

        return mav;
    }
}

Vuewは次となる。

 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
<!-- src/main/resources/templates/index.html -->
<!DOCTYPE html>
<html lang="jp" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <h1>All Users</h1>
    <table border="1">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Age</th>
            </tr>
        </thead>
        <tbody>
            <tr th:each="user : ${users}">
                <td th:text="${user.userId}"></td>
                <td th:text="${user.name}"></td>
                <td th:text="${user.age}"></td>
            </tr>
        </tbody>
    </table>
</body>
</html>

Apache Druid

データベース管理システムやデータ監視ツール

Quartz Scheduler

Code値の設定

dict_type

1
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (130, '支払いチャネルのコードタイプ', 'pay_channel_code', 0, '支払いチャネルコード', '1', '2021-12-03 10:35:08', '1', '2023-07-10 10:11:39', b'0', NULL);

dict_data

1
2
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (113, 1, 'WeChat公式アカウント支払い', 'wx_pub', 'pay_channel_code', 0, 'success', '', 'WeChat公式アカウント支払い', '1', '2021-12-03 10:40:24', '1', '2023-07-19 20:08:47', b'0');
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (114, 2, 'WeChatミニプログラム支払い', 'wx_lite', 'pay_channel_code', 0, 'success', '', 'WeChatミニプログラム支払い', '1', '2021-12-03 10:41:06', '1', '2023-07-19 20:08:50', b'0');

enum

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum PayChannelEnum {

    WX_PUB("wx_pub", "WeChat公式アカウント支払い", WxPayClientConfig.class), 
    WX_LITE("wx_lite", "WeChat ミニ プログラムの支払い", WxPayClientConfig.class),

    private final String code;
    private final String name;
    private final Class<? extends PayClientConfig> configClass;
}

config

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Data
public class WxPayClientConfig implements PayClientConfig {
    public static final String API_VERSION_V2 = "v2";
    public static final String API_VERSION_V3 = "v3";

    @NotBlank(message = "APPID 空にすることはできません", groups = {V2.class, V3.class})
    private String appId;

    /**
     * 事業者番号
     */
    @NotBlank(message = "販売者番号を空にすることはできません", groups = {V2.class, V3.class})
    private String mchId;

    /**
     * APIバージョン
     */
    @NotBlank(message = "API バージョンを空にすることはできません", groups = {V2.class, V3.class})
    private String apiVersion;
}

Menuの設定

1
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1009, 'ロール新規', 'system:role:create', 3, 2, 101, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Tag(name = "管理システム - 役割")
@RestController
@RequestMapping("/system/role")
@Validated
public class RoleController {
    @GetMapping("/get")
    @Operation(summary = "ロール情報を取得する")
    @PreAuthorize("@ss.hasPermission('system:role:query')")
    public CommonResult<RoleRespVO> getRole(@RequestParam("id") Long id) {
        RoleDO role = roleService.getRole(id);
        return success(BeanUtils.toBean(role, RoleRespVO.class));
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import org.springframework.context.annotation.Bean;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@AutoConfiguration
@EnableConfigurationProperties(SecurityProperties.class)
public class YudaoSecurityAutoConfiguration {
    @Bean("ss") // spring security
    public SecurityFrameworkService securityFrameworkService(PermissionApi permissionApi) {
        return new SecurityFrameworkServiceImpl(permissionApi);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import lombok.AllArgsConstructor;

@AllArgsConstructor
public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {

    private final PermissionApi permissionApi;
    @Override
    public boolean hasPermission(String permission) {
        return hasAnyPermissions(permission);
    }

    @Override
    public boolean hasAnyPermissions(String... permissions) {
        return permissionApi.hasAnyPermissions(getLoginUserId(), permissions);
    }
}
 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
**
 * パーミッションサービス実装クラス
 *
 * @author 太郎のソースコード
 */
@Service
@Slf4j
public class PermissionServiceImpl implements PermissionService {

    @Resource
    private RoleMenuMapper roleMenuMapper;
    @Resource
    private UserRoleMapper userRoleMapper;

    @Resource
    private RoleService roleService;
    @Resource
    private MenuService menuService;
    @Resource
    private DeptService deptService;
    @Resource
    private AdminUserService userService;

    @Override
    public boolean hasAnyPermissions(Long userId, String... permissions) {
        // 空の場合は、すでに許可があることを意味
        if (ArrayUtil.isEmpty(permissions)) {
            return true;
        }

        // 現在ログインしているロールを取得。空の場合は権限がないことを意味
        List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
        if (CollUtil.isEmpty(roles)) {
            return false;
        }

        // ケース 1: それぞれの権限をトラバースして判断し、いずれか 1 つが満たされていれば、権限があることを意味する
        for (String permission : permissions) {
            if (hasAnyPermission(roles, permission)) {
                return true;
            }
        }

        // シナリオ 2: 過剰管理である場合は、許可があることも意味する
        return roleService.hasAnySuperAdmin(convertSet(roles, RoleDO::getId));
    }
}
1
2
3
4
5
6
public interface PermissionApi {
    Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds);
    boolean hasAnyPermissions(Long userId, String... permissions);
    boolean hasAnyRoles(Long userId, String... roles);
    DeptDataPermissionRespDTO getDeptDataPermission(Long userId);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Data
public class DeptDataPermissionRespDTO {

    /**
     * すべてのデータを閲覧することはできるか
     */
    private Boolean all;
    /**
     * 自分のデータを表示できるかr
     */
    private Boolean self;
    /**
     * 表示可能な部門番号の配列
     */
    private Set<Long> deptIds;

    public DeptDataPermissionRespDTO() {
        this.all = false;
        this.self = false;
        this.deptIds = new HashSet<>();
    }

}

Springフレームワークのアノテーション

 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
// リクエストパラメータ用
@GetMapping("/users")
public User getUserByName(@RequestParam("name") String name) {
}

// パス用
@GetMapping("/users/{id}")
public User getUserById(@PathVariable("id") Long id) {
}

// JSON用
@PostMapping("/users")
public User createUser(@RequestBody User user) {
}

// ヘッダー用
@GetMapping("/users")
public User getUser(@RequestHeader("Authorization") String authToken) {
}

// クッキー用
@GetMapping("/users")
public User getUser(@CookieValue("sessionId") String sessionId) {
}

// フォーム用
@PostMapping("/users")
public String createUser(@ModelAttribute User user) {
}

// マルチパート用
@PostMapping("/upload")
public String uploadFile(@RequestPart("file") MultipartFile file) {
}

Spring SecurityのSpEL(Spring Expression Language)

スレッドセーフ

Built with Hugo
テーマ StackJimmy によって設計されています。