Java入门到精通-37 MyBatis与MyBatis-Plus——ORM映射、动态SQL与代码生成

🗃️ 37 MyBatis与MyBatis-Plus——ORM映射、动态SQL与代码生成

更新日期:2026年6月 | Java入门到精通系列 · 第五阶段·企业级开发
© 版权声明:本文为原创技术文章,转载请联系作者并注明出处。



一、ORM与MyBatis概述

1.1 什么是ORM

ORM(Object-Relational Mapping,对象关系映射) 是一种将数据库表与Java对象进行映射的技术,让我们可以用面向对象的方式操作数据库。

数据库表  ←→  Java类
表字段    ←→  类属性
表行记录  ←→  类实例
SQL操作   ←→  方法调用

1.2 常见ORM框架对比

框架类型SQL控制学习成本适用场景
JDBC原生API完全手写底层操作
MyBatis半自动ORM手写SQL复杂SQL、性能要求高
MyBatis-Plus增强ORM自动生成+手写简单CRUD + 复杂查询
JPA/Hibernate全自动ORM自动生成简单业务、快速开发
Spring JDBC模板封装手写SQL轻量级操作

1.3 MyBatis架构

┌─────────────────────────────────────────────┐
│              应用程序                         │
├─────────────────────────────────────────────┤
│          MyBatis API                        │
│  ┌─────────┐  ┌──────────┐  ┌───────────┐  │
│  │SqlSession│  │Mapper接口 │  │ 动态SQL   │  │
│  └─────────┘  └──────────┘  └───────────┘  │
├─────────────────────────────────────────────┤
│          核心层                              │
│  ┌─────────┐  ┌──────────┐  ┌───────────┐  │
│  │配置解析  │  │SQL执行   │  │ 结果映射   │  │
│  └─────────┘  └──────────┘  └───────────┘  │
├─────────────────────────────────────────────┤
│          基础层                              │
│  ┌─────────┐  ┌──────────┐  ┌───────────┐  │
│  │事务管理  │  │连接池    │  │ 缓存       │  │
│  └─────────┘  └──────────┘  └───────────┘  │
├─────────────────────────────────────────────┤
│          数据库(MySQL/PostgreSQL/...)       │
└─────────────────────────────────────────────┘

二、MyBatis快速入门

2.1 引入依赖

<!-- Spring Boot项目 -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.3.0</version>
</dependency>

2.2 配置文件

# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  # XML映射文件位置
  mapper-locations: classpath:mapper/**/*.xml
  # 实体类别名包
  type-aliases-package: com.example.entity
  configuration:
    # 开启驼峰命名自动映射
    map-underscore-to-camel-case: true
    # 开启日志
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
    # 开启延迟加载
    lazy-loading-enabled: true

