PageHelper,数据绑定,动态SQL

发布于 18 天前 5 次阅读 1133 字 预计阅读时间: 5 分钟 JAVA


PageHelper

使用

//EmpServiceImpl
@Override
public PageResult page(EmpQueryParam queryParam) {
        //1. 设置PageHelper分页参数
        PageHelper.startPage(queryParam.getPage(), queryParam.getPageSize());
        //2. 执行查询
        List empList = empMapper.list(queryParam);
        //3. 封装分页结果
        Page p = (Page) empList;
        return new PageResult(p.getTotal(), p.getResult());
    }

注意:

  • PageHelper实现分页查询时,SQL语句的结尾一定一定一定不要加分号(;)。
  • PageHelper只会对紧跟在其后的第一条SQL语句进行分页处理。

application.yml中,引入如下配置

pagehelper:
  reasonable: true
  helper-dialect: mysql

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

数据绑定

package site.suiyue.pojo;

import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import java.time.LocalDate;

/**
 * 员工查询参数封装类
 */
@Data
public class EmpQueryParam {
    private Integer page = 1; // 页码,默认值为1
    private Integer pageSize = 10; // 每页大小,默认值为10
    private String name; // 姓名
    private Integer gender; // 性别
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate begin; // 入职开始时间
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate end; // 入职结束时间
}

为什么定义了一个实体类之后,下列代码简略了:

before:

@Slf4j
@RestController
@RequestMapping("/emps")
public class EmpController {

    @Autowired
    private EmpService empService;

    @GetMapping
    public Result page(@RequestParam(defaultValue = "1") Integer page,
                       @RequestParam(defaultValue = "10") Integer pageSize,
                       String name, Integer gender,
                       @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
                       @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
        log.info("查询请求参数: {}, {}, {}, {}, {}, {}", page, pageSize, name, gender, begin, end);
        PageResult pageResult = empService.page(page, pageSize);
        return Result.success(pageResult);
    }
}

after:

@GetMapping
public Result page(EmpQueryParam empQueryParam) {
    log.info("查询请求参数:{}", empQueryParam);
    PageResult pageResult = empService.page(empQueryParam);
    return Result.success(pageResult);
}
  • 方法参数只有一个 EmpQueryParam 对象。
  • Spring 会自动将请求参数(如 pagenamebegin 等)绑定到该对象的对应属性上。
  • 原来方法参数上的注解(如 @DateTimeFormat)移到了实体类的字段上,同样生效。

为什么不需要原来的那些参数和注解了?

  • 自动绑定:Spring 会调用 EmpQueryParam 的无参构造器创建对象,然后遍历请求参数,将参数名与对象的属性名匹配,通过 setter 方法赋值。
  • 默认值:原来 @RequestParam(defaultValue = "1") 的功能,现在通过实体类字段的初始值实现:private Integer page = 1;。当请求中没有 page 参数时,对象会保留这个初始值。
  • 类型转换@DateTimeFormat 移到 begin 和 end 字段上后,Spring 在将字符串 "2026-03-04" 赋值给 LocalDate 字段时会自动应用该格式。
  • 参数集中管理:所有查询参数的定义(名称、类型、默认值、格式)都集中在 EmpQueryParam 类中,便于复用和维护,也避免了多个接口重复编写相同参数列表。

动态SQL

<if>:判断条件是否成立,如果条件为true,则拼接SQL。

<where>:根据查询条件,来生成where关键字,并会自动去除条件前面多余的and或or。

concat 函数的基本作用

  • 定义concat(str1, str2, ...) 接收若干个字符串参数,将它们按顺序连接成一个新字符串。
  • 示例concat('Hello', ' ', 'World') 结果为 'Hello World'

为什么必须用 concat 而不是直接拼接?

初学者容易写出类似 '%#{name}%' 的错误写法,但这在 MyBatis 中行不通,原因在于:

(1) #{} 预编译占位符不能在字符串字面量内

  • '%#{name}%' 会被 MyBatis 当作普通字符串处理,其中的 #{name} 不会被解析成参数占位符,而是原样保留。最终传递给数据库的 SQL 可能变成:
e.name like '%#{name}%'
  • 数据库找不到名为 name 的列,或者将其视为固定字符串,查询结果必然出错。
  • 正确的方式是使用 concat 将 % 和参数值在 SQL 层面 拼接起来。#{name} 单独出现,会被替换成 ? 预编译占位符,参数值安全传入。

(2) 防止 SQL 注入

  • 使用 #{} 是预编译方式,MyBatis 会为参数值设置占位符 ?,并由数据库驱动进行转义处理。即使参数中包含特殊字符(如 %_'),也能正确转义,避免 SQL 注入风险。
  • 如果尝试用字符串拼接(如 '%' + #{name} + '%' 在某些数据库中可行),但更容易引入注入漏洞,且不同数据库语法不统一。