使用spring-validation和@RequestParam(required = false)字符串默认值的校验问题

在Spring MVC中,使用@RequestParam注解时,若参数未提供,默认为null(除基本类型)。当结合spring-validation进行校验时,对于字符串参数,空字符串会引发校验失败。解决方案包括:请求前移除空值参数、自定义验证规则或利用Validator的组合策略,允许空字符串。本文探讨了这些方法并提出了一种更优雅的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

众所周知,使用@RequestParam(required = false) 封装请求参数的时候,如果客户端不提交参数,或者是只声明参数,并不赋值。那么方法的形参值,默认为null(基本数据类型除外)。

一个Controller方法,有2个参数

@GetMapping
public Object update(@RequestParam(value = "number", required = false) Integer number,
				@RequestParam(value = "phone", required = false) String phone) {
	LOGGER.info("number={}, phone={}", number, phone);
	return Message.success(phone);
}

很简单的一个Controller方法。有两个参数,都不是必须的。只是这俩参数的数据类型不同。

// 都不声明参数
http://localhost:8080/test
日志输出:number=null, phone=null

// 都只声明参数,但是不赋值
http://localhost:8080/test?number=&phone=
日志输出出:number=null, phone=

这里可以看出,String类型的参数。在声明,不赋值的情况下。默认值为空字符串。

使用spring-validation遇到@RequestParam(required = false)字符串参数的问题

一个验证手机号码的注解

极其简单,通过正则验证字符串是否是手机号码

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.Pattern;