2.3 实体类

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class User {
    private Long id;
    private String username;
    private String email;
    private Integer age;
    private Integer status;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

2.4 Mapper接口

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

@Mapper
public interface UserMapper {
    
    List<User> findAll();
    
    Optional<User> findById(Long id);
    
    int insert(User user);
    
    int update(User user);
    
    int deleteById(Long id);
}

2.5 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.mapper.UserMapper">
    
    <!-- 结果映射 -->
    <resultMap id="UserResultMap" type="User">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="email" column="email"/>
        <result property="age" column="age"/>
        <result property="status" column="status"/>
        <result property="createTime" column="create_time"/>
        <result property="updateTime" column="update_time"/>
    </resultMap>
    
    <!-- 查询所有 -->
    <select id="findAll" resultMap="UserResultMap">
        SELECT id, username, email, age, status, create_time, update_time
        FROM users
    </select>
    
    <!-- 根据ID查询 -->
    <select id="findById" parameterType="Long" resultMap="UserResultMap">
        SELECT id, username, email, age, status, create_time, update_time
        FROM users
        WHERE id = #{id}
    </select>
    
    <!-- 插入 -->
    <insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO users (username, email, age, status, create_time)
        VALUES (#{username}, #{email}, #{age}, #{status}, NOW())
    </insert>
    
    <!-- 更新 -->
    <update id="update" parameterType="User">
        UPDATE users
        SET username = #{username}, email = #{email}, age = #{age},
            update_time = NOW()
        WHERE id = #{id}
    </update>
    
    <!-- 删除 -->
    <delete id="deleteById" parameterType="Long">
        DELETE FROM users WHERE id = #{id}
    </delete>
    
</mapper>

2.6 #{} 与 ${} 的区别

语法说明安全性用途
#{ }预编译参数占位符✅ 安全(防SQL注入)传参(99%场景)
${ }字符串直接替换❌ 不安全动态列名、表名、ORDER BY
<!-- ✅ 安全:参数化查询 -->
<select id="findById" resultType="User">
    SELECT * FROM users WHERE id = #{id}
</select>

<!-- ⚠️ 动态列名场景必须用${},但要确保参数来源可信 -->
<select id="findByColumn" resultType="User">
    SELECT * FROM users ORDER BY ${columnName} ${order}
</select>

三、XML映射详解

3.1 查询结果映射

<!-- 1. 自动映射(列名与属性名一致,或开启驼峰映射) -->
<select id="findById" resultType="User">
    SELECT * FROM users WHERE id = #{id}
</select>

<!-- 2. 手动映射(推荐,明确映射关系) -->
<resultMap id="UserResultMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="user_name"/>
    <result property="createTime" column="create_time"/>
</resultMap>

<!-- 3. 关联查询映射(一对一) -->
<resultMap id="UserWithDeptMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <association property="department" javaType="Department">
        <id property="id" column="dept_id"/>
        <result property="name" column="dept_name"/>
    </association>
</resultMap>

<!-- 4. 集合映射(一对多) -->
<resultMap id="DeptWithUsersMap" type="Department">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <collection property="users" ofType="User">
        <id property="id" column="user_id"/>
        <result property="username" column="username"/>
    </collection>
</resultMap>

3.2 关联查询

<!-- 方式1:嵌套结果(一次查询,推荐) -->
<select id="findUserWithDept" resultMap="UserWithDeptMap">
    SELECT u.id, u.username, d.id AS dept_id, d.name AS dept_name
    FROM users u
    LEFT JOIN departments d ON u.dept_id = d.id
    WHERE u.id = #{id}
</select>

<!-- 方式2:嵌套查询(多次查询,N+1问题) -->
<resultMap id="UserWithDeptLazyMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <association property="department" javaType="Department"
                 select="findDeptById" column="dept_id"/>
</resultMap>

<select id="findDeptById" resultType="Department">
    SELECT * FROM departments WHERE id = #{id}
</select>

四、注解开发

对于简单SQL,可以直接使用注解替代XML:

@Mapper
public interface UserMapper {
    
    @Select("SELECT * FROM users WHERE id = #{id}")
    Optional<User> findById(Long id);
    
    @Select("SELECT * FROM users WHERE status = #{status}")
    List<User> findByStatus(Integer status);
    
    @Insert("INSERT INTO users (username, email, age) VALUES (#{username}, #{email}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);
    
    @Update("UPDATE users SET username = #{username}, email = #{email} WHERE id = #{id}")
    int update(User user);
    
    @Delete("DELETE FROM users WHERE id = #{id}")
    int deleteById(Long id);
    
    // 复杂SQL使用@SelectProvider
    @SelectProvider(type = UserSqlProvider.class, method = "findUsers")
    List<User> findUsers(UserQuery query);
}

// SQL构建器
public class UserSqlProvider {
    public String findUsers(UserQuery query) {
        return new SQL() {{
            SELECT("id, username, email, age");
            FROM("users");
            if (query.getUsername() != null) {
                WHERE("username LIKE CONCAT('%', #{username}, '%')");
            }
            if (query.getMinAge() != null) {
                WHERE("age >= #{minAge}");
            }
            ORDER_BY("create_time DESC");
        }}.toString();
    }
}

五、动态SQL

5.1 if 标签

<select id="findUsers" resultType="User">
    SELECT * FROM users
    WHERE 1 = 1
    <if test="username != null and username != ''">
        AND username LIKE CONCAT('%', #{username}, '%')
    </if>
    <if test="minAge != null">
        AND age >= #{minAge}
    </if>
    <if test="maxAge != null">
        AND age &lt;= #{maxAge}
    </if>
    <if test="status != null">
        AND status = #{status}
    </if>
</select>

5.2 where 标签

<!-- where标签自动去除多余的AND/OR -->
<select id="findUsers" resultType="User">
    SELECT * FROM users
    <where>
        <if test="username != null">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="minAge != null">
            AND age >= #{minAge}
        </if>
    </where>
</select>

5.3 choose/when/otherwise

<!-- 类似switch-case -->
<select id="findUsers" resultType="User">
    SELECT * FROM users
    <where>
        <choose>
            <when test="id != null">
                AND id = #{id}
            </when>
            <when test="username != null">
                AND username = #{username}
            </when>
            <otherwise>
                AND status = 1
            </otherwise>
        </choose>
    </where>
</select>

5.4 set 标签

<!-- set标签自动去除多余的逗号 -->
<update id="updateSelective" parameterType="User">
    UPDATE users
    <set>
        <if test="username != null">username = #{username},</if>
        <if test="email != null">email = #{email},</if>
        <if test="age != null">age = #{age},</if>
        <if test="status != null">status = #{status},</if>
        update_time = NOW()
    </set>
    WHERE id = #{id}
</update>

5.5 foreach 标签

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

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

<!-- 批量更新 -->
<foreach collection="list" item="user" separator=";">
    UPDATE users SET username = #{user.username} WHERE id = #{user.id}
</foreach>

5.6 sql片段

<!-- 定义可复用的SQL片段 -->
<sql id="userColumns">
    id, username, email, age, status, create_time, update_time
</sql>

<select id="findAll" resultType="User">
    SELECT <include refid="userColumns"/> FROM users
</select>

<select id="findById" resultType="User">
    SELECT <include refid="userColumns"/> FROM users WHERE id = #{id}
</select>

六、MyBatis-Plus入门

6.1 什么是MyBatis-Plus

MyBatis-Plus(简称MP) 是MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,简化开发、提升效率。

6.2 核心特性

特性说明
基础CRUD内置通用Mapper和Service,无需编写基础SQL
条件构造器强大的LambdaQueryWrapper
代码生成器根据数据库表自动生成Entity/Mapper/Service/Controller
分页插件内置分页插件,物理分页
逻辑删除支持逻辑删除,数据不物理移除
自动填充创建时间、更新时间等字段自动填充
乐观锁内置乐观锁插件

6.3 引入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.7</version>
</dependency>

6.4 实体类注解

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("users")   // 指定表名
public class User {
    
    @TableId(type = IdType.AUTO)   // 主键自增
    private Long id;
    
    private String username;
    
    private String email;
    
    private Integer age;
    
    @TableField("status")   // 指定列名
    private Integer status;
    
    @TableField(fill = FieldFill.INSERT)   // 插入时自动填充
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)   // 插入和更新时自动填充
    private LocalDateTime updateTime;
    
    @TableLogic   // 逻辑删除字段
    private Integer deleted;
    
    @Version   // 乐观锁版本号
    private Integer version;
}

6.5 Mapper接口

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
    // 继承BaseMapper即可获得基础CRUD能力,无需编写任何SQL
}

6.6 基础CRUD操作

@Service
@RequiredArgsConstructor
public class UserServiceImpl {
    
    private final UserMapper userMapper;
    
    // ========== 查询 ==========
    
    // 根据ID查询
    public User getById(Long id) {
        return userMapper.selectById(id);
    }
    
    // 查询所有
    public List<User> list() {
        return userMapper.selectList(null);
    }
    
    // 条件查询
    public List<User> findActiveUsers() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getStatus, 1)
               .ge(User::getAge, 18)
               .orderByDesc(User::getCreateTime);
        return userMapper.selectList(wrapper);
    }
    
    // 统计
    public long countByAge(Integer age) {
        return userMapper.selectCount(
            new LambdaQueryWrapper<User>().ge(User::getAge, age)
        );
    }
    
    // ========== 新增 ==========
    
    public void insert(User user) {
        userMapper.insert(user);
    }
    
    // ========== 更新 ==========
    
    public void updateById(User user) {
        userMapper.updateById(user);
    }
    
    // 条件更新
    public void updateStatus(Long id, Integer status) {
        LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(User::getId, id)
               .set(User::getStatus, status);
        userMapper.update(null, wrapper);
    }
    
    // ========== 删除 ==========
    
    public void deleteById(Long id) {
        userMapper.deleteById(id);   // 逻辑删除:UPDATE users SET deleted=1 WHERE id=?
    }
}

