• 两个难搞的Java Error/Exception


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

    Entity定义

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

    1. import jakarta.persistence.*;
    2. import java.io.Serializable;
    3. import java.util.*;
    4. import org.hibernate.annotations.*;
    5. import org.hibernate.envers.Audited;
    6. import org.springframework.data.jpa.domain.support.AuditingEntityListener;
    7. @Audited
    8. @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
    9. @Entity
    10. @EntityListeners({AuditingEntityListener.class, AuditingEntityListener.class})
    11. @MappedSuperclass
    12. @Table(name = "book_store")
    13. public class BookStore implements Serializable {
    14. private static final long serialVersionUID = 1L;
    15. @Id
    16. @org.springframework.data.annotation.Id
    17. @GeneratedValue(strategy = GenerationType.IDENTITY)
    18. private Long id;
    19. @Column(name = "name") private String name;
    20. @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    21. @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
    22. @JoinTable(name = "book_store_author",
    23. joinColumns =
    24. @JoinColumn(name = "book_stores_id", referencedColumnName = "id"),
    25. inverseJoinColumns =
    26. @JoinColumn(name = "authors_id", referencedColumnName = "id"))
    27. private Set authors = new HashSet<>();
    28. public Long getId() {
    29. return id;
    30. }
    31. public void setId(Long id) {
    32. this.id = id;
    33. }
    34. public String getName() {
    35. return name;
    36. }
    37. public void setName(String name) {
    38. this.name = name;
    39. }
    40. public Set getAuthors() {
    41. return authors;
    42. }
    43. public Author addAuthor(Author author) {
    44. this.authors.add(author);
    45. return this;
    46. }
    47. public Author removeAuthor(Author author) {
    48. this.authors.remove(author);
    49. return this;
    50. }
    51. public void setAuthors(Set authors) {
    52. this.authors = authors;
    53. }
    54. }
    55. @Audited
    56. @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
    57. @Entity
    58. @EntityListeners({AuditingEntityListener.class, AuditingEntityListener.class})
    59. @MappedSuperclass
    60. @Table(name = "author")
    61. public class Author implements Serializable {
    62. private static final long serialVersionUID = 1L;
    63. @Id
    64. @org.springframework.data.annotation.Id
    65. @GeneratedValue(strategy = GenerationType.IDENTITY)
    66. private Long id;
    67. @Column(name = "name") private String name;
    68. @OneToMany(
    69. cascade = {CascadeType.ALL, CascadeType.PERSIST, CascadeType.MERGE},
    70. fetch = FetchType.LAZY, orphanRemoval = true)
    71. @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
    72. @JoinColumn(name = "author_id")
    73. private Set books = new HashSet<>();
    74. public Long getId() {
    75. return id;
    76. }
    77. public void setId(Long id) {
    78. this.id = id;
    79. }
    80. public String getName() {
    81. return name;
    82. }
    83. public void setName(String name) {
    84. this.name = name;
    85. }
    86. public Set getBooks() {
    87. return books;
    88. }
    89. public Book addBook(Book book) {
    90. this.books.add(book);
    91. book.setAuthor(this);
    92. return this;
    93. }
    94. public Book removeBook(Book book) {
    95. this.books.remove(book);
    96. book.setAuthor(null);
    97. return this;
    98. }
    99. public void setBooks(Set books) {
    100. this.books = books
    101. }
    102. }
    103. @Audited
    104. @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
    105. @Entity
    106. @EntityListeners({AuditingEntityListener.class, AuditingEntityListener.class})
    107. @MappedSuperclass
    108. @Table(name = "book")
    109. public class Book implements Serializable {
    110. private static final long serialVersionUID = 1L;
    111. @Id
    112. @org.springframework.data.annotation.Id
    113. @GeneratedValue(strategy = GenerationType.IDENTITY)
    114. private Long id;
    115. @Column(name = "name") private String name;
    116. @ManyToOne(optional = false)
    117. @JoinColumn(nullable = false)
    118. private Author author;
    119. public Long getId() {
    120. return id;
    121. }
    122. public void setId(Long id) {
    123. this.id = id;
    124. }
    125. public String getName() {
    126. return name;
    127. }
    128. public void setName(String name) {
    129. this.name = name;
    130. }
    131. public Author getAuthor() {
    132. return author;
    133. }
    134. public void setAuthor(Author author) {
    135. this.author = author;
    136. }
    137. }

    错误及其修正

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

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

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

    1. public void setAuthors(Set authors) {
    2. this.authors.clear();
    3. this.authors.addAll(authors);
    4. }

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

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

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

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

    1. public void setAuthors(Set authors) {
    2. this.authors.clear();
    3. if (authors != null) {
    4. this.authors.addAll(authors);
    5. }
    6. }

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

    1. public void setBooks(Set books) {
    2. this.books.clear();
    3. if (books != null) {
    4. this.books.addAll(books);
    5. }
    6. }

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

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

    后续 

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

    1. public void setAuthors(Set authors) {
    2. this.authors =
    3. Optional.ofNullable(authors)
    4. .map(s -> s.stream().filter(Objects::nonNull).collect(Collectors.toSet()))
    5. .orElse(new HashSet<>());
    6. }
    7. public void setBooks(Set books) {
    8. this.books =
    9. Optional.ofNullable(books)
    10. .map(s -> s.stream().filter(Objects::nonNull).collect(Collectors.toSet()))
    11. .orElse(new HashSet<>());
    12. }

  • 相关阅读:
    WebSocket is already in CLOSING or CLOSED state
    FFmpeg入门详解之16:音频编码原理
    非对称密钥在ssh远程登陆Linux时的使用
    舆情监控软件
    【Windows网络编程】二.TCP套接字编程与主机上线实验
    代码随想录训练营day53, 最长公共子序列, 不相交的线, 最大子序和
    中国传统美食网页HTML代码 学生网页课程设计期末作业下载 美食大学生网页设计制作成品下载 DW餐饮美食网页作业代码下载
    【UE4.x像素推流】
    Python实现深度森林(Deep Forest)分类模型(deepforest分类算法)项目实战
    微服务项目:尚融宝(10)(后端接口:统一异常处理)
  • 原文地址:https://blog.csdn.net/BlackMonkey/article/details/134059883