mcpField
M
mybatis-patterns

mybatis-patterns

github.com/full-stack-skills开发7104 次安装4 周前更新
代码仓库

关于 mybatis-patterns

mybatis-patterns 是提供 MyBatis ORM 映射与 SQL 模式指导的技能,面向使用 MyBatis 的开发者。内容涵盖查询设计、结果映射、分页及动态 SQL 的安全处理。

主要功能

  1. 01选择 XML 或注解方式
  2. 02复用 ResultMap 映射
  3. 03关联与 JOIN 查询模式
  4. 04构建动态 SQL
  5. 05安全处理参数与标识符
  6. 06分页与 SQL 片段复用

适用场景

  1. 01为 CRUD 和复杂查询选择映射方式
  2. 02映射多表 JOIN 查询结果
  3. 03防止动态排序中的注入风险
  4. 04构建动态筛选与分页查询

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/mybatis-patterns确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

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

详细介绍

来自项目 README

MyBatis 核心 ORM

来源:https://mybatis.org/mybatis-3/
GitHub:https://github.com/mybatis/mybatis-3

Capability Boundaries

✅ Strong Suits
  1. XML vs 注解选择 — 简单CRUD用注解,复杂查询/动态SQL用XML
  2. ResultMap复用 — extends继承、discriminator鉴别器、columnPrefix 同表多JOIN
  3. 关联查询 — association(一对一)/collection(一对多),Join查询(推荐) vs 嵌套select(N+1风险)
  4. 动态SQL — if/choose/when/otherwise/where/set/foreach/trim/return
  5. 防注入 — #{}预编译 vs ${}表名/列名(必须白名单校验)
  6. 分页 — RowBounds(不推荐) / PageHelper(推荐) 物理分页
  7. sql片段 + include — 公共SQL片段复用
❌ Out of Scope
  1. MyBatis-Plus LambdaWrapper → mybatis-plus-patterns
  2. Spring Data JPA → spring-data-jpa(已有技能)

LLM最常犯的错误

#错误正确做法
1简单CRUD也用XML写@Select("SELECT * FROM user WHERE id=#{id}") 注解即可
2N+1查询:循环查子表Join查询(推荐) 或 association/collection + batch select
3${column} 动态排序无白名单先检查 columnName 是否在白名单中,否则 SQL 注入
4不配置 resultMap 用别名映射定义 <resultMap> + <result column="user_name" property="userName"/>
5SELECT * 全部字段只查需要的字段(尤其大字段/TEXT/BLOB)
6模糊查询 LIKE '%${keyword}%' SQL注入LIKE CONCAT('%', #{keyword}, '%') 预编译
7多表 JOIN 不处理列名冲突用别名 + resultMap column 映射
8不同表 JOIN 同表两次,手动写所有列用 <association columnPrefix="addr_" 复用 resultMap

核心模式

模式 1: ResultMap 复用 + extends
<!-- 基础 ResultMap -->
<resultMap id="BaseResultMap" type="com.example.User">
    <id column="id" property="id"/>
    <result column="user_name" property="userName"/>
    <result column="email" property="email"/>
    <result column="status" property="status"/>
</resultMap>

<!-- 继承 + 关联(推荐 Join 方式) -->
<resultMap id="UserWithDept" extends="BaseResultMap" type="com.example.UserVO">
    <association property="dept" javaType="Department"
        resultMap="com.example.DeptMapper.BaseResultMap"
        columnPrefix="dept_"/>  <!-- ← 同名字段加前缀 -->
    <collection property="roles" ofType="Role"
        resultMap="com.example.RoleMapper.BaseResultMap"
        columnPrefix="role_"/>
</resultMap>
模式 2: Join 查询(推荐,替代嵌套 select)
<!-- ✅ 推荐:Join 查询 → 一次SQL,无N+1 -->
<select id="selectUserWithDept" resultMap="UserWithDept">
    SELECT u.id, u.user_name, u.email, u.status,
           d.id AS dept_id, d.dept_name AS dept_dept_name,  <!-- ← 加前缀 -->
           r.id AS role_id, r.role_name AS role_role_name
    FROM sys_user u
    LEFT JOIN sys_dept d ON u.dept_id = d.id
    LEFT JOIN sys_user_role ur ON u.id = ur.user_id
    LEFT JOIN sys_role r ON ur.role_id = r.id
    WHERE u.id = #{id}
</select>

<!-- ❌ 避免:嵌套 select 导致 N+1 -->
<resultMap id="UserWithDeptNested" extends="BaseResultMap" type="com.example.UserVO">
    <association property="dept" javaType="Department"
        column="dept_id" select="selectDeptById"/>  <!-- ← 每次查询一个部门 -->
</resultMap>
<!-- 如果查100个用户 → 1(主查询) + 100(部门查询) = 101次SQL -->
模式 3: 动态 SQL 完整模式
<select id="findByCondition" resultMap="BaseResultMap">
    SELECT id, user_name, email, status, create_time
    FROM sys_user
    <where>  <!-- ← 自动处理 AND/OR 前缀 -->
        <if test="username != null and username != ''">
            AND user_name LIKE CONCAT('%', #{username}, '%')  <!-- ✅ 防注入 -->
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
        <if test="deptId != null">
            AND dept_id = #{deptId}
        </if>
    </where>
    <if test="orderBy != null">
        ORDER BY ${orderBy}  <!-- ⚠️ 必须白名单校验 -->
    </if>
</select>

<!-- 批量插入 -->
<insert id="batchInsert">
    INSERT INTO sys_user (user_name, email, status)
    VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.userName}, #{user.email}, #{user.status})
    </foreach>
</insert>

<!-- 动态更新(set 自动去尾逗号) -->
<update id="updateSelective">
    UPDATE sys_user
    <set>
        <if test="userName != null">user_name = #{userName},</if>
        <if test="email != null">email = #{email},</if>
        <if test="status != null">status = #{status},</if>
    </set>
    WHERE id = #{id}
</update>

<!-- choose/when/otherwise 多分支 -->
<select id="findByCondition" resultMap="BaseResultMap">
    SELECT * FROM sys_user
    <where>
        <choose>
            <when test="queryType == 'name'">AND user_name LIKE CONCAT('%', #{keyword}, '%')</when>
            <when test="queryType == 'email'">AND email = #{keyword}</when>
            <otherwise>AND status = 'ACTIVE'</otherwise>
        </choose>
    </where>
</select>
模式 4: SQL 片段复用
<sql id="BaseColumns">
    id, user_name, email, status, create_time, update_time
</sql>

<select id="selectById" resultMap="BaseResultMap">
    SELECT <include refid="BaseColumns"/>
    FROM sys_user WHERE id = #{id}
</select>
模式 5: 注解方式(简单查询)
@Select("SELECT * FROM sys_user WHERE id = #{id}")
@Results(id = "userMap", value = {
    @Result(column = "user_name", property = "userName"),
    @Result(column = "dept_id", property = "deptId")
})
User findById(Long id);
模式 6: 排序字段白名单校验
// ✅ 安全:校验排序字段在白名单中
private static final Set<String> SORT_COLUMNS = Set.of(
    "create_time", "update_time", "user_name", "status"
);

public List<User> findByCondition(String orderBy, boolean asc) {
    if (!SORT_COLUMNS.contains(orderBy)) {
        throw new SecurityException("非法排序字段: " + orderBy);
    }
    // 传入 ${orderBy} 安全使用
}

Gotchas

  1. #{} 会加引号自动转义,${} 直接拼接 — 用户输入必须用 #{}
  2. ${} 用于动态表名/列名/排序时 — 必须白名单校验,否则 SQL 注入
  3. association 的 select 属性是延迟加载 — 每个关联触发一次查询 = N+1,用 Join 查询替代
  4. resultType 和 resultMap 不能同时使用 — 二选一
  5. Mapper 接口和 XML 必须在同一 package 路径 — 否则配置 mybatis.mapper-locations
  6. Spring Boot 默认开启驼峰映射 — mybatis.configuration.map-underscore-to-camel-case=true
  7. @Select/@Results 不定义 @ResultMap ID — 不能在 XML 中复用
  8. JOIN 查询中 resultMap columnPrefix — 解决同名字段的唯一方式,比写别名更简洁
  9. WHERE 1=1 不如 <where> — <where> 自动处理 AND/OR 前缀,更优雅
  10. <foreach> 注意 IN 列表为空 — 空列表导致 SQL 语法错误,提前判空
  11. PageHelper 分页的原理是 ThreadLocal — 分页后必须紧跟查询,中间不能有其他操作
  12. MyBatis 二级缓存慎用 — 跨 session 缓存导致数据不一致,分布式环境不要用

Data Privacy

本技能不收集、存储或传输任何用户数据。

评论

支持 Markdown