6.Mybatis分页插件(PageHelper),解决PageHelper.startPage()不安全分页

目录


Mybatis专栏目录(点击进入…)


Mybatis%E5%88%86%E9%A1%B5%E6%8F%92%E4%BB%B6%EF%BC%88PageHelper%EF%BC%89%EF%BC%8C%E8%A7%A3%E5%86%B3PageHelper.startPage%28%29%E4%B8%8D%E5%AE%89%E5%85%A8%E5%88%86%E9%A1%B5


Mybatis分页插件(PageHelper)

1.引入分页插件(依赖/Jar)

引入分页插件有下面2种方式,推荐使用Maven方式

(1)引入Jar包

可以从下面的地址中下载最新版本的jar包
https://oss.sonatype.org/content/repositories/releases/com/github/pagehelper/pagehelper/

http://repo1.maven.org/maven2/com/github/pagehelper/pagehelper/

由于使用了sql解析工具,还需要下载jsqlparser.jar:
http://repo1.maven.org/maven2/com/github/jsqlparser/jsqlparser/0.9.5/

(2)使用Maven在pom.xml中添加如下依赖。此版本依赖mybatis 3.4.6
<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper</artifactId>
	<version>5.1.8</version>
</dependency>
<!-- 编译依赖项 -->
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>1.2</version>
</dependency>

最新版本号可以从首页查看

(3)Spring Boot Starter(启动器)
//推荐使用下面这种方式
<!--pagehelper-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.3</version>
</dependency>

2.配置拦截器插件

特别注意,新版拦截器是com.github.pagehelper.PageInterceptor
com.github.pagehelper.PageHelper现在是一个特殊的dialect实现类,是分页插件的默认实现类,提供了和以前相同的用法

(1)在MyBatis核心配置文件(Xml)中配置拦截器插件
<plugins>
	<!-- com.github.pagehelper为PageHelper类所在包名 -->
	<plugin interceptor="com.github.pagehelper.PageInterceptor">
		<property name="helperDialect" value="mysql" />
	</plugin>
</plugins>

plugins在配置文件中的位置必须符合要求(顺序),否则会报错
顺序如下:
①properties
②settings
③typeAliases
④typeHandlers
⑤objectFactory
⑥objectWrapperFactory
⑦plugins
⑧environments
⑨databaseIdProvider
⑩mapper

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

使用Spring的属性配置方式,可以使用plugins属性像下面这样配置:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<!-- 注意其他配置 -->
	<property name="plugins">
		<array>
			<bean class="com.github.pagehelper.PageInterceptor">
				<property name="properties">
					<!--使用下面的方式配置参数,一行配置一个 -->
					<value>
						<!--使用的数据库类型 -->
						helperDialect=mysql
						reasonable=true
						supportMethodsArguments=true
						params=count=countSql
						autoRuntimeDialect=true
					</value>
				</property>
			</bean>
		</array>
	</property>
</bean>
(3)Spring Boot(application.properties)
#分页插件
pagehelper.helper-dialect=mysql
pagehelper.params=count=countSql
pagehelper.reasonable=true
pagehelper.support-methods-arguments=true
分页插件参数介绍

分页插件提供了多个可选参数,这些参数使用时,按照上面两种配置方式中的示例配置即可

dialect :默认情况下会使用PageHelper方式进行分页,如果想要实现自己的分页逻辑,可以实现
Dialect(com.github.pagehelper.Dialect)接口,然后配置该属性为实现类的全限定名称

下面几个参数都是针对默认dialect情况下的参数。使用自定义dialect实现时,下面的参数没有任何作用

(1)helperDialect:数据库方言

分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。可以配置helperDialect属性来指定分页插件使用哪种方言

配置时,可以使用下面的缩写值:
oracle、mysql、mariadb、sqlite、hsqldb、postgresql、db2
sqlserver、informix、h2、sqlserver2012、derby

特别注意 :使用Sql Server 2012数据库时,需要手动指定为Sql Server 2012,否则会使用Sql Server
2005的方式进行分页。也可以实现AbstractHelperDialect,然后配置该属性为实现类的全限定名称即可使用自定义的实现方法。

(2)offsetAsPageNum:RowBounds的offset作pageNum

该参数对使用RowBounds作为分页参数时有效。当该参数设置为true时,会将RowBounds中的offset参数当成pageNum使用
,可以用页码和页面大小两个参数进行分页(默认值为false)

(3)rowBoundsWithCount:RowBounds进行count查询

该参数对使用RowBounds作为分页参数时有效。当该参数设置为true时,使用RowBounds分页会进行count查询(默认值为false)

