ค้นหาเว็บไซต์

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

เพื่อแก้ไขสิ่งนี้:

  1. ในคลาสเอนทิตี้ Question ของคุณ ให้ลบคำอธิบายประกอบ @JsonIgnore ออกจากฟิลด์ List<Answer> answers

  2. ในทางกลับกัน ในคลาสเอนทิตี้ 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;
    ...
}