Brak zmian podczas modyfikacji encji

0

Witam

Mam taki problem że w momencie zapisu obiektu(przyjmijmy obiekt o nazwie A) Hibernate nie widzi zmian. W javie wszystko dobrze się dodaje(debuger) lecz w momencie wykonania metody save na obiekcie A, pola w obiekcie B nie są ustawiane. Już w metodzie @PrePersist(obiektu B) pola widnieją jako null. Dodam także że dodałem zapis kaskadowy PERSIST.

przykładowy scenariusz

  • pobieram obiekt A z bazy, który zawiera listę obiektów typu B
  • tworze n obiektów typu B
  • dodaje nowe obiekty B do listy w obiekcie A
  • zapis repository.save(A)

Z góry dzięki za pomoc.

0

A w jaki sposób zarządzasz transakcją?

0

To znaczy? Na metodzie w serwisie używam adnotacji @Transactional bez parametrów.

0

Pokaz kod

0
@EqualsAndHashCode(exclude = {"questions", "studentScores"})
@ToString(exclude = {"questions", "studentScores"})
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter(value = AccessLevel.PACKAGE)
@Entity
@Table(name = "exam")
class ExamEntity {

    @Id
    @Setter(value = AccessLevel.NONE)
    private UUID id;

    @OneToMany(mappedBy = "exam", cascade = CascadeType.PERSIST)
    private List<StudentScoreEntity> studentScores;

...
}
@EqualsAndHashCode(exclude = {"exam"})
@ToString(exclude = {"studentDetails", "exam", /*"studentQuestions"*/})
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter(value = AccessLevel.PACKAGE)
@Entity
@Table(name = "student_score")
class StudentScoreEntity {

    @Id
    @GeneratedValue
    private UUID id;

    @Enumerated(value = EnumType.STRING)
    private StudentScoreStatus status;

    @JoinColumn(name = "fk_exam")
    @ManyToOne
    private ExamEntity exam;

...
}
    @Transactional
    void addStudentsToExamByGroup(final UUID examId, final UUID groupId) {
        final ExamEntity exam = findExamById(examId);
        exam.validateExamStatusForModification();
        exam.validateStudentsFromGroupAlreadyExist(groupId);
        prepareStudentScore(groupId, exam);
        examRepository.save(exam);
        log.info("Exam with id '{}' has been updated by student from groups with id '{}'.", examId, groupId);
    }

