MyBatis 示例-插件
简介
利用 MyBatis Plugin 插件技术实现分页功能。
分页插件实现思路如下:
- 业务代码在 ThreadLocal 中保存分页信息;
- MyBatis Interceptor 拦截查询请求,获取分页信息,实现分页操作,封装分页列表数据返回;
测试类:com.yjw.demo.PageTest
插件开发过程
确定需要拦截的签名
MyBatis 插件可以拦截四大对象中的任意一个,从 Plugin 源码中可以看到它需要注册签名才能够运行插件,签名需要确定一些要素。
确定需要拦截的对象
- Executor 是执行 SQL 的全过程,包括组装参数,组装结果集返回和执行 SQL 过程,都可以拦截。
- StatementHandler 是执行 SQL 的过程,我们可以重写执行 SQL 的过程。
- ParameterHandler 是拦截执行 SQL 的参数组装,我们可以重写组装参数规则。
- ResultSetHandler 用于拦截执行结果的组装,我们可以重写组装结果的规则。
拦截方法和参数
当确定了需要拦截什么对象,接下来就要确定需要拦截什么方法和方法的参数。比如分页插件需要拦截 Executor 的 query 方法,我们先看看 Executor 接口的定义,代码清单如下:
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
List<BatchResult> 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);
}
以上的任何方法都可以拦截,从接口定义而言,query 方法有两个,我们可以按照代码清单来定义签名。
@Intercepts({
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class, CacheKey.class, BoundSql.class})})
其中,@Intercepts 说明它是一个拦截器。@Signature 是注册拦截器签名的地方,type 是四大对象中的一个,method 是需要拦截的方法,args 是方法的参数。
插件接口定义
在 MyBatis 中开发插件,需要实现 Interceptor 接口,接口的定义如下:
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
- intercept 方法:它将直接覆盖你所拦截对象原有的方法,因此它是插件的核心方法。通过 invocation 参数可以反射调度原来对象的方法。
- plugin 方法:target 是被拦截对象,它的作用是给被拦截对象生成一个代理对象,并返回它。为了方便 MyBatis 使用 org.apache.ibatis.plugin.Plugin 中的 wrap 静态方法提供生成代理对象。
- setProperties 方法:允许在 plugin 元素中配置所需参数,方法在插件初始化的时候就被调用了一次,然后把插件对象存入到配置中,以便后面再取出。
实现类
根据分页插件的实现思路,定义了三个类。
Page 类
Page 类继承了 ArrayList 类,用来封装分页信息和列表数据。
/**
* 分页返回对象
*
* @author yinjianwei
* @date 2018/11/05
*/
public class Page<E> extends ArrayList<E> { private static final long serialVersionUID = 1L; /**
* 页码,从1开始
*/
private int pageNum;
/**
* 页面大小
*/
private int pageSize;
/**
* 起始行
*/
private int startRow;
/**
* 末行
*/
private int endRow;
/**
* 总数
*/
private long total;
/**
* 总页数
*/
private int pages; public int getPageNum() {
return pageNum;
} public void setPageNum(int pageNum) {
this.pageNum = pageNum;
} public int getPageSize() {
return pageSize;
} public void setPageSize(int pageSize) {
this.pageSize = pageSize;
} public int getStartRow() {
return startRow;
} public void setStartRow(int startRow) {
this.startRow = startRow;
} public int getEndRow() {
return endRow;
} public void setEndRow(int endRow) {
this.endRow = endRow;
} public long getTotal() {
return total;
} public void setTotal(long total) {
this.total = total;
this.pages = (int)(total / pageSize + (total % pageSize == 0 ? 0 : 1));
if (pageNum > pages) {
pageNum = pages;
}
this.startRow = this.pageNum > 0 ? (this.pageNum - 1) * this.pageSize : 0;
this.endRow = this.startRow + this.pageSize * (this.pageNum > 0 ? 1 : 0);
} public int getPages() {
return pages;
} public void setPages(int pages) {
this.pages = pages;
} /**
* 返回当前对象
*
* @return
*/
public List<E> getResult() {
return this;
} }
PageHelper 类
PageHelper 类是分页的帮助类,主要利用 ThreadLocal 线程变量存储分页信息。代码清单如下:
/**
* 分页帮助类
*
* @author yinjianwei
* @date 2018/11/05
*/
@SuppressWarnings("rawtypes")
public class PageHelper { private static final ThreadLocal<Page> PAGE_THREADLOCAT = new ThreadLocal<Page>(); /**
* 设置线程局部变量分页信息
*
* @param page
*/
public static void setPageThreadLocal(Page page) {
PAGE_THREADLOCAT.set(page);
} /**
* 获取线程局部变量分页信息
*
* @return
*/
public static Page getPageThreadLocal() {
return PAGE_THREADLOCAT.get();
} /**
* 清空线程局部变量分页信息
*/
public static void pageThreadLocalClear() {
PAGE_THREADLOCAT.remove();
} /**
* 设置分页参数
*
* @param pageNum
* @param pageSize
*/
public static void startPage(Integer pageNum, Integer pageSize) {
Page page = new Page();
page.setPageNum(pageNum);
page.setPageSize(pageSize);
setPageThreadLocal(page);
} }
PageInterceptor 类
PageInterceptor 类实现了 Interceptor 接口,是分页插件的核心类。代码清单如下:
/**
* 分页拦截器
*
* @author yinjianwei
* @date 2018/11/05
*/
@Intercepts({
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class, CacheKey.class, BoundSql.class})})
public class PageInterceptor implements Interceptor { private Field additionalParametersField; @SuppressWarnings({"rawtypes", "unchecked"})
@Override
public Object intercept(Invocation invocation) throws Throwable {
Executor executor = (Executor)invocation.getTarget();
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement)args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds)args[2];
ResultHandler resultHandler = (ResultHandler)args[3];
CacheKey cacheKey;
BoundSql boundSql;
// 4个参数
if (args.length == 4) {
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
}
// 6个参数
else {
cacheKey = (CacheKey)args[4];
boundSql = (BoundSql)args[5];
}
// 判断是否需要分页
Page page = PageHelper.getPageThreadLocal();
// 不执行分页
if (page.getPageNum() <= 0) {
return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
// count查询
MappedStatement countMs = newCountMappedStatement(ms);
String sql = boundSql.getSql();
String countSql = "select count(1) from (" + sql + ") _count";
BoundSql countBoundSql =
new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
Map<String, Object> additionalParameters = (Map<String, Object>)additionalParametersField.get(boundSql);
for (Entry<String, Object> additionalParameter : additionalParameters.entrySet()) {
countBoundSql.setAdditionalParameter(additionalParameter.getKey(), additionalParameter.getValue());
}
CacheKey countCacheKey = executor.createCacheKey(countMs, parameter, rowBounds, countBoundSql);
Object countResult =
executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countCacheKey, countBoundSql);
Long count = (Long)((List)countResult).get(0);
page.setTotal(count);
// 分页查询
String pageSql = sql + " limit " + page.getStartRow() + "," + page.getPageSize();
BoundSql pageBoundSql =
new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
for (Entry<String, Object> additionalParameter : additionalParameters.entrySet()) {
pageBoundSql.setAdditionalParameter(additionalParameter.getKey(), additionalParameter.getValue());
}
CacheKey pageCacheKey = executor.createCacheKey(ms, parameter, rowBounds, pageBoundSql);
List listResult = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageCacheKey, pageBoundSql);
page.addAll(listResult);
// 清空线程局部变量分页信息
PageHelper.pageThreadLocalClear();
return page;
} @Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
} @Override
public void setProperties(Properties properties) {
try {
additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
additionalParametersField.setAccessible(true);
} catch (NoSuchFieldException | SecurityException e) {
e.printStackTrace();
}
} /**
* 创建count的MappedStatement
*
* @param ms
* @return
*/
private MappedStatement newCountMappedStatement(MappedStatement ms) {
MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId() + "_count",
ms.getSqlSource(), ms.getSqlCommandType());
builder.resource(ms.getResource());
builder.fetchSize(ms.getFetchSize());
builder.statementType(ms.getStatementType());
builder.keyGenerator(ms.getKeyGenerator());
if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
StringBuilder keyProperties = new StringBuilder();
for (String keyProperty : ms.getKeyProperties()) {
keyProperties.append(keyProperty).append(",");
}
keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
builder.keyProperty(keyProperties.toString());
}
builder.timeout(ms.getTimeout());
builder.parameterMap(ms.getParameterMap());
// count查询返回值int
List<ResultMap> resultMaps = new ArrayList<ResultMap>();
ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId() + "_count", Long.class,
new ArrayList<ResultMapping>(0)).build();
resultMaps.add(resultMap);
builder.resultMaps(resultMaps);
builder.resultSetType(ms.getResultSetType());
builder.cache(ms.getCache());
builder.flushCacheRequired(ms.isFlushCacheRequired());
builder.useCache(ms.isUseCache()); return builder.build();
} }
配置
MyBatis 配置文件增加 plugin 配置项。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings> <typeHandlers>
<typeHandler javaType="com.yjw.demo.mybatis.common.constant.Sex"
jdbcType="TINYINT"
handler="com.yjw.demo.mybatis.common.type.SexEnumTypeHandler"/>
</typeHandlers> <plugins>
<plugin interceptor="com.yjw.demo.mybatis.common.page.PageInterceptor">
</plugin>
</plugins>
</configuration>
MyBatis 实用篇
MyBatis 示例-插件的更多相关文章
- MyBatis 示例-传递多个参数
映射器的主要元素: 本章介绍 select 元素中传递多个参数的处理方式. 测试类:com.yjw.demo.MulParametersTest 使用 Map 传递参数(不建议使用) 使用 MyBat ...
- MyBatis 示例-类型处理器
MyBatis 提供了很多默认类型处理器,参考官网地址:链接,除了官网提供的类型处理器,我们也可以自定义类型处理器. 具体做法为:实现 org.apache.ibatis.type.TypeHandl ...
- MyBatis 示例-简介
简介 为了全面熟悉 MyBatis 的使用,整理一个 MyBatis 的例子,案例中包含了映射器.动态 SQL 的使用.本章先介绍项目结构和配置. 项目地址:链接 数据库表的模型关系:链接 项目结构 ...
- MyBatis 示例-联合查询
简介 MyBatis 提供了两种联合查询的方式,一种是嵌套查询,一种是嵌套结果.先说结论:在项目中不建议使用嵌套查询,会出现性能问题,可以使用嵌套结果. 测试类:com.yjw.demo.JointQ ...
- MyBatis 示例-缓存
MyBatis 提供两种类型的缓存,一种是一级缓存,另一种是二级缓存,本章通过例子的形式描述 MyBatis 缓存的使用. 测试类:com.yjw.demo.CacheTest 一级缓存 MyBati ...
- MyBatis 示例-动态 SQL
MyBatis 的动态 SQL 包括以下几种元素: 详细的使用参考官网文档:http://www.mybatis.org/mybatis-3/zh/dynamic-sql.html 本章内容简单描述这 ...
- MyBatis 示例-主键回填
测试类:com.yjw.demo.PrimaryKeyTest 自增长列 数据库表的主键为自增长列,在写业务代码的时候,经常需要在表中新增一条数据后,能获得这条数据的主键 ID,MyBatis 提供了 ...
- Mybatis分页插件
mybatis配置 <!-- mybatis分页插件 --> <bean id="pagehelper" class="com.github.pageh ...
- Mybatis学习---Mybatis分页插件 - PageHelper
1. Mybatis分页插件 - PageHelper说明 如果你也在用Mybatis,建议尝试该分页插件,这个一定是最方便使用的分页插件. 该插件目前支持Oracle,Mysql,MariaDB,S ...
随机推荐
- QtCreator集成的MSVC套件有问题
MSVC编译出来的内部签名算法的程序,相同的代码,验签结果和MINGW编译出来的不一样.MINGW编译出来的结果是正确的 怀疑是因为QtCreator集成的msvc有问题,可能是编码问题,可能是其他问 ...
- Embedded based learning
简单整理了一些嵌入式底层需要接触的相关概念. # CPU CU. Control Unit. send need-clac-data -> ALU clac -> get resul ...
- 数据分析 - Power BI
BI 目的 单表的展示有限很多的时候只能体现现象, 仅仅进行监控级别没问题 但是就数据分析而言实在不够用, 大部分的事情需要多表多图展示才可以通过现象深入挖掘诱因 BI 安装 这里使用 microso ...
- laravel5.5的定时任务详解(demo)
原文地址:https://blog.csdn.net/LJFPHP/article/details/80417552
- 纯css实现表单输入验证
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 利用先电云iaas平台搭建apache官方大数据平台(ambari2.7+hdp3.0)
一.ambari架构解析 二.基础环境配置 以两台节点为例来组件Hadoop分布式集群,这里采用的系统版本为Centos7 1511,如下表所示: 主机名 内存 硬盘 IP地址 角色 master 8 ...
- 【miscellaneous】语音识别工具箱综述和产品介绍
原文:http://www.thinkface.cn/thread-893-1-1.html 今天是周末,想来想去,还是写一篇这样的博文吧.算是对语音识别这一段时间的总结,为后来的人融入铺好前面的路. ...
- MySQL的安装、配置与优化
MySQL 安装配置 参考网址:https://www.runoob.com/linux/mysql-install-setup.html MySQL 是最流行的关系型数据库管理系统,由瑞典MySQL ...
- 顺序表的基本操作【c语言】【创建、插入、删除、输出】
作为数据结构初学者,上课时对一些知识点掌握得不是很透彻,所以利用课余时间通过微博平台总结所学知识,加深对知识的见解,记录学习历程便于后需要时参考. #include<stdio.h> #i ...
- Java集合框架——Set接口
第三阶段 JAVA常见对象的学习 集合框架--Set接口 List集合的特点是有序的,可重复的,是不是存在这一种无序,且能保证元素唯一的集合呢?(HashSet )这就涉及到我们今天所要讲的Set集合 ...