mcpField
M
mybatis-plus-patterns

mybatis-plus-patterns

github.com/full-stack-skills開発7117 回インストール4 週間前に更新
リポジトリ

mybatis-plus-patterns について

mybatis-plus-patterns は、Java プロジェクトで MyBatis-Plus ORM を使うためのコーディングスキルです。型安全なクエリ、ページング、同時更新制御、論理削除、安全な更新パターンを扱います。

主な機能

  1. 01LambdaQueryWrapper による型安全なクエリ
  2. 02ページング設定と Page パターン
  3. 03楽観ロックと論理削除
  4. 04フィールドの自動入力
  5. 05ActiveRecord と Mapper の使い分け
  6. 06全件更新・削除の防止

活用シーン

  1. 01文字列のフィールド名を使わず型安全なクエリを作成
  2. 02ページングを設定し PageQuery の結果をラップ
  3. 03同時更新による上書きを回避
  4. 04条件なしの更新や削除を誤って実行するのを防止

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/full-stack-skills/java-skills --skill mybatis-plus-patterns

インストール後、Claude Code で/mybatis-plus-patternsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

agents/
openai.yaml
examples/
mybatis-plus-realworld.md
references/
mybatis-plus-resources.md

詳細

MyBatis-Plus 拡張 ORM

出典:https://baomidou.com/
GitHub:https://github.com/baomidou/mybatis-plus

対応範囲

✅ 得意分野
  1. LambdaQueryWrapper — 型安全なクエリ(文字列のフィールド名を記述する必要はありません)
  2. ページネーションプラグイン — PaginationInnerInterceptor の設定と使用方法
  3. 楽観ロック — @Version + OptimisticLockerInnerInterceptor
  4. 論理削除 — @TableLogic のグローバル設定とテーブル単位の設定
  5. 自動入力 — @TableField(fill) + MetaObjectHandler の実装
  6. ActiveRecord と Mapper の比較 — Entity extends Model と独立した Mapper インターフェースの選択
  7. 全件操作の防止 — BlockAttackInnerInterceptor
  8. 汎用ページネーションのラッパー — PageQuery → PageDTO のジェネリックパターン
❌ 対象外
  1. MyBatis XML/ResultMap/動的 SQL → mybatis-patterns
  2. MyBatis-Plus Generator によるコード生成 → mybatis-plus-generator スキルがすでにあります
  3. MyBatis-Plus のデータ権限 → プラグイン体系であり、LLM でよくあるニーズではありません

最小限の設定

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); // ← 推荐
        return interceptor;
    }
}
mybatis-plus:
  global-config:
    db-config:
      id-type: auto                    # 主键自增
      logic-delete-field: deleted      # 逻辑删除字段
      logic-delete-value: 1
      logic-not-delete-value: 0
      insert-strategy: not_null
      update-strategy: not_null
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 开发环境

LLM がよく犯す間違い

#間違い正しい方法
1eq("user_name", username) のように文字列のフィールド名を使うeq(User::getUsername, username) のように Lambda で型安全に指定する
2PageHelper だけを使い、ページネーションしないIPage + PaginationInnerInterceptor を使う
3楽観ロックを設定せず、同時更新で上書きが発生する@Version + OptimisticLockerInnerInterceptor を使う
4UPDATE SET is_deleted = 1 で手動削除する@TableLogic により自動的に UPDATE に変換する
5ActiveRecord モードを知らないextends Model → entity.insert/updateById/selectById
6全件更新・削除を防止しないBlockAttackInnerInterceptor が WHERE のない UPDATE/DELETE を阻止する
7ページネーションクエリで total/pages を手動計算するPage が total、pages、records を自動的にラップする
8createTime/updateTime を手動設定するMetaObjectHandler で自動入力する
9Wrapper で文字列のフィールド名をハードコードするWrappers.lambdaQuery() + LambdaQueryWrapper を使う

コアパターン

パターン 1: LambdaQueryWrapper(常に Lambda を使う)
// ✅ 推荐:LambdaQueryWrapper(类型安全)
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
wrapper.eq(User::getUsername, "张三")
       .ge(User::getAge, 18)
       .in(User::getStatus, List.of(1, 2))
       .orderByDesc(User::getCreateTime)
       .last("limit 10");  // 慎用 last——与分页冲突
List<User> list = userMapper.selectList(wrapper);

// ✅ 简化:lambdaQuery() 链式调用
List<User> adults = userService.lambdaQuery()
    .ge(User::getAge, 18)
    .in(User::getStatus, Arrays.asList(1, 2))
    .orderByDesc(User::getCreateTime)
    .list();

// ✅ 更新:lambdaUpdate()
userService.lambdaUpdate()
    .eq(User::getUsername, "张三")
    .set(User::getEmail, "[email protected]")
    .update();

