写业务 / 各层的分工及注意事项 / 各实体类 / 数据库设计 参数校验
实体类:
pojo 包下的类是对应数据库表的类vo 包下的类是后端封装数据返回给前端的类,便于展示数据,一般与pojo类不一样dos 包下的类是用来封装sql语句从数据库查出的数据 \ 有时也会在 entity 包下 (下面用entity代表封装数据库表的实体类)params 包下的类可以用来封装前端发送给后端的数据to 分布式情况下,一个服务调用另一个服务,会把数据封装到这个类里面
封装一个Result类来返回数据给前端比较好
@Data
@AllArgsConstructor
public class Result {
private boolean success;
private int code;
private String msg;
private Object data;
public static Result success(Object data){
return new Result(true,200,"success",data);
}
public static Result fail(int code,String msg){
return new Result(false,code,msg,null);
}
}
实体类的属性,使用包装类!!!
原始类型会 赋值 为0
包装类型会 赋值 为null
用于封装数据库表 的实体类,用原始类型可能会导致 更新出错!
实体类,默认实现序列化接口!!!
implements Serializable
private static final long serialVersionUID = 1L; //序列化ID 便于反序列化
Entity注解:(使用Mybatis-plus)
@TableName(“pms_attr_attrgroup_relation”)
将指定的数据库表和 JavaBean 进行映射
@TableId
如果表的id名字 和 类的id名字不同,要用此注解【不管怎么样,加上就行】
@TableField:
非主键字段,value映射表字段名:
@TableField(value = "sort")
private Integer attrSort;
类中属性不存在表中:
@TableField(exist = false)
private Integer attrSort;
Entity的创建时间和更新时间可以自动填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date createTime;
实体类注解:(lombok,使得实体类不用写各种方法)
lombok使用参考网址:https://www.jianshu.com/p/2543c71a8e45
1、idea装插件
2、导入依赖
3、在实体类上标明注解,常用@Data
实体类参数校验(比如不能为空或者要符合什么要求)在最后面
写业务,
一定要先明确接口的 路径 参数 以及 后端返回的值
通过路径,判断对应的是哪张表,判断是在哪个controller写业务,判断对应的是哪个entity(封装数据库表的实体类)
然后用 vo 封装 后端和前端 互相传递的参数
controller 处理请求,接受和校验数据
Service 接受controller传来的数据,进行业务处理。
1、涉及到哪些表,就注入哪些表的service
2、往往就是vo和entity的转换,然后再进行操作。
同名的属性,先用BeanUtils.copyProperties(源对象,目标对象)
不同名的属性,再一个个赋值上去
3、如果是单表操作,看看Iservice有没有直接可以用的。
如果是BaseMapper里面有直接可以用的,而且这张表不是属于自己处理的。先在其他service1里面封装函数再调用
Controller 接受Service处理完的数据,封装页面指定的vo
Controller层注意事项:
1、不要用 map接收前端传过来的值,因为别人不知道里面有什么参数
最好还是写一个实体类进行封装
Service层注意事项:
1、 赋值到entity时,不用给实体类的id赋值,它会在数据库中自动生成
2、 当实体类保存过数据库之后(之前没赋id值),可以直接get实体类的id!!!
3、Service层最好还是注入service层,不要用其他service的dao
service层使用baseMapper , Iservice
this.(Iservice里面的方法)
-
- service接口要 extends IService<封装数据库表的对象>
- service接口的实习类 要 extends ServiceImpl<Mapper接口, 封装数据库表的对象> implements LoginService
this.baseMapper.(baseMapper里面的方法)
-
- Dao层的Mapper接口要 extends BaseMapper<Tb_User>
QueryWrapper<>写法:
- QueryWrapper queryWrapper = new QueryWrapper<>();
- //如果传过来的数据不是空的,就进行多参数查询
- if (!StringUtils.isEmpty(key)) {
- queryWrapper.eq(“brand_id”,key).or().like(“name”,key);
- }
- 或者
- this.getOne (new QueryWrapper<Tb_User> ().eq (“mobile”,userLoginVo.getMobile ()));
Dao层注意事项:
1、业务量很大的时候,不应该使用联表查询,两种表的笛卡儿积数据量太大会有危险
数据库设计注意事项:
分布式 数据库表的设计会有冗余,以便分库分表。以及减少联表查询。【多表查询是笛卡儿积】
但是这样也会导致修改表中某一字段时,也要修改其他表字段的问题【不过查询比修改的数量多很多,所以冗余是值得的】
后端字段校验功能:(包含错误枚举 和 处理异常 知识点)
1.简单使用JSR303
1.0 导入依赖
springboot 2.3.2之后使用的版本:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.3.7.RELEASE</version>
</dependency>
springboot 2.3.2以前使用的版本:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
1.1 给Bean添加校验注解
如:@NotNull
【上网搜一下能配哪些注解】
【其他能配的:比如@URL / @Pattern(),里面写正则表达式,注意两边要去掉斜杠】
注解里有默认的message,如果想修改,可以这样写:@NotNull(message = "id不能为空")
1.2 给方法的参数添加上 @Valid 注解 。也可以直接在方法获取错误信息进行处理
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){ // @Valid 放在 @RequestBody前面
brandService.save(brand);
return R.ok();
}
@PostMapping("/regist")
public String regist(@Valid UserRegistVo vo, BindingResult result, RedirectAttributes redirectAttributes){
// 校验参数是否出错
if(result.hasErrors()){
Map<String, String> errors = result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
redirectAttributes.addFlashAttribute("model",errors);
return "redirect:/reg.html";
}
// 注册
return "redirect:/login.html";
}
1.3 定义异常枚举,方便后面的异常处理器调用
/***
* 错误码和错误信息定义类
* 1. 错误码定义规则为5位数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
*/
public enum BizCodeEnume {
UNKNOW_EXCEPTION(10000,"系统未知异常"),
VALID_EXCEPTION(10001,"参数格式校验失败");
private int code;
private String msg;
BizCodeEnume(int code,String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
1.4 定义异常处理器处理参数校验异常
/**
* 集中处理所有异常
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.xxxx.gulimall.product.controller")
public class ExceptionControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handlerVaildException(MethodArgumentNotValidException e){
log.error("数据校验出现问题{},异常类型{}",e.getMessage(),e.getClass());
BindingResult bindResult = e.getBindingResult();
Map<String,String> errorMap = new HashMap<>();
bindResult.getFieldErrors().forEach((fieldError -> {
errorMap.put(fieldError.getField(), fieldError.getField());
}));
return R.error(BizCodeEnume.VALID_EXCEPTION.getCode(),BizCodeEnume.VALID_EXCEPTION.getMsg()).put("data",errorMap);
}
//不建议这样写,加上没有报错信息,坑
// 若上面没有捕获,就会到这个方法。【处理所有异常】
//@ExceptionHandler(value = Throwable.class)
//public R handlerException(Exception e){
// e.printStackTrace();
// return R.error();
//}
}
2.有多个情况时,使用分组校验(比如新增 和 修改的情况不同) ,与上面不同的地方:
2.1 与上面1.1不同,注解时需要加上分组,不加分组则默认失效【如果需要所有分组情况都校验,加上全部分组】
定义分组:分组类型可以任意,但推荐新建一个空接口,然后在这里写上
定义分组:
public interface AddGroup {}
public interface UpdateGroup {}
Bean注解:
@NotNull(message = "修改必须指定id", groups = {UpdateGroup.class}) // 更新时需校验
@NotBlank(message = "品牌名不能为空", groups = {AddGroup.class,UpdateGroup.class}) // 所有情况需校验
2.2 与1.2不同,要给方法的参数添加上 @Validated( {分组} ) 注解
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
3.使用自定义校验(就是新增一个自定义的校验注解)
3.1 编写一个自定义的校验注解
【message要关联一个文件,要新建这个文件。这样注解就有默认的message。当然在使用的时候也可以重新写message】
新建 ValidationMessages.properties :
com.xxxx.common.valid.ListValue.message=只能在指定范围里取值
@Documented
@Constraint(validatedBy = { com.xxxx.common.valid.ListValueConstraintValidator.class }) // 校验器,需要自定义,可以多个
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
String message() default "{com.xxxx.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default { };
}
3.2 编写一个自定义的校验器 (实例里的校验器作用是:值只能在 用注解时指定的值的范围内)
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> { //泛型:第一个是注解 第二个是校验的字段
private Set<Integer> set = new HashSet<>();
//初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
//判断是否校验成功
/**
* @param value 需要校验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}