1

ここで質問して申し訳ありませんが、何が起こっているのか理解できません。運が悪いので、何時間もウェブ全体で答えを探していました。

Hibernate 5.0.7.Final を使用する WildFly 10.0.0.Final サーバーで実行されているVRaptor (MVC フレームワーク)を使用して、JPA でモデル化された簡単なクイズがあります。クイズには、それぞれ2 ~ 10の選択肢がある多くの質問があります。

現在、ユーザーがクイズで質問を追加/削除する方法を実装しています。呼び出す前merge(quiz)に、検証を実行して、すべてが有効であることを確認します。合格です。エラーは発生しません。

検証エラーがないため、呼び出しmerge(quiz)て、最終的に次の例外が表示されます。

javax.validation.ConstraintViolationException: Validation failed for classes [game.Question] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
    ConstraintViolationImpl{interpolatedMessage='Cannot be empty', propertyPath=alternatives, rootBeanClass=class game.Question, messageTemplate='{org.hibernate.validator.constraints.NotEmpty.message}'}
]

[編集] 意図的に何かを空白のままにすると、検証エラーが表示され、検証が試行されないmerge()ため、検証は期待どおりに実行されます。

私は手動ですべてをチェックしましたが、実際にはエラーはありません。この「代替」メソッドを使用して、検証エラーを確認して出力しました。

private void val(final Object obj, final String s) {
    final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    final javax.validation.Validator validator = factory.getValidator();
    final Set<ConstraintViolation<Object>> constraintViolations = validator.validate(obj);
    for (final ConstraintViolation cv : constraintViolations) {
        log.info("-------------");
        log.info(s + " ValidatationConstraint: " + cv.getConstraintDescriptor().getAnnotation());
        log.info(s + " ValidatationConstraint: " + cv.getConstraintDescriptor());
        log.info(s + " ValidatationConstraint: " + cv.getMessageTemplate());
        log.info(s + " ValidatationConstraint: " + cv.getInvalidValue());
        log.info(s + " ValidatationConstraint: " + cv.getLeafBean());
        log.info(s + " ValidatationConstraint: " + cv.getRootBeanClass());
        log.info(s + " ValidatationConstraint: " + cv.getPropertyPath().toString());
        log.info(s + " ValidatationConstraint: " + cv.getMessage());
        log.info("-------------");
    }
}

これは、質問の追加/削除メソッドが行うこととほぼ同じです。

@Transactional
public void updateQuestions(final String quizId, final List<Question> questions) {
    // Quizzes might have slugs (/quiz-name)
    final Quiz quiz = findQuizByIdString(quizId);
    if (quiz != null) {
        for (final Question question : questions) {
            question.setQuiz(quiz);

            if (question.getAlternatives() != null) {
                for (final Alternative alt : question.getAlternatives()) {
                    alt.setQuestion(question);
                }
            }

            if (question.getId() != null) {
                final Question old = (Question) ps.createQuery("FROM Question WHERE id = :id AND quiz = :quiz").setParameter("id", question.getId()).setParameter("quiz", quiz).getSingleResult();

                // Making sure the Question do belong to the this Quiz
                if (old == null) {
                    question.setId(null);
                }
            }

            if (question.getId() == null) {
                // Set the new question up (who created, timestamp, etc.)
            }
        }

        quiz.setQuestions(questions);

        if (!validator.validate(quiz).hasErrors()) {
            try {
                entityManager.merge(quiz);
            } catch (final Exception e) {
                if (log.isErrorEnabled()) { log.error("Error while updating Quiz Questions", e); }
            }
        }
    }
    else {
        // Send an error to the user
    }
}

そして最後に、これらは私のエンティティの(私が思うに)関連する部分です:

@Entity
public class Quiz {
    /* ... */
    @Valid // FYI: This just makes the validation cascade
    @OneToMany(mappedBy = "quiz", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
    private List<Question> questions;
    /* ... */
}

@Entity
public class Question {
    /* ... */
    @Valid
    @NotEmpty
    @Size(min = 2, max = 10)
    @OneToMany(mappedBy = "question", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
    private List<Alternative> alternatives;
    /* ... */
}

@Entity
public class Alternative {
    /* ... */
    @NotBlank
    @Size(max = 0xFF)
    @Column(length = 0xFF, nullable = false)
    private String text; // The only field that must be filled
    /* ... */
}
4

1 に答える 1

2

とった。persist()の前に新しい質問を使用することを友人が提案しましたmerge()

彼はそれmerge()が新しいエンティティを作成しないためだと言いましたが。JPA 仕様によると、エンティティが存在しない場合、その新しいインスタンスが永続化コンテキストで作成され、元のインスタンスがそれにコピーされます。

Java では、コピーはすべての生命の破滅です。Question のすべてのインスタンスには、コピーされListいないAlternatives の配列(のインスタンス)があるため(私の知る限り、おそらくエンティティではないか、単にコピーが浅いためです)。List

まあ、助けようとした人にはとにかく感謝し、将来これにぶつかるかもしれない人には幸運を祈ります.

[編集] 問題は JPA のコピーが原因でした。あるのでコピーQuizList<Question>れますが、理由がよくわかりません(浅いコピーですか?)List<Alternative>それぞれがコピーされていませんでしたQuestion。のフィールドで@NotEmpty検証が失敗したのはそのためです。alternativesQuestion

persist()マージする前にそれぞれの新しいものを呼び出すQuestionと、それらは永続コンテキストの一部になり、コピーはもう必要ありません。

これを行うことでそれを行いました:

for (int i = 0, max = questions.size(); i < max; i++) {
    Question question = questions.get(i);

    /* all of that previous code */

    if (question.getId() == null) {
        entityManager.persist(question);
    }
    else {
        /* merge() returns the newly merged and managed (as in it is now part of
           the persistence context) instance, so replace the "old" one */
        questions.set(i, entityManager.merge(question));
    }
}
于 2016-06-02T17:21:16.090 に答える