// ❌ 避免:QueryWrapper(字符串硬编码,重构时不易发现)
// QueryWrapper<User> wrapper = new QueryWrapper<>();
// wrapper.eq("user_name", "张三");  // 字段名写死,重构时断裂
パターン 2: 汎用ページネーションのラッパー
// 通用分页查询参数
@Data
public class PageQuery {
    @Min(1) private Integer pageNo = 1;
    @Min(1) @Max(5000) private Integer pageSize = 10;
    private String sortBy;
    private boolean isAsc = true;

    public <T> Page<T> toPage() {
        Page<T> page = Page.of(pageNo, pageSize);
        if (StrUtil.isNotBlank(sortBy)) {
            page.addOrder(new OrderItem(sortBy, isAsc));
        }
        return page;
    }
}

// 通用分页返回
@Data
public class PageDTO<T> {
    private Long total;
    private Long pages;
    private List<T> list;

    public static <T> PageDTO<T> of(IPage<T> page) {
        PageDTO<T> dto = new PageDTO<>();
        dto.setTotal(page.getTotal());
        dto.setPages(page.getPages());
        dto.setList(page.getRecords());
        return dto;
    }
}

// 使用
public PageDTO<User> queryUsers(PageQuery query) {
    Page<User> page = userService.page(query.toPage(),
        Wrappers.lambdaQuery<User>().ge(User::getAge, 18));
    return PageDTO.of(page);
}
パターン 3: 楽観ロック + 論理削除 + 自動入力 Entity
@Data
@TableName("sys_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;

    private String username;
    private String email;

    @Version private Integer version;          // ← 乐观锁(更新时自动+1)

    @TableLogic private Integer deleted;        // ← 逻辑删除(0=正常, 1=删除)

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;           // ← 插入时自动填充

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;           // ← 更新时自动填充
}

// 自动填充处理器
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}
パターン 4: ActiveRecord と Mapper の比較
// ActiveRecord: Entity extends Model<T>
@Data
@EqualsAndHashCode(callSuper = true) // ← 必须加
@TableName("sys_user")
public class User extends Model<User> {
    @TableId private Long id;
    private String username;
}
// 使用
User user = new User();
user.setUsername("张三");
user.insert();                         // 直接操作
User found = user.selectById(1L);
user.setEmail("[email protected]");
user.updateById();
user.deleteById();

// Mapper 模式: 独立 Mapper 接口 + IService
public interface UserMapper extends BaseMapper<User> {}

public interface UserService extends IService<User> {}
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}
// 使用
userService.save(user);
userService.getById(1L);
userService.lambdaQuery().eq(User::getUsername, "张三").list();
パターン 5: 全件更新・削除の防止
// 配置 BlockAttackInnerInterceptor 后:
userService.update(new User().setEmail("[email protected]"));  // ❌ 抛出异常
// 必须加 where 条件
userService.lambdaUpdate()
    .eq(User::getStatus, "ACTIVE")
    .set(User::getEmail, "[email protected]")
    .update();  // ✅ 安全

注意点

  1. LambdaQueryWrapper には、メタデータを生成するアノテーションプロセッサーが必要です — そうでないと、Lambda 式からフィールド名を解決できません
  2. 楽観ロックの @Version フィールドでサポートされる型は int/Integer/long/Long/Date/LocalDateTime/Timestamp です
  3. 楽観ロックでは、先にクエリを実行してから更新する必要があります — selectById → setXxx → updateById の順にしないと、version が null のままとなり機能しません
  4. @TableLogic はすべての削除操作に影響します — deleteById/deleteBatchIds はすべて UPDATE に変換されます
  5. カスタム SQL に論理削除条件は自動追加されません — SQL を手書きする場合は AND deleted = 0 を手動で追加する必要があります
  6. BlockAttackInnerInterceptor は全件更新を阻止します — 開発時は有効にすることを推奨します。本番環境では必要に応じて設定してください
  7. PageHelper があるとページネーションプラグインが競合する場合があります — どちらか一方を選び、MyBatis-Plus のページネーションを推奨します
  8. ActiveRecord モードでは Entity が Model を継承する必要があります — そうでないと insert/updateById などのメソッドがありません
  9. 論理削除後のクエリには deleted=0 が自動的に追加されます — 削除済みデータを検索する必要がある場合は、eq(User::getDeleted, 1) を手動で指定してください
  10. 自動入力を行う MetaObjectHandler は Spring Bean(@Component)である必要があります — そうでないと機能しません
  11. strictInsertFill/strictUpdateFill ではフィールドの型を正確に一致させる必要があります — 新しいバージョンの MyBatis-Plus では strict メソッドの使用が推奨されています
  12. Page のコンストラクター引数は pageNo と pageSize です — offset と limit ではありません

データプライバシー

このスキルはユーザーデータを収集、保存、送信しません。

コメント