(4)pageSizeZero:查询全部结果

当该参数设置为true时。如果pageSize = 0或者RowBounds.limit = 0就会查询出全部的结果
(相当于没有执行分页查询,但是返回结果仍然是Page类型)(默认值为false)

(5)reasonable:查询第一页、最后一页、参数查询

分页合理化参数,默认值为false。当该参数设置为true时,pageNum <=0时会查询第一页,
pageNum>pages
(超过总数时),会查询最后一页。为false时,直接根据参数进行查询

(6)params:参数映射

为了支持startPage(Object
params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值,可以配置pageNum、pageSize、count、pageSizeZero、reasonable

不配置映射的用默认值,默认值为

pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero
(7)supportMethodsArguments

支持通过Mapper接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面params配置的字段中取值,查找到合适的值时就会自动分页。使用方法可以参考测试代码中的com.github.pagehelper.test.basic包下的ArgumentsMapTest和ArgumentsObjTest

(8)autoRuntimeDialect

设置为true时,允许在运行时根据多数据源自动识别对应方言的分页(不支持自动选择sqlserver2012,只能使用sqlserver),用法和注意事项参考下面的场景五(默认值为
false)

(9)closeConn:是否关闭数据库连接

当使用运行时动态数据源或没有设置helperDialect属性自动获取数据库类型时,会自动获取一个数据库连接,通过该属性来设置是否关闭获取的这个连接,默认true关闭,设置为false后,不会关闭获取的连接,这个参数的设置要根据自己选择的数据源来决定(默认值为true)

重要提示:
当offsetAsPageNum=false的时候,由于PageNum问题,RowBounds查询的时候reasonable会强制为false。使用PageHelper.startPage()不受影响

如何选择配置这些参数
场景一

如果仍然在用类似Mybatis式的命名空间调用方式,也许会用到rowBoundsWithCount,分页插件对RowBounds支持和MyBatis默认的方式是一致,默认情况下不会进行count查询,如果想在分页查询时进行count查询,以及使用更强大的PageInfo类,需要设置该参数为true
注意:PageRowBounds想要查询总数也需要配置该属性为true

场景二

如果仍然在用类似Mybatis式的命名空间调用方式,RowBounds中的两个参数offset、limit不如
pageNum、pageSize容易理解,可以使用offsetAsPageNum参数,将该参数设置为true后,offset会当成pageNum使用,limit和pageSize含义相同

场景三

如果觉得某个地方使用分页后,仍然想通过控制参数查询全部的结果,可以配置pageSizeZero为
true,配置后,当pageSize=0或者RowBounds.limit = 0就会查询出全部的结果

场景四

如果分页插件使用于类似分页查看列表式的数据。如新闻列表、软件列表,希望用户输入的页数不在合法范围(第一页到最后一页之外)时能够正确的响应到正确的结果页面,那么可以配置reasonable为true,这时如果pageNum<=0会查询第一页,如果pageNum>总页数会查询最后一页

场景五

如果在Spring中配置了动态数据源。并且连接不同类型的数据库,这时可以配置
autoRuntimeDialect为true,这样在使用不同数据源时,会使用匹配的分页进行查询。这种情况下,还需要特别注意closeConn参数,由于获取数据源类型会获取一个数据库连接,所以需要通过这个参数来控制获取连接后,是否关闭该连接。
默认为true,有些数据库连接关闭后就没法进行后续的数据库操作。而有些数据库连接不关闭就会很快由于连接数用完而导致数据库无响应。所以在使用该功能时,特别需要注意你使用的数据源是否需要关闭数据库连接。

当不使用动态数据源而只是自动获取 helperDialect
时,数据库连接只会获取一次,所以不需要担心占用的这一个连接是否会导致数据库出错,但是最好也根据数据源的特性选择是否关闭连接

分页注意
(1)PageHelper.startPage()重要提示

只有紧跟在PageHelper.startPage方法后的第一个Mybatis的查询(Select)方法会被分页

(2)不要配置多个分页插件

请不要在系统中配置多个分页插件(使用Spring时。mybatis-config.xml和Spring配置方式,选择其中一种,不要同时配置多个分页插件)

(3)分页插件不支持带有for update语句的分页

对于带有for update的sql,会抛出运行时异常,对于这样的sql建议手动分页,毕竟这样的sql需要重视

(4)分页插件不支持嵌套结果映射

由于嵌套结果方式会导致结果集被折叠,因此分页查询的结果在折叠后总数会减少,所以无法保证分页结果数量正确

3.常用分页方式详细

(1)RowBounds方式的调用(mybatis)

Mybatis提供了一个简单的逻辑分页使用类RowBounds,在DefaultSqlSession提供的某些查询接口中我们可以看到RowBounds是作为参数用来进行分页,逻辑分页会将所有的结果都查询到,然后根据RowBounds中提供的offset和limit值来获取最后的结果。

RowBounds();
RowBounds(int offset, int limit);  

参数:
offset :偏移量
limit :每页展示多少数据

List<Student> list = studentMapper.find(new RowBounds(0, 10));
Page page = ((Page) list;

Page<Student>  page = studentMapper.find(new RowBounds(0, 10));

mappep.xml里面正常配置,不用对rowBounds任何操作。mybatis的拦截器自动操作rowBounds进行分页。使用RowBounds最大好处就是节省了在xml再拼装limit
总结:Mybatis的逻辑分页比较简单,简单来说就是取出所有满足条件的数据,然后舍弃掉前面offset条数据,然后再取剩下的数据的limit条

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

分页插件检测到使用了RowBounds参数时,就会对该查询进行物理分页。关于这种方式的调用,有两个特殊的参数是针对RowBounds(场景一和场景二)

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

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

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

(2)PageHelper.offsetPage()静态方法
//start:开始处,rowNum:数目
PageHelper.offsetPage(int offset, int limit);
List<User> list = userMapper.selectAll();

跟startPage()使用一样,只是限制、参数不同。

(3)PageHelper.startPage静态方法调用(推荐)

除了PageHelper.startPage方法外,还提供了类似用法的PageHelper.offsetPage方法
在需要进行分页的MyBatis查询方法前调用PageHelper.startPage静态方法即可,紧跟在这个方法后的第一个MyBatis查询方法会被进行分页

//获取第1页,10条内容,默认查询总数count
PageHelper.startPage(1, 10);
//紧跟着的第一个select方法会被分页,后面的不会被分页,除非再次调用PageHelper.startPage()
List<User> list = userMapper.selectAll();

//分页时,实际返回的结果list类型是Page<E>,如果想取出分页信息,需要强制转换为Page<E>或者使用PageInfo类包装
// ①Page
Page page = (Page) list;
page.getTotal();

// ②PageInfo
PageInfo page = new PageInfo(list);
page.getTotal();
(4)使用参数方法方式

想要使用参数方式,需要配置supportMethodsArguments参数为 true,同时要配置params参数。

<plugins>
	<!-- com.github.pagehelper为PageHelper类所在包名 -->
	<plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- 配置supportMethodsArguments=true -->
		<property name="supportMethodsArguments" value="true" />
		<property name="params"
			value="pageNum=pageNumKey;pageSize=pageSizeKey;" />
	</plugin>
</plugins>

在MyBatis Mapper方法中

List<Country> selectByPageNumSize(
	@Param("user") User user,
	@Param("pageNumKey") int pageNum,
	@Param("pageSizeKey") int pageSize);

// 在代码中直接调用:
List<User> userList = userMapper.selectByPageNumSize(user, 1, 10);

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

(5)参数对象方式

除了上面这种方式外,如果 User 对象中包含这两个参数值,也可以有下面的方法:

// 如果pageNum和pageSize存在于User对象中,只要参数有值,也会被分页
public class User {
	// 其他fields
	// 下面两个参数名和params配置的名字一致
	private Integer pageNum;
	private Integer pageSize;
}

// 存在以下Mapper接口方法,不需要在xml处理后两个参数
public interface UserMapper {
	List<User> selectByPageNumSize(User user);
}

// 当user中的pageNum!=null&&pageSize!=null时,会自动分页
List<User> listUser = userMapper.selectByPageNumSize(user);

当从User中同时发现了pageNum Key和pageSize Key参数,这个方法就会被分页

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

(6)ISelect接口方式
//第六种,ISelect 接口方式
//jdk6,7用法,创建接口
Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() {
    @Override
    public void doSelect() {
        countryMapper.selectGroupBy();
    }
});

//jdk8 lambda用法
Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(()-> countryMapper.selectGroupBy());

//也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() {
    @Override
    public void doSelect() {
        countryMapper.selectGroupBy();
    }
});

