如何开发MyBatis插件
本篇内容介绍了“如何开发MyBatis插件”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
在浮山等地区,都构建了全面的区域性战略布局,加强发展的系统性、市场前瞻性、产品创新能力,以专注、极致的服务理念,为客户提供网站建设、成都做网站 网站设计制作按需网站建设,公司网站建设,企业网站建设,高端网站设计,网络营销推广,外贸网站建设,浮山网站建设费用合理。
1.MyBatis 插件接口
即使你没开发过 MyBatis 插件,估计也能猜出来,MyBatis 插件是通过拦截器来起作用的,MyBatis 框架在设计的时候,就已经为插件的开发预留了相关接口,如下:
public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; default Object plugin(Object target) { return Plugin.wrap(target, this); } default void setProperties(Properties properties) { // NOP } }
这个接口中就三个方法,第一个方法必须实现,后面两个方法都是可选的。三个方法作用分别如下:
鸿蒙官方战略合作共建——HarmonyOS技术社区
intercept:这个就是具体的拦截方法,我们自定义 MyBatis 插件时,一般都需要重写该方法,我们插件所完成的工作也都是在该方法中完成的。
plugin:这个方法的参数 target 就是拦截器要拦截的对象,一般来说我们不需要重写该方法。Plugin.wrap 方法会自动判断拦截器的签名和被拦截对象的接口是否匹配,如果匹配,才会通过动态代理拦截目标对象。
setProperties:这个方法用来传递插件的参数,可以通过参数来改变插件的行为。我们定义好插件之后,需要对插件进行配置,在配置的时候,可以给插件设置相关属性,设置的属性可以通过该方法获取到。插件属性设置像下面这样:
2.MyBatis 拦截器签名
拦截器定义好了后,拦截谁?
这个就需要拦截器签名来完成了!
拦截器签名是一个名为 @Intercepts 的注解,该注解中可以通过 @Signature 配置多个签名。@Signature 注解中则包含三个属性:
type: 拦截器需要拦截的接口,有 4 个可选项,分别是:Executor、ParameterHandler、ResultSetHandler 以及 StatementHandler。
method: 拦截器所拦截接口中的方法名,也就是前面四个接口中的方法名,接口和方法要对应上。
args: 拦截器所拦截方法的参数类型,通过方法名和参数类型可以锁定唯一一个方法。
一个简单的签名可能像下面这样:
@Intercepts(@Signature( type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class} )) public class CamelInterceptor implements Interceptor { //... }
3.被拦截的对象
根据前面的介绍,被拦截的对象主要有如下四个:
Executor
public interface Executor { ResultHandler NO_RESULT_HANDLER = null; int update(MappedStatement ms, Object parameter) throws SQLException;List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException; List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException; Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException; List flushStatements() throws SQLException; void commit(boolean required) throws SQLException; void rollback(boolean required) throws SQLException; CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql); boolean isCached(MappedStatement ms, CacheKey key); void clearLocalCache(); void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class targetType); Transaction getTransaction(); void close(boolean forceRollback); boolean isClosed(); void setExecutorWrapper(Executor executor); }
各方法含义分别如下:
update:该方法会在所有的 INSERT、 UPDATE、 DELETE 执行时被调用,如果想要拦截这些操作,可以通过该方法实现。
query:该方法会在 SELECT 查询方法执行时被调用,方法参数携带了很多有用的信息,如果需要获取,可以通过该方法实现。
queryCursor:当 SELECT 的返回类型是 Cursor 时,该方法会被调用。
flushStatements:当 SqlSession 方法调用 flushStatements 方法或执行的接口方法中带有 @Flush 注解时该方法会被触发。
commit:当 SqlSession 方法调用 commit 方法时该方法会被触发。
rollback:当 SqlSession 方法调用 rollback 方法时该方法会被触发。
getTransaction:当 SqlSession 方法获取数据库连接时该方法会被触发。
close:该方法在懒加载获取新的 Executor 后会被触发。
isClosed:该方法在懒加载执行查询前会被触发。
ParameterHandler
public interface ParameterHandler { Object getParameterObject(); void setParameters(PreparedStatement ps) throws SQLException; }
各方法含义分别如下:
getParameterObject:在执行存储过程处理出参的时候该方法会被触发。
setParameters:设置 SQL 参数时该方法会被触发。
ResultSetHandler
public interface ResultSetHandler {List handleResultSets(Statement stmt) throws SQLException; Cursor handleCursorResultSets(Statement stmt) throws SQLException; void handleOutputParameters(CallableStatement cs) throws SQLException; }
各方法含义分别如下:
handleResultSets:该方法会在所有的查询方法中被触发(除去返回值类型为 Cursor的查询方法),一般来说,如果我们想对查询结果进行二次处理,可以通过拦截该方法实现。
handleCursorResultSets:当查询方法的返回值类型为 Cursor时,该方法会被触发。
handleOutputParameters:使用存储过程处理出参的时候该方法会被调用。
StatementHandler
public interface StatementHandler { Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException; void parameterize(Statement statement) throws SQLException; void batch(Statement statement) throws SQLException; int update(Statement statement) throws SQLException;List query(Statement statement, ResultHandler resultHandler) throws SQLException; Cursor queryCursor(Statement statement) throws SQLException; BoundSql getBoundSql(); ParameterHandler getParameterHandler(); }
各方法含义分别如下:
prepare:该方法在数据库执行前被触发。
parameterize:该方法在 prepare 方法之后执行,用来处理参数信息。
batch:如果 MyBatis 的全剧配置中配置了 defaultExecutorType=”BATCH”,执行数据操作时该方法会被调用。
update:更新操作时该方法会被触发。
query:该方法在 SELECT 方法执行时会被触发。
queryCursor:该方法在 SELECT 方法执行时,并且返回值为 Cursor 时会被触发。
在开发一个具体的插件时,我们应当根据自己的需求来决定到底拦截哪个方法。
4.开发分页插件
4.1 内存分页
MyBatis 中提供了一个不太好用的内存分页功能,就是一次性把所有数据都查询出来,然后在内存中进行分页处理,这种分页方式效率很低,基本上没啥用,但是如果我们想要自定义分页插件,就需要对这种分页方式有一个简单了解。
内存分页的使用方式如下,首先在 Mapper 中添加 RowBounds 参数,如下:
public interface UserMapper { ListgetAllUsersByPage(RowBounds rowBounds); }
然后在 XML 文件中定义相关 SQL:
可以看到,在 SQL 定义时,压根不用管分页的事情,MyBatis 会查询到所有的数据,然后在内存中进行分页处理。
Mapper 中方法的调用方式如下:
@Test public void test3() { UserMapper userMapper = sqlSessionFactory.openSession().getMapper(UserMapper.class); RowBounds rowBounds = new RowBounds(1,2); Listlist = userMapper.getAllUsersByPage(rowBounds); for (User user : list) { System.out.println("user = " + user); } }
构建 RowBounds 时传入两个参数,分别是 offset 和 limit,对应分页 SQL 中的两个参数。也可以通过 RowBounds.DEFAULT 的方式构建一个 RowBounds 实例,这种方式构建出来的 RowBounds 实例,offset 为 0,limit 则为 Integer.MAX_VALUE,也就相当于不分页。
这就是 MyBatis 中提供的一个很不实用的内存分页功能。
了解了 MyBatis 自带的内存分页之后,接下来我们就可以来看看如何自定义分页插件了。
4.2 自定义分页插件
首先要声明一下,这里松哥带大家自定义 MyBatis 分页插件,主要是想通过这个东西让小伙伴们了解自定义 MyBatis 插件的一些条条框框,了解整个自定义插件的流程,分页插件并不是我们的目的,自定义分页插件只是为了让大家的学习过程变得有趣一些而已。
接下来我们就来开启自定义分页插件之旅。
首先我们需要自定义一个 RowBounds,因为 MyBatis 原生的 RowBounds 是内存分页,并且没有办法获取到总记录数(一般分页查询的时候我们还需要获取到总记录数),所以我们自定义 PageRowBounds,对原生的 RowBounds 功能进行增强,如下:
public class PageRowBounds extends RowBounds { private Long total; public PageRowBounds(int offset, int limit) { super(offset, limit); } public PageRowBounds() { } public Long getTotal() { return total; } public void setTotal(Long total) { this.total = total; } }
可以看到,我们自定义的 PageRowBounds 中增加了 total 字段,用来保存查询的总记录数。
接下来我们自定义拦截器 PageInterceptor,如下:
@Intercepts(@Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} )) public class PageInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; Object parameterObject = args[1]; RowBounds rowBounds = (RowBounds) args[2]; if (rowBounds != RowBounds.DEFAULT) { Executor executor = (Executor) invocation.getTarget(); BoundSql boundSql = ms.getBoundSql(parameterObject); Field additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters"); additionalParametersField.setAccessible(true); MapadditionalParameters = (Map ) additionalParametersField.get(boundSql); if (rowBounds instanceof PageRowBounds) { MappedStatement countMs = newMappedStatement(ms, Long.class); CacheKey countKey = executor.createCacheKey(countMs, parameterObject, RowBounds.DEFAULT, boundSql); String countSql = "select count(*) from (" + boundSql.getSql() + ") temp"; BoundSql countBoundSql = new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject); Set keySet = additionalParameters.keySet(); for (String key : keySet) { countBoundSql.setAdditionalParameter(key, additionalParameters.get(key)); } List