行业资讯 2025年08月6日
0 收藏 0 点赞 552 浏览 6164 个字
摘要 :

文章目录 一、MyBatis插件运行原理与编写方法 (一)插件运行原理 (二)如何编写一个插件 二、MyBatis延迟加载 (一)是否支持延迟加载 (二)配置方式 (三)延迟……




  • 一、MyBatis插件运行原理编写方法
    • (一)插件运行原理
    • (二)如何编写一个插件
  • 二、MyBatis延迟加载
    • (一)是否支持延迟加载
    • (二)配置方式
    • (三)延迟加载原理
  • 三、MyBatis缓存:一级缓存与二级缓存
    • (一)一级缓存
    • (二)二级缓存
    • (三)对比分析
  • 四、MyBatis接口绑定:原理与示例
    • (一)接口绑定原理
    • (二)示例
    • (三)源码分析

    MyBatis是一款非常受欢迎的持久层框架,今天咱们就深入探讨下MyBatis里几个关键特性的原理,包括插件运行原理、延迟加载原理、一级缓存与二级缓存原理,还有接口绑定原理,顺便也讲讲怎么编写MyBatis插件。

    一、MyBatis插件运行原理与编写方法

    (一)插件运行原理

    MyBatis的插件机制很巧妙,它是基于拦截器(Interceptor)来实现的,利用动态代理对核心组件进行拦截。通过这个机制,开发者能在特定的执行点,比如执行器(Executor)、语句处理器(StatementHandler)、参数处理器(ParameterHandler)、结果处理器(ResultSetHandler)这些地方,插入自己定义的逻辑。而且,插件的运行还依赖于MyBatis的责任链模式。

    MyBatis提供了四种可以拦截的核心对象:

    • Executor(执行器):主要负责SQL语句的执行,同时还管理着缓存。
    • StatementHandler(语句处理器):它的任务是对SQL语句进行预编译,然后执行这些语句。
    • ParameterHandler(参数处理器):负责给SQL语句设置参数。
    • ResultSetHandler(结果处理器):将查询结果进行映射处理。

    MyBatis插件的运行流程大概是这样的:

    1. 在MyBatis初始化的时候,会通过Configuration加载插件。
    2. 插件会通过动态代理的方式,把目标对象包装起来。
    3. 当目标方法执行的时候,就会调用插件的intercept方法,这时候咱们自定义的逻辑就能派上用场了。

    (二)如何编写一个插件

    编写MyBatis插件,需要实现Interceptor接口,并且用注解指定拦截的目标。下面是一个简单的分页插件示例:

    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.plugin.*;
    import java.sql.Connection;
    import java.util.Properties;
    
    // 使用@Intercepts和@Signature注解指定拦截的对象和方法
    @Intercepts({
        @Signature(type = StatementHandler.class, method = \"prepare\", args = {Connection.class, Integer.class})
    })
    public class SimplePagePlugin implements Interceptor {
        // 定义每页大小和当前页码
        private int pageSize; 
        private int pageNum;  
    
        // 实现intercept方法,编写拦截逻辑
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            // 获取被代理的StatementHandler对象
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            // 获取原始SQL语句
            String sql = statementHandler.getBoundSql().getSql();
            // 修改SQL,添加分页逻辑
            String pageSql = sql + \" LIMIT \" + (pageNum - 1) * pageSize + \", \" + pageSize;
            // 通过反射修改SQL
            Field field = statementHandler.getBoundSql().getClass().getDeclaredField(\"sql\");
            field.setAccessible(true);
            field.set(statementHandler.getBoundSql(), pageSql);
            // 继续执行原方法
            return invocation.proceed();
        }
    
        // 决定是否包装目标对象,只有符合拦截条件的对象才会被代理
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
    
        // 从配置中获取参数
        @Override
        public void setProperties(Properties properties) {
            this.pageSize = Integer.parseInt(properties.getProperty(\"pageSize\", \"10\"));
            this.pageNum = Integer.parseInt(properties.getProperty(\"pageNum\", \"1\"));
        }
    }
    

    写好插件后,还得在mybatis-config.xml文件里注册插件:

    <plugins>
        <plugin interceptor=\"com.example.SimplePagePlugin\">
            <property name=\"pageSize\" value=\"5\"/>
            <property name=\"pageNum\" value=\"1\"/>
        </plugin>
    </plugins>
    

    这里简单分析下代码原理:

    • @Intercepts@Signature用来明确指定要拦截的对象和方法。
    • intercept方法里写的就是具体的拦截逻辑,invocation.proceed()表示调用原始方法。
    • plugin方法决定是否对目标对象进行包装,用Plugin.wrap生成代理。
    • setProperties方法用来接收配置文件里的参数。

    总的来说,MyBatis插件通过动态代理和责任链实现功能扩展,编写插件时要清楚拦截点,实现Interceptor接口,像分页、日志这些功能都能用插件来实现。

    二、MyBatis延迟加载

    (一)是否支持延迟加载

    MyBatis是支持延迟加载(Lazy Loading)的,不过默认是关闭状态,需要手动配置才能开启。

    (二)配置方式

    mybatis-config.xml文件里进行如下配置:

    <settings>
        <setting name=\"lazyLoadingEnabled\" value=\"true\"/> <!-- 全局启用延迟加载 -->
        <setting name=\"aggressiveLazyLoading\" value=\"false\"/> <!-- 是否激进加载,默认 false -->
    </settings>
    

    (三)延迟加载原理

    MyBatis的延迟加载依赖于动态代理和结果映射机制。当执行查询主对象的操作时,与之关联的对象并不会马上加载,而是生成一个代理对象。只有在首次访问这个关联对象的时候,才会真正触发加载操作。

    这里面有两个核心组件:

    • ResultMap:主要用来定义对象之间的关联关系。
    • ProxyFactory:负责生成代理对象,默认使用Javassist或CGLIB。

    执行流程如下:

    1. 执行主查询,返回主对象。
    2. 关联对象的字段会被设置为代理对象。
    3. 当访问关联对象时,代理对象会触发子查询来加载数据。

    假设UserOrder有关联关系,示例代码如下:

    <resultMap id=\"userMap\" type=\"User\">
        <id property=\"id\" column=\"id\"/>
        <result property=\"name\" column=\"name\"/>
        <association property=\"order\" column=\"order_id\" javaType=\"Order\" select=\"com.example.OrderMapper.selectOrderById\"/>
    </resultMap>
    
    <select id=\"selectUser\" resultMap=\"userMap\">
        SELECT id, name, order_id FROM user WHERE id = #{id}
    </select>
    
    <select id=\"selectOrderById\" resultType=\"Order\">
        SELECT * FROM order WHERE id = #{id}
    </select>
    
    SqlSession session = sqlSessionFactory.openSession();
    User user = session.selectOne(\"com.example.UserMapper.selectUser\", 1);
    System.out.println(user.getName()); // 主查询执行
    System.out.println(user.getOrder().getOrderNo()); // 子查询触发
    

    原理分析:
    lazyLoadingEnabled=true时,MyBatis会为order属性生成代理。当访问getOrder()方法时,代理就会调用selectOrderById去查询数据库。

    不过要注意,虽然延迟加载能减少初始查询的开销,但可能会出现N+1问题,也就是多次执行子查询。

    三、MyBatis缓存:一级缓存与二级缓存

    (一)一级缓存

    1. 作用范围:一级缓存的作用范围是SqlSession级别,默认是开启的。
    2. 实现原理:它使用PerpetualCache(基于HashMap)来存储数据,位于BaseExecutor中。缓存的键由MappedStatement ID + 参数 + SQL组成,对应的值就是查询结果。
    3. 生命周期:在SqlSession创建的时候初始化,关闭SqlSession时销毁。另外,执行增删改操作或者调用clearCache()方法,都会清空一级缓存。
    4. 代码示例
    SqlSession session = sqlSessionFactory.openSession();
    User user1 = session.selectOne(\"com.example.UserMapper.selectUser\", 1); // 查询数据库
    User user2 = session.selectOne(\"com.example.UserMapper.selectUser\", 1); // 缓存命中
    session.close();
    

    (二)二级缓存

    1. 作用范围:二级缓存的作用范围是Mapper级别,它可以跨SqlSession共享数据,不过需要手动开启。
    2. 实现原理:二级缓存使用Cache接口,默认实现也是PerpetualCache,存储在Configurationcaches中。而且,还能集成第三方缓存,比如Ehcache。
    3. 配置方式
    <settings>
        <setting name=\"cacheEnabled\" value=\"true\"/>
    </settings>
    <mapper namespace=\"com.example.UserMapper\">
        <cache/>
    </mapper>
    
    1. 生命周期:二级缓存跟随Mapper的生命周期,执行增删改操作会清空对应Mapper的缓存。
    2. 代码示例
    SqlSession session1 = sqlSessionFactory.openSession();
    User user1 = session1.selectOne(\"com.example.UserMapper.selectUser\", 1); // 查询数据库
    session1.close();
    
    SqlSession session2 = sqlSessionFactory.openSession();
    User user2 = session2.selectOne(\"com.example.UserMapper.selectUser\", 1); // 缓存命中
    session2.close();
    

    (三)对比分析

    下面用表格对比一下一级缓存和二级缓存:

    特性 一级缓存 二级缓存
    作用范围 SqlSession Mapper
    默认状态 开启 关闭
    存储位置 BaseExecutor Configuration
    清空条件 增删改、关闭session 增删改
    配置复杂度 无需配置 需要手动配置

    总的来说,一级缓存简单高效,适合在单次会话中使用;二级缓存能跨会话共享,在读取操作多、写入操作少的场景下很适用,但要注意数据一致性的问题。

    四、MyBatis接口绑定:原理与示例

    (一)接口绑定原理

    MyBatis的接口绑定是通过动态代理实现的,它能把Mapper接口和XML文件或者注解里的SQL语句绑定起来,这样咱们就不用手动去实现接口了。

    这里面的核心组件有:

    • MapperProxy:动态代理类。
    • MapperRegistry:负责注册和管理Mapper接口。

    执行流程如下:

    1. Configuration初始化的时候,会解析Mapper接口和对应的XML文件。
    2. 使用MapperProxyFactory为接口生成代理对象。
    3. 调用接口方法时,代理对象会根据方法名和命名空间定位MappedStatement,然后执行对应的SQL语句。

    (二)示例

    定义接口:

    public interface UserMapper {
        User selectUser(int id);
    }
    

    编写XML文件:

    <mapper namespace=\"com.example.UserMapper\">
        <select id=\"selectUser\" resultType=\"User\">
            SELECT * FROM user WHERE id = #{id}
        </select>
    </mapper>
    

    使用示例:

    SqlSession session = sqlSessionFactory.openSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    User user = mapper.selectUser(1); // 代理执行 SQL
    

    (三)源码分析

    getMapper方法:

    public <T> T getMapper(Class<T> type) {
        return configuration.getMapper(type, this);
    }
    

    代理生成:

    public class MapperProxy<T> implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 根据方法名和参数执行对应的 MappedStatement
            return mapperMethod.execute(sqlSession, args);
        }
    }
    

    MyBatis通过动态代理实现接口绑定,简化了开发过程,提高了开发的灵活性。

微信扫一扫

支付宝扫一扫

版权: 转载请注明出处:https://www.zuozi.net/10373.html

管理员

相关推荐
2025-08-06

文章目录 一、Reader 接口概述 1.1 什么是 Reader 接口? 1.2 Reader 与 InputStream 的区别 1.3 …

988
2025-08-06

文章目录 一、事件溯源 (一)核心概念 (二)Kafka与Golang的优势 (三)完整代码实现 二、命令…

465
2025-08-06

文章目录 一、证明GC期间执行native函数的线程仍在运行 二、native线程操作Java对象的影响及处理方…

348
2025-08-06

文章目录 一、事务基础概念 二、MyBatis事务管理机制 (一)JDBC原生事务管理(JdbcTransaction)…

456
2025-08-06

文章目录 一、SnowFlake算法核心原理 二、SnowFlake算法工作流程详解 三、SnowFlake算法的Java代码…

517
2025-08-06

文章目录 一、本地Jar包的加载操作 二、本地Class的加载方法 三、远程Jar包的加载方式 你知道Groo…

832
发表评论
暂无评论

还没有评论呢,快来抢沙发~

助力内容变现

将您的收入提升到一个新的水平

点击联系客服

在线时间:08:00-23:00

客服QQ

122325244

客服电话

400-888-8888

客服邮箱

122325244@qq.com

扫描二维码

关注微信客服号