`

0
  1. Co robi findExamById?
  2. Moje oczy krwawią od tego Lomboka.
0

Pobiera encje z bazy

    private ExamEntity findExamById(final UUID examId) {
        return examRepository.findById(examId).orElseThrow(() -> new ExamNotFound(examId));
    }

A co jest nie tak z Lombokiem?

0

1.Co nie tak z lombokiem? Poza adnotacjami za adnotacjami na widok których chce sie rzygać, nie jest to Javovy kod który możesz łatwo zdebugować itd?
2.Jak generujesz idka do StudentScoreStatus? na pewno prawidłowo, przez Hibernate?
3.Jak wygląda kod examRepository?

0

StudentScoreStatus to enum, nie posiada własnego id.

ExamRepository code: nic ciekawego się tutaj nie dzieje

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
interface ExamRepository extends JpaRepository<ExamEntity, UUID> {
}

Natomiast jeśli chodzi o generowanie idków to używam adnotacji @GeneratedValue

1

Hmmm dobra, ustalmy fakty. Po pierwsze, używając poprawnie JPA/Hibernate nie musisz wołać jawnie save/update. Po to używasz ORM-a, żeby nie musieć - masz dirty checking z paczki. Po drugie, faktyczne inserty/updaty lecą po zacomittowaniu transakcji, a wiec poza Twoim kodem. Masz tego świadomość?

Napisz dokładnie jakie pola nie są ustawione? Dostajesz jakiś exception? Co robi ten prepareStudentScore? Pokazałeś bardzo mało istotnego kodu.

0

StudentScoreStatus

Chodziło mi o StudentScoreStatusEntit, sory :D

0
Charles_Ray napisał(a):

Hmmm dobra, ustalmy fakty. Po pierwsze, używając poprawnie JPA/Hibernate nie musisz wołać jawnie save/update. Po to używasz ORM-a, żeby nie musieć - masz dirty checking z paczki. Po drugie, faktyczne inserty/updaty lecą po zacomittowaniu transakcji, a wiec poza Twoim kodem. Masz tego świadomość?

Napisz dokładnie jakie pola nie są ustawione? Dostajesz jakiś exception? Co robi ten prepareStudentScore? Pokazałeś bardzo mało istotnego kodu.

  • Nie bardzo rozumiem jak to nie trzeba robić 'repository.save' żeby pchnąć zmiany do DB?
  • Napisz dokładnie jakie pola nie są ustawione? Wszystkie relacje
  • Exception leci w metodzie 'PrePersist' w klasie 'StudentScoreEntity' ze względu na to że lista w tym momencie jest pusta mimo iż w metodzie 'addStudentQuestion' jest not null. W sumie wrzucę całe encje może coś przeoczyłem.
@EqualsAndHashCode(exclude = {"questions", "studentScores"})
@ToString(exclude = {"questions", "studentScores"})
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter(value = AccessLevel.PACKAGE)
@Entity
@Table(name = "exam")
class ExamEntity {

    @Id
    @Setter(value = AccessLevel.NONE)
    private UUID id;

    @Enumerated(EnumType.STRING)
    @Column
    private ExamStatus status;

    @OneToMany(mappedBy = "exam", cascade = CascadeType.PERSIST)
    private List<StudentScoreEntity> studentScores;

    @ManyToMany
    @JoinTable(name = "rel_question_exam",
            joinColumns = @JoinColumn(name = "fk_exam"),
            inverseJoinColumns = @JoinColumn(name = "fk_question"))
    private List<QuestionEntity> questions;

    void addStudentScore(final StudentScoreEntity studentScore) {
        studentScores.add(studentScore);
    }

    void validateExamStatusForModification() {
        if (status != ExamStatus.CREATED) {
            throw new ExamModificationException(id, status);
        }
    }

    void validateStudentsFromGroupAlreadyExist(final UUID groupId) {
        final boolean haveStudents = studentScores.stream()
                .map(StudentScoreEntity::getStudentDetails)
                .map(StudentDetailsEntity::getStudentGroup)
                .map(StudentGroupEntity::getId)
                .anyMatch(x -> x.equals(groupId));

        if (haveStudents) {
            throw new StudentGroupAlreadyExistInExamException(id, groupId);
        }
    }
}
@EqualsAndHashCode(exclude = {"studentDetails", "exam", "studentQuestions"})
@ToString(exclude = {"studentDetails", "exam", "studentQuestions"})
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter(value = AccessLevel.PACKAGE)
@Entity
@Table(name = "student_score")
class StudentScoreEntity {

    @Id
    @GeneratedValue
    private UUID id;

    @Enumerated(value = EnumType.STRING)
    private StudentScoreStatus status;

    @JoinColumn(name = "fk_exam")
    @ManyToOne
    private ExamEntity exam;

    @JoinColumn(name = "fk_student_details")
    @ManyToOne
    private StudentDetailsEntity studentDetails;

    @JoinColumn(name = "fk_student_score")
    @OneToMany(cascade = CascadeType.PERSIST)
    private List<StudentQuestionEntity> studentQuestions;

    @Builder
    StudentScoreEntity(final StudentDetailsEntity studentDetails, final ExamEntity exam) {
        this.studentDetails = studentDetails;
        this.exam = exam;
        studentQuestions = new ArrayList<>();
    }

    void addStudentQuestion(final StudentQuestionEntity StudentQuestion) {
        studentQuestions.add(StudentQuestion);
    }

    @PrePersist
    private void prePersist() {
        status = StudentScoreStatus.CREATED;
        Collections.shuffle(studentQuestions);
    }
}
@EqualsAndHashCode(exclude = {"studentAnswers", "studentScore"})
@ToString(exclude = {"studentAnswers", "studentScore"})
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter(value = AccessLevel.PACKAGE)
@Setter(value = AccessLevel.PACKAGE)
@Entity
@Table(name = "student_question")
class StudentQuestionEntity {

    @Id
    @Setter(value = AccessLevel.NONE)
    @GeneratedValue
    private UUID id;

    @JoinColumn(name = "fk_question")
    @ManyToOne
    private QuestionEntity question;

    @JoinColumn(name = "fk_student_score")
    @ManyToOne
    private StudentScoreEntity studentScore;

    @JoinColumn(name = "fk_student_question")
    @OneToMany(cascade = CascadeType.PERSIST)
    private List<StudentAnswerEntity> studentAnswers;

    @Builder
    StudentQuestionEntity(final QuestionEntity question, final StudentScoreEntity studentScore) {
        this.question = question;
        this.studentScore = studentScore;
        studentAnswers = new ArrayList<>();
    }

    void addStudentAnswer(final StudentAnswerEntity studentAnswer) {
        studentAnswers.add(studentAnswer);
    }

    @PrePersist
    private void prePersist() {
        Collections.shuffle(studentAnswers);
    }
}

package com.exam.feature.admin.exam.update;

import com.exam.commons.exception.custom.not_found.ExamNotFound;
import com.exam.commons.exception.custom.not_found.StudentGroupNotFound;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.UUID;

@Slf4j
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
@Service
class UpdateExamService {

    private final ExamRepository examRepository;
    private final StudentGroupRepository studentGroupRepository;

    @Transactional
    public void addStudentsToExamByGroup(final UUID examId, final UUID groupId) {
        final ExamEntity exam = findExamById(examId);
        exam.validateExamStatusForModification();
        exam.validateStudentsFromGroupAlreadyExist(groupId);
        prepareStudentScores(groupId, exam);
        examRepository.save(exam);
        log.info("Exam with id '{}' has been updated by student from groups with id '{}'.", examId, groupId);
    }


    private void prepareStudentScores(final UUID groupId, final ExamEntity exam) {
        final List<StudentDetailsEntity> students = findStudentByGroupId(groupId).getStudentDetails();
        final List<QuestionEntity> question = exam.getQuestions();
        for (final StudentDetailsEntity student: students) {
            final StudentScoreEntity studentScore = createStudentScore(exam, student);
            prepareStudentQuestions(question, studentScore);
            exam.addStudentScore(studentScore);
        }
    }

    private void prepareStudentQuestions(final List<QuestionEntity> selectedQuestions, final StudentScoreEntity studentScore) {
        for (final QuestionEntity question: selectedQuestions) {
            final StudentQuestionEntity studentQuestion = createStudentQuestion(studentScore, question);
            prepareStudentAnswers(studentQuestion);
            studentScore.addStudentQuestion(studentQuestion);
        }
    }

    private void prepareStudentAnswers(final StudentQuestionEntity studentQuestion) {
        final List<AnswerEntity> answers = studentQuestion.getQuestion().getAnswers();
        for (final AnswerEntity answer: answers) {
            final StudentAnswerEntity studentAnswer = createStudentAnswer(studentQuestion, answer);
            studentQuestion.addStudentAnswer(studentAnswer);
        }
    }

    private StudentScoreEntity createStudentScore(final ExamEntity exam, final StudentDetailsEntity student) {
        return StudentScoreEntity.builder()
                .studentDetails(student)
                .exam(exam)
                .build();
    }

    private StudentAnswerEntity createStudentAnswer(final StudentQuestionEntity studentQuestion, final AnswerEntity answer) {
        return StudentAnswerEntity.builder()
                .answer(answer)
                .studentQuestion(studentQuestion)
                .build();
    }

    private StudentQuestionEntity createStudentQuestion(final StudentScoreEntity studentScore, final QuestionEntity question) {
        return StudentQuestionEntity.builder()
                .question(question)
                .studentScore(studentScore)
                .build();
    }

    private ExamEntity findExamById(final UUID examId) {
        return examRepository.findById(examId).orElseThrow(() -> new ExamNotFound(examId));
    }

    private StudentGroupEntity findStudentByGroupId(final UUID groupId) {
        return studentGroupRepository.findById(groupId).orElseThrow(() -> new StudentGroupNotFound(groupId));
    }
}
0

Ciekawe, jak usunąłem repository.save z metody to nagle zaczęło działać

    @Transactional
    public void addStudentsToExamByGroup(final UUID examId, final UUID groupId) {
        final ExamEntity exam = findExamById(examId);
        exam.validateExamStatusForModification();
        exam.validateStudentsFromGroupAlreadyExist(groupId);
        prepareStudentScores(groupId, exam);
        examRepository.save(exam); // <---------------------------- usunięte 
        log.info("Exam with id '{}' has been updated by student from groups with id '{}'.", examId, groupId);
    }

Czy ktoś może mi powiedzieć kiedy używać repository.save a kiedy nie? Bez save działa przy modyfikacji encji czy przy tworzeniu też?

1

W temacie tego sejwa na końcu:

0

Wielkie dzięki za zainteresowanie, temat do zamknięcia. A prelekcję sobie obejrzę.

1 użytkowników online, w tym zalogowanych: 0, gości: 1