干翻Mybatis源码系列之第十二篇:自写Mybatis拦截器实现分页操作

简介: 干翻Mybatis源码系列之第十二篇:自写Mybatis拦截器实现分页操作

给自己的每日一句

不从恶人的计谋,不站罪人的道路,不坐亵慢人的座位,惟喜爱耶和华的律法,昼夜思想,这人便为有福!他要像一棵树栽在溪水旁,按时候结果子,叶子也不枯干。凡他所做的尽都顺利。

如何找到孙帅本人

本文内容整理自《孙哥说Mybatis系列视频课程》,老师实力十分雄厚,B站搜孙帅可以找到本人,视频中有老师的微信号。

前言

Mybatis当中可以处理通用的分页,Mybatis开发中传统的分页是怎么接入Mybatis呢?

Mybatis传统开发就:entity、别名、建表、Dao、Mapper、注册、API编程,这七步骤。

Mybatis的分页显然是在Dao和Mapper设计上体现的。在Dao中我们如果引入分页的话,那么就得有页码和每页显示数量了。

UserDao{
  List<User> queryAllUsers(int pageIndex,int number);
  List<User> queryUsersByName(int pageIndex,int number,String name);
}
UserDaoMapper.xml
<select id = "queryAllUsers">
  select * from t_user limit pageindex-1,5<>
</select>
<select id = "queryUsersByname">
  select * from t_user where name like #{name} limit pageindex-1,5
</select>

如果这么玩的话,这个Mapper.xml文件中的Dao层分页查询,得在所有的select标签当中进行添加,这么操作十分冗余。冗余还不是最可怕的,最可怕的是修改起来,那真的是要了命了,假如我们切换了Oracle的数据源,那么select标签中的limit可就不好使了!这样的代码就废了

所以这么干的话,SQL就与厂商特性耦合了。这一块JPA是解决的非常好的,他是通过Dialect方言解决的。

一:分页处理集中处理

解决冗余和解决分页厂商方言问题。这样就适用于我们的拦截器了。我们直接在拦截器当中加上limit 操作即可。

二:不太完善版