七、MyBatis-Plus进阶

7.1 LambdaQueryWrapper详解

// 查询条件构建
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();

// 等值查询
wrapper.eq(User::getStatus, 1);

// 不等查询
wrapper.ne(User::getStatus, 0);

// 模糊查询
wrapper.like(User::getUsername, "张");
wrapper.likeLeft(User::getUsername, "张");   // %张
wrapper.likeRight(User::getUsername, "张");  // 张%

// 范围查询
wrapper.between(User::getAge, 18, 30);
wrapper.in(User::getStatus, Arrays.asList(1, 2, 3));
wrapper.notIn(User::getStatus, Arrays.asList(0, -1));

// NULL判断
wrapper.isNull(User::getEmail);
wrapper.isNotNull(User::getEmail);

// 排序
wrapper.orderByAsc(User::getAge);
wrapper.orderByDesc(User::getCreateTime);

// 只查询部分字段
wrapper.select(User::getId, User::getUsername, User::getEmail);

// 分组
wrapper.groupBy(User::getStatus);

// HAVING
wrapper.having("count(*) > {0}", 5);

// OR条件
wrapper.eq(User::getStatus, 1)
       .or()
       .eq(User::getStatus, 2);

// 嵌套条件
wrapper.and(w -> w.eq(User::getAge, 18).or().eq(User::getAge, 20));

