如何编写MyBatis插件:延迟加载、缓存与接口绑定原理

次元: 线上365bet投注 时间戳: 2025-09-01 10:22:57 观察者: admin 访问量: 2584 能量值: 447
如何编写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插件的运行流程大概是这样的:

在MyBatis初始化的时候,会通过Configuration加载插件。

插件会通过动态代理的方式,把目标对象包装起来。

当目标方法执行的时候,就会调用插件的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文件里注册插件:

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

@Intercepts和@Signature用来明确指定要拦截的对象和方法。

intercept方法里写的就是具体的拦截逻辑,invocation.proceed()表示调用原始方法。

plugin方法决定是否对目标对象进行包装,用Plugin.wrap生成代理。

setProperties方法用来接收配置文件里的参数。

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

二、MyBatis延迟加载

(一)是否支持延迟加载

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

(二)配置方式

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

(三)延迟加载原理

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

这里面有两个核心组件:

ResultMap:主要用来定义对象之间的关联关系。

ProxyFactory:负责生成代理对象,默认使用Javassist或CGLIB。

执行流程如下:

执行主查询,返回主对象。

关联对象的字段会被设置为代理对象。

当访问关联对象时,代理对象会触发子查询来加载数据。

假设User和Order有关联关系,示例代码如下:

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缓存:一级缓存与二级缓存

(一)一级缓存

作用范围:一级缓存的作用范围是SqlSession级别,默认是开启的。

实现原理:它使用PerpetualCache(基于HashMap)来存储数据,位于BaseExecutor中。缓存的键由MappedStatement ID + 参数 + SQL组成,对应的值就是查询结果。

生命周期:在SqlSession创建的时候初始化,关闭SqlSession时销毁。另外,执行增删改操作或者调用clearCache()方法,都会清空一级缓存。

代码示例:

SqlSession session = sqlSessionFactory.openSession();

User user1 = session.selectOne("com.example.UserMapper.selectUser", 1); // 查询数据库

User user2 = session.selectOne("com.example.UserMapper.selectUser", 1); // 缓存命中

session.close();

(二)二级缓存

作用范围:二级缓存的作用范围是Mapper级别,它可以跨SqlSession共享数据,不过需要手动开启。

实现原理:二级缓存使用Cache接口,默认实现也是PerpetualCache,存储在Configuration的caches中。而且,还能集成第三方缓存,比如Ehcache。

配置方式:

生命周期:二级缓存跟随Mapper的生命周期,执行增删改操作会清空对应Mapper的缓存。

代码示例:

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接口。

执行流程如下:

在Configuration初始化的时候,会解析Mapper接口和对应的XML文件。

使用MapperProxyFactory为接口生成代理对象。

调用接口方法时,代理对象会根据方法名和命名空间定位MappedStatement,然后执行对应的SQL语句。

(二)示例

定义接口:

public interface UserMapper {

User selectUser(int id);

}

编写XML文件:

使用示例:

SqlSession session = sqlSessionFactory.openSession();

UserMapper mapper = session.getMapper(UserMapper.class);

User user = mapper.selectUser(1); // 代理执行 SQL

(三)源码分析

getMapper方法:

public T getMapper(Class type) {

return configuration.getMapper(type, this);

}

代理生成:

public class MapperProxy implements InvocationHandler {

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// 根据方法名和参数执行对应的 MappedStatement

return mapperMethod.execute(sqlSession, args);

}

}

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

版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。本文链接:https://www.panziye.com/back/16178.html

喜欢 (0)赏【请潘老师喝杯Coffee吧!】分享 (0)

相关维度

醫砭 » 中醫症狀鑒別診斷學 » 瘰癧

醫砭 » 中醫症狀鑒別診斷學 » 瘰癧

吴昕减肥成功,从110斤瘦到92斤,大方分享减肥方法

吴昕减肥成功,从110斤瘦到92斤,大方分享减肥方法

【QQ影音】怎样合并视频或合并音频文件

【QQ影音】怎样合并视频或合并音频文件

淘宝店铺付款后多久到账?付款后淘宝商家多久之内发货?

淘宝店铺付款后多久到账?付款后淘宝商家多久之内发货?