MyBatis整体架构理解

1、 mybatis配置

SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。

mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。

2、 通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂

3、 由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。

4、 mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。

5、 Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。

6、 Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。

Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。

#{}和${}的区别是什么?

在 MyBatis 中,#{} 和 ${} 都是用来占位的语法,但它们的作用范围和使用方式有所不同。

#{} 占位符:用于绑定参数值到 SQL 语句中的占位符。它可以绑定基本类型、Java 对象、Map 等数据类型。例如:

SELECT * FROM users WHERE username = #{username} AND password = #{password}

${} 占位符:用于绑定常量值到 SQL 语句中的占位符。它只能绑定常量值,不能绑定动态数据。例如:

SELECT * FROM users WHERE age = ${age} AND gender = #{gender}

#{}一般被认为更安全,因为它可以防止 SQL 注入等安全问题。在处理可能来自外部输入的值时,使用#{}可以进行更严格的控制和验证

${}直接进行字符串替换,如果替换的值来自不可信的来源,可能会导致安全漏洞。

Mybatis动态SQL如何实现

  1. 使用if、choose、when、otherwise等标签进行条件判断和分支处理,例如:

<select id="selectUser" resultType="User">
  select * from user
  <where>
    <if test="username != null">
      and username like #{username}%
    </if>
  </where>
</select>
  1. 使用foreach标签遍历集合或数组,并根据元素的属性值动态拼接SQL语句,例如:

<select id="selectUserByIdList" resultType="User">
  select * from user where id in
  <foreach collection="idList" item="id" open="(" close=")">
    #{id}
  </foreach>
</select>

在第二种方式中,需要注意以下几点:

  • foreach标签中的item属性表示当前循环迭代的对象,open属性表示开始拼接字符串时使用的符号,close属性表示结束拼接字符串时使用的符号。

  • 如果遍历的集合或数组比较大,建议将其中的属性值转换为Java对象的属性名,以提高SQL语句的可读性和性能。

MyBatis 中实体类中的属性名和表中的字段名不一样 ,怎么办 ?

在 MyBatis 中,实体类中的属性名和表中的字段名不一致时,可以使用 @Param 注解来指定参数名称

public class User {
    @Id
    private Long id;
    
    @Column(name = "user_name")
    private String userName;
    
    @Column(name = "age")
    private Integer age;
    
    // ... 其他属性
}

public interface UserMapper {
    @Select("SELECT * FROM users WHERE user_name = #{userName} AND age = #{age}")
    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
    int getUserByNameAndAge(@Param("userName") String userName, @Param("age") Integer age);
}

在上面的例子中,@Column 注解中的 name 属性与实体类中的属性名不同,但我们可以通过指定 @Param 注解的参数名称来解决这个问题。这样,MyBatis 就能够正确地将参数绑定到 SQL 语句中的占位符上。

Mybatis映射文件中用到哪些标签?

select : 查询标签
update : 更新标签
delete : 删除标签
insert : 插入标签
where : 类似于SQL语句的where
if : 动态SQL对条件进行判断
foreach : 遍历循环 , 拼接SQL , 一般用于批量处理
set : 类似于SQL语句中 的set关键词
resultMap : 结果集映射标签

项目中为什么使用MyBatisPlus?

项目中使用 MyBatis-Plus 的主要原因是其简化了常见的 CRUD 操作,减少了重复代码,提高了开发效率,并且提供了动态查询、分页、自动生成代码等增强功能。同时,它完全兼容 MyBatis,集成简单,且具备丰富的插件和良好的社区支持,使得它成为提升开发效率和代码质量的一个很好的选择。

在项目中使用 MyBatis-Plus 的原因可以从以下几个方面来回答:

1. 简化 CRUD 操作

MyBatis-Plus 提供了大量的内置方法,简化了常见的 CRUD(增删改查)操作。开发者不需要编写繁琐的 SQL 或手动编写 XML 配置文件,只需通过继承 BaseMapper 接口,即可获得多种常见操作的方法,如 insert、update、delete、selectById 等。

示例:

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

通过继承 BaseMapper<User>,可以直接使用 CRUD 方法。

2. 减少重复代码,提高开发效率

MyBatis-Plus 提供了大量内置功能,如自动分页、乐观锁、逻辑删除、字段自动填充等。使用这些功能可以减少大量重复的代码,提升开发效率。

分页查询示例:

