删除班级时发生错误
新增班级后删除班级时发生报错,但是单元测试时没有报错,可正常通过。
报错信息为
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: The given id must not be null!; nested exception is java.lang.IllegalArgumentException: The given id must not be null!] with root cause
其中比较关键的一句为 The given id must not be null!
。
再到网页控制台中查看报错信息为500,确认时后台发生的错误。
之后再到C层用logger.info()打印之后发现从前台传过来的id却是为null,之后再检查了一下单元测试中的内容
@Test
public void delete() {
Teacher teacher = new Teacher();
Klass klass = new Klass();
klass.setName("测试123123班级");
klassRepository.save(klass);
klassService.delete(klass.getId());
Optional<Klass> klassOptional = klassRepository.findById(klass.getId());
Assertions.assertThat(klassOptional.isPresent()).isFalse();
}
发现单元测试时的班级ID是直接调用klass.getId()获得的id,再回到C层检查发现在delete
方法中的参数没有添加@PathVariable
注解。单元测试并没有检测到前台数据是否传到了后台。
这也就是说在写完后台后要优先测试前台数据能否传送到后台。
删除与班级绑定的教师时发生错误
教师与班级绑定时理应不允许被删除,但是删除时前台应该做相应提示或是连带与之绑定的班级一并删除(或者是解除绑定,即把teacher_id设为空),而不是报500错误。
报错信息为:
Cannot delete or update a parent row: a foreign key constraint fails
在此,先尝试第二种解决方法
出现这个报错的原因是:想要删除的数据或表与其他数据或表拥有主外键关系,Mysql规定,为了维护表结构的稳定,禁止执行该操作,即外键约束失败。
如果我们想强制删除可以执行以下语句:
String sql = String.format("delete from `teacher` where id = %s", id);
this.jdbcTemplate.update("SET foreign_key_checks = 0");
this.jdbcTemplate.update(sql);
this.jdbcTemplate.update("SET foreign_key_checks = 1");
进行操作后可以删除教师,但是相应班级没有被删除,在查看班级时也会发生报错。
我们可以执行以下语句来操作该教师所属的班级
Teacher teacher = new Teacher();
teacher = this.teacherService.getById(id);
RowCallbackHandler rowCallbackHandler = new RowCallbackHandler() {
@Override
public void processRow(ResultSet resultSet) throws SQLException {
Klass klass = new Klass();
klass.setTeacher(null);
klass.setName(resultSet.getString("name"));
klass.setId(resultSet.getLong("id"));
klassService.update(klass.getId(), klass);
}};
String newSql = String.format("select id, name, teacher_id from klass where teacher_id = %d", teacher.getId());
jdbcTemplate.query(newSql, rowCallbackHandler);
之后又发现查找教师所对应的班级可由CrudRepository
中的方法直接实现,我们只需要在Repository里声明一下我们要用到的方法就可以。不需要我们写sql代码,所以以上代码还可以简化为
public void delete(Long id) {
Teacher teacher;
teacher = this.teacherService.getById(id);
List<Klass> klasses = klassService.findAllByTeacher(teacher);
klasses.forEach(
klass -> {
klass.setTeacher(null);
klassService.update(klass.getId(), klass);
}
);
teacherRepository.deleteById(id);
}
此时我们再进行删除教师就会将其所属班级的教师设为空,实现教师的正常删除。
除此之外我们还可以当我们要删除教师时如果该教师有所属班级,我们直接给出提示——该教师已绑定班级,无法进行删除来解决问题。
我们可以在前台先查询后台中是否存在班级与要删除的教师绑定,若存在则提示——删除失败,存在班级与该教师绑定。
前台代码
onDelete(teacher: Teacher): void {
const index = this.teachers.indexOf(teacher);
this.httpClient.get(`${this.url}haveKlass/${teacher.id}`)
.subscribe((flag) => {
console.log(flag);
if (flag) {
console.log('删除失败,存在班级与该教师绑定');
} else {
this.httpClient.delete(`${this.url}/${teacher.id}`)
.subscribe(() => {
this.teachers.splice(index, 1);
console.log('delete success');
},
error => console.log('delete error', error));
}
});
}
后台haveKlass接口对应代码:
public Boolean haveKlass(Long id) {
final Boolean[] flag = new Boolean[1];
flag[0] = false;
RowCallbackHandler rowCallbackHandler = new RowCallbackHandler() {
@Override
public void processRow(ResultSet resultSet) throws SQLException {
flag[0] = true;
}
};
String newSql = String.format("select id, name, teacher_id from klass where teacher_id = %d", id);
jdbcTemplate.query(newSql,rowCallbackHandler);
return fongli lag[0];
}
同理,以上代码可以简化为
public Boolean haveKlass(Long id) {
return klassService.existsByTeacher(teacherService.getById(id));
}
JAVA中 String/Boolean...与string/boolean的区别
以boolean为例
boolean 是java中最基本8种类型中的一种,java为8中基本类型提供了封装类,用来表示一个对象。
不过在有些框架中,例如参数和值要求是对象类型,所以不能用基本类型。
boolean是基本数据类型Boolean是它的封装类,和其他类一样,有属性有方法,可以new,例如:Boolean flag = new Boolean("true"); // boolean 则不可以!
添加@Column(unique = true)注解后不起作用
起初认为是没有重启后台,但是重启后仍然可以存储相同学号的学生(无论是前台还是单元测试),打开数据库查看Student表的索引项也没有更新sno
字段的对应类型为UNIQUE
。
查询后发现这些设置只有新建表的时候才会更新,即使连接方式为update
也不会随之更新。但是用@Column(nullable = false)设置该值为非空时不需要重建表就可实现。
可见是否需要重建表与该注解无直接关系,与其要设置的选项有关。
单元测试中的数据和后台是双向绑定的
在测试数据唯一性的时候出现了以下测试代码。
private Klass klass;
private Student student;
@Test
public void snoUniqueTest() {
boolean called = false;
this.studentRepository.save(this.student);
System.out.println(this.student.getId());// 2--尝试输出ID
try {
this.before();//为什么要再调用一次before()
this.studentRepository.save(this.student);
} catch (DataIntegrityViolationException e) {
called = true;
}
System.out.println(this.student.getId()); //3--尝试输出ID (4)
Assertions.assertThat(called).isTrue();
}
@Before
public void before() {
this.student = new Student();
if (this.klass == null) {
this.klass = new Klass();
this.klassRepository.save(this.klass);
}
this.student.setSno("123456");
this.student.setName("测试名称");
this.student.setKlass(this.klass);
System.out.println(this.student.getId()); //1--尝试输出ID (3)
}
去掉this.before后发现执行测试的时候没called为false,之后尝试着输出学生的ID,分别对应着上面三个位置。
当调用 this.before()
时输出结果对应为——null
,1
,null
,null
;
注释掉 this.before()
后的输出结果为—— null
,1
,1
;
也就是说用before创造出来的student确实是不含ID的,当我们把它存到数据库后数据库会自动给它附上ID,然后测试里的实体也会跟着数据库附上ID,这时再调用save()方法的时候由于ID也相同自然就会默认为这是在更新一个数据,也就不会发生错误。
当我们再调用before()时又创建了一个新对象,ID为空,此时再去调用save方法表示要存储数据,但又因为对字段的设置不允许存储相同的字段,就会发生报错也就符合我们的预期了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。