Mybatis学习笔记

MyBatis 深入学习笔记

一、MyBatis 核心概念

1.1 什么是 MyBatis

MyBatis 是一款优秀的持久层半自动化 ORM 框架。

ORM (Object Relational Mapping) 对象关系映射

  • O:Object,Java 对象
  • R:Relational,关系型数据库中的记录
  • M:Mapping,映射关系

实现 Java 对象与数据库记录之间的双向自动映射,开发者无需手动转换数据。

1.2 半自动化特性

  • 全自动框架(如 Hibernate):适合单表操作
  • 半自动框架(如 MyBatis):适合复杂多表操作

MyBatis 的特点

  • SQL 语句需要手动编写,但参数映射和结果集映射自动完成
  • 提供了强大的动态 SQL 功能
  • 支持存储过程和高级映射

1.3 MyBatis 架构图

应用程序
    ↓
MyBatis API
    ↓
SqlSessionFactory
    ↓
SqlSession
    ↓
Executor(执行器)
    ↓
MappedStatement(映射语句)
    ↓
数据库

二、快速入门

2.1 环境搭建步骤

1. 创建 Maven/Gradle 项目
2. 添加 MyBatis 依赖
3. 创建核心配置文件 mybatis-config.xml
4. 创建实体类(POJO)
5. 创建 Mapper 接口
6. 创建 SQL 映射文件
7. 编写测试代码

2.2 Maven 依赖配置

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.10</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

2.3 核心配置文件示例

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 1. 引入外部属性文件 -->
    <properties resource="jdbc.properties"/>
    
    <!-- 2. 设置 -->
    <settings>
        <!-- 开启驼峰命名自动映射 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>
        <!-- 开启延迟加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>
    
    <!-- 3. 类型别名 -->
    <typeAliases>
        <package name="com.example.entity"/>
    </typeAliases>
    
    <!-- 4. 环境配置 -->
    <environments default="development">
        <environment id="development">
            <!-- 事务管理器 -->
            <transactionManager type="JDBC"/>
            <!-- 数据源 -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    
    <!-- 5. 映射器配置 -->
    <mappers>
        <package name="com.example.mapper"/>
    </mappers>
</configuration>

2.4 基础使用示例

// 1. 加载配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);

// 2. 创建 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = 
    new SqlSessionFactoryBuilder().build(inputStream);

// 3. 获取 SqlSession
try (SqlSession session = sqlSessionFactory.openSession()) {
    // 4. 获取 Mapper 代理对象
    UserMapper userMapper = session.getMapper(UserMapper.class);
    
    // 5. 执行查询
    User user = userMapper.selectById(1);
    System.out.println(user);
    
    // 6. 提交事务(增删改需要)
    session.commit();
}

三、SQL 映射文件详解

3.1 基本结构

<?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.mapper.UserMapper">
    <!-- SQL 语句定义 -->
</mapper>

3.2 参数传递的三种方式

方式一:使用 Map 传递参数

// Mapper 接口
List<User> selectByMap(Map<String, Object> params);

// 映射文件
<select id="selectByMap" parameterType="map" resultType="User">
    SELECT * FROM users 
    WHERE name LIKE #{name} 
    AND age = #{age}
</select>

// 使用
Map<String, Object> params = new HashMap<>();
params.put("name", "%张%");
params.put("age", 20);
List<User> users = userMapper.selectByMap(params);

方式二:使用 @Param 注解

// Mapper 接口
List<User> selectByCondition(
    @Param("name") String name,
    @Param("age") Integer age
);

// 映射文件
<select id="selectByCondition" resultType="User">
    SELECT * FROM users 
    WHERE name LIKE #{name} 
    AND age = #{age}
</select>

方式三:使用默认参数名

// Mapper 接口
List<User> selectByCondition(String name, Integer age);

// 映射文件
<select id="selectByCondition" resultType="User">
    SELECT * FROM users 
    WHERE name LIKE #{arg0}  <!-- 或 #{param1} -->
    AND age = #{arg1}         <!-- 或 #{param2} -->
</select>

3.3 #{} 和 ${} 的区别

特性 #{} ${}
安全性 预编译,防止 SQL 注入 直接拼接,有 SQL 注入风险
使用场景 参数值传递 动态表名、列名等
处理方式 添加引号,类型安全 直接替换字符串
性能 一次编译,多次执行 每次重新编译
<!-- 安全的使用方式 -->
SELECT * FROM #{tableName} WHERE id = #{id}

<!-- 危险的使用方式(有SQL注入风险) -->
SELECT * FROM users WHERE name = '${name}'