// 链式调用
List<User> users = userMapper.selectList(wrapper);

7.2 LambdaUpdateWrapper

LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(User::getStatus, 0)
             .set(User::getStatus, 1)
             .set(User::getUpdateTime, LocalDateTime.now());

userMapper.update(null, updateWrapper);

7.3 条件拼接工具类

/**
 * 条件拼接,避免大量if判断
 */
public LambdaQueryWrapper<User> buildQuery(UserQuery query) {
    return new LambdaQueryWrapper<User>()
        .like(StringUtils.hasText(query.getUsername()), 
              User::getUsername, query.getUsername())
        .eq(query.getStatus() != null, 
            User::getStatus, query.getStatus())
        .ge(query.getMinAge() != null, 
            User::getAge, query.getMinAge())
        .le(query.getMaxAge() != null, 
            User::getAge, query.getMaxAge())
        .orderByDesc(User::getCreateTime);
}

7.4 自动填充

@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());
    }
}

7.5 逻辑删除配置

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted   # 逻辑删除字段
      logic-delete-value: 1         # 已删除的值
      logic-not-delete-value: 0     # 未删除的值
// 执行删除时,实际执行:UPDATE users SET deleted=1 WHERE id=?
userMapper.deleteById(1L);

// 查询时自动过滤已删除数据:SELECT * FROM users WHERE deleted=0
userMapper.selectList(null);