Page<User> page = new Page<>(1, 10);  // 分页参数
IPage<User> userPage = userMapper.selectPage(page, null);

3. 增强的动态查询功能

MyBatis-Plus 提供了 Wrapper 对象,用于构建动态查询条件。相比传统的 MyBatis 需要手动拼接 SQL,MyBatis-Plus 的 Wrapper 能更灵活地构建复杂查询条件。

示例:

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name", "John").eq("age", 25);
List<User> users = userMapper.selectList(queryWrapper);

4. 支持自动生成代码

MyBatis-Plus 提供了代码生成器(Code Generator),可以根据数据库表结构自动生成基础的 Mapper、Service 和 Controller 类,从而减少手工创建类的工作量。

示例:
使用代码生成器,可以一键生成基础的 CRUD 操作代码:

AutoGenerator autoGenerator = new AutoGenerator();
// 设置数据源、模板、全局配置等
autoGenerator.execute();

5. 集成简单,兼容 MyBatis

MyBatis-Plus 是对 MyBatis 的增强和扩展,完全兼容 MyBatis 原生功能。如果项目中已经使用了 MyBatis,MyBatis-Plus 可以无缝集成,不需要对现有的代码和配置进行大量修改。

6. 性能优化

MyBatis-Plus 针对一些常见的查询场景做了性能优化,例如批量插入优化,避免每次插入都发送单独的 SQL 语句,提升了数据库操作的效率。

7. 丰富的插件支持

MyBatis-Plus 提供了丰富的插件支持,如分页插件、SQL 性能分析插件、乐观锁插件等,便于项目中快速引入所需的功能,并保证代码的简洁性和可维护性。

8. 社区支持和文档完善

MyBatis-Plus 拥有良好的社区支持和详细的文档,使得开发者可以方便地查阅相关资料,快速解决问题,减少学习成本。

Mybatis都有哪些Executor执行器?它们之间的区别是什么?

SimpleExecutor是最简单的执行器,根据对应的sql直接执行即可,不会做一些额外的操作;

BatchExecutor执行器,顾名思义,通过批量操作来优化性能。通常需要注意的是批量更新操作,由于内部有缓存的实现,使用完成后记得调用flushStatements来清除缓存。

ReuseExecutor 可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能。内部的实现是通过一个HashMap来维护Statement对象的。由于当前Map只在该session中有效,所以使用完成后记得调用flushStatements来清除Map。

谈一谈你对Mybatis缓存机制的了解

MyBatis 缓存机制分为一级缓存和二级缓存,可减轻数据库压力,提高数据库性能。

一、一级缓存

  1. 缓存范围:一级缓存为 SqlSession 缓存,缓存的数据只在 SqlSession 内有效。

  2. 存储结构:在 SqlSession 会话对象中有一个 HashMap 用于存储缓存数据,此 HashMap 是当前会话对象私有的,别的 SqlSession 会话对象无法访问。

  3. 具体流程:

    • 第一次执行 select 完毕会将查到的数据写入 SqlSession 内的 HashMap 中缓存起来。

    • 第二次执行 select 会从缓存中查数据,如果 select 同传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率。

  4. 注意事项:

    • 如果 SqlSession 执行了 DML 操作(insert、update、delete),并 commit 了,那么 MyBatis 就会清空当前 SqlSession 缓存中的所有缓存数据,以保证缓存中的数据与数据库一致。

    • 当一个 SqlSession 结束后,它里面的一级缓存也就不存在了。MyBatis 默认是开启一级缓存,不需要配置。

    • MyBatis 的缓存是基于 [namespace:sql 语句:参数] 来进行缓存的,即 SqlSession 的 HashMap 存储缓存数据时,是使用 [namespace:sql:参数] 作为 key,查询返回的语句作为 value 保存的。

二、二级缓存

  1. 缓存范围:二级缓存是 mapper 级别的缓存,也就是同一个 namespace 的 mapper.xml,当多个 SqlSession 使用同一个 Mapper 操作数据库的时候,得到的数据会缓存在同一个二级缓存区域。

  2. 开启步骤:

    • 在 conf.xml 配置全局变量开启二级缓存。

   <settings>
       <setting name="cacheEnabled" value="true"/>默认是 false:关闭二级缓存
  </settings>
  • 在 userMapper.xml 中配置当前 mapper 下所有语句开启二级缓存,这里配置了一个 LRU 缓存,并每隔 60 秒刷新,最大存储 512 个对象,而返回的对象是只读的。若想禁用当前 select 语句的二级缓存,添加 useCache=“false”。