@Retention(RUNTIME)
@Target(value = { ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Constraint(validatedBy = {})
@ReportAsSingleViolation
@Pattern(regexp = "^1[3-9]\\d{9}$")
public @interface Phone {
	String message() default "手机号码不正确,只支持大陆手机号码";
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
}

一般这样使用

private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class);

@GetMapping
public Object update(@RequestParam(value = "number", required = false) Integer number,
				@RequestParam(value = "phone", required = false) @Phone String phone) {
	LOGGER.info("number={}, phone={}", number, phone);
	return Message.success(phone);
}

这是一个修改接口,允许用户修改自己的手机号码,但手机号码并不是必须的,允许以空字符串的形式存储在数据库。通俗的说就是,phone参数,要么是一个合法的手机号码。要么是空字符串,或者null。

客户端发起了请求

// 假如用户什么也不输入,清空了 phone 输入框,客户端js序列化表单后提交。
http://localhost:8080/test?number=&phone=

果然得到了异常:
javax.validation.ConstraintViolationException: update.phone: 手机号码不正确,只支持大陆手机号码

很显然,空字符串 “”,并不符合手机号码的正则校验。

这种情况就是,在校验规则,和默认值之间,出现了一点点冲突

解决办法

求前端大哥改巴改巴

提交之前,遍历一下请求参数。把空值参数,从请求体中移除。那么后端接收到的形参就是,null。是业务可以接受的数据类型。

修改验证规则

这个也不算难,自己修改一下验证的正则,或者重新实现一个自定义的 ConstraintValidator,允许手机号码为空字符串。但是,也有一个问题,这个注解就不能用在必填的手机号码参数上了。例如:注册业务,因为它的规则是允许空字符串的。

当然,也可以维护多个不同的验证规则注解。
@Phone 验证必须是标准手机号码
@Retention(RUNTIME)
@Target(value = { ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Constraint(validatedBy = {})
@ReportAsSingleViolation
@Pattern(regexp = "^1[3-9]\\d{9}$")
public @interface Phone {
	String message() default "手机号码不正确,只支持大陆手机号码";
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
}
@PhoneOrEmpty 可以是空字符串或者标准的手机号码
@Retention(RUNTIME)
@Target(value = { ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Constraint(validatedBy = {})
@ReportAsSingleViolation
@Pattern(regexp = "^(1[3-9]\\d{9})|(.{0})$")  
public @interface PhoneOrEmpty {
	String message() default "手机号码不正确,只支持大陆手机号码";
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
}

好了,这俩可以用在不同的验证地方。唯一的不同就是验证的正则不同。这也是让我觉得不舒服的地方。需要维护两个正则表达式

一种我认为比较"优雅"的方式

还是一样,定义不同的注解来处理不同的验证场景。但是,我并不选择自立门户(单独维护一个正则),而是在@Phone的基础上,进行一个加强

@Retention(RUNTIME)
@Target(value = { ElementType.FIELD, ElementType.PARAMETER })
@Constraint(validatedBy = {})
@ReportAsSingleViolation

@Phone						// 使用已有的@Phone作为校验规则,参数必须是一个合法的手机号码
@Length(max = 0, min = 0)	// 使用Hiberante提供的字符串长度校验规则,在这里,表示惨参数字符串的长度必须:最短0,最长0(就是空字符串)

@ConstraintComposition(CompositionType.OR)	// 核心的来了,这个注解表示“多个验证注解之间的逻辑关系”,这里使用“or”,满足任意即可
public @interface PhoneOrEmpty {
	String message() default "手机号码不正确,只支持大陆手机号码";
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
}

核心的说明,都在上面的注释代码上了。

自定义使用组合Constraint,在原来@Phone的验证规则上,再添加一个 @Length(max = 0, min = 0)规则。使用@ConstraintComposition描述这两个验证规则的逻辑关系。

ConstraintComposition只有一个枚举属性

@Documented
@Target({ ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface ConstraintComposition {
	/**
	 * The value of this element specifies the boolean operator,
	 * namely disjunction (OR), negation of the conjunction (ALL_FALSE),
	 * or, the default, simple conjunction (AND).
	 *
	 * @return the {@code CompositionType} value
	 */
	CompositionType value() default AND;
}
public enum CompositionType {
	OR,   // 多个验证规则中,只要有一个通过就算验证成功
	AND,  // 多个验证规则中,必须全部通过才算成功(默认)
	ALL_FALSE // 多个验证规则中,必须全部失败,才算通过(少见)
}

试试看

Controller
@GetMapping
public Object update (@RequestParam(value = "number", required = false) Integer number,
				@RequestParam(value = "phone", required = false) @PhoneOrEmpty String phone) {
	LOGGER.info("number={}, phone={}", number, phone);
	return Message.success(phone);
}
空参数:空字符串,是合法的

合法参数:更是合法

非法参数:验证失败

原文:https://springboot.io/t/topic/2312

### MyBatis Plus 分页查询 使用 QueryWrapper 参数校验默认值设置 在 MyBatis Plus 中,`QueryWrapper` `LambdaQueryWrapper` 是常用的条件构造器工具类。通过它们可以方便地构建复杂的 SQL 查询条件,并结合分页功能完成高效的数据检索。 #### 实现分页查询方法 `selectStudentCourse1` 以下是针对 `selectStudentCourse1` 方法的具体实现方式: ```java import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.springframework.stereotype.Service; import java.util.List; @Service public class StudentService { public IPage<StudentCourse> selectStudentCourse1(int index, Integer classId, String name) { // 设置分页对象,默认每页大小为 10 int pageSize = Math.max(index, 1); // 确保索引有效 Page<StudentCourse> page = new Page<>(pageSize, 10); // 构建 QueryWrapper 并添加条件 QueryWrapper<StudentCourse> queryWrapper = new QueryWrapper<>(); // 如果 classId 不为空,则加入等于条件 if (classId != null && classId > 0) { queryWrapper.eq("class_id", classId); } // 如果 name 不为空,则加入模糊匹配条件 if (name != null && !name.isEmpty()) { queryWrapper.like("student_name", name); } // 执行分页查询 return studentCourseMapper.selectPage(page, queryWrapper); } } ``` 上述代码实现了以下几个关键点: - **分页初始化**:创建分页对象时设置了默认的页面大小为 10 行[^4]。 - **参数校验默认值设定**:对于输入参数进行了简单的有效性判断,例如确保 `index` 至少为 1,避免非法值引发错误[^1]。 - **动态条件拼接**:利用 `QueryWrapper` 动态添加查询条件,支持按班级 ID 或学生姓名筛选[^2]。 --- #### 示例代码中的注意事项 为了进一步增强代码的安全性可维护性,还可以引入以下改进措施: ##### 1. 参数校验框架集成 如果项目中已经集成了 Spring Validation 框架,可以通过注解的方式对入参进行更严格的校验。例如,在 Controller 层定义如下接口: ```java @RestController @RequestMapping("/students") public class StudentController { @GetMapping("/courses") public ResponseEntity<IPage<StudentCourse>> getStudentCourses( @RequestParam(required = false, defaultValue = "1") int pageIndex, @RequestParam(required = false) Integer classId, @RequestParam(required = false) String name) { return ResponseEntity.ok(studentService.selectStudentCourse1(pageIndex, classId, name)); } } ``` 在此基础上,可以在 Service 层或 DTO 类上增加校验规则,比如限定字符串长度、数值范围等。 ##### 2. 默认值处理优化 为了避免硬编码过多的默认值逻辑,建议将这些配置提取到全局常量文件或者配置中心统一管理。例如: ```properties # application.properties default.page.size=10 min.index.value=1 ``` 随后在 Java 代码中读取对应的属性值作为默认参数[^3]。 --- ### 总结 以上展示了如何基于 MyBatis Plus 的 `QueryWrapper` 完成分页查询的功能开发,同时兼顾了参数校验默认值设定的需求。这种方法不仅提高了程序运行的稳定性,还增强了业务逻辑的灵活性。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值