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 显示时没有他们的答案。经过一些研究,我发现即使 Answer
和 Question
object 正确“连接”并存储在数据库中,答案列表结果仍未定义,如以下控制台:
{id: 10, question: 'Question1', questionsForUser: Array(0), answers:
undefined}
Answers: homepage.component.ts:32
undefined
homepage.component.ts:33
我该如何处理这个问题?
这里是Question
和Answer
的关系:
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.
时忽略(即不包括)该字段
解决这个问题:
在您的 Question
实体 class 中,从 List<Answer> answers
字段中删除 @JsonIgnore
注释
另一方面,在您的 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;
...
}
我有一个 Question
的列表,每个 Question
类型的 object 与 Answer
类型的 object 有一个 1:n 关系](所以每个 Question
object 都有一个 Answer
object 的列表)。我试图在浏览器上显示,使用 Angular material,所有 Question
objects 在点击事件后都有他们的答案,但是当我尝试这样做时, Question
object 显示时没有他们的答案。经过一些研究,我发现即使 Answer
和 Question
object 正确“连接”并存储在数据库中,答案列表结果仍未定义,如以下控制台:
{id: 10, question: 'Question1', questionsForUser: Array(0), answers: undefined}
Answers: homepage.component.ts:32
undefined homepage.component.ts:33
我该如何处理这个问题?
这里是Question
和Answer
的关系:
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.
解决这个问题:
在您的
Question
实体 class 中,从List<Answer> answers
字段中删除@JsonIgnore
注释另一方面,在您的
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;
...
}