这是一个Spring + Angular先后台分离的项目,目前有一个查看做业列表的功能,而且已经设置了分页和几个查询参数,如图。数据库
如今须要增长一个已评阅和未评阅的查询功能。app
Work实体的属性以下:ui
id: number; content: string; createTime: Date; item = new Item(); score: number; student = new Student(); updateTime: Date; reviewed: boolean; attachments = new Array<Attachment>();
已评阅对应的属性是reviewed,是boolean类型。this
目前前台服务层的代码:url
// 已经写了分页参数、按学生姓名查找做业、按学号查找做业,共5个参数 getAll(params: {page: number, size: number, studentName?: string, studentSno?: string, itemId?: number }): Observable<Page<Work>> { const _params = { page: params.page.toLocaleString(), size: params.size.toLocaleString(), studentName: params.studentName ? params.studentName : null, studentNo: params.studentSno ? params.studentSno : null, itemId: params.itemId ? params.itemId.toLocaleString() : null }; return this.httpClient.get<Page<Work>>(`${this.url}/getAll`, {params: _params}); }
因为先后台中间隔着一层Http,而且boolean具备特殊性,因此向后台传值比较困难,做为新手,我首先能想到几种方法:spa
这个是最简单的,由于后台不用动,直接请求整页的数据,只在前台显示未批阅的做业便可。3d
/** * 单选框被用户点击时 * @param $event 弹射值 * @param reviewed 评阅状态码1默认2已评阅3未评阅 */ onCheckBoxChange($event: Event, reviewed: number) { this.reviewed = reviewed; this.load(); }
那么问题来了,因为请求的是一整页(10条)数据,若是这一页都是已批阅,那么这页直接就是白的...一条数据也没有。code
虽然不符合操做逻辑,但这个又不算错误,系统不会报错...orm
前台过滤,失败。对象
直接在前台的M层加入参数:
reviewed: params.reviewed ? params.reviewed : null,
那么问题来了,httpClient只能传输String和对象(即便是传输对象,也是序列化变成字符串再传输的),不能传输number、boolean等类型。
对于number,须要转化成String类型来传输,好比:
itemId: params.itemId ? params.itemId.toLocaleString() : null
但对于boolean类型,并无直接转化成String类型的方法。
所以,直接传boolean类型的参数的方式,失败。
除了Http请求,其余位置继续用Boolean传值,到发起请求的时候转化为String。
这样的好处是:只须要在前台的服务层和后台的C层增长转化代码,其余位置继续按照常规的逻辑,直接用Boolean。
在前台向后台的传输过程当中:
true -> "true" -> true false -> "false" -> false undefined -> 前台不传值 -> null
这样就用三个值表示了“已评阅”“未评阅”“所有”三种状态。
缺点也比较明显,布尔值只有true和false,若是判断写成:
reviewed: params.reviewed ? 'true' : null,
那么undefined 和false都按false来处理,都不会传值。
因此就必须分开处理undefined和false,能够用一个嵌套判断来实现:
reviewed: params.reviewed === undefined ? null : params.reviewed ? '1' : '0',
同理,后台也须要判断,转化成Boolean:
Boolean _reviewed = null; if (reviewed != null){ switch (reviewed) { case "1": _reviewed = true;break; case "0": _reviewed = false;break; } }
如今,实现了效果:
转化为字符串的方法,成功。
代码逻辑:
“尝试三”的写法,主要有两个问题:
因为boolean具备特殊性,只有两个值,因此能够不传输这个参数,经过调用不一样的方法,间接传输boolean。
现有的后台C层方法:
/** * 获取全部做业 * @param pageable 分页信息 * @return 全部做业 */ @GetMapping("getAll") @JsonView(GetAllJsonView.class) public Page<Work> findAll( @RequestParam(required = false) Long itemId, @RequestParam(required = false) String studentName, @RequestParam(required = false) String studentNo, Pageable pageable) { return this.workService.getAll( itemId, studentName, studentNo, pageable); }
目前有三个参数,若是不想增长参数,就再增长两个方法,而参数不变:
// 原C层的方法 public Page<Work> findAll(){} // 获取已评阅的做业 public Page<Work> findAllReviewed(){} // 获取未评阅的做业 public Page<Work> findAllUnReviewed(){}
写到这里你们应该明白了:在前台向后台的传输过程当中
true -> 请求findAllReviewed() -> true false -> 请求findAllUnreviewed() -> false undefined -> 请求findAll() -> null
因此前台这么写:
getAll(params: {page: number, size: number, studentName?: string, studentSno?: string, itemId?: number, reviewed?: boolean}): Observable<Page<Work>> { // 准备参数 const _params = { page: params.page.toLocaleString(), size: params.size.toLocaleString(), studentName: params.studentName ? params.studentName : null, studentNo: params.studentSno ? params.studentSno : null, itemId: params.itemId ? params.itemId.toLocaleString() : null }; // 判断,向对应的方法发起请求 switch (params.reviewed) { case true: return this.httpClient.get<Page<Work>>(`${this.url}/getAllReviewed`, {params: _params}); break; case false: return this.httpClient.get<Page<Work>>(`${this.url}/getAllUnReviewed`, {params: _params}); break; } // 默认请求getAll() return this.httpClient.get<Page<Work>>(`${this.url}/getAll`, {params: _params}); }
而后,后台的三个方法都调用同一个服务层,这时候就能够按照常规逻辑,传入Boolean类型了。
代码逻辑:
经过Http传输boolean参数的方法:
做为新手,我知道这必定不是最优解,若是有更好的方式,欢迎来打脸,哈哈哈。
在实际项目中,除了传值,还遇到了一个难处理的问题:数据库的条件查询(使用JPA)。
后台的仓库层WorkRepository功能:
default Page getAll(Item item, String studentName, String studentSno, @NotNull Pageable pageable) { Assert.notNull(pageable, "传入的Pageable不能为null"); Specification<Work> specification = WorkSpecs.containingName(studentName) .and(WorkSpecs.startWithNo(studentSno)) .and(WorkSpecs.belongToItem(item)); return this.findAll(specification, pageable); }
其中的
Specification<Work> specification = WorkSpecs.containingName(studentName) .and(WorkSpecs.startWithNo(studentSno)) .and(WorkSpecs.belongToItem(item));
就是查询条件,它分别调用了三个方法,分别按照姓名、学号、实验项目来查询。
查看WorkSpecs类,三个方法的代码以下:
public class WorkSpecs { public static Specification<Work> belongToItem(Item item) { if (null == item || null == item.getId()) { return Specification.where(null); } return (Specification<Work>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get("item").as(Item.class), item); } public static Specification<Work> containingName(String studentName) { if (studentName != null) { return (Specification<Work>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.like(root.get("student").get("name").as(String.class), String.format("%%%s%%", studentName)); } else { return Specification.where(null); } } public static Specification<Work> startWithNo(String studentNo) { if (studentNo == null) { return Specification.where(null); } return (Specification<Work>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.like(root.get("student").get("no").as(String.class), String.format("%s%%", studentNo)); } }
如今增长一个reviewed参数,就要增长相应的查询条件。
因此第四个条件这样写:
public static Specification<Work> isReviewed(Boolean reviewed) { if (reviewed == null) { return Specification.where(null); } return (Specification<Work>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get("reviewed").as(Boolean.class), reviewed); }
在仓库层getAll()方法增长:
.and(WorkSpecs.isReviewed(reviewed));
就是实现JPA经过Boolean参数查询数据库了。