//对应的lambda用法
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> countryMapper.selectGroupBy());

//count查询,返回一个查询语句的count数
long total = PageHelper.count(new ISelect() {
    @Override
    public void doSelect() {
        countryMapper.selectLike(country);
    }
});

//lambda
total = PageHelper.count(()->countryMapper.selectLike(country));

PageHelper安全调用

1.使用RowBounds和PageRowBounds参数方式是极其安全的

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

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

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

PageHelper.startPage()什么时候会导致不安全的分页?解决?

PageHelper.startPage()方法使用了静态的ThreadLocal参数,分页参数和线程是绑定的。只要可以保证在PageHelper方法调用后紧跟MyBatis查询方法,就是安全的。因为PageHelper在finally代码段中自动清除了ThreadLocal存储的对象

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

(1)不安全的用法

PageHelper.startPage(1, 10);
List<User> list;
if (param != null) {
	list = userMapper.selectAll(param);
} else {
	list = new ArrayList<User>();
}

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

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

List<User> list;
if (param != null) {
	PageHelper.startPage(1, 10);
	list = userMapper.selectAll(param);
} else {
	list = new ArrayList<User>();
}

这种写法就能保证安全

如果对此不放心,可以手动清理ThreadLocal存储的分页参数

List<User> list;
if (param != null) {
	PageHelper.startPage(1, 10);
	try {
		list = userMapper.selectAll(param);
	} finally {
		PageHelper.clearPage();
	}
} else {
	list = new ArrayList<User>();
}

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