<select id="getCountByName" parameterType="java.util.Map" resultType="INTEGER" statementType="CALLABLE" useCache="false">
  1. 具体流程:

    • 当一个 sqlseesion 执行了一次 select 后,在关闭此 session 的时候,会将查询结果缓存到二级缓存。

    • 当另一个 sqlsession 执行 select 时,首先会在他自己的一级缓存中找,如果没找到,就去二级缓存中找,找到了就返回,就不用去数据库了,从而减少了数据库压力提高了性能。

  2. 注意事项:

    • 如果 SqlSession 执行了 DML 操作(insert、update、delete),并 commit 了,那么 MyBatis 就会清空当前 mapper 缓存中的所有缓存数据,以保证缓存中的数据与数据库一致。

    • MyBatis 的缓存是基于 [namespace:sql 语句:参数] 来进行缓存的,即 SqlSession 的 HashMap 存储缓存数据时,是使用 [namespace:sql:参数] 作为 key,查询返回的语句作为 value 保存的。

MyBatis-Plus注解有哪些?

以下是这些注解在常见的Java开发框架(如MyBatis-Plus)中的扩展具体使用示例及说明:

1. @Select

  • 示例
    假设我们有一个UserMapper接口,要查询所有用户信息。

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.Select;

public interface UserMapper extends BaseMapper<User> {

    @Select("SELECT * FROM user")
    List<User> selectAllUsers();
}
  • 说明
    这里通过@Select注解直接在接口方法上定义了SQL查询语句,当调用selectAllUsers方法时,就会执行该注解中定义的SELECT * FROM user查询,从数据库中获取所有用户记录,并将结果映射为User实体对象列表返回。

2. @Update

  • 示例
    比如要更新用户的年龄信息。

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.Update;

public interface UserMapper extends BaseMapper<User> {

    @Update("UPDATE user SET age = #{age} WHERE id = #{id}")
    int updateUserAgeById(@Param("id") Long id, @Param("age") Integer age);
}
  • 说明
    @Update注解的SQL语句中,通过#{}占位符来接收方法参数的值。当调用updateUserAgeById方法时,传入的idage参数会替换到SQL语句相应的位置,然后执行更新操作,返回受影响的行数。

3. @Insert

  • 示例
    插入一条新用户记录。

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.Insert;

public interface UserMapper extends BaseMapper<User> {

    @Insert("INSERT INTO user (name, age) VALUES (#{name}, #{age})")
    int insertUser(@Param("name") String name, @Param("age") Integer age);
}
  • 说明
    类似@Update@Insert注解的SQL语句也使用#{}接收方法传入的参数值。执行insertUser方法时,会将传入的nameage参数值插入到数据库的user表相应字段中,返回插入操作影响的行数(通常为1,表示成功插入一条记录)。

4. @Delete

  • 示例
    删除指定ID的用户记录。

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.Delete;

public interface UserMapper extends BaseMapper<User> {

    @Delete("DELETE FROM user WHERE id = #{id}")
    int deleteUserById(@Param("id") Long id);
}
  • 说明
    @Delete注解中的SQL语句通过#{id}接收方法传入的要删除用户的ID值。调用deleteUserById方法时,就会根据传入的ID执行删除操作,返回受影响的行数(如果成功删除一条记录,返回值为1)。

5. @BatchInsert

  • 示例
    假设要批量插入多条用户记录。

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.BatchInsert;

public interface UserMapper extends BaseMapper<User> {

    @BatchInsert("INSERT INTO user (name, age) VALUES ")
    int batchInsertUsers(List<User> userList);
}

