删除班级时发生错误

新增班级后删除班级时发生报错,但是单元测试时没有报错,可正常通过。
报错信息为

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方法表示要存储数据,但又因为对字段的设置不允许存储相同的字段,就会发生报错也就符合我们的预期了。


李明
441 声望19 粉丝