问题一
在写新增模块时遇到了这样一个问题——备用联系电话为非必填字段,那么在验证时就不用添加Validators.required
字段进行验证,但是由于是填写电话号码,就需要验证电话号码是否符合规范,但是在已有的验证电话号码方法中输入为空也会被验证为不符合规范,这也就导致了如果用[disabled]="formGroup.invalid"
判断保存按钮是否可用与实际规划不同——即备用联系电话成了必填字段。于是我作了以下尝试。
一开始想着只需要像这样订阅备用联系电话变化,当其有内容时增加验证,没有内容时取消验证。
this.formGroup.get(this.formKeys.alternatePhone).valueChanges.subscribe(
(value) => {
if(value) {
const alternatePhoneFormControl = new FormControl(value, YzValidators.isChinaMobileNumber);
this.formGroup.setControl(this.formKeys.alternatePhone, alternatePhoneFormControl);
} else {
const newAlternatePhoneFormControl = new FormControl(value);
this.formGroup.setControl(this.formKeys.alternatePhone, newAlternatePhoneFormControl);
}
}
)
但是这样的话订阅部分代码只会在第一次输入数据时执行一次,之后再有变化便不再执行,对此个人猜测虽然这是通过setControl()
改变所订阅的formControl,但是订阅关系并不会改变,即这相当于新建了一个formControl,我们订阅的还是之前的formComtrol的变化。
之后我又想试试直接订阅整个formGroup的变化来实现所需要求
this.formGroup.get(this.formKeys.alternatePhone).valueChanges.subscribe(
(value) => {
if(value) {
const alternatePhoneFormControl = new FormControl(value, YzValidators.isChinaMobileNumber);
this.formGroup.setControl(this.formKeys.alternatePhone, alternatePhoneFormControl);
} else {
const newAlternatePhoneFormControl = new FormControl(value, YzValidators.isChinaMobileNumber);
this.formGroup.setControl(this.formKeys.alternatePhone, newAlternatePhoneFormControl);
}
}
)
但是这样的话也显然是不对的,setControl()
也改变了formGroup,这也就导致进入了死循环。
最后我又尝试着把订阅方法独立出来,当我们设置完formControl后再对我们设置的formControl进行订阅,从而实现目的。
ngOnInit(): void {
this.initFormGroup();
this.subscribeAlternatePhoneChange(this.formGroup.get(this.formKeys.alternatePhone))
}
subscribeAlternatePhoneChange(formControl: AbstractControl): void {
formControl.valueChanges.subscribe(
(value) => {
console.log(value);
if (value) {
const alternatePhoneFormControl = new FormControl(value, YzValidators.isChinaMobileNumber);
this.formGroup.setControl(this.formKeys.alternatePhone, alternatePhoneFormControl);
this.subscribeAlternatePhoneChange(alternatePhoneFormControl);
} else {
const newAlternatePhoneFormControl = new FormControl(value);
this.subscribeAlternatePhoneChange(newAlternatePhoneFormControl);
this.formGroup.setControl(this.formKeys.alternatePhone, newAlternatePhoneFormControl);
}
}
)
}
问题二
@JsonView的使用
在当前项目的后台中@JsonView的使用很频繁,于是我就简单地了解了一下@JsonView的使用。
个人认为@JsonView的功能就是根据自己的需求过滤由后台返还给前台的Json数据。
比如我们有一个教师实体,教师实体中有name.username,email等字段,但是我们只想在前台展示name字段那么返回的json数据中就不应该包含其他字段数据。这时我们就可以使用@Jsonview来达成目的。
@JsonView(NameJsonView.class)
private String name;
private Boolean sex;
private String username;
private String email;
. . .
public interface NameJsonView {
}
@GetMapping
@JsonView(PageJsonView.class)
public List<Teacher> getAll() {
return (List<Teacher>) teacherService.getAll();
}
. . .
public static class PageJsonView implements Teacher.NameJsonView{
}
这样的话前台就只接收到了name的json数据。
但是这种过滤是单向的,比如我们前台传来了name,email,username等数据,按照如上方法对C层的save方法添加上@JsonView(PageJsonView.class)
,实际传来的数据并没有被过滤掉,这些数据还是会被传到后台并保存到数据库中。
并且值得一提的是,如果在关联对象上使用@JsonView的话会出现下面这种情况
比如我们在班级实体中对teacher设置jsonView
@JsonView(TeacherJsonView.class)
@ManyToOne
private Teacher teacher;
. . .
public interface TeacherJsonView {
}
@GetMapping
@JsonView(Klass.TeacherJsonView.class)
public List<Klass> getAll(@RequestParam String name) {
return this.klassService.getAll(name);
}
那么返回前台的数据就是[{"teacher":{}}]
一个空的teacher。
如果我们想要显示teacherName的数据,我们就需要这样
@GetMapping
@JsonView(Teacher.NameJsonView.class)
public List<Klass> getAll(@RequestParam String name) {
return this.klassService.getAll(name);
}
返回前台的数据[{"teacher":{"name":"李四"}}]
之后我又测试了如果在实体的某一个字段上面加入@JsonView注解,然后调用page()方法时使用一个没有实现任何JsonView接口的类观察返回的值的情况。
@JsonView(NameJsonView.class)
private String name;
private Boolean sex;
private String username;
private String email;
private Long createTime;
private Long updateTime;
@GetMapping
@CrossOrigin("*")
@JsonView(NullJsonView.class)
public List<Teacher> getAll() {
logger.info("gerAll");
/*定义查询字符串*/
return (List<Teacher>) teacherService.getAll();
}
. . .
public static class NullJsonView {
}
经过查看发现前台返回结果为 [{}] 与预期结果一致,但是如果我们page方法不使用@JsonView注解但是实体中使用@JsonView注解那么就不会发生过滤即返回正常结果即
[{"id":6,"name":"123","sex":false,"username":"123","email":"123","createTime":null,"updateTime":null,"deleted":false,"password":"yunzhi"}]
但是如果我们尝试对Teacher实体使用@JsonView注解
@JsonView(Teacher.TeacherJsonView.class)
public class Teacher {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@JsonView(NameJsonView.class)
private String name;
private Boolean sex;
private String username;
@JsonView(EmailJsonView.class)
private String email;
private Long createTime;
private Long updateTime;
protected Boolean deleted = false;
private String password = "yunzhi";
. . .
public interface TeacherJsonView {}
}
public static class PageJsonView implements Teacher.TeacherJsonView {
}
那么返回结果就是如下
[{"id":6,"sex":false,"username":"123","createTime":null,"updateTime":null,"deleted":false,"password":"yunzhi"}]
我们会发现只有name,email没有被传到前台,因为他们也被设置了@JsonView注解,如果想要他们也返回到前台就需要实现他们相应的接口像下面这样
public static class PageJsonView implements Teacher.TeacherJsonView, Teacher.NameJsonView, Teacher.EmailJsonView {
}
此时我们再查看返回的数据就是Teacher所有的数据
[{"id":6,"name":"123","sex":false,"username":"123","email":"123","createTime":null,"updateTime":null,"deleted":false,"password":"yunzhi"}]
问题三
项目中很多实体都会有这样一个注解
@SQLDelete(sql = "update `entity_name` set deleted = 1 where id = ?")
经过搜索后发现,在项目开发中,数据库的删除一般都是逻辑删除,可以很好的保持数据的完整性,在sql中写上删除的时候的SQL语句,到调用delete方法的时候,hibernate将自动执行该语句将实现软删除。即数据还保留在数据库中,只是deleted字段被设置为了true。
通常情况下要配合@Where注解使用,比如:
@SQLDelete(sql = "update `teacher` set deleted = 1 where id = ?")
@Where(clause = "deleted = false")
并且当我们再想要查询这条数据时也会返回null,即表示该数据已经被删除。
@Where表示在执行hibernate查询操作时 会自动在sql的where后加一个条件deleted = false;
但是通常情况下我们并不会直接在实体上使用@Where注解,比如我们有一个通过班级查询学生的页面,我们可以在班级实体中对学生字段这样操作
@OneToMany(mappedBy = "klass")
@Where(clause = "deleted = false")
private List<Student> students;
这样就实现了我们想要的目的