这里可能需要在实际框架实现中进一步处理,比如将userList中的每个用户对象的nameage值拼接成完整的批量插入SQL语句形式(如(#{userList[0].name}, #{userList[0].age}), (#{userList[1].name}, #{userList[1].age})...),不同框架处理方式可能略有差异。

  • 说明
    @BatchInsert用于高效地批量插入数据。通常需要将待插入的多条记录以某种数据结构(如这里的List<User>)传递给方法,然后框架会根据注解中的基础SQL语句和传入的数据进行处理,一次性将多条记录插入到数据库中,相比逐个插入能提高插入效率。

6. @TableField

  • 示例
    假设User实体类中有一个字段userName,但数据库表中的字段名为name

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("user")
public class User {

    @TableField("name")
    private String userName;

    // 其他字段省略
}
  • 说明
    通过@TableField注解指定了实体类中的userName字段与数据库表中的name字段相对应,这样在进行数据查询、插入、更新等操作时,框架就能正确地将实体类中的数据映射到数据库表中的相应字段,以及将数据库表中的数据映射回实体类。

7. @TableName

  • 示例

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("user")
public class User {

    // 字段省略
}
  • 说明
    明确指定了该实体类对应的数据库查询表名是user。当执行与该实体类相关的数据库操作(如查询、插入、更新、删除等)时,框架就知道要操作的是哪个数据库表。

8. @Version

  • 示例
    假设在User实体类中添加版本号字段。

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;

@Data
@TableName("user")
public class User {

    // 其他字段省略
    @Version
    private Integer version;
}
  • 说明
    @Version注解用于添加版本号字段。在并发操作场景下,比如多个线程同时对同一条记录进行更新操作时,可以利用版本号机制来实现乐观锁。每次更新操作前会先检查版本号是否与数据库中的一致,如果一致则允许更新,并同时更新版本号(通常是自增),否则更新操作失败。

9. @LogicDelete

  • 示例
    User实体类中标记逻辑删除字段。

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.LogicDelete;
import lombok.Data;

@Data
@TableName("user")
public class User {

    // 其他字段省略
    @LogicDelete
    private Boolean isDeleted;
}
  • 说明
    @LogicDelete注解用于标记逻辑删除字段。当执行删除操作时,并不是真正从数据库中物理删除记录,而是通过更新该逻辑删除字段的值(比如将isDeletedfalse更新为true)来表示该记录已被删除。在后续的查询等操作中,可以根据该字段的值来过滤掉已被标记为删除的记录,实现逻辑上的删除效果。

10. @TableId

  • 示例
    指定User实体类中的id字段为自动生成主键。

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("user")
public class User {

    @TableId
    private Long id;

    // 其他字段省略
}
  • 说明
    @TableId注解用于指定实体类中的某个字段作为自动生成主键。不同的数据库有不同的主键自动生成策略(如MySQL的自增主键、UUID等),框架会根据配置和数据库的特性来自动处理主键的生成,确保在插入新记录时能正确生成唯一的主键值。

简单描述一下mybatis工作原理?

MyBatis 是一个流行的持久层框架,用于简化 Java 应用程序与数据库之间的交互。以下是 MyBatis 的工作原理的简单描述:

  1. 配置文件
    MyBatis 使用 XML 或注解配置文件来定义数据库连接信息、SQL 语句和映射关系。主要配置文件包括:

  • mybatis-config.xml:用于配置全局属性(如数据库连接、类型处理器等)。

  • Mapper XML 文件:定义具体的 SQL 语句和结果映射。

  1. SqlSessionFactory
    在应用程序启动时,MyBatis 创建一个 SqlSessionFactory 对象。该对象负责创建 SqlSession,它是与数据库交互的主要接口。SqlSessionFactory 从配置文件中读取配置并加载必要的 SQL 语句。

  2. SqlSession
    SqlSession 是 MyBatis 的核心对象,提供了执行 SQL 语句的方法,如 select, insert, update, 和 delete。通过 SqlSession,可以访问映射器接口,并执行相应的 SQL 操作。

  3. Mapper 接口
    MyBatis 通过 Mapper 接口定义数据访问的方法。每个方法对应一个 SQL 语句,MyBatis 会根据方法名和参数自动映射到相应的 SQL 语句。

  4. SQL 映射
    当调用 SqlSession 的方法时,MyBatis 查找与该方法相关的 SQL 语句,并执行它。SQL 语句的参数会通过预处理(使用 JDBC)来传递,结果会被映射到 Java 对象。

  5. 结果映射
    MyBatis 使用结果映射将 SQL 查询的结果转换为 Java 对象。通过 XML 或注解定义的映射关系,MyBatis 确定如何将结果集中的列与 Java 对象的属性匹配。

  6. 事务管理
    MyBatis 提供了对事务的支持,可以通过 SqlSession 进行手动提交或回滚。也可以与 Spring 框架集成,以使用 Spring 的声明式事务管理。

简单流程概述

  1. 应用程序请求数据操作。

  2. 通过 SqlSessionFactory 创建 SqlSession

  3. 调用 Mapper 接口中的方法,MyBatis 查找相应的 SQL 语句。

  4. 执行 SQL 语句,将参数传递给数据库。

  5. 获取结果并进行结果映射。

  6. 返回结果给应用程序。

通过以上步骤,MyBatis 提供了一种灵活且高效的方式来处理数据库操作,使得开发者能够专注于业务逻辑,而不必处理复杂的 JDBC 代码。

是否使用过Mybatis的拦截器,有什么作用?

MyBatis 拦截器的作用

MyBatis 拦截器(Interceptor)可以用来拦截和增强 MyBatis 在执行 SQL 语句时的行为,通常用于 SQL 语句修改、参数调整、日志记录、分页优化等。

1. MyBatis 拦截器的核心作用

MyBatis 提供了四种拦截点,你可以针对 SQL 执行的不同阶段进行拦截:

拦截器作用点

拦截对象

主要用途

拦截 SQL 语句

StatementHandler

SQL 语句改造(如增加 WHERE 条件、修改 LIMIT 等)

拦截参数处理

ParameterHandler

修改 SQL 语句的入参(如自动填充 tenant_id,实现多租户)

拦截结果集处理

ResultSetHandler

处理查询返回结果(如脱敏、数据转换)

拦截数据库执行流程

Executor

事务管理、SQL 缓存、批量更新优化

2. 具体应用场景

① SQL 语句拦截与修改

拦截 StatementHandler.prepare() 方法,修改 SQL 语句,如:

  • 自动追加查询条件(如 WHERE status = 1,用于逻辑删除)

  • SQL 语句解析与改写(如 LIMIT 优化)

  • 防止 SELECT * 查询

示例:

@Intercepts({
    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class SqlModifyInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler handler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        
        // 追加 WHERE 条件
        String modifiedSql = sql + " WHERE status = 1";
        
        ReflectUtil.setFieldValue(boundSql, "sql", modifiedSql);
        return invocation.proceed();
    }
}

② 动态 SQL 参数处理

拦截 ParameterHandler.setParameters() 方法,可用于:

  • 动态多租户:拦截参数,自动填充 tenant_id

  • 数据权限控制:根据用户身份调整参数

示例:

@Intercepts({
    @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})
})
public class TenantInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];

        // 假设参数是 Map,手动添加租户 ID
        Map<String, Object> params = (Map<String, Object>) parameterHandler.getParameterObject();
        params.put("tenantId", getCurrentTenantId());

        return invocation.proceed();
    }
}

