干翻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);
        }
    }
相关文章
|
3月前
|
SQL XML Java
mybatis-源码深入分析(一)
mybatis-源码深入分析(一)
|
22天前
|
SQL Java 数据库连接
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
MyBatis-Plus是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。本文讲解了最新版MP的使用教程,包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段等核心功能。
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
|
1月前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
2月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
65 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
3月前
|
SQL Java 数据库连接
解决mybatis-plus 拦截器不生效--分页插件不生效
本文介绍了在使用 Mybatis-Plus 进行分页查询时遇到的问题及解决方法。依赖包包括 `mybatis-plus-boot-starter`、`mybatis-plus-extension` 等,并给出了正确的分页配置和代码示例。当分页功能失效时,需将 Mybatis-Plus 版本改为 3.5.5 并正确配置拦截器。
849 6
解决mybatis-plus 拦截器不生效--分页插件不生效
|
2月前
|
前端开发 Java 数据库连接
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
本文是一份全面的表白墙/留言墙项目教程,使用SpringBoot + MyBatis技术栈和MySQL数据库开发,涵盖了项目前后端开发、数据库配置、代码实现和运行的详细步骤。
65 0
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
|
2月前
|
Java 数据库连接 mybatis
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
该文档详细介绍了如何在Springboot Web项目中整合Mybatis,包括添加依赖、使用`@MapperScan`注解配置包扫描路径等步骤。若未使用`@MapperScan`,系统会自动扫描加了`@Mapper`注解的接口;若使用了`@MapperScan`,则按指定路径扫描。文档还深入分析了相关源码,解释了不同情况下的扫描逻辑与优先级,帮助理解Mybatis在Springboot项目中的自动配置机制。
151 0
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
|
3月前
|
SQL XML Java
springboot整合mybatis-plus及mybatis-plus分页插件的使用
这篇文章介绍了如何在Spring Boot项目中整合MyBatis-Plus及其分页插件,包括依赖引入、配置文件编写、SQL表创建、Mapper层、Service层、Controller层的创建,以及分页插件的使用和数据展示HTML页面的编写。
springboot整合mybatis-plus及mybatis-plus分页插件的使用
|
4月前
|
Web App开发 前端开发 关系型数据库
基于SpringBoot+Vue+Redis+Mybatis的商城购物系统 【系统实现+系统源码+答辩PPT】
这篇文章介绍了一个基于SpringBoot+Vue+Redis+Mybatis技术栈开发的商城购物系统,包括系统功能、页面展示、前后端项目结构和核心代码,以及如何获取系统源码和答辩PPT的方法。
|
4月前
|
SQL Java 关系型数据库
MyBatis-Plus 分页魅力绽放!紧跟技术热点,带你领略数据分页的高效与便捷
【8月更文挑战第29天】在 Java 开发中,数据处理至关重要,尤其在大量数据查询与展示时,分页功能尤为重要。MyBatis-Plus 作为一款强大的持久层框架,提供了便捷高效的分页解决方案。通过封装数据库分页查询语句,开发者能轻松实现分页功能。在实际应用中,只需创建 `Page` 对象并设置页码和每页条数,再通过 `QueryWrapper` 构建查询条件,调用 `selectPage` 方法即可完成分页查询。MyBatis-Plus 不仅生成分页 SQL 语句,还自动处理参数合法性检查,并支持条件查询和排序等功能,极大地提升了系统性能和稳定性。
65 0