版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
分页概述
分页技术分类如下:
- 数组分页,效率和空间占用率都很差
- sql分页,效率和空间占用率都很好,但是编写sql会很麻烦
- RowBounds分页(底层也是数组分页),同数组分页
- 拦截器分页(推荐)
mybatis的四大对象
mybatis四大对象指的是:executor,statementHandler,parameterHandler和resultHandler对象。这四个对象在sqlSession内部共同协作完成sql语句的执行,同时也是我们自定义插件拦截的四大对象。
下面是mybatis查询的大体流程
分页插件
因为需要在发送sql语句查询前实现分页操作,所以我们需要在sql执行前将分页sql插入进去,因为statementHandler是负责执行sql语句的,所以需要拦截的对象就是statementHandler。我选择拦截的方法则是prepare,因为这个方法是在执行sql语句之前执行的。
拦截后的流程如下:
- 执行一条count语句
- 重写sql,在sql语句后面加上分页语句
- 将statementHandler中的sql语句替换成重写的sql
- 让拦截器执行下去(因为可能不止有一个拦截器)
这里约定mapper的id结尾是ByPage的才会去是实现分页功能
插件对象
import com.zdd.util.PageUtil;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Map;
import java.util.Properties;
//配置拦截的对象和方法和方法要传递的参数的类型
//args : 你需要mybatis传入什么参数给你 type :你需要拦截的对象 method=要拦截的方法
@Intercepts(@Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class}))
public class MyPagePlugin implements Interceptor {
String dataName = "mysql";
String pageSqlId = ".*ByPage$";
/**
* 我们自己拦截器里面的逻辑
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
/**
* 获得MetaObject对象可以不通过反射就可以拿到StatementHandler的属性
*/
MetaObject metaObject = MetaObject.forObject(
statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());
String sqlId = (String)metaObject.getValue("delegate.mappedStatement.id");
//判断一下是否是分页
// <!--第一步 执行一条couunt语句-->
//1.1拿到连接
//1.2 预编译SQL语句 拿到绑定的sql语句
//1.3 执行 count语句 怎么返回你执行count的结果
// <!--第二部 重写sql select * from luban_product limit start,limit -->
//2.1 ? 怎么知道 start 和limit
//2.2拼接start 和limit
//2.3 替换原来绑定sql
//拿到原来应该执行的sql
if (sqlId.matches(pageSqlId)){
ParameterHandler parameterHandler = statementHandler.getParameterHandler();
//原来应该执行的sql
String sql = statementHandler.getBoundSql().getSql();
//sql= select * from product select count(0) from (select * from product) as a
//select * from luban_product where name = #{name}
//执行一条count语句
//拿到数据库连接对象
Connection connection = (Connection) invocation.getArgs()[0];
String countSql = "select count(0) from ("+sql+") a";
System.out.println(countSql);
//渲染参数
PreparedStatement preparedStatement = connection.prepareStatement(countSql);
//条件交给mybatis
parameterHandler.setParameters(preparedStatement);
ResultSet resultSet = preparedStatement.executeQuery();
int count =0;
if (resultSet.next()) {
count = resultSet.getInt(1);
}
resultSet.close();
preparedStatement.close();
//获得你传进来的参数对象
Map<String, Object> parameterObject = (Map<String, Object>) parameterHandler.getParameterObject();
//limit page
PageUtil pageUtil = (PageUtil) parameterObject.get("page");
//limit 1 ,10 十条数据 总共可能有100 count 要的是 后面的100
pageUtil.setCount(count);
//拼接分页语句(limit) 并且修改mysql本该执行的语句
String pageSql = getPageSql(sql, pageUtil);
metaObject.setValue("delegate.boundSql.sql",pageSql);
System.out.println(pageSql);
}
//推进拦截器调用链
return invocation.proceed();
}
public String getPageSql(String sql,PageUtil pageUtil){
if(dataName.equals("mysql")){
return sql+" limit "+pageUtil.getStart()+","+pageUtil.getLimit();
}else if(dataName.equals("oracle")){
//拼接oracle的分语句
}
return sql+" limit "+pageUtil.getStart()+","+pageUtil.getLimit();
}
/**
* 返回一个动态代理对象
* 这个参数o是目标对象,就是你要拦截的对象,这里指的是StatementHandler
* @param o
* @return
*/
@Override
public Object plugin(Object o) {
return Plugin.wrap(o,this);
}
/**
* 会传入xml文件中配置的属性
* 可以通过xml做一些初始化工作
* @param properties
*/
@Override
public void setProperties(Properties properties) {
dataName = (String) properties.get("dataName");
}
}
分页工具类
public class PageUtil {
private int page;
private int limit;
private int count;
private int start;
public PageUtil(){
}
public PageUtil(int page, int limit){
this.page = page;
this.limit = limit;
this.start = (page-1)*limit;
}
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public static void main(String[] args) {
Map<String,PageUtil> map = new HashMap<>();
PageUtil pageUtil = new PageUtil();
pageUtil.setPage(1);
map.put("key",pageUtil);
//调用逻辑修改map
方法1(map);
//直接使用原来对象
System.out.println(pageUtil.getPage());
}
public static void 方法1(Map<String,PageUtil> map){
map.get("key").setPage(2);
}
}
插件注入
- xml注入
还可以配置属性,配置的属性值可以在自定义的插件中拿到
- java注入
分页测试
public class MybatisTest {
public static void main(String[] args) {
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
PageUtil pageUtil = new PageUtil(2, 5);
HashMap<String, Object> map1 = new HashMap<>();
map1.put("page",pageUtil);
List<Map<String, Object>> list = mapper.listPersonByPage(map1);
// List<Map<String, Object>> list = mapper.listPerson();
for (Map<String, Object> map : list) {
System.out.print(map.get("PERSON_ID")+"\t");
System.out.print(map.get("NAME")+"\t");
System.out.print(map.get("PERSON_ADDR")+"\t");
System.out.print(map.get("BIRTHDAY")+"\t");
System.out.print(map.get("GENDER")+"\t");
System.out.println();
}
// Map<String, Object> map = mapper.selectPerson(1);
// System.out.println(map.get("PERSON_ID"));
// System.out.println(map.get("NAME"));
// System.out.println(map.get("PERSON_ADDR"));
// System.out.println(map.get("BIRTHDAY"));
// System.out.println(map.get("GENDER"));
// PersonMapper mapper1 = sqlSession.getMapper(PersonMapper.class);
// Map<String, Object> map1 = mapper1.selectPerson(1);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}