两个难搞的Java Error/Exception

文章讲述了作者在维护公司产品时遇到的两个Java异常,涉及到SpringBoot中的多对多关系映射,包括错误原因分析、错误修正以及调试困难。最后提供了优化后的setAuthors和setBooks方法,以处理null参数和null元素的问题。
摘要由CSDN通过智能技术生成

最近维护公司的产品时,我碰到了两个头痛的Java异常。未免以后忘记了,所以写篇blog记录下这些问题和解决方法。

Entity定义

由于不能展示公司的代码,我就用书店、书、作者这些对象来说明。书店与作者之间是m:n的关系,作者与书之间是1:n的关系。各个对象定义如下:

import jakarta.persistence.*;
import java.io.Serializable;
import java.util.*;
import org.hibernate.annotations.*;
import org.hibernate.envers.Audited;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@Audited
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Entity
@EntityListeners({AuditingEntityListener.class, AuditingEntityListener.class})
@MappedSuperclass
@Table(name = "book_store")
public class BookStore implements Serializable {
  private static final long serialVersionUID = 1L;

  @Id
  @org.springframework.data.annotation.Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(name = "name") private String name;

  @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
  @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
  @JoinTable(name = "book_store_author",
      joinColumns =
          @JoinColumn(name = "book_stores_id", referencedColumnName = "id"),
      inverseJoinColumns =
          @JoinColumn(name = "authors_id", referencedColumnName = "id"))
  private Set<Author> authors = new HashSet<>();

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Set<Author> getAuthors() {
    return authors;
  }

  public Author addAuthor(Author author) {
    this.authors.add(author);
    return this;
  }

  public Author removeAuthor(Author author) {
    this.authors.remove(author);
    return this;
  }

  public void setAuthors(Set<Author> authors) {
    this.authors = authors;
  }
}

@Audited
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Entity
@EntityListeners({AuditingEntityListener.class, AuditingEntityListener.class})
@MappedSuperclass
@Table(name = "author")
public class Author implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @org.springframework.data.annotation.Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(name = "name") private String name;

  @OneToMany(
      cascade = {CascadeType.ALL, CascadeType.PERSIST, CascadeType.MERGE},
      fetch = FetchType.LAZY, orphanRemoval = true)
  @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
  @JoinColumn(name = "author_id")
  private Set<Book> books = new HashSet<>();

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Set<Book> getBooks() {
    return books;
  }

  public Book addBook(Book book) {
    this.books.add(book);
    book.setAuthor(this);
    return this;
  }

  public Book removeBook(Book book) {
    this.books.remove(book);
    book.setAuthor(null);
    return this;
  }

  public void setBooks(Set<Book> books) {
    this.books = books
  }
}

@Audited
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Entity
@EntityListeners({AuditingEntityListener.class, AuditingEntityListener.class})
@MappedSuperclass
@Table(name = "book")
public class Book implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @org.springframework.data.annotation.Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(name = "name") private String name;

  @ManyToOne(optional = false)
  @JoinColumn(nullable = false)
  private Author author;

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Author getAuthor() {
    return author;
  }

  public void setAuthor(Author author) {
    this.author = author;
  }
}

错误及其修正

当尝试创建一个书店,内含两个作者,每个作者三本书的时候,程序抛出了第一个错误:

A collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance ...

Google了半天,不少人碰到了同样的问题,但没有人讲清楚根本原因是什么,只是建议将方法setAuthors()做如下修改:

public void setAuthors(Set<Author> authors) {
  this.authors.clear();
  this.authors.addAll(authors);
}

我照着修改了下,果然有效。但为啥直接给成员authors赋一个新的实例要出错,而修改其内容却不会?完全搞不清楚Spring Boot在背后检查了什么,以后有空还是要深挖一下Spring Boot的代码。

然而,在后续测试时,又碰到了第二个错误:

JSON parse error: Cannot invoke "java.util.Collection.iterator()" because "c" is null

又Google了一天半,终于发现了原因:setAuthors方法里,没有检查参数authors。若它为null,就会引发这个错误。遂继续修改setAuthors():

public void setAuthors(Set<Author> authors) {
  this.authors.clear();
  if (authors != null) {
    this.authors.addAll(authors);
  }
}

以防万一,按同样的方式修改Author.setBooks():

public void setBooks(Set<Book> books) {
  this.books.clear();
  if (books != null) {
    this.books.addAll(books);
  }
}

至此,终于解决了上述两个错误。

解决这两个错误的难点在于,哪怕我把log leve调到最低,程序都只是输出了一行错误信息,没有任何相关的stacktrace,导致我无法迅速定位相关代码。而且这两个错误都是在执行RESTful API callback函数之前就触发了,只能在Spring Boot的代码里打断点,调试起来很不方便。

后续 

在后续调试代码的时候,我终于发现并不是不能给成员authors赋一个新的实例,问题还是在于传入的参数,参数本身可能为null,或其内部含有null元素。所以最终,setAuthors()和setBooks()应该修改成:

public void setAuthors(Set<Author> authors) {
  this.authors =
    Optional.ofNullable(authors)
      .map(s -> s.stream().filter(Objects::nonNull).collect(Collectors.toSet()))
      .orElse(new HashSet<>());
}

public void setBooks(Set<Book> books) {
  this.books =
    Optional.ofNullable(books)
      .map(s -> s.stream().filter(Objects::nonNull).collect(Collectors.toSet()))
      .orElse(new HashSet<>());
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>