-
前言
-
mybatis简单了解
-
分页类型
-
分页方式
-
1.数组分页
-
2.数据库分页
-
3.Rowbounds分页
-
4.自定义插件分页
-
5.聊下第三方分页插件
前言
这是一篇简单总结,mybatis分页的文章。
mybatis简单了解
在很久以前,我们会使用jdbc对数据库进行crud,随着ORM框架的诞生,为了效率,可能就选择了hibernate和mybatis等技术。hibernate由于比较笨重,虽然切换数据库也不会太大改变我们的程序,但是也不是太灵活,比如我们无法利用数据库比较重要的索引等技巧,所以现在很多项目中都是使用mybais的。今天只聊mybatis的分页技术。
分页类型
物理分页:直接从数据库中拿出我们需要的数据,例如在Mysql中使用limit。
逻辑分页:从数据库中拿出所有符合要求的数据,然后再从这些数据中拿到我们需要的分页数据。
分页方式
mybatis中几种写法:
1.数组分页
这种的思路是一次性查询所有数据,得到一个集合,然后在根据页码等参数进行切分数组,返回前端指定页码的数据。demo如下:
mapper层
//数组分页
List<Map<String, Object>> getProductByArrayPage();
sql语句
select * from test
service层方法
@Override
public R getProductByArrayPage(int page, int limit) {
List<Map<String,Object>> data = productMapper.getProductByArrayPage();
//从第几条开始
int startNum = (page-1)*limit;
//到第几条结束
int lastNum = page * limit;
if (lastNum>data.size()){
lastNum = data.size();
}
return R.success().set("count",data.size()).data(data.subList(startNum,lastNum));
}
//统一返回格式(根据自己业务来定)
public class R extends HashMap{
public static String SUCCESS_CODE="200";
public static String ERROR_CODE="500";
public static String DATA_KEY = "data";
public static String MSG_KEY = "msg";
private R(){
}
public R set(String key, Object object){
super.put(key,object);
return this;
}
private static R ok(){
return new R();
}
public static R success(){
return R.ok().set("code", R.SUCCESS_CODE).set(R.MSG_KEY,"操作成功");
}
public static R success(String msg){
return R.ok().set("code", R.SUCCESS_CODE).set(R.MSG_KEY,msg);
}
public static R success(String msg, Object object){
return R.ok().set("code", R.SUCCESS_CODE).set(R.MSG_KEY,msg).set(R.DATA_KEY,object);
}
public R data(Object obj){
return this.set("data",obj);
}
public static R error(){
return R.ok().set(R.MSG_KEY,"操作失败").set("code", R.ERROR_CODE);
}
public static R error(String msg){
return R.ok().set(R.MSG_KEY,msg).set("code", R.ERROR_CODE);
}
public static R error(String msg, Object object){
return R.ok().set(R.MSG_KEY,msg).set(R.DATA_KEY,object).set("code", R.ERROR_CODE);
}
}
2.数据库分页
思路:手动写一个查询集合的sql,一个获取总数的接口,属于物理分页。
-
sql层
<select id="getProductPage" parameterType="Map" resultType="Map">
select * from test limit #{start} , #{limit}
</select>
<select id="getProductCount" resultType="int">
select count(0) from test
</select>
-
mapper层
//数据库分页
List<Map<String, Object>> getProductPage(Map<String, Object> map);
//获取总数
int getProductCount();
-
service 层
public R getProductPage(int page,int limit) {
Map<String,Object> map = new HashMap<>();
map.put("start",(page-1)*limit);
map.put("limit",limit);
System.out.println("调用了sql分页");
return R.success().data(productMapper.getProductPage(map)).set("count",productMapper.getProductCount());
}
3.Rowbounds分页
Mybatis可以通过传递RowBounds对象,来进行数据库数据的分页操作,该分页操作是对ResultSet结果集进行分页,也就是人们常说的逻辑分页,而非物理分页
。demo如下: sql:
<select id="getProductByPage" parameterType="Map" resultType="Map">
select * from test
</select>
<select id="getProductCount" resultType="int">
select count(0) from test
</select>
mapper层
//Rowbounds分页
List<Map<String, Object>> getProductByRowboundsPage(RowBounds rowBounds);
//获取总数
int getProductCount();
-
service 层
public R getProductByRowboundsPage(int page,int limit) {
RowBounds rowBounds = new RowBounds((page-1)*limit,limit);
System.out.println("调用了RowBounds分页");
return R.success().data(productMapper.getProductByRowboundsPage(rowBounds)).set("count",productMapper.getProductCount());
}
4.自定义插件分页
自定义分页原理
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
-
Executor ( update, query, flushStatements, commit, rollback, getTransaction, close, isClosed
) -
ParameterHandler ( getParameterObject, setParameters
) -
ResultSetHandler ( handleResultSets, handleOutputParameters
) -
StatementHandler ( prepare, parameterize, batch, update, query
)
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
Interceptor 如下图。分页无非就是获取总数和获取每页的数据,我们自定义实现拦截器,在mybatis拼接sql时,自动为我们计算count记录数,然后执行分页查询即可。
自定义分页实战
demo如下:
-
自定义分页插件(拦截器)
//args : 你需要mybatis传入什么参数给你 type :你需要拦截的对象 method=要拦截的方法
@Intercepts(@Signature(type = StatementHandler.class,method ="prepare",args = {Connection.class,Integer.class}))
public class MyPagePlugin implements Interceptor {
String databaseType = "";
String pageSqlId = "";
public String getDatabaseType() {
return databaseType;
}
public void setDatabaseType(String databaseType) {
this.databaseType = databaseType;
}
public String getPageSqlId() {
return pageSqlId;
}
public void setPageSqlId(String pageSqlId) {
this.pageSqlId = pageSqlId;
}
//我们自己拦截器里面的逻辑
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
// statementHandler.getDelegate().getmappedStatement().getId();
// Field delegate = StatementHandler.class.getDeclaredField("delegate");
// delegate.setAccessible(true);
// Object o = delegate.get(statementHandler);
// o.getClass().getDeclaredField("mappedStatement").
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(databaseType.equals("mysql")){
return sql+" limit "+pageUtil.getStart()+","+pageUtil.getLimit();
}else if(databaseType.equals("oracle")){
//拼接oracle的分语句
}
return sql+" limit "+pageUtil.getStart()+","+pageUtil.getLimit();
}
//需要你返回一个动态代理后的对象 target :StatementHandler
@Override
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}
//会传入配置文件内容 用户可根据配置文件自定义
@Override
public void setProperties(Properties properties) {
}
}
其他代码 sql
<select id="getProductByPage" parameterType="Map" resultType="Map">
select * from test
</select>
mapper
//插件分页
List<Map<String, Object>> getProductByPage(Map<String, Object> map);
service层就很简单了。自定义个分页对象
@Data
public class PageUtil {
private int page;
private int limit;
private int count;
private int start;
//分页的话 入参对象中添加page对象属性。
@Override
public R getProductByPage(int page,int limit) {
Map<String,Object> map =new HashMap<>();
//只要在参数中 添加page属性就自动我们分页了
PageUtil pageUtil = new PageUtil(page,limit);
map.put("page",pageUtil);
return R.success().data(productMapper.getProductByPage(map)).set("count",pageUtil.getCount());
}
聊下第三方分页插件
其他的第三方插件页无非就是和我们自定义插件一样的原理,只是做了其他优化,比如放在一个ThreadLocal中保证线程安全,对sql进行优化等。
pageHelper分页插件
这个分页插件功能代码很多,我们抓主干只看关键的地方:
-
让我们封装进什么对象作为参数传入接口 -
分页拦截器里面哪里执行了分页控制,即是否要进行分页 -
分页拦截器里面在哪里给我们写了查询总数count的接口,在哪里执行了分页查询 -
返回时包装成PageInfo统一对象为我们做了什么。 使用上就是使用pageHelper.startPage(..)这个方法。我们看下他的实现原理:
这里面会获取到一个Page对象,放在ThreadLocal对象中,后面会根据是否有page对象进行是否进行分页:
-
拦截器 很熟悉,就是我们之前自定义分页拦截器一样的原理。 也是实现了mybatis的拦截器进行处理sql
获取总数的方法
-
在拦截器里面看下怎么过滤需不需要分页的? 没有分页的话,我们不会调用
PageHelper.startPage
方法,也就从ThreadLocalLOCAL_PAGE这个属性中取不出page,就不分页。如果不分页就走自己的不分页的sql: -
返回时包装成PageInfo统一对象为我们做了什么。 返回后page只给我们赋值了记录数。所以需要我们手动把处理后的数据,放在返回pageInfo对象中:
显示调用
new pageInfo的逻辑其实就是:PageInfo因为继承了PageSerializable,所以含有total和list2个属性,给与其赋值。
这样pagehelper的分页就简单梳理完了。再来聊聊另一个分页插件。
mybats-plus的分页插件
使用
mybats-plus的分页插件的使用先很简单 首先引入其分页插件:使用mybatis-plus分页插件
在mapper中传入Ipage对象
分析
个人觉得mybatis-plus分页很清晰,不是很绕。这里显示在StatementHandler拼接sql的时候可以做些事情:对StatementHandler的prepare方法处进行拦截添加业务逻辑
最后还是需要我们自己把集合对象赋值给Ipage对象

以上就是mybatis的分页个人理解,实现自定义分页插件封装,对第三方的分页插件进行总体介绍。
关注微信公众号: 怒放de每一天
不定时推送相关文章,期待和大家一起成长!!
完
原文始发于微信公众号(怒放de每一天):mybatis常用分页写法和自定义分页原理实战
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/106787.html