四、结果集映射

4.1 自动映射

<select id="selectAll" resultType="User">
    SELECT user_id, user_name, user_email FROM users
</select>

4.2 resultMap 手动映射

<resultMap id="userResultMap" type="User">
    <!-- 主键映射 -->
    <id property="id" column="user_id"/>
    
    <!-- 普通字段映射 -->
    <result property="username" column="user_name"/>
    <result property="email" column="user_email"/>
    <result property="createTime" column="create_time"/>
    
    <!-- 一对一关联映射 -->
    <association property="department" javaType="Department">
        <id property="id" column="dept_id"/>
        <result property="deptName" column="dept_name"/>
    </association>
    
    <!-- 一对多关联映射 -->
    <collection property="roles" ofType="Role">
        <id property="id" column="role_id"/>
        <result property="roleName" column="role_name"/>
    </collection>
</resultMap>

<select id="selectUserWithDetails" resultMap="userResultMap">
    SELECT u.*, d.*, r.*
    FROM users u
    LEFT JOIN department d ON u.dept_id = d.dept_id
    LEFT JOIN user_role ur ON u.user_id = ur.user_id
    LEFT JOIN roles r ON ur.role_id = r.role_id
</select>

4.3 驼峰命名映射

<!-- 在配置文件中开启 -->
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

<!-- 使用示例 -->
<select id="selectUser" resultType="User">
    SELECT user_name, user_email, create_time 
    FROM users
    <!-- 自动映射到 userName, userEmail, createTime -->
</select>

五、动态 SQL

5.1 if 条件判断

<select id="findUsers" parameterType="map" resultType="User">
    SELECT * FROM users
    <where>
        <if test="name != null and name != ''">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
        <if test="email != null">
            AND email = #{email}
        </if>
    </where>
    ORDER BY id DESC
</select>

5.2 choose-when-otherwise

<select id="findActiveUsers" parameterType="map" resultType="User">
    SELECT * FROM users
    <where>
        <choose>
            <when test="status == 'active'">
                AND status = 1
            </when>
            <when test="status == 'inactive'">
                AND status = 0
            </when>
            <otherwise>
                AND status IN (0, 1)
            </otherwise>
        </choose>
    </where>
</select>

5.3 foreach 循环

<!-- 批量查询 -->
<select id="selectByIds" parameterType="list" resultType="User">
    SELECT * FROM users
    WHERE id IN
    <foreach collection="list" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

<!-- 批量插入 -->
<insert id="batchInsert" parameterType="list">
    INSERT INTO users(name, age, email) VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.name}, #{user.age}, #{user.email})
    </foreach>
</insert>

5.4 set 更新

<update id="updateUser" parameterType="User">
    UPDATE users
    <set>
        <if test="name != null">name = #{name},</if>
        <if test="age != null">age = #{age},</if>
        <if test="email != null">email = #{email},</if>
        <if test="updateTime != null">update_time = #{updateTime},</if>
    </set>
    WHERE id = #{id}
</update>

5.5 trim 自定义修剪

<select id="dynamicQuery" parameterType="map" resultType="User">
    SELECT * FROM users
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
        <if test="name != null">
            AND name = #{name}
        </if>
        <if test="age != null">
            OR age = #{age}
        </if>
    </trim>
</select>

5.6 bind 创建变量

<select id="searchUsers" parameterType="string" resultType="User">
    <bind name="pattern" value="'%' + keyword + '%'"/>
    SELECT * FROM users
    WHERE name LIKE #{pattern}
       OR email LIKE #{pattern}
</select>

5.7 SQL 片段重用

<!-- 定义 SQL 片段 -->
<sql id="baseColumns">
    id, name, age, email, create_time, update_time
</sql>

<sql id="whereConditions">
    <where>
        <if test="name != null">AND name = #{name}</if>
        <if test="age != null">AND age = #{age}</if>
        <if test="email != null">AND email = #{email}</if>
    </where>
</sql>

<!-- 使用 SQL 片段 -->
<select id="selectUsers" parameterType="map" resultType="User">
    SELECT 
    <include refid="baseColumns"/>
    FROM users
    <include refid="whereConditions"/>
    ORDER BY id DESC
</select>

六、高级特性

6.1 主键回填