学习网络安全技术的方法无非三种:

第一种是报网络安全专业,现在叫网络空间安全专业,主要专业课程:程序设计、计算机组成原理原理、数据结构、操作系统原理、数据库系统、 计算机网络、人工智能、自然语言处理、社会计算、网络安全法律法规、网络安全、内容安全、数字取证、机器学习,多媒体技术,信息检索、舆情分析等。

第二种是自学,就是在网上找资源、找教程,或者是想办法认识一-些大佬,抱紧大腿,不过这种方法很耗时间,而且学习没有规划,可能很长一段时间感觉自己没有进步,容易劝退。

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

第三种就是去找培训。

image.png

接下来,我会教你零基础入门快速入门上手网络安全。

网络安全入门到底是先学编程还是先学计算机基础?这是一个争议比较大的问题,有的人会建议先学编程,而有的人会建议先学计算机基础,其实这都是要学的。而且这些对学习网络安全来说非常重要。但是对于完全零基础的人来说又或者急于转行的人来说,学习编程或者计算机基础对他们来说都有一定的难度,并且花费时间太长。

第一阶段:基础准备 4周~6周

这个阶段是所有准备进入安全行业必学的部分,俗话说:基础不劳,地动山摇
image.png

第二阶段:web渗透

学习基础 时间:1周 ~ 2周:

① 了解基本概念:(SQL注入、XSS、上传、CSRF、一句话木马、等)为之后的WEB渗透测试打下基础。
② 查看一些论坛的一些Web渗透,学一学案例的思路,每一个站点都不一样,所以思路是主要的。
③ 学会提问的艺术,如果遇到不懂得要善于提问。
image.png

配置渗透环境 时间:3周 ~ 4周:

① 了解渗透测试常用的工具,例如(AWVS、SQLMAP、NMAP、BURP、中国菜刀等)。
② 下载这些工具无后门版本并且安装到计算机上。
③ 了解这些工具的使用场景,懂得基本的使用,推荐在Google上查找。

渗透实战操作 时间:约6周:

① 在网上搜索渗透实战案例,深入了解SQL注入、文件上传、解析漏洞等在实战中的使用。
② 自己搭建漏洞环境测试,推荐DWVA,SQLi-labs,Upload-labs,bWAPP。
③ 懂得渗透测试的阶段,每一个阶段需要做那些动作:例如PTES渗透测试执行标准。
④ 深入研究手工SQL注入,寻找绕过waf的方法,制作自己的脚本。
⑤ 研究文件上传的原理,如何进行截断、双重后缀欺骗(IIS、PHP)、解析漏洞利用(IIS、Nignix、Apache)等,参照:上传攻击框架。
⑥ 了解XSS形成原理和种类,在DWVA中进行实践,使用一个含有XSS漏洞的cms,安装安全狗等进行测试。
⑦ 了解一句话木马,并尝试编写过狗一句话。
⑧ 研究在Windows和Linux下的提升权限,Google关键词:提权
image.png
以上就是入门阶段

第三阶段:进阶

已经入门并且找到工作之后又该怎么进阶?详情看下图
image.png

给新手小白的入门建议:
新手入门学习最好还是从视频入手进行学习,视频的浅显易懂相比起晦涩的文字而言更容易吸收,这里我给大家准备了一套网络安全从入门到精通的视频学习资料包免费领取哦!

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值