MyBatis 使用规则文档

一、架构概述

1.1 技术栈

  • MyBatis 中间件: ins-framework-mybatis:6.2.1-SNAPSHOT
  • Spring Boot: 2.7.18
  • MyBatis Spring Boot Starter: 自动配置(通过中间件引入)
  • 数据库: PostgreSQL(默认)/ MySQL / H2 / Oracle 等

1.2 架构层次

┌─────────────────────────────────────────┐
│         Controller 层 (Web)             │
│    - 接收 HTTP 请求                      │
│    - 参数验证                            │
│    - 调用 Service 层                     │
└─────────────────┬───────────────────────┘
                  │
┌─────────────────▼───────────────────────┐
│         Service 层 (业务逻辑)            │
│    - 业务逻辑处理                         │
│    - 事务管理                            │
│    - PO/VO 转换                          │
└─────────────────┬───────────────────────┘
                  │
┌─────────────────▼───────────────────────┐
│         DAO 层 (数据访问)                │
│    - 继承 MybatisBaseDao<T, I>          │
│    - 定义数据访问接口                     │
└─────────────────┬───────────────────────┘
                  │
┌─────────────────▼───────────────────────┐
│      Mapper XML (SQL 映射)              │
│    - 定义 SQL 语句                       │
│    - 结果映射                            │
└─────────────────┬───────────────────────┘
                  │
┌─────────────────▼───────────────────────┐
│    MyBatis 中间件 (ins-framework)       │
│    - MybatisBaseDao 基础接口             │
│    - 分页拦截器 (OffsetLimitInterceptor) │
│    - Page/PageParam/Order 类             │
└─────────────────┬───────────────────────┘
                  │
┌─────────────────▼───────────────────────┐
│           数据库 (PostgreSQL)            │
└─────────────────────────────────────────┘

1.3 核心组件

  1. MybatisBaseDao<T, I>: 基础 DAO 接口,提供标准 CRUD 方法
  2. OffsetLimitInterceptor: 分页拦截器,自动处理分页 SQL
  3. PageParam: 分页参数类,包含页码、每页数量、排序信息
  4. Page: 分页结果类,包含数据列表和分页信息
  5. Order: 排序类,用于定义排序规则

二、依赖配置

2.1 Maven 依赖

pom.xml 中配置:

<!-- MyBatis Framework 中间件 -->
<dependency>
    <groupId>ins.framework</groupId>
    <artifactId>ins-framework-mybatis</artifactId>
    <version>6.2.1-SNAPSHOT</version>
    <exclusions>
        <!-- 排除 logback,使用 log4j2 -->
        <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </exclusion>
        <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

2.2 应用配置

application.properties 中配置:

# MyBatis 配置
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=pdfc.monitor.domain.sys.po
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.log-impl=org.apache.ibatis.logging.slf4j.Slf4jImpl

配置说明:

  • mapper-locations: Mapper XML 文件位置
  • type-aliases-package: PO 类的包路径(用于简化 XML 中的类型引用)
  • map-underscore-to-camel-case: 自动映射下划线命名到驼峰命名
  • log-impl: MyBatis SQL 日志实现

2.3 MyBatis 配置类

创建 MonitorMybatisConfig.java 配置类:

@Configuration
@MapperScan("pdfc.monitor.domain.sys.dao")
@Slf4j
public class MonitorMybatisConfig {
    
    @Autowired
    private DataSource dataSource;

    /**
     * 创建分页拦截器 Bean
     * 自动检测数据库类型并配置对应的分页方言
     */
    @Bean
    public OffsetLimitInterceptor offsetLimitInterceptor() throws SQLException {
        OffsetLimitInterceptor interceptor = new OffsetLimitInterceptor();
        // 根据数据库类型自动配置方言
        // 支持: PostgreSQL, MySQL, Oracle, H2, DB2, SQL Server 等
        // ...
        return interceptor;
    }

    /**
     * 注册分页拦截器到 MyBatis 配置
     */
    @Bean
    public ConfigurationCustomizer mybatisConfigurationCustomizer(OffsetLimitInterceptor interceptor) {
        return configuration -> {
            configuration.addInterceptor(interceptor);
            log.info("MyBatis pagination interceptor configured successfully");
        };
    }
}

三、DAO 层开发规则

3.1 DAO 接口定义

规则 1: DAO 接口必须继承 MybatisBaseDao<T, I>

@Mapper
public interface SysUserDao extends MybatisBaseDao<SysUser, Integer> {
    // T: 实体类型 (PO)
    // I: 主键类型
}