<!-- 方式一:useGeneratedKeys -->
<insert id="insertUser" parameterType="User" 
        useGeneratedKeys="true" keyProperty="id">
    INSERT INTO users(name, age, email)
    VALUES(#{name}, #{age}, #{email})
</insert>

<!-- 方式二:selectKey -->
<insert id="insertUser" parameterType="User">
    <selectKey keyProperty="id" resultType="int" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
    
    INSERT INTO users(name, age, email)
    VALUES(#{name}, #{age}, #{email})
</insert>

6.2 延迟加载(懒加载)

<!-- 1. 在配置文件中开启 -->
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

<!-- 2. 在映射文件中配置 -->
<resultMap id="userWithOrdersMap" type="User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    
    <!-- 延迟加载订单信息 -->
    <collection property="orders" ofType="Order" 
                select="com.example.mapper.OrderMapper.selectByUserId"
                column="id" fetchType="lazy"/>
</resultMap>

<select id="selectUserWithOrders" resultMap="userWithOrdersMap">
    SELECT id, name FROM users WHERE id = #{id}
</select>

6.3 分页查询

// 使用 RowBounds(逻辑分页)
public List<User> selectUsersByPage(int offset, int limit) {
    try (SqlSession session = sqlSessionFactory.openSession()) {
        RowBounds rowBounds = new RowBounds(offset, limit);
        return session.selectList("com.example.mapper.UserMapper.selectAll", 
                                  null, rowBounds);
    }
}

// 使用 PageHelper(物理分页,推荐)
public PageInfo<User> selectUsersByPage(int pageNum, int pageSize) {
    PageHelper.startPage(pageNum, pageSize);
    List<User> users = userMapper.selectAll();
    return new PageInfo<>(users);
}

七、缓存机制

7.1 一级缓存(本地缓存)

  • 作用域:SqlSession 级别
  • 生命周期:与 SqlSession 相同
  • 默认开启:无需配置
  • 失效场景
    1. 执行了增删改操作
    2. 调用了 clearCache() 方法
    3. 执行了 commit() 或 rollback()
    4. 不同的 SqlSession 之间不共享

7.2 二级缓存(全局缓存)

7.2.1 配置步骤

<!-- 1. 在配置文件中开启 -->
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

<!-- 2. 在映射文件中启用 -->
<mapper namespace="com.example.mapper.UserMapper">
    <!-- 使用默认缓存 -->
    <cache/>
    
    <!-- 或使用第三方缓存(如 EhCache) -->
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
</mapper>

<!-- 3. 实体类需要实现 Serializable 接口 -->
public class User implements Serializable {
    // ...
}

7.2.2 缓存配置属性

<cache
  eviction="FIFO"              <!-- 回收策略:LRU, FIFO, SOFT, WEAK -->
  flushInterval="60000"        <!-- 刷新间隔(毫秒) -->
  size="512"                   <!-- 最大缓存对象数量 -->
  readOnly="true"              <!-- 是否只读 -->
/>

7.2.3 控制缓存使用

<!-- 禁用二级缓存 -->
<select id="selectById" resultType="User" useCache="false">
    SELECT * FROM users WHERE id = #{id}
</select>

<!-- 刷新缓存 -->
<insert id="insertUser" parameterType="User" flushCache="true">
    INSERT INTO users(name, age) VALUES(#{name}, #{age})
</insert>

八、注解开发

8.1 基础 CRUD 注解

@Mapper
public interface UserMapper {
    
    @Select("SELECT * FROM users WHERE id = #{id}")
    User selectById(@Param("id") Integer id);
    
    @Insert("INSERT INTO users(name, age) VALUES(#{name}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);
    
    @Update("UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id}")
    int update(User user);
    
    @Delete("DELETE FROM users WHERE id = #{id}")
    int deleteById(@Param("id") Integer id);
}

8.2 结果集映射注解

@Results(id = "userResultMap", value = {
    @Result(property = "id", column = "user_id", id = true),
    @Result(property = "username", column = "user_name"),
    @Result(property = "email", column = "user_email"),
    @Result(property = "createTime", column = "create_time")
})
@Select("SELECT * FROM users WHERE id = #{id}")
User selectUserWithMap(@Param("id") Integer id);

// 复用结果集映射
@ResultMap("userResultMap")
@Select("SELECT * FROM users WHERE name = #{name}")
List<User> selectUsersByName(@Param("name") String name);

8.3 动态 SQL 注解

// 使用 @SelectProvider
@SelectProvider(type = UserSqlProvider.class, method = "buildSelectSql")
List<User> selectByCondition(UserCondition condition);

// SQL 提供者类
public class UserSqlProvider {
    public String buildSelectSql(UserCondition condition) {
        return new SQL() {{
            SELECT("*");
            FROM("users");
            if (condition.getName() != null) {
                WHERE("name LIKE CONCAT('%', #{name}, '%')");
            }
            if (condition.getAge() != null) {
                WHERE("age = #{age}");
            }
            ORDER_BY("id DESC");
        }}.toString();
    }
}

九、执行流程与原理

9.1 MyBatis 执行流程

graph TD A[加载配置文件] --> B[创建SqlSessionFactory] B --> C[创建SqlSession] C --> D[获取Mapper接口代理] D --> E[执行SQL] E --> F[返回结果]

9.2 四大核心接口

  1. Executor(执行器)

    • SimpleExecutor:简单执行器,每次执行都创建 Statement
    • ReuseExecutor:重用执行器,重用 Statement
    • BatchExecutor:批处理执行器,用于批量操作
  2. StatementHandler(语句处理器)

    • PreparedStatementHandler:预处理语句处理器
    • SimpleStatementHandler:简单语句处理器
    • CallableStatementHandler:存储过程处理器
  3. ParameterHandler(参数处理器)

    • 负责将 Java 对象转换为 JDBC 参数
  4. ResultSetHandler(结果集处理器)

    • 负责将 JDBC 结果集转换为 Java 对象

9.3 插件机制

// 自定义插件示例
@Intercepts({
    @Signature(type = Executor.class, method = "query", 
               args = {MappedStatement.class, Object.class, 
                      RowBounds.class, ResultHandler.class})
})
public class MyPlugin implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 执行前处理
        System.out.println("Before query...");
        
        // 执行原方法
        Object result = invocation.proceed();
        
        // 执行后处理
        System.out.println("After query...");
        
        return result;
    }
    
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    
    @Override
    public void setProperties(Properties properties) {
        // 设置插件属性
    }
}

十、最佳实践与优化

10.1 SQL 优化建议

  1. **避免使用 SELECT ***:明确指定需要的列
  2. 合理使用索引:为查询条件创建索引
  3. 批量操作:使用 foreach 进行批量插入/更新
  4. 分页查询:使用物理分页而非逻辑分页
  5. 避免 N+1 查询:使用关联查询或延迟加载

10.2 配置优化

<settings>
    <!-- 开启二级缓存 -->
    <setting name="cacheEnabled" value="true"/>
    
    <!-- 开启懒加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
    
    <!-- 开启驼峰命名 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    
    <!-- 设置默认执行器 -->
    <setting name="defaultExecutorType" value="REUSE"/>
    
    <!-- 设置超时时间 -->
    <setting name="defaultStatementTimeout" value="30"/>
</settings>

10.3 事务管理

// 编程式事务管理
SqlSession session = sqlSessionFactory.openSession();
try {
    UserMapper mapper = session.getMapper(UserMapper.class);
    
    // 执行多个操作
    mapper.insert(user1);
    mapper.insert(user2);
    
    // 提交事务
    session.commit();
} catch (Exception e) {
    // 回滚事务
    session.rollback();
    throw e;
} finally {
    // 关闭会话
    session.close();
}

十一、常见问题与解决方案

11.1 常见异常及解决

  1. BindingException

    // 原因:Mapper接口与XML映射文件不匹配
    // 解决:检查namespace、id、parameterType、resultType是否一致
    
  2. TooManyResultsException

    // 原因:查询结果多于一个,但期望单个结果
    // 解决:使用selectOne或修改查询条件
    
  3. TypeException

    // 原因:类型转换失败
    // 解决:检查数据库字段与Java属性类型是否匹配
    

11.2 性能优化技巧

  1. 使用连接池:配置合适的连接池参数
  2. 缓存策略:根据业务需求选择合适的缓存策略
  3. SQL 监控:使用日志或监控工具分析SQL性能
  4. 批量处理:大量数据操作时使用批量模式
  5. 索引优化:为频繁查询的字段创建索引

总结

MyBatis 是一个功能强大且灵活的持久层框架,通过合理的配置和使用,可以大大提高开发效率和系统性能。掌握 MyBatis 的核心概念、动态 SQL、缓存机制和高级特性,对于构建高性能的 Java 应用至关重要。

关键要点回顾

  1. MyBatis 是半自动化 ORM 框架,平衡了灵活性和开发效率
  2. 动态 SQL 是 MyBatis 的核心特性,支持复杂查询条件
  3. 合理的缓存配置可以显著提升系统性能
  4. 注解和 XML 配置各有优势,可根据项目需求选择
  5. 理解 MyBatis 的执行流程和原理有助于排查问题和优化性能

持续关注 MyBatis 的更新和最佳实践,结合实际项目需求进行灵活应用,才能充分发挥 MyBatis 的优势。

posted @ 2026-01-27 20:26  窥光卿  阅读(5)  评论(0)    收藏  举报