objects 列表未显示,因为在 Spring 引导中未定义

List of objects not displayed because undefined in Spring boot

我有一个 Question 的列表,每个 Question 类型的 object 与 Answer 类型的 object 有一个 1:n 关系](所以每个 Question object 都有一个 Answer object 的列表)。我试图在浏览器上显示,使用 Angular material,所有 Question objects 在点击事件后都有他们的答案,但是当我尝试这样做时, Question object 显示时没有他们的答案。经过一些研究,我发现即使 AnswerQuestion object 正确“连接”并存储在数据库中,答案列表结果仍未定义,如以下控制台:

{id: 10, question: 'Question1', questionsForUser: Array(0), answers: undefined}
Answers: homepage.component.ts:32
undefined homepage.component.ts:33

我该如何处理这个问题?

这里是QuestionAnswer的关系:

homepage.component是发生如下点击事件的组件:

<div class="button">
          <button mat-button (click)="getQuestions()" routerLink="questions/getAllQuestions" routerLinkActive="active">Show</button>
</div>

homepage.component.ts:

export class HomepageComponent implements OnInit {
  longText = `...`;
  public questions: Question[] = [];

  constructor(private questionService: QuestionService, private  shared: SharedService) { }

  ngOnInit(): void {
    this.shared.castQuestions.subscribe(questions=>this.questions=questions);
  }

  public getQuestions():void{
    this.questionService.getQuestions().subscribe(
      (response: Question[]) => {
        this.questions =response;

        this.shared.showQuestions(this.questions);
        console.log(response);
        for(let i=0; i<response.length; i++){
          this.questions[i].answers=response[i].answers;
          console.log(response[i]);
          console.log("Answers:");
          console.log(response[i].answers);
        }

      },
      (error: HttpErrorResponse) => {
        alert(error.message);
      }
    );
  }

}

点击事件后,由于 Angular 路由,应该执行 tool.component 代码。

tool.component.html:

<table mat-table [dataSource]="questions" class="mat-elevation-z8">
 
  <!-- Question Column -->
  <ng-container matColumnDef="question">
    <th mat-header-cell *matHeaderCellDef> Question </th>
    <td mat-cell *matCellDef="let question"> {{question.question}} </td>
  </ng-container>

  <!-- Answers Column -->
  <ng-container matColumnDef="answers">
    <th mat-header-cell *matHeaderCellDef> Answers </th>
    <td mat-cell *matCellDef="let question"> {{question.answers}} </td>
  </ng-container>



  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

tool.component.ts:

export class ToolComponent implements OnInit {
  public questions: Question[] = [];
  displayedColumns = ['question', 'answers'];
  constructor(private shared: SharedService) { }

  ngOnInit(): void {
    this.shared.castQuestions.subscribe(questions=>this.questions=questions);
  }

}

shared.service.ts:

@Injectable({
  providedIn: 'root'
})
export class SharedService {
    private questions= new BehaviorSubject<Array<Question>>([]);
      castQuestions = this.questions.asObservable();

    showQuestions(data: Question[]){
      this.questions.next(data);

  }
}

console.log(响应):

question.service.ts:

@Injectable({
  providedIn: 'root'
})
public getQuestions(): Observable<Question[]> {
    return this.http.get<Question[]>('http://localhost:8080/questions/getAllQuestions');
  }
}

Back/api 我用它来创建响应:

问题实体:

@Getter
@Setter
@EqualsAndHashCode
@ToString
@Entity
@Table(name = "question", schema = "purchase")
public class Question {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private int id;

    @Basic
    @Column(name = "question", nullable = true, length = 1000)
    private String question;

    @OneToMany(mappedBy = "question", cascade = CascadeType.MERGE)
    private List<QuestionForUser> questionsForUser;

    @OneToMany(mappedBy = "question", cascade = CascadeType.MERGE)
    @JsonIgnore
    private List<Answer> answers;
}

答案实体:

@Getter
@Setter
@EqualsAndHashCode
@ToString
@Entity
@Table(name = "answer", schema = "purchase")
public class Answer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private int id;

    @Basic
    @Column(name = "answer", nullable = true, length = 1000)
    private String answer;

    @ManyToOne
    @JoinColumn(name = "question")

    private Question question;


    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}

问题库:

@Repository
public interface QuestionRepository extends JpaRepository<Question, Integer> {

    List<Question> findByQuestionContaining(String question);

    boolean existsByQuestion(String question);

}

问题服务:

@Service
public class QuestionService {

    @Autowired
    private QuestionRepository questionRepository;
@Transactional(readOnly = true)
    public List<Question> getAllQuestions(){
        return questionRepository.findAll();
    }
}

问题控制器:

@GetMapping("/getAllQuestions")
    public List<Question> getAll(){
        List<Question> ques = questionService.getAllQuestions();
        for(Question q:ques){
            System.out.println(q.getQuestion());
            if(q.getAnswers()!=null){
                System.out.println("The answers are: "+q.getAnswers().size());
            }
        }

        return questionService.getAllQuestions();
    }

按照建议,我尝试在 QuestionController 中添加对 getAll() 的测试。为了让测试return成为一个字符串,我暂时把方法getAll()改成这样:

@GetMapping("/getAllQuestions")//funziona
    public String getAll(){
        List<Question> result = questionService.getAllQuestions();
        String answer = result.get(0).getAnswers().get(0).getAnswer();
        return answer;
    }

然后,我写了下面的测试:

class QuestionControllerTest {

    @Test
    void getAll() {
        QuestionController controller = new QuestionController(); //Arrange
        String response = controller.getAll(); //Act
        assertEquals("a1", response); //Assert
    }
}

第一个问题的第一个答案应该是a1,但是当我在IntelliJ上执行测试时,我有以下结果:

java.lang.NullPointerException: Cannot invoke "com.ptoject.demo.services.QuestionService.getAllQuestions()" because "this.questionService" is null

at com.ptoject.demo.controller.QuestionController.getAll(QuestionController.java:64) at com.ptoject.demo.controller.QuestionControllerTest.getAll(QuestionControllerTest.java:12) <31 internal calls> at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)<9 internal calls> at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)<25 internal calls>

此问题是由 Question 实体 class 中 List<Answer> answers 字段的 @JsonIgnore 注释引起的。这是告诉 Jackson 在将对象序列化为 JSON.

时忽略(即不包括)该字段

解决这个问题:

  1. 在您的 Question 实体 class 中,从 List<Answer> answers 字段中删除 @JsonIgnore 注释

  2. 另一方面,在您的 Answer 实体 class 中,您应该将 @JsonIgnore 注释添加到Question question 字段 -- 这是为了避免潜在的 Jackson infinite recursion issue 由双向关系引起的。

问题实体:

...
public class Question {
    ...

    // remove @JsonIgnore
    @OneToMany(mappedBy = "question", cascade = CascadeType.MERGE)
    private List<Answer> answers;
    ...
}

答案实体:

...
public class Answer {
    ...

    @ManyToOne
    @JoinColumn(name = "question")
    @JsonIgnore // add this here
    private Question question;
    ...
}