7.6 乐观锁

// 1. 注册乐观锁插件
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

// 2. 使用乐观锁
public void updateWithLock(User user) {
    // 先查询获取当前版本号
    User current = userMapper.selectById(user.getId());
    user.setVersion(current.getVersion());
    
    // 更新时自动带上版本条件
    // UPDATE users SET username=?, version=version+1 WHERE id=? AND version=?
    userMapper.updateById(user);
}

八、代码生成器

8.1 引入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.7</version>
</dependency>
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.32</version>
</dependency>

8.2 代码生成器使用

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Map;

public class CodeGenerator {
    
    public static void main(String[] args) {
        String projectPath = System.getProperty("user.dir");
        
        FastAutoGenerator.create(
                "jdbc:mysql://localhost:3306/testdb", "root", "123456")
            
            // 全局配置
            .globalConfig(builder -> {
                builder.author("yourname")
                       .outputDir(projectPath + "/src/main/java")
                       .disableOpenDir()     // 生成后不打开目录
                       .enableSwagger();      // 启用Swagger注解
            })
            
            // 包配置
            .packageConfig(builder -> {
                builder.parent("com.example")
                       .moduleName("demo")
                       .entity("entity")
                       .mapper("mapper")
                       .service("service")
                       .serviceImpl("service.impl")
                       .controller("controller")
                       .pathInfo(Map.of(
                           OutputFile.xml, projectPath + "/src/main/resources/mapper"
                       ));
            })
            
            // 策略配置
            .strategyConfig(builder -> {
                builder.addInclude("users", "orders", "products")  // 要生成的表
                       .addTablePrefix("t_", "sys_")                // 表前缀
                       
                       // Entity策略
                       .entityBuilder()
                       .enableLombok()
                       .enableTableFieldAnnotation()
                       .logicDeleteColumnName("deleted")
                       
                       // Mapper策略
                       .mapperBuilder()
                       .enableMapperAnnotation()
                       
                       // Service策略
                       .serviceBuilder()
                       .formatServiceFileName("%sService")
                       
                       // Controller策略
                       .controllerBuilder()
                       .enableRestStyle();
            })
            
            // 使用Freemarker模板引擎
            .templateEngine(new FreemarkerTemplateEngine())
            
            // 执行生成
            .execute();
    }
}

8.3 生成的文件结构

src/main/java/com/example/demo/
├── entity/
│   └── User.java
├── mapper/
│   └── UserMapper.java
├── service/
│   ├── UserService.java
│   └── impl/
│       └── UserServiceImpl.java
└── controller/
    └── UserController.java

src/main/resources/mapper/
└── UserMapper.xml

九、分页插件

9.1 配置分页插件

@Configuration
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 添加分页插件
        PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        paginationInterceptor.setMaxLimit(500L);   // 单页最大500条
        interceptor.addInnerInterceptor(paginationInterceptor);
        
        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        
        return interceptor;
    }
}

9.2 分页查询

@Service
@RequiredArgsConstructor
public class UserServiceImpl {
    
    private final UserMapper userMapper;
    
    /**
     * 简单分页
     */
    public IPage<User> page(int pageNum, int pageSize) {
        Page<User> page = new Page<>(pageNum, pageSize);
        return userMapper.selectPage(page, null);
    }
    
    /**
     * 条件分页
     */
    public IPage<User> pageWithCondition(int pageNum, int pageSize, UserQuery query) {
        Page<User> page = new Page<>(pageNum, pageSize);
        
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
            .like(StringUtils.hasText(query.getUsername()), User::getUsername, query.getUsername())
            .eq(query.getStatus() != null, User::getStatus, query.getStatus())
            .orderByDesc(User::getCreateTime);
        
        return userMapper.selectPage(page, wrapper);
    }
}

