• SpringMVC 整合 Mybatis 和 pagehelper分页插件


    一、整合

    Spring + SpringMVC 是已经配置好了。以下是整合 MyBatis 和 pagehelper分页插件 的部分。

    1、导入依赖

    <!-- MyBatis 依赖 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>
    <!-- 整合依赖 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.5</version>
    </dependency>
    <!-- 使用 MyBatis-plus, 以上的两个依赖都不导入。
    	注:本人再内网开发,则在外网创建maven项目,导入以下依赖。
    	使用maven以war包方式打包,即可从lib目录获取jar包依赖 
    	基本的有 mybatis-plus-extension、mybatis-plus-core、mybatis-plus-annotation 
    -->
    <!-- MyBatis-plus 依赖 -->
    <!--
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus</artifactId>
        <version>3.5.2</version>
    </dependency>
    -->
    <!-- pagehelper 分页插件依赖 -->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper</artifactId>
        <version>4.1.1</version>
    </dependency>
    <!-- sql 解析工具(需要和 PageHelper 依赖的版本一致) -->
    <dependency>
        <groupId>com.github.jsqlparser</groupId>
        <artifactId>jsqlparser</artifactId>
        <version>x.x.x</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    2、进行配置

    2.1、配置类配置

    /**
     * @desc
     * @auth llp
     * @date 2022/6/22 10:41
     */
    @Configuration
    @MapperScan(basePackages = {"com.exmple.xxx.mapper"})
    public class MyBatisConfig {
    
        @Value("${xxx.datasource.url}")
        private String url;
        @Value("${xxx.datasource.username}")
        private String username;
        @Value("${xxx.datasource.password}")
        private String password;
    
        /** mybatis 配置文件路径 */
        private static final String CONFIG_LOCATION = "config/xxx/mybatis-config.xml";
        /** mybatis mapper文件路径 */
        private static final String MAPPER_LOCATION = "classpath:mapper/xxx/*.xml";
    
        /**
         * @desc 数据源配置
         * @auth llp
         * @date 2022/6/22 10:57
         * @return javax.sql.DataSource
         */
        @Bean(name = "mybatis_dataSource")
        public DataSource dataSource(){
            DruidDataSource druidDataSource = new DruidDataSource();
            druidDataSource.setUrl(url);
            druidDataSource.setUsername(username);
            druidDataSource.setPassword(password);
            return druidDataSource;
        }
    
       /**
         * @desc 可以想象为数据库连接池
         * @auth llp
         * @date 2022/6/22 10:57
         * @return org.apache.ibatis.session.SqlSessionFactory
         */
        @Bean(name = "mybatis_sqlSessionFactory")
        public SqlSessionFactory sqlSessionFactory() throws Exception {
        	// mybatis
            SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
            // mybatis-plus
            // MyBatisSqlSessionFactoryBean factoryBean = new MyBatisSqlSessionFactoryBean();
            factoryBean.setDataSource(dataSource());
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            factoryBean.setConfigLocation(new ClassPathResource(CONFIG_LOCATION));
            factoryBean.setMapperLocations(resolver.getResources(MAPPER_LOCATION));
            // pageHelper 分页插件配置
            // pageHelper 5.0 以后的版本使用 => new PageInterceptor()
            PageHelper pageHelper = new PageHelper();
            Properties properties = new Properties();
            // 4.0.0 以后版本可以不设置该参数 5.0 以前的版本使用
            properties.setProperty("dialect", "postgresql");
            // 5.0 后的版本使用以下
            // properties.setProperty("helperDialect", "postgresql");
            // reasonable:分页合理化参数,默认值为false。
    		// 当该参数设置为 true 时,pageNum<=0 时会查询第一页,pageNum>pages(超过总数时),会查询最后一页。
            // 默认false 时,直接根据参数进行查询。
            properties.setProperty("reasonable", "true");
            pageHelper.setProperties(properties);
            factoryBean.setPlugins(pageHelper);
    		// mybatis-plus 自动填充配置
    		// MetaObjectHandler 配置
    		// GlobalConfig globalConfig = new GlobalConfig();
    		// globalConfig.setMetaObjectHandler(new MyBatisPlusTimeMetaObjectHandler());
    		// globalConfig.setBanner(false);
    		// factoryBean.setGlobalConfig(globalConfig);
    
            return factoryBean.getObject();
        }
    
       /**
         * @desc 
         * @auth llp
         * @date 2022/6/22 10:57
         * @return org.apache.ibatis.session.SqlSessionFactory
         */
        @Bean(name = "mybatis_sqlSessionTemplate")
        public SqlSessionTemplate sqlSessionTemplate() throws Exception {
            return new SqlSessionTemplate(sqlSessionFactory());
        }
    
        /**
         * @desc 使用事物还需要使用 @EnableTransactionManagement 注解
         * @auth llp
         * @date 2022/6/22 11:10
         * @return org.springframework.jdbc.datasource.DataSourceTransactionManager
         */
        @Bean(name = "mybatis_transactionManager")
        public DataSourceTransactionManager  transactionManager(){
            return new DataSourceTransactionManager(dataSource());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99

    2.2、配置拦截器插件另外两种方法

    1)pageHelper 分页插件可在 Mybatis 配置文件中配置

    mybatis-config.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <!-- 
        plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
        properties, settings, typeAliases, 
    	typeHandlers, objectFactory,objectWrapperFactory, 
        plugins, 
        environments, databaseIdProvider, mappers
    -->
    <configuration>
    	<plugins>
            <!-- com.github.pagehelper为PageHelper类所在包名 -->
            <plugin interceptor="com.github.pagehelper.PageHelper">
                <!-- 4.0.0以后版本可以不设置该参数 -->
                <property name="dialect" value="mysql"/>
                <!-- 该参数默认为false -->
                <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
                <!-- 和startPage中的pageNum效果一样-->
                <property name="offsetAsPageNum" value="true"/>
                <!-- 该参数默认为false -->
                <!-- 设置为true时,使用RowBounds分页会进行count查询 -->
                <property name="rowBoundsWithCount" value="true"/>
                <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
                <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)
                <property name="pageSizeZero" value="true"/>-->
                <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
                <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
                <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
                <property name="reasonable" value="true"/>
                <!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
                <!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
                <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 -->
                <!-- 不理解该含义的前提下,不要随便复制该配置 
                <property name="params" value="pageNum=start;pageSize=limit;"/>    -->
            </plugin>
      	</plugins>
    </configuration>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    2) 在 Spring 配置文件中配置拦截器插件

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    	<!-- 注意其他配置 -->
    	<property name="plugins">
        	<array>
            	<bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
                        <!--使用下面的方式配置参数,一行配置一个 -->
                        <value>
                            dialect=postgresql
                            reasonable=true
                        </value>
                    </property>
            	</bean>
        	</array>
    	</property>
    </bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3、使用测试

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.example.xxx.mapper.PersonInfoMapper">
        <select id="findAllUser" resultType="com.example.xxx.entity.PersonInfoEntity">
            select * from person_info_test
        </select>
    </mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4、自动填充类

    /**
     * @desc
     * @auth llp
     * @date 2022/6/23 16:59
     */
    @Component
    public class MyBatisPlusTimeMetaObjectHandler implements MetaObjectHandler {
        private static final Logger LOG = LoggerFactory.getLogger(MyBatisPlusTimeMetaObjectHandler.class);
        /**
         * @desc 插入的时候自动填充
         * @auth llp
         * @date 2022/6/23 16:59
         * @param metaObject
         */
        @Override
        public void insertFill(MetaObject metaObject) {
            LOG.info("start insert fill....");
            Long cur = System.currentTimeMillis();
    
            this.strictInsertFill(metaObject, "createTime", Long.class, cur);
            this.strictInsertFill(metaObject, "updateTime", Long.class, cur);
        }
    
        /**
         * @desc 插入或者更新的时候自动填充
         * @auth llp
         * @date 2022/6/23 16:59
         * @param metaObject
         */
        @Override
        public void updateFill(MetaObject metaObject) {
            LOG.info("start update fill....");
            Long cur = System.currentTimeMillis();
    
            this.setFieldValByName("updateTime", cur, metaObject);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    二、PageHelper 分页插件

    推荐查看文档学习:Mybatis-PageHelper-HowToUse

    1、注意事项

    ​ 1)PageHelper.startPage方法重要提示。只有紧跟在PageHelper.startPage方法后的第一个Mybatis的 查询(Select) 方法会被分页。

    ​ 2)请不要在系统中配置多个分页插件(使用Spring时,mybatis-config.xmlSpring<bean>配置方式,请选择其中一种,不要同时配置多个分页插件)!

    ​ 3)分页插件不支持带有for update语句的分页。 对于带有for update的sql,会抛出运行时异常,对于这样的sql建议手动分页,毕竟这样的sql需要重视。

    ​ 4)分页插件不支持嵌套结果映射。 由于嵌套结果方式会导致结果集被折叠,因此分页查询的结果在折叠后总数会减少,所以无法保证分页结果数量正确。

    2、如何在代码中使用

    2.1、RowBounds方式的调用

    // 第一种,RowBounds方式的调用
    List<User> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(0, 10));
    
    • 1
    • 2

    使用这种调用方式时,你可以使用RowBounds参数进行分页,这种方式侵入性最小,我们可以看到,通过RowBounds方式调用只是使用了这个参数,并没有增加其他任何内容。

    分页插件检测到使用了RowBounds参数时,就会对该查询进行物理分页

    注: 不只有命名空间方式可以用RowBounds,使用接口的时候也可以增加RowBounds参数,例如:

    //这种情况下也会进行物理分页查询
    List<User> selectAll(RowBounds rowBounds);
    
    • 1
    • 2

    注意: 由于默认情况下的 RowBounds 无法获取查询总数,分页插件提供了一个继承自 RowBoundsPageRowBounds,这个对象中增加了 total 属性,执行分页查询后,可以从该属性得到查询总数。

    2.2、Mapper接口方式的调用

    // 第二种,Mapper接口方式的调用,推荐这种使用方式。
    PageHelper.startPage(1, 10);
    List<User> list = userMapper.selectIf(1);
    
    // 第三种,Mapper接口方式的调用,推荐这种使用方式。
    PageHelper.offsetPage(1, 10);
    List<User> list = userMapper.selectIf(1);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    PageHelper.startPage 静态方法调用

    除了 PageHelper.startPage 方法外,还提供了类似用法的 PageHelper.offsetPage 方法。

    在你需要进行分页的 MyBatis 查询方法前调用 PageHelper.startPage 静态方法即可,紧跟在这个方法后的第一个MyBatis 查询方法会被进行分页。

    例一:

    // 获取第1页,10条内容,默认查询总数count
    PageHelper.startPage(1, 10);
    // 紧跟着的第一个select方法会被分页
    List<User> list = userMapper.selectIf(1);
    assertEquals(2, list.get(0).getId());
    assertEquals(10, list.size());
    // 分页时,实际返回的结果list类型是Page<E>,如果想取出分页信息,需要强制转换为Page<E>
    assertEquals(182, ((Page) list).getTotal());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    例二:

    // request: url?pageNum=1&pageSize=10
    // 支持 ServletRequest,Map,POJO 对象,需要配合 params 参数
    PageHelper.startPage(request);
    // *紧跟着的第一个select方法会被分页
    List<User> list = userMapper.selectIf(1);
    // *后面的不会被分页,除非再次调用 PageHelper.startPage
    List<User> list2 = userMapper.selectIf(null);
    // list1
    assertEquals(2, list.get(0).getId());
    assertEquals(10, list.size());
    // 分页时,实际返回的结果list类型是Page<E>,如果想取出分页信息,需要强制转换为Page<E>,
    // 或者使用PageInfo类(下面的例子有介绍)
    assertEquals(182, ((Page) list).getTotal());
    // list2
    assertEquals(1, list2.get(0).getId());
    assertEquals(182, list2.size());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    例三,使用PageInfo的用法:

    // 获取第1页,10条内容,默认查询总数count
    PageHelper.startPage(1, 10);
    List<User> list = userMapper.selectAll();
    // 用PageInfo对结果进行包装
    PageInfo page = new PageInfo(list);
    // 测试PageInfo全部属性
    // PageInfo包含了非常全面的分页属性
    assertEquals(1, page.getPageNum());
    assertEquals(10, page.getPageSize());
    assertEquals(1, page.getStartRow());
    assertEquals(10, page.getEndRow());
    assertEquals(183, page.getTotal());
    assertEquals(19, page.getPages());
    assertEquals(1, page.getFirstPage());
    assertEquals(8, page.getLastPage());
    assertEquals(true, page.isFirstPage());
    assertEquals(false, page.isLastPage());
    assertEquals(false, page.isHasPreviousPage());
    assertEquals(true, page.isHasNextPage());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2.3、参数方法调用

    // 第四种,参数方法调用
    // 存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
    public interface CountryMapper {
        List<User> selectByPageNumSize(
                @Param("user") User user,
                @Param("pageNum") int pageNum, 
                @Param("pageSize") int pageSize);
    }
    // 配置 supportMethodsArguments=true
    // 在代码中直接调用:
    List<User> list = userMapper.selectByPageNumSize(user, 1, 10);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    // 第五种,参数对象
    // 如果 pageNum 和 pageSize 存在于 User 对象中,只要参数有值,也会被分页
    // 有如下 User 对象
    public class User {
        // 其他fields
        // 下面两个参数名和 params 配置的名字一致
        private Integer pageNum;
        private Integer pageSize;
    }
    // 存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
    public interface CountryMapper {
        List<User> selectByPageNumSize(User user);
    }
    // 当 user 中的 pageNum!= null && pageSize!= null 时,会自动分页
    List<User> list = userMapper.selectByPageNumSize(user);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    想要使用参数方式,需要配置 supportMethodsArguments 参数为 true,同时要配置 params 参数。 例如下面的配置:

    <plugins>
        <!-- com.github.pagehelper为PageHelper类所在包名 -->
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
            <property name="supportMethodsArguments" value="true"/>
            <property name="params" value="pageNum=pageNumKey;pageSize=pageSizeKey;"/>
    	</plugin>
    </plugins>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在 MyBatis 方法中:

    List<User> selectByPageNumSize(
            @Param("user") User user,
            @Param("pageNumKey") int pageNum, 
            @Param("pageSizeKey") int pageSize);
    
    • 1
    • 2
    • 3
    • 4

    当调用这个方法时,由于同时发现pageNumKeypageSizeKey 参数,这个方法就会被分页。params 提供的几个参数都可以这样使用。

    使用 POJO 对象时:

    注意: pageNumpageSize 两个属性同时存在才会触发分页操作,在这个前提下,其他的分页参数才会生效。

    2.4、jdk8 lambda 用法

    // 第六种,ISelect 接口方式
    // jdk8 lambda用法
    Page<User> page = PageHelper.startPage(1, 10).doSelectPage(() -> userMapper.selectGroupBy());
    
    // 也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage
    pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> userMapper.selectGroupBy());
    
    // count查询,返回一个查询语句的count数
    total = PageHelper.count(() -> userMapper.selectLike(user));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3、PageHelper 安全调用

    1)使用 RowBoundsPageRowBounds 参数方式是极其安全的

    2)使用参数方式是极其安全的

    3)使用 ISelect 接口调用是极其安全的

    ​ ISelect 接口方式除了可以保证安全外,还特别实现了将查询转换为单纯的 count 查询方式,这个方法可以将任意的查询方法,变成一个 select count(*) 的查询方法。

    4)什么时候会导致不安全的分页?

    PageHelper 方法使用了静态的 ThreadLocal 参数,分页参数和线程是绑定的。

    只要你可以保证在 PageHelper 方法调用后紧跟 MyBatis 查询方法,这就是安全的。因为 PageHelperfinally 代码段中自动清除了 ThreadLocal 存储的对象。

    如果代码在进入 Executor 前发生异常,就会导致线程不可用,这属于人为的 Bug(例如接口方法和 XML 中的不匹配,导致找不到 MappedStatement 时), 这种情况由于线程不可用,也不会导致 ThreadLocal 参数被错误的使用。

    但是如果你写出下面这样的代码,就是不安全的用法:

    PageHelper.startPage(1, 10);
    List<User> list;
    if(param1 != null){
        list = userMapper.selectIf(param1);
    } else {
        list = new ArrayList<User>();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这种情况下由于 param1 存在 null 的情况,就会导致 PageHelper 生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。

    上面这个代码,应该写成下面这个样子:

    List<User> list;
    if(param1 != null){
        PageHelper.startPage(1, 10);
        list = userMapper.selectIf(param1);
    } else {
        list = new ArrayList<User>();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这种写法就能保证安全。

    如果你对此不放心,你可以手动清理 ThreadLocal 存储的分页参数,可以像下面这样使用:

    List<User> list;
    if(param1 != null){
        PageHelper.startPage(1, 10);
        try{
            list = userMapper.selectAll();
        } finally {
            PageHelper.clearPage();
        }
    } else {
        list = new ArrayList<User>();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这么写很不好看,而且没有必要。

    三、MyBatis Mapper

    https://mapper.mybatis.io/

  • 相关阅读:
    JZ19 正则表达式匹配
    万字详解 | Java 流式编程
    Spring Cloud Alibaba整合Seata实战
    C++官网 Tutorials C++ Language Program structure:Functions
    18数据接口和selenium
    视频号爆款数据在哪里获取?
    戏说领域驱动设计(十八)——内验
    力扣 -- 712. 两个字符串的最小ASCII删除和
    如何对待工作中的失误?
    VUEX全网最详细讲解之一
  • 原文地址:https://blog.csdn.net/weixin_43989102/article/details/125418862