③ 查询结果拦截(脱敏、数据转换)

拦截 ResultSetHandler.handleResultSets() 方法,可用于:

  • 数据脱敏(如隐藏手机号、身份证号)

  • 自动转换数据格式(如数据库 timestamp 转换为 LocalDateTime

示例:

@Intercepts({
    @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class DataMaskingInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = invocation.proceed();
        
        if (result instanceof List) {
            for (Object obj : (List<?>) result) {
                if (obj instanceof User) {
                    User user = (User) obj;
                    user.setPhone(maskPhone(user.getPhone())); // 数据脱敏
                }
            }
        }
        return result;
    }
}

④ 拦截 MyBatis 执行流程(事务优化、批量更新)

拦截 Executor.update()Executor.query() 方法,可用于:

  • 动态开启/关闭缓存

  • 事务优化

  • 批量 SQL 处理

示例:

@Intercepts({
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class BatchUpdateInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Executor executor = (Executor) invocation.getTarget();
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];

        if (ms.getId().contains("batch")) {
            // 处理批量更新逻辑
        }

        return invocation.proceed();
    }
}

3. 拦截器的注册方式

Spring Boot 配置 MyBatis 拦截器

@Configuration
public class MyBatisConfig {
    @Bean
    public SqlModifyInterceptor sqlModifyInterceptor() {
        return new SqlModifyInterceptor();
    }
}

4. 总结

拦截器类型

适用方法

作用

SQL 语句拦截

StatementHandler.prepare()

修改 SQL 语句,如动态添加 WHERE 条件

参数处理拦截

ParameterHandler.setParameters()

修改 SQL 参数,如自动填充 tenant_id

查询结果拦截

ResultSetHandler.handleResultSets()

处理查询返回值,如脱敏、数据转换

执行流程拦截

Executor.update() / Executor.query()

事务控制、缓存优化、批量操作


青い空