9.3 分页结果对象

// IPage<User> 包含以下信息:
IPage<User> result = userService.page(1, 10);

result.getRecords();    // 当前页数据列表
result.getTotal();      // 总记录数
result.getPages();      // 总页数
result.getCurrent();    // 当前页码
result.getSize();       // 每页大小
result.hasNext();       // 是否有下一页
result.hasPrevious();   // 是否有上一页

9.4 自定义SQL分页

// Mapper接口
@Mapper
public interface UserMapper extends BaseMapper<User> {
    
    // 自定义SQL分页(Page参数必须在第一个位置)
    IPage<User> selectUserPage(Page<User> page, @Param("query") UserQuery query);
}
<!-- UserMapper.xml -->
<select id="selectUserPage" resultType="User">
    SELECT u.*, d.name AS dept_name
    FROM users u
    LEFT JOIN departments d ON u.dept_id = d.id
    <where>
        <if test="query.username != null">
            AND u.username LIKE CONCAT('%', #{query.username}, '%')
        </if>
        <if test="query.status != null">
            AND u.status = #{query.status}
        </if>
    </where>
    ORDER BY u.create_time DESC
</select>

十、常见面试题解析

Q1:MyBatis的#{ }和${}有什么区别?

  • #{}:预编译处理,会将参数替换为 ?,然后通过 PreparedStatement 设置参数值,能防止SQL注入
  • ${}:字符串替换,直接将参数值拼接到SQL中,存在SQL注入风险

一般参数值使用 #{},动态表名、列名、ORDER BY等只能使用 ${}

Q2:MyBatis的一级缓存和二级缓存?

级别作用域默认状态生命周期
一级缓存SqlSession开启SqlSession创建到关闭
二级缓存Mapper(namespace)关闭应用级别

一级缓存在同一个SqlSession中相同查询会命中缓存;二级缓存需要手动开启,跨SqlSession共享。

Q3:MyBatis-Plus和MyBatis的关系?

MyBatis-Plus是MyBatis的增强工具,不是替代品:

  • 完全兼容MyBatis的所有功能
  • 新增了BaseMapper、条件构造器、代码生成器、分页插件等
  • 简单CRUD用MP的内置方法,复杂SQL仍使用MyBatis的XML编写

Q4:如何避免MyBatis的N+1查询问题?

<!-- 1. 使用嵌套结果(JOIN查询)替代嵌套查询 -->
<resultMap id="UserWithDeptMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <association property="dept" javaType="Dept">
        <id property="id" column="dept_id"/>
        <result property="name" column="dept_name"/>
    </association>
</resultMap>

<select id="findUserWithDept" resultMap="UserWithDeptMap">
    SELECT u.*, d.id AS dept_id, d.name AS dept_name
    FROM users u LEFT JOIN departments d ON u.dept_id = d.id
</select>

<!-- 2. 使用批量查询 -->
<!-- 3. 使用MyBatis-Plus的batch模式 -->

十一、总结与下篇预告

本篇要点回顾

主题核心内容
MyBatis基础配置、Mapper接口、XML映射、#{}与${}
XML映射结果映射、关联查询、嵌套结果/查询
注解开发@Select/@Insert/@Update/@Delete、Provider
动态SQLif/where/set/choose/foreach/sql
MyBatis-PlusBaseMapper、条件构造器、自动填充、逻辑删除
代码生成器FastAutoGenerator、模板引擎
分页插件配置、IPage、自定义SQL分页

💬 互动问题

  1. 你在项目中使用MyBatis还是MyBatis-Plus?有什么使用心得?
  2. 复杂的动态SQL你是怎么编写和调试的?
  3. 遇到过哪些MyBatis的坑?

📢 下篇预告

下一篇:【38 Spring Boot入门】——我们将进入Spring Boot的世界,学习自动配置原理、核心注解、配置文件、多环境切换、Starter机制等核心知识!


参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值