java - รายการของวัตถุไม่แสดงเพราะไม่ได้กำหนดใน Spring boot
ฉันมีรายการของ Question โดยแต่ละออบเจ็กต์ประเภท Question มีความสัมพันธ์แบบ 1:n กับออบเจ็กต์ประเภท Answer (ดังนั้นแต่ละออบเจ็กต์ Question จะมีรายการของออบเจ็กต์ Answer) ฉันกำลังพยายามแสดงบนเบราว์เซอร์ โดยใช้ Angular material ทุกออบเจ็กต์ Question กับคำตอบของมันหลังจากเหตุการณ์คลิก แต่เมื่อฉันลองทำ มันจะแสดงออบเจ็กต์ Question โดยไม่มีคำตอบ หลังจากการค้นคว้าบางอย่าง ฉันพบว่าถึงแม้ออบเจ็กต์ Answer และ Question จะ "เชื่อมต่อ" อย่างถูกต้องและเก็บไว้ในฐานข้อมูล รายการคำตอบยังคงปรากฏว่าเป็น undefined ดังที่แสดงในคอนโซลต่อไปนี้:
{id: 10, question: 'คำถาม1', questionsForUser: Array(0), answers: ไม่กำหนด}
คำตอบ: homepage.component.ts:32
ไม่กำหนด 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(response):
question.service.ts:
@Injectable({
providedIn: 'root'
})
public getQuestions(): Observable<Question[]> {
return this.http.get<Question[]>('http://localhost:8080/questions/getAllQuestions');
}
}
แบ็ก/เอพีไอที่ฉันใช้ในการสร้างการตอบกลับ:
เอนทิตีคำถาม:
@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();
}
ตามที่แนะนำ ฉันได้พยายามเพิ่มการทดสอบให้กับ getAll() ใน QuestionController เพื่อให้การทดสอบคืนค่ากลับเป็นสตริง ฉันได้เปลี่ยนวิธีการ 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: ไม่สามารถเรียกใช้ "com.ptoject.demo.services.QuestionService.getAllQuestions()" ได้เพราะ "this.questionService" เป็นค่า null
ที่ com.ptoject.demo.controller.QuestionController.getAll(QuestionController.java:64) ที่ com.ptoject.demo.controller.QuestionControllerTest.getAll(QuestionControllerTest.java:12) <31 การเรียกภายใน> ที่ java.base/java.util.ArrayList.forEach(ArrayList.java:1511)<9 การเรียกภายใน> ที่ java.base/java.util.ArrayList.forEach(ArrayList.java:1511)<25 การเรียกภายใน>
คำตอบ #1
ปัญหานี้เกิดจากการใช้คำสั่ง @JsonIgnore บนฟิลด์ List<Answer> answers ในคลาสเอนทิตี Question ของคุณ ซึ่งสิ่งนี้บอกให้ Jackson มองข้าม (เช่น ไม่ต้องรวม) ฟิลด์นี้เมื่อทำการแปลงวัตถุเป็น JSON
เพื่อแก้ไขสิ่งนี้:
ในคลาสเอนทิตี้
Questionของคุณ ให้ลบคำอธิบายประกอบ@JsonIgnoreออกจากฟิลด์List<Answer> answersในทางกลับกัน ในคลาสเอนทิตี้
Answerของคุณ คุณควรเพิ่มคำอธิบายประกอบ@JsonIgnoreไปที่ฟิลด์Question question-- นี่เป็นการป้องกันปัญหา recursion แบบไม่สิ้นสุดของ Jackson ที่อาจเกิดขึ้นจากความสัมพันธ์แบบสองทาง
เอนทิตีคำถาม:
...
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;
...
}