规则 2: 使用 @Mapper 注解标记接口

规则 3: 如需自定义参数名,重写方法并添加 @Param 注解

@Mapper
public interface SysUserDao extends MybatisBaseDao<SysUser, Integer> {
    
    /**
     * 重写方法以支持参数绑定
     * 在 XML 中可以使用 #{idList} 引用参数
     */
    @Override
    List<SysUser> selectBatchByPrimaryKeys(@Param("idList") List<Integer> idList);
    
    /**
     * 分页查询(重写以支持参数绑定)
     */
    @Override
    ins.framework.mybatis.Page<SysUser> selectPage(
        @Param("pageParam") PageParam pageParam, 
        @Param("entity") SysUser entity
    );
}

3.2 MybatisBaseDao 提供的方法

方法名 说明 参数 返回值
insert 插入记录(全字段) T entity int
insertSelective 插入记录(选择性字段) T entity int
deleteByPrimaryKey 根据主键删除 I id int
deleteBatchByPrimaryKeys 批量删除 List<I> idList int
updateByPrimaryKey 根据主键更新(全字段) T entity int
updateSelectiveByPrimaryKey 根据主键更新(选择性字段) T entity int
selectByPrimaryKey 根据主键查询 I id T
selectBatchByPrimaryKeys 批量查询 List<I> idList List<T>
selectPage 分页查询 PageParam pageParam, T entity Page<T>

四、Mapper XML 编写规则

4.1 文件位置和命名

  • 位置: src/main/resources/mapper/
  • 命名: {Entity}Mapper.xml,如 SysUserMapper.xml
  • namespace: 必须与 DAO 接口的完整类名一致
<mapper namespace="pdfc.monitor.domain.sys.dao.SysUserDao">
    <!-- SQL 映射 -->
</mapper>

4.2 结果映射 (ResultMap)

规则: 定义 BaseResultMap 用于结果映射

<resultMap id="BaseResultMap" type="pdfc.monitor.domain.sys.po.SysUser">
    <id column="id" property="id" jdbcType="INTEGER"/>
    <result column="name" property="name" jdbcType="VARCHAR"/>
    <result column="work" property="work" jdbcType="VARCHAR"/>
    <result column="age" property="age" jdbcType="INTEGER"/>
    <result column="version" property="version" jdbcType="INTEGER"/>
    <result column="insert_time_for_his" property="insertTimeForHis" jdbcType="TIMESTAMP"/>
    <result column="operate_time_for_his" property="operateTimeForHis" jdbcType="TIMESTAMP"/>
</resultMap>

说明:

  • <id>: 主键字段映射
  • <result>: 普通字段映射
  • column: 数据库列名(下划线命名)
  • property: Java 属性名(驼峰命名)
  • jdbcType: JDBC 类型

4.3 SQL 片段复用

规则: 使用 <sql> 标签定义可复用的 SQL 片段

<sql id="Base_Column_List">
    id, name, work, age, version, insert_time_for_his, operate_time_for_his
</sql>

<!-- 在查询中使用 -->
<select id="selectByPrimaryKey" resultMap="BaseResultMap">
    SELECT 
    <include refid="Base_Column_List"/>
    FROM sys_user
    WHERE id = #{id,jdbcType=INTEGER}
</select>

4.4 CRUD 操作 SQL 编写

4.4.1 插入操作

全字段插入:

<insert id="insert" parameterType="pdfc.monitor.domain.sys.po.SysUser" 
        useGeneratedKeys="true" keyProperty="id" keyColumn="id">
    INSERT INTO sys_user (
        name, work, age, version, 
        insert_time_for_his, operate_time_for_his
    ) VALUES (
        #{name,jdbcType=VARCHAR}, 
        #{work,jdbcType=VARCHAR}, 
        #{age,jdbcType=INTEGER}, 
        #{version,jdbcType=INTEGER},
        #{insertTimeForHis,jdbcType=TIMESTAMP}, 
        #{operateTimeForHis,jdbcType=TIMESTAMP}
    )
</insert>

选择性插入:

<insert id="insertSelective" parameterType="pdfc.monitor.domain.sys.po.SysUser"
        useGeneratedKeys="true" keyProperty="id" keyColumn="id">
    INSERT INTO sys_user
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="name != null">
            name,
        </if>
        <if test="work != null">
            work,
        </if>
        <!-- 其他字段 -->
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
        <if test="name != null">
            #{name,jdbcType=VARCHAR},
        </if>
        <if test="work != null">
            #{work,jdbcType=VARCHAR},
        </if>
        <!-- 其他字段 -->
    </trim>