@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class PageHelperInterceptor1 extends MyMybatisInterceptorAdapter {
    private static final Logger log = LoggerFactory.getLogger(PageHelperInterceptor1.class);
    private String queryMethodPrefix;
    private String queryMethodSuffix;
    private String databaseType;
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (log.isInfoEnabled())
            log.info("----pageHelperInterceptor------");
        //获得sql语句 拼接字符串 limit
        MetaObject metaObject = SystemMetaObject.forObject(invocation);
        String sql = (String) metaObject.getValue("target.delegate.boundSql.sql");
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("target.delegate.mappedStatement");
        String id = mappedStatement.getId();
    //基于拦截器propertis灵活掌握是否分页的手段。
        if (id.indexOf(queryMethodPrefix) != -1 && id.endsWith(queryMethodSuffix)) {
            //分页相关的操作封装 对象(vo dto)
            //获得Page对象 并设置Page对象 totalSize属性 算出总页数
            //假设 Page
            //Page page = new Page(1);
            //直接通过DAO方法的参数 获得Page对象
            //Page page = (Page) metaObject.getValue("target.delegate.parameterHandler.parameterObject");
            //通过ThreadLocalUtils
            Page page = ThreadLocalUtils.get();
            //清空一下
            //select id,name from t_user 获得 全表有多少条数据
            // select count(*) from t_user
            //select id,name from t_user where name = ?;
            //select count(*)fromt t_user where name = ?
            //select id,name from t_user where  name = ? and id = ?;
            String countSql = "select count(*) " + sql.substring(sql.indexOf("from"));
            //JDBC操作
            //1 Connection  PreapredStatement
            Connection conn = (Connection) invocation.getArgs()[0];
            PreparedStatement preparedStatement = conn.prepareStatement(countSql);
           /* preparedStatement.setString(1,?)
            preparedStatement.setString(2,?);*/
            //这里是什么操作呢? 
            //Mybatis当中ParameterHandler提供的处理参数的方法。自动取这个值,自动取组装值即可。
            ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue("target.delegate.parameterHandler");
            parameterHandler.setParameters(preparedStatement);
9jh
            ResultSet resultSet = preparedStatement.executeQuery();
            if(resultSet.next()){
               page.setTotalSize(resultSet.getInt(1));
            }
            //page.setTotalSize();
  `            //做一个判断 如果当前是MySQL 如果是Oracle....
            //if databaseType == "oracle" or "mysql"
            String newSql = sql + " limit "+page.getFirstItem()+","+page.getPageCount();
            metaObject.setValue("target.delegate.boundSql.sql", newSql);
        }
        return invocation.proceed();
    }
    @Override
    public void setProperties(Properties properties) {
        this.queryMethodPrefix = properties.getProperty("queryMethodPrefix");
        this.queryMethodSuffix = properties.getProperty("queryMethodSuffix");
    }
}
/**
 * 目的 封装分页相关操作
 * pageIndex 页号
 * web 前端传递
 * pageCount 每页显示多少条
 * 写死 3
 * totalSize 总共有多少条
 * count(*) 算出来
 * <p>
 * *    pageSize  一共有多少页
 * totalSize / pageCount
 * 6 / 3 = 2
 * 7 / 3 = 3
 */
public class Page {
    private Integer pageIndex;
    private Integer pageCount;
    //查询数据库的
    //sql语句相关
    //计算在哪里完成? 拦截器操作
    private Integer totalSize;
    private Integer pageSize;
    public Page(Integer pageIndex) {
        this.pageIndex = pageIndex;
        this.pageCount = 5;
    }
    public Page(Integer pageIndex, Integer pageCount) {
        this.pageIndex = pageIndex;
        this.pageCount = pageCount;
    }
    public Integer getPageIndex() {
        return pageIndex;
    }
    public void setPageIndex(Integer pageIndex) {
        this.pageIndex = pageIndex;
    }
    public Integer getPageCount() {
        return pageCount;
    }
    public void setPageCount(Integer pageCount) {
        this.pageCount = pageCount;
    }
    public Integer getTotalSize() {
        return totalSize;
    }
    public void setTotalSize(Integer totalSize) {
        this.totalSize = totalSize;
        if (totalSize % pageCount == 0) {
            this.pageSize = totalSize / pageCount;
        } else {
            this.pageSize = totalSize / pageCount + 1;
        }
    }
    public Integer getPageSize() {
        return pageSize;
    }
    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }
    // limit getFirstItem,pageSize;
    public Integer getFirstItem() {
        return pageIndex - 1;
    }
}
@Test
    public void test2() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        SqlSession sqlSession3 = sqlSessionFactory.openSession();
        UserDAO userDAO1 = sqlSession1.getMapper(UserDAO.class);
        UserDAO userDAO2 = sqlSession2.getMapper(UserDAO.class);
        UserDAO userDAO3 = sqlSession3.getMapper(UserDAO.class);
       /* List<User> users1 = userDAO1.queryAllUsersByPage();//--- ms ---> cache
        for (User user : users1) {
            System.out.println("user = " + user);
        }*/
        User user1 = userDAO1.queryUserById(4); //---ms ---> cache
        sqlSession1.commit();
        //userDAO1.queryAllUsers();
        System.out.println("-----------------------------------------");
        /*List<User> users2 = userDAO2.queryAllUsersByPage();
        for (User user : users2) {
            System.out.println("user = " + user);
        }*/
        sqlSession2.commit();
        System.out.println("---------------------------------2人
        --------");
//
 /*       User user = new User(4, "xiaowb");
        userDAO3.update(user);
        sqlSession3.commit();*/
    }

三:如何获取Page对象

如何获取Page对象的获取和封装,这个位置应该发生在什么位置呢?应该在Controller层当中。服务器解析请求数据的时候会在Controller当中进行解析。controller会通过Request对象获取到页号。并在Controller当中封装成page对象。把Page对象放置到ThreadLocal当中一直送到Dao当中,也可以直接在Dao层当中基于参数传递过来即可。

直接使用ThreadLocal会更好。

1:Tomcat处理一个请求

Tomcat接收到一个请求之后,会从线程池大当中去取出来一个线程来处理请求。请求完毕之后会将线程放回到线程池当中。我们把Page对象放到线程的ThreadLocal当中即可。

ThreadLocal是sun公司为我们提供的一个工具,把一个对象存储在当前线程中。可以准确的获取当前线程,也可以知道对象存储在线程的什么位置。ThreadLocal在Thread当中以自己作为Key,存储在Map中。

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

ThreadLocal就是操作了当前Thread当中的Map,ThreadLocal对象本身作为Map的key,value是存储在其中的值,神奇的是,Thread中的Map是定义在ThreadLocal当中的static class.

2:问题集锦

1):ThreadLocal存在内存泄露

内存泄露核心就是,你所存储的东西太多了,导致你的内存不够用了,所以Dao用完了,我得把Map中的数据给清掉。用完之后就把他干掉。

2):ThreadLocal到底把数据存储在了哪里?

Thread当中。

Connection、SqlSession、Page这些对象经常会存储到Thread当中。

3):什么时候调用ThreadLocal把Page存储在线程中?

Controller当中。当然,我们认为这个还不够早,我们觉得在filter过滤器当中就可以执行这个操作了。

四:Mybatis拦截器中操作Sql的工具?

1:SQLparser解析器

/**
     * 用于测试:jSqlparser
     */
    @Test
    public void testSQLParser() throws JSQLParserException {
        CCJSqlParserManager parserManager = new CCJSqlParserManager();
        Select select = (Select) parserManager.parse(new StringReader("select id,name from t_user where name = 'suns' "));
        PlainSelect selectBody = (PlainSelect) select.getSelectBody();
        //FromItem table = selectBody.getFromItem();
        //System.out.println("table = " + table);
     /*   Expression where = selectBody.getWhere();
        System.out.println("where = " + where);*/
       /* List<SelectItem> selectItems = selectBody.getSelectItems();
        for (SelectItem selectItem : selectItems) {
            System.out.println("selectItem = " + selectItem);
        }*/
    }
@Test
    public void testSQLParser1() throws JSQLParserException {
        CCJSqlParserManager parserManager = new CCJSqlParserManager();
        Update update = (Update) parserManager.parse(new StringReader("update t_user set name='suns',password='12345' where id=1 "));
        /*Table table = update.getTable();
        System.out.println("table = " + table);*/
        List<Column> columns = update.getColumns();
        for (Column column : columns) {
            System.out.println(column);
        }
        List<Expression> expressions = update.getExpressions();
        for (Expression expression : expressions) {
            System.out.println(expression);
        }
    }
目录
打赏
0
0
0
0
0
分享
相关文章
微服务——MyBatis分页
本文介绍了分页的多种实现方式,包括自带RowBounds分页、第三方插件PageHelper分页、SQL分页、数组分页及拦截器分页。其中,RowBounds是先查询全部结果再内存分页;PageHelper通过修改SQL动态添加分页关键字;SQL分页依赖数据库自身的分页功能如`LIMIT`;数组分页则是查询全量数据后用`subList`方法截取;拦截器分页则统一在SQL后添加分页语句。最后总结逻辑分页适合小数据量,但大数据量易内存溢出;物理分页虽小数据量效率较低,但更适合大数据场景,优先推荐使用。
19 0
【YashanDB知识库】Mybatis-Plus调用YashanDB怎么设置分页
【YashanDB知识库】Mybatis-Plus调用YashanDB怎么设置分页
MyBatis篇-分页
本文介绍了多种分页方式,包括自带rowbound内存分页、第三方插件pagehelper(通过修改SQL实现分页)、SQL分页(依赖limit或rownum等关键字)、数组分页(先查询全部数据再用subList分页)、拦截器分页(自定义拦截器为SQL添加分页语句)。最后总结了逻辑分页(内存分页,适合小数据量)和物理分页(直接在数据库层面分页,适合大数据量)的优缺点,强调物理分页优先于逻辑分页。
【YashanDB 知识库】Mybatis-Plus 调用 YashanDB 怎么设置分页
数据库状态分为正常与异常两种情况。当出现异常时,首先查看告警列表确认问题(如实例无法连接),并尝试用数据库用户名和密码登录。若能登录,说明主实例故障已切换至备库;若无法登录或为单节点,则需进一步排查。接着检查监控项,若有数据表明主实例故障,无数据则可能是通信中断。随后检查主机上的服务是否存在,若存在但通信受限,需排查安全设置或网络;若服务不存在,可能因重启或断电导致,需手动启动相关服务。最终在YashanDB列表中确认状态恢复。
MyBatis 实现分页的机制
MyBatis 的分页机制主要依赖于 `RowBounds` 对象和分页插件。`RowBounds` 实现内存分页,适合小数据量场景,通过设定偏移量和限制条数对结果集进行筛选。而针对大数据量,则推荐使用分页插件(如 PageHelper),实现物理分页。插件通过拦截 SQL 执行,动态修改语句添加分页逻辑,支持多种数据库方言。配置插件后,无需手动调整查询方法即可完成分页操作,提升性能与灵活性。
|
2月前
|
十二、MyBatis分页插件
十二、MyBatis分页插件
81 17
【YashanDB 知识库】Mybatis-Plus 调用 YashanDB 怎么设置分页
**Mybatis-Plus 自动分页配置问题简介** Mybatis-Plus 是 Mybatis 的增强工具,简化 CRUD 操作并适配多种数据库,包括 YashanDB。自动分页配置错误会导致应用开发受影响。解决方法:1. 配置 pagehelper 为 oracle 或 mysql;2. 设置分页拦截器为 oracle 或 mysql。确保返回设置后的对象。正确配置后可在 service 层使用 page 方法实现自动分页。
Mybatis拦截器实现公共字段填充
通过使用MyBatis拦截器,可以实现对公共字段的自动填充,简化代码,提高开发效率。拦截器通过拦截SQL操作,在插入和更新操作时自动填充公共字段,使得开发者不再需要手动设置这些字段。本文详细介绍了实现步骤,并通过示例代码展示了具体实现方法,希望能为您的开发工作提供实用的指导和帮助。
148 13
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
MyBatis-Plus是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。本文讲解了最新版MP的使用教程,包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段等核心功能。
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等