mybatis的插件,实际上是拦截器,通过这些插件可以改变mybatis的默认行为。mybatis可以拦截的对象有:
(1)Executor
,执行的SQL
全过程,包括组装参数、组装结果返回和执行SQL
的过程等都可以拦截
(2)StatementHandler
,执行SQL
的过程,拦截该对象可以重写执行SQL
的过程
(3)ParameterHandler
,执行SQL
的参数组装,拦截该对象可以重写组装参数的规则
(4)ResultSetHandler,
执行结果的组装,拦截该对象可以重写组装结果的规则
下面,将自定义一个简单的分页小插件,来了解插件的开发流程。
示例代码,已放入GitHub上:https://github.com/qiuxinfa/mybatis-study
1.自定义分页对象
这里,为了简单起见,只是设置了当期页和每页的大小:
public class MyPage {
private Integer currentPage; //当前页是第几页
private Integer pageSize; //每页的大小
//省略set、get...
}
2.自定义插件
mybatis的插件,需要实现Interceptor接口,并且需要添加注解@Intercepts,简单解释下注解的参数:
(1)type,要拦截的对象
(2)method,要拦截的方法
(3)args,要拦截方法的参数,注意要一一对应
详细的说明,写在代码中了:
//表示拦截StatementHandler类的prepare方法
@Intercepts({@Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class})})
public class MyPageInterceptor implements Interceptor{
private Integer defaultPageSize; //默认每页的大小
private Integer defaultCurrentPage; //默认页数
//实现拦截逻辑
public Object intercept(Invocation invocation) throws Throwable {
//获取目标对象
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
//获取元数据
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
//获取绑定的sql
BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
//获取参数对象
Object parameterObject = boundSql.getParameterObject();
//获取传入分页参数
MyPage myPage = this.getPage(parameterObject);
String sql = (String) metaObject.getValue("delegate.boundSql.sql");
boolean isSelect = sql.trim().toLowerCase().startsWith("select");
//如果不是查询语句,则直接进入下一个操作
if(!isSelect){
return invocation.proceed();
}
System.out.println("原来的sql:"+sql);
//修改sql
String page_sql = "select * from ("+sql+") page_table limit ?,?";
//设置修改过的sql
metaObject.setValue("delegate.boundSql.sql",page_sql);
//如果没有传入相应的分页参数,则设置为默认值
if (myPage.getCurrentPage() == null){
//设置默认值
myPage.setCurrentPage(this.defaultCurrentPage);
}
if (myPage.getPageSize() == null){
//设置默认值
myPage.setPageSize(this.defaultPageSize);
}
//获取参数
PreparedStatement ps = (PreparedStatement) invocation.proceed();
// 获取sql的总参数个数
int paramCount = ps.getParameterMetaData().getParameterCount();
// 设置分页参数
ps.setInt(paramCount - 1, (myPage.getCurrentPage() - 1) * myPage.getPageSize());
ps.setInt(paramCount, myPage.getPageSize());
return ps;
}
//返回被拦截对象的代理对象
public Object plugin(Object target) {
System.out.println("开始包装目标对象");
return Plugin.wrap(target,this);
}
//设置插件配置的参数
public void setProperties(Properties properties) {
this.defaultCurrentPage = Integer.valueOf(properties.getProperty("default.currentPage", "1"));
this.defaultPageSize = Integer.valueOf(properties.getProperty("default.pageSize", "3"));
}
/**
* 获取分页参数
*
* @param parameterObject
* @return
*/
private MyPage getPage(Object parameterObject) {
if (parameterObject == null) {
return null;
}
if (parameterObject instanceof Map) {
// 如果传入的参数是map类型的,则遍历map取出Page对象
Map parameMap = (Map) parameterObject;
Set keySet = parameMap.keySet();
for (String key : keySet) {
Object value = parameMap.get(key);
if (value instanceof MyPage) {
// 返回MyPage对象
return (MyPage) value;
}
}
} else if (parameterObject instanceof MyPage) {
// 如果传入的是Page类型,则直接返回该对象
return (MyPage) parameterObject;
}
// 初步判断并没有传入MyPage类型的参数,返回null
return null;
}
}
3.配置插件
需要在mybatis的主配置文件mybatis-config.xml中,添加插件配置:
4.测试
添加测试类:
public class PluginDemo {
public static void main(String[] args) throws IOException{
//读取配置信息
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//根据配置信息,创建SqlSession工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
//SqlSession工厂创建SqlSession
SqlSession sqlSession = factory.openSession();
//获取接口的代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//执行相应的接口方法
MyPage myPage = new MyPage();
List users = mapper.getAllUserByPage(myPage);
System.out.println(users);
//关闭连接
sqlSession.close();
}
}
看测试结果:
这里在调用方法时,并没有给分页对象传递参数,所以使用的是默认的参数。
可以发现,已经实现了拦截。