</insert>
4.4.2 删除操作

单条删除:

<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    DELETE FROM sys_user WHERE id = #{id,jdbcType=INTEGER}
</delete>

批量删除:

<delete id="deleteBatchByPrimaryKeys" parameterType="java.util.List">
    DELETE FROM sys_user WHERE id IN
    <foreach collection="list" item="id" open="(" separator="," close=")">
        #{id,jdbcType=INTEGER}
    </foreach>
</delete>

注意: 如果没有 @Param 注解,集合参数默认名为 listcollection

4.4.3 更新操作

全字段更新:

<update id="updateByPrimaryKey" parameterType="pdfc.monitor.domain.sys.po.SysUser">
    UPDATE sys_user
    SET name = #{name,jdbcType=VARCHAR},
        work = #{work,jdbcType=VARCHAR},
        age = #{age,jdbcType=INTEGER},
        version = #{version,jdbcType=INTEGER},
        operate_time_for_his = #{operateTimeForHis,jdbcType=TIMESTAMP}
    WHERE id = #{id,jdbcType=INTEGER}
</update>

选择性更新:

<update id="updateSelectiveByPrimaryKey" parameterType="pdfc.monitor.domain.sys.po.SysUser">
    UPDATE sys_user
    <set>
        <if test="name != null">
            name = #{name,jdbcType=VARCHAR},
        </if>
        <if test="work != null">
            work = #{work,jdbcType=VARCHAR},
        </if>
        <!-- 其他字段 -->
    </set>
    WHERE id = #{id,jdbcType=INTEGER}
</update>
4.4.4 查询操作

单条查询:

<select id="selectByPrimaryKey" parameterType="java.lang.Integer" 
        resultMap="BaseResultMap">
    SELECT 
    <include refid="Base_Column_List"/>
    FROM sys_user
    WHERE id = #{id,jdbcType=INTEGER}
</select>

批量查询:

<select id="selectBatchByPrimaryKeys" parameterType="java.util.List" 
        resultMap="BaseResultMap">
    SELECT 
    <include refid="Base_Column_List"/>
    FROM sys_user
    WHERE id IN
    <foreach collection="idList" item="id" open="(" separator="," close=")">
        #{id,jdbcType=INTEGER}
    </foreach>
</select>

注意: 如果 DAO 方法使用了 @Param("idList"),XML 中应使用 collection="idList"

4.5 分页查询 SQL 编写

重要规则: 分页查询的 SQL 不需要手动添加 LIMITOFFSET,分页拦截器会自动处理

<!-- 分页查询(分页拦截器会自动添加 LIMIT 和 OFFSET) -->
<select id="selectPage" resultMap="BaseResultMap">
    SELECT 
    <include refid="Base_Column_List"/>
    FROM sys_user
    <where>
        <if test="entity != null">
            <if test="entity.id != null">
                AND id = #{entity.id,jdbcType=INTEGER}
            </if>
            <if test="entity.name != null and entity.name != ''">
                AND name LIKE '%' || #{entity.name,jdbcType=VARCHAR} || '%'
            </if>
            <!-- 其他条件 -->
        </if>
    </where>
    ORDER BY insert_time_for_his DESC
</select>

说明:

  • 参数使用 entity.xxx 访问实体属性
  • 不要添加 LIMITOFFSET 子句
  • 排序可以在 SQL 中定义,也可以通过 PageParam 动态添加

五、Service 层使用规则

5.1 基础 CRUD 操作

@Service
public class SysUserServiceImpl implements SysUserService {
    
    @Autowired
    private SysUserDao sysUserDao;
    
    @Override
    public int insert(SysUserVo entity) {
        // VO 转 PO
        SysUser po = BeanConverter.voToPo(entity);
        // 调用 DAO
        return sysUserDao.insert(po);
    }
    
    @Override
    public SysUserVo selectByPrimaryKey(Integer id) {
        SysUser po = sysUserDao.selectByPrimaryKey(id);
        // PO 转 VO
        return BeanConverter.poToVo(po);
    }
    
    // 其他 CRUD 方法类似...
}

5.2 分页查询使用

@Override
public Page<SysUserVo> getPage(Integer pageIndex, Integer pageSize) {
    // 1. 设置默认值
    if (pageIndex == null || pageIndex < 1) {
        pageIndex = 1;
    }
    if (pageSize == null || pageSize < 1) {
        pageSize = 10;
    }
    
    // 2. 创建分页参数
    PageParam pageParam = new PageParam(pageIndex, pageSize);
    
    // 3. 添加排序(可选)
    Order order = Order.create("insert_time_for_his", "desc", null);
    pageParam.getOrders().add(order);
    
    // 4. 创建查询条件
    SysUser condition = new SysUser();
    // 设置查询条件...
    
    // 5. 执行分页查询
    Page<SysUser> poPage = sysUserDao.selectPage(pageParam, condition);
    
    // 6. 转换为 VO 列表
    List<SysUserVo> voList = BeanConverter.poListToVoList(poPage);
    
    // 7. 创建返回的 Page 对象(保留分页信息)
    Page<SysUserVo> voPage = new Page<>(voList, poPage.getPaginator());
    
    return voPage;
}

关键点:

  • PageParam 构造函数: PageParam(int page, int limit)
  • Order.create(): 创建排序对象,参数为 (property, direction, orderExpr)
  • Page<T> 构造函数: Page(Collection<E> c, Paginator p) 保留分页信息
  • 通过 page.getTotalCount() 获取总记录数

5.3 批量操作使用

@Override
public List<SysUserVo> selectBatchByPrimaryKeys(List<SysUserVo> idList) {
    // 1. 提取 ID 列表
    List<Integer> ids = new ArrayList<>();
    for (SysUserVo vo : idList) {
        if (vo != null && vo.getId() != null) {
            ids.add(vo.getId());
        }
    }
    
    // 2. 批量查询
    List<SysUser> poList = sysUserDao.selectBatchByPrimaryKeys(ids);
    
    // 3. 转换为 VO 列表
    return BeanConverter.poListToVoList(poList);
}

六、参数绑定规则

6.1 单个参数

规则: 单个参数可以直接使用,无需 @Param 注解

// DAO 接口
SysUser selectByPrimaryKey(Integer id);

// XML
<select id="selectByPrimaryKey" parameterType="java.lang.Integer">
    SELECT * FROM sys_user WHERE id = #{id}
</select>

6.2 多个参数

规则: 多个参数必须使用 @Param 注解

// DAO 接口
Long selectPageCount(
    @Param("pageParam") PageParam pageParam, 
    @Param("condition") SysUser condition
);

// XML
<select id="selectPageCount" resultType="java.lang.Long">
    SELECT COUNT(1) FROM sys_user
    <where>
        <if test="condition.id != null">
            AND id = #{condition.id}
        </if>
    </where>
</select>

6.3 集合参数

规则 1: 如果没有 @Param 注解,默认参数名为 listcollection

// DAO 接口(无 @Param)
int deleteBatchByPrimaryKeys(List<Integer> idList);

// XML(使用 list)
<foreach collection="list" item="id">
    #{id}
</foreach>

规则 2: 如果有 @Param 注解,使用注解中的名称

// DAO 接口(有 @Param)
List<SysUser> selectBatchByPrimaryKeys(@Param("idList") List<Integer> idList);

// XML(使用 idList)
<foreach collection="idList" item="id">
    #{id}
</foreach>

6.4 对象属性访问

规则: 使用点号访问对象属性

<!-- 访问实体属性 -->
<if test="entity.name != null">
    AND name = #{entity.name}
</if>

<!-- 访问嵌套对象属性 -->
<if test="entity.user.name != null">
    AND user_name = #{entity.user.name}
</if>

七、分页功能详解

7.1 PageParam 类

PageParam pageParam = new PageParam(pageIndex, pageSize);

// 添加排序
Order order1 = Order.create("name", "asc", null);
Order order2 = Order.create("age", "desc", null);
pageParam.getOrders().add(order1);
pageParam.getOrders().add(order2);

Order.create() 参数说明:

  • property: 排序字段名(数据库列名)
  • direction: 排序方向(“asc” 或 “desc”)
  • orderExpr: 排序表达式(可选,用于特殊排序需求,如 Oracle 的 NLSSORT)

7.2 Page 类

Page<SysUser> page = sysUserDao.selectPage(pageParam, condition);

// 获取数据列表
List<SysUser> list = page;  // Page 继承自 ArrayList

// 获取分页信息
int totalCount = page.getTotalCount();  // 总记录数
int pageNo = page.getPageNo();          // 当前页码
int pageSize = page.getPageSize();      // 每页数量
Paginator paginator = page.getPaginator();  // 分页器对象

7.3 分页拦截器工作原理

  1. 拦截查询: 拦截 selectPage 方法的 SQL 执行
  2. 解析 PageParam: 从方法参数中提取分页信息
  3. 生成总数查询: 自动生成 SELECT COUNT(*) 查询
  4. 修改原 SQL: 在原 SQL 基础上添加 LIMITOFFSET
  5. 执行查询: 执行修改后的 SQL 和总数查询
  6. 封装结果: 将结果封装到 Page<T> 对象中

支持的数据库方言:

  • PostgreSQL
  • MySQL
  • Oracle
  • H2
  • DB2
  • SQL Server
  • HSQL
  • Sybase
  • Informix

八、最佳实践

8.1 DAO 层

  1. 统一继承 MybatisBaseDao: 所有 DAO 接口都应继承 MybatisBaseDao<T, I>
  2. 合理使用 @Param: 多参数或需要自定义参数名时使用
  3. 避免重复定义: 基础 CRUD 方法已在 MybatisBaseDao 中定义,无需重复

8.2 Mapper XML

  1. 统一 ResultMap: 使用 BaseResultMap 统一结果映射
  2. SQL 片段复用: 使用 <sql><include> 复用 SQL 片段
  3. 动态 SQL: 合理使用 <if>, <where>, <set>, <trim> 等标签
  4. 分页 SQL: 不要手动添加 LIMITOFFSET
  5. 参数类型: 明确指定 jdbcType 避免类型转换问题

8.3 Service 层

  1. PO/VO 分离: Service 层处理 PO 和 VO 的转换
  2. 事务管理: 在 Service 方法上使用 @Transactional
  3. 异常处理: 统一异常处理,不要将 SQL 异常直接抛出
  4. 参数验证: 在 Service 层进行业务参数验证

8.4 性能优化

  1. 批量操作: 使用批量方法减少数据库交互
  2. 分页查询: 合理设置每页数量,避免一次查询过多数据
  3. 索引优化: 为常用查询字段添加数据库索引
  4. SQL 优化: 避免 SELECT *,只查询需要的字段

九、常见问题

9.1 参数绑定错误

问题: Parameter 'xxx' not found. Available parameters are [collection, list]

原因: 集合参数没有使用 @Param 注解,但 XML 中使用了自定义参数名

解决:

  • 方法 1: 在 DAO 方法上添加 @Param("xxx") 注解
  • 方法 2: 在 XML 中使用 collection="list"collection="collection"

9.2 分页不生效

问题: 分页查询返回所有数据,没有分页效果

原因:

  • 分页拦截器未正确配置
  • 数据库方言配置错误
  • PageParam 参数传递错误

解决:

  • 检查 MonitorMybatisConfig 配置
  • 确认数据库类型检测正确
  • 确认 selectPage 方法参数正确

9.3 结果映射错误

问题: 查询结果字段为 null

原因:

  • ResultMap 配置错误
  • 数据库列名与 Java 属性名不匹配
  • 未启用 map-underscore-to-camel-case

解决:

  • 检查 ResultMap 配置
  • 确认列名和属性名映射正确
  • 启用自动映射: mybatis.configuration.map-underscore-to-camel-case=true

9.4 主键回填失败

问题: 插入后实体对象的 ID 仍为 null

原因: insert 标签缺少 useGeneratedKeys 配置

解决:

<insert id="insert" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
    <!-- SQL -->
</insert>

十、总结

10.1 核心要点

  1. 使用中间件: 通过 ins-framework-mybatis 中间件简化开发
  2. 继承基础接口: DAO 接口继承 MybatisBaseDao 获得基础功能
  3. 分页自动化: 使用分页拦截器自动处理分页 SQL
  4. 参数绑定: 合理使用 @Param 注解进行参数绑定
  5. XML 映射: 遵循 XML 编写规范,注意参数名和集合处理

10.2 开发流程

  1. 创建 PO 类(持久化对象)
  2. 创建 DAO 接口,继承 MybatisBaseDao<PO, ID>
  3. 创建 Mapper XML 文件,实现 SQL 映射
  4. 在 Service 层调用 DAO 方法
  5. 处理 PO/VO 转换和业务逻辑

10.3 参考示例

完整示例请参考项目中的:

  • DAO 接口: src/main/java/pdfc/monitor/domain/sys/dao/SysUserDao.java
  • Mapper XML: src/main/resources/mapper/SysUserMapper.xml
  • Service 实现: src/main/java/pdfc/monitor/service/impl/SysUserServiceImpl.java
  • 配置类: src/main/java/pdfc/monitor/config/MonitorMybatisConfig.java

文档版本: 1.0
最后更新: 2025-11-17
维护者: Allen

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