Thymeleaf + Spring Boot,使用ArrayList的动态字段

huangapple 未分类评论51阅读模式
英文:

Thymeleaf + Spring Boot, dynamic fields with ArrayList

问题

以下是您提供的内容的翻译部分:


我正在使用Spring Boot与Thymeleaf,并尝试使用表单设置列表中的元素(这些元素是对象)。

更准确地说,我有两个实体:Question(它聚合了Answers列表),我将Question对象传递给模型,并尝试在迭代中设置每个Answer对象。在按下保存(提交表单)时,我遇到了异常:

> 对象'question'的字段错误,在字段'answers'上:被拒绝的值[[Answer(id=0, answerNumber=1, text=, correct=false),Answer(id=0, answerNumber=2, text=, correct=false),Answer(id=0, answerNumber=3, text=, correct=false),Answer(id=0, answerNumber=4, text=, correct=false)]];代码[typeMismatch.question.answers,typeMismatch.answers,typeMismatch.java.util.List,typeMismatch];参数[org.springframework.context.support.DefaultMessageSourceResolvable:代码[question.answers,answers];参数[];默认消息[answers];默认消息[无法将类型为'java.lang.String'的属性值转换为所需类型'java.util.List'以用于属性'answers';嵌套异常是java.lang.IllegalStateException:无法将类型为'java.lang.String'的值转换为属性'answers[0]'所需的类型'com.easetest.website.model.Answer':找不到匹配的编辑器或转换策略]]

我的控制器类的question方法

@PostMapping("/editQuestion")
public String editQuestion(Model model, @RequestParam("test_id") int id, @RequestParam(value = "question_id", required = false) Integer question_id) {
    // ...
}

@PostMapping("/saveQuestion")
public String saveQuestion(Model model, @ModelAttribute("question") Question question, @RequestParam("test_id") int id) {
    // ...
}

Question Form

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <!-- Bootstrap依赖项 -->
    <!-- ... -->
</head>
<body class="mb-2 bg-secondary text-white">
<div class="container">
    <!-- ... -->
</div>
</body>
</html>

Question实体

@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class Question {
    // ...
}

Answer实体

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Answer {
    // ...
}

有人知道是什么导致了这个错误,以及我如何解决它吗?


英文:

I'm using Spring Boot with Thymeleaf and trying to set elements (that are objects) of list using form.
More precisely I have 2 entities: Question that aggregates List of Answers, I'm passing Question object to the model and trying to set each Answer object in iteration. When pressing save (submiting form) i got exception:

> Field error in object 'question' on field 'answers': rejected value [[Answer(id=0, answerNumber=1, text=, correct=false), Answer(id=0, answerNumber=2, text=, correct=false), Answer(id=0, answerNumber=3, text=, correct=false), Answer(id=0, answerNumber=4, text=, correct=false)]]; codes [typeMismatch.question.answers,typeMismatch.answers,typeMismatch.java.util.List,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [question.answers,answers]; arguments []; default message [answers]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.List' for property 'answers'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.easetest.website.model.Answer' for property 'answers[0]': no matching editors or conversion strategy found]]

My Controller class question methods

@PostMapping(&quot;/editQuestion&quot;)
public String editQuestion(Model model, @RequestParam(&quot;test_id&quot;) int id, @RequestParam(value = &quot;question_id&quot;, required = false) Integer question_id) {
    Test test = testService.getById(id);
    List&lt;Question&gt; questions = test.getQuestions();
    System.out.println(questions);
    int realNumberOfQuestion = questions.size(); // mozliwe ze nie potrzebne
    test.setMultipleAnswers(false);
    Question q = question_id == null ? null : questionService.getById(question_id);
    if(q == null) {
        q = new Question();
        if(realNumberOfQuestion &lt; 1) {
            q.setQuestionNumber(1);
        } else if(questions.get(realNumberOfQuestion - 1).getQuestionBody().isEmpty()){
            q = questions.get(realNumberOfQuestion - 1);
        } else {
            q.setQuestionNumber(realNumberOfQuestion);
        }
    }

    model.addAttribute(&quot;test&quot;, test);
    model.addAttribute(&quot;question&quot;, q);
    System.out.println(q);

    return &quot;business/question_form&quot;;
}

@PostMapping(&quot;/saveQuestion&quot;)
public String saveQuestion(Model model, @ModelAttribute(&quot;question&quot;) Question question, @RequestParam(&quot;test_id&quot;) int id) {
    System.out.println(&quot;Lubu dubu&quot;);
    Test test = testService.getById(id);
    test.setQuestion(question);
    testService.save(test);
    model.addAttribute(&quot;test&quot;, test);
    model.addAttribute(&quot;question&quot;, question);
    return &quot;business/question_form&quot;;
}

Question Form

&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot; xmlns:th=&quot;http://www.w3.org/1999/xhtml&quot;&gt;
&lt;head&gt;
    &lt;!--Bootstrap dependencies--&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css&quot;
          integrity=&quot;sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk&quot; crossorigin=&quot;anonymous&quot;&gt;
    &lt;script src=&quot;https://code.jquery.com/jquery-3.5.1.slim.min.js&quot;
            integrity=&quot;sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj&quot;
            crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js&quot;
            integrity=&quot;sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo&quot;
            crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js&quot;
            integrity=&quot;sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI&quot;
            crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js&quot;&gt;&lt;/script&gt;
    
    &lt;script th:unless=&quot;${test.multipleAnswers}&quot;&gt;
        $(document).on(&#39;click&#39;, &#39;input[type=&quot;checkbox&quot;]&#39;, function () {
            $(&#39;input[type=&quot;checkbox&quot;]&#39;).not(this).prop(&#39;checked&#39;, false);
        });
    &lt;/script&gt;


    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;title&gt;Create Question&lt;/title&gt;
&lt;/head&gt;
&lt;body class=&quot;mb-2 bg-secondary text-white&quot;&gt;
&lt;div class=&quot;container&quot;&gt;
    &lt;div&gt;
        &lt;h2 th:text=&quot;${test.testName}&quot;&gt;&lt;/h2&gt;

    &lt;/div&gt;
    &lt;div class=&quot;form-group&quot;&gt;
        &lt;h4&gt;Add question&lt;/h4&gt;
        &lt;form action=&quot;#&quot; th:action=&quot;@{/business/saveQuestion}&quot; th:object=&quot;${question}&quot; method=&quot;post&quot;&gt;
            &lt;div class=&quot;form-group&quot;&gt;
                &lt;input type=&quot;hidden&quot; th:field=&quot;*{id}&quot;&gt;
                &lt;input type=&quot;hidden&quot; th:field=&quot;*{answers}&quot;&gt;
                &lt;input type=&quot;hidden&quot; th:name=&quot;test_id&quot; th:value=&quot;${test.id}&quot;&gt;
                &lt;div class=&quot;form-group&quot;&gt;
                    &lt;label for=&quot;exampleFormControlTextarea1&quot;&gt;Question body&lt;/label&gt;
                    &lt;textarea class=&quot;form-control&quot; id=&quot;exampleFormControlTextarea1&quot;
                              th:value=&quot;${question.questionBody}&quot; rows=&quot;3&quot;&gt;&lt;/textarea&gt;
                &lt;/div&gt;
                &lt;br&gt;

                &lt;div th:each=&quot;answer, itemStat : *{answers}&quot;&gt;
                    &lt;div class=&quot;input-group mb-3&quot;&gt;
                        &lt;div class=&quot;input-group-prepend&quot;&gt;
                            &lt;div class=&quot;input-group-text&quot;&gt;
                                &lt;input type=&quot;hidden&quot; th:field=&quot;*{answers[__${itemStat.index}__].id}&quot;&gt;
                                &lt;input type=&quot;hidden&quot; th:field=&quot;*{answers[__${itemStat.index}__].answerNumber}&quot;&gt;
                                &lt;input type=&quot;checkbox&quot; th:field=&quot;*{answers[__${itemStat.index}__].correct}&quot;&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                        &lt;input type=&quot;text&quot; class=&quot;form-control&quot; th:field=&quot;*{answers[__${itemStat.index}__].text}&quot;&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;br&gt;
            &lt;input type=&quot;submit&quot; th:value=&quot;Save&quot; class=&quot;btn btn-info&quot;&gt;
        &lt;/form&gt;
    &lt;/div&gt;
    &lt;ul class=&quot;pagination&quot;&gt;
        &lt;th:block th:each=&quot;question: ${test.questions}&quot;&gt;
            &lt;form method=&quot;post&quot; action=&quot;#&quot; th:action=&quot;@{/business/editQuestion}&quot;&gt;
                &lt;input type=&quot;hidden&quot; th:name=&quot;test_id&quot; th:value=&quot;${test.id}&quot;&gt;
                &lt;input type=&quot;hidden&quot; th:name=&quot;question_id&quot; th:value=&quot;${question.id}&quot;&gt;
                &lt;input type=&quot;submit&quot; class=&quot;btn btn-danger&quot; th:value=${question.questionNumber}&gt;
            &lt;/form&gt;
        &lt;/th:block&gt;
        &lt;form method=&quot;post&quot; action=&quot;#&quot; th:action=&quot;@{/business/addQuestion}&quot;&gt;
            &lt;input type=&quot;hidden&quot; th:name=&quot;test_id&quot; th:value=&quot;${test.id}&quot;&gt;
            &lt;input type=&quot;hidden&quot; th:name=&quot;question_id&quot; th:value=&quot;${question.id}&quot;&gt;
            &lt;input type=&quot;submit&quot; class=&quot;btn btn-success&quot; value=&quot;New Question&quot;&gt;
        &lt;/form&gt;
    &lt;/ul&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

Question entity

@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class Question {

    @Id
    @GeneratedValue(strategy= GenerationType.AUTO, generator=&quot;native&quot;)
    @GenericGenerator(name = &quot;native&quot;, strategy = &quot;native&quot;)
    private int id;

    @EqualsAndHashCode.Exclude
    private int questionNumber;

    @EqualsAndHashCode.Exclude
    private String questionBody = &quot;&quot;;

    @EqualsAndHashCode.Exclude
    @ToString.Exclude
    @ManyToOne
    @JoinColumn(name = &quot;test_id&quot;, nullable = false)
    private Test test;

    @EqualsAndHashCode.Exclude
    @OneToMany(cascade = CascadeType.ALL, mappedBy = &quot;question&quot;, orphanRemoval = true)
    private List&lt;Answer&gt; answers = Arrays.asList(
            new Answer(1, &quot;&quot;, false),
            new Answer(2, &quot;&quot;, false),
            new Answer(3, &quot;&quot;, false),
            new Answer(4, &quot;&quot;, false));

    public Question(int questionNumber) {
        this.questionNumber = questionNumber;
    }

    public void setAnswer(Integer num, String answer, boolean correct) {
        System.out.println(answers);
        if(answers == null) {
            answers = new ArrayList&lt;&gt;();
        }
        Answer a = answers.get(num - 1) != null ? answers.get(num - 1) : new Answer();
        a.setAnswerNumber(num);
        a.setText(answer);
        a.setCorrect(correct);
        a.setQuestion(this);
        answers.set(num-1, a);
    }

    public void removeAnswer(Integer num) {
        if(answers == null) {
            return;
        }
        answers.set(num-1, new Answer(num, &quot;&quot;, false));
        }
    }

Answer entity

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Answer {
    @Id
    @GeneratedValue(strategy= GenerationType.AUTO, generator=&quot;native&quot;)
    @GenericGenerator(name = &quot;native&quot;, strategy = &quot;native&quot;)
    private int id;
    @EqualsAndHashCode.Exclude
    private int answerNumber;
    @EqualsAndHashCode.Exclude
    private String text;
    @EqualsAndHashCode.Exclude
    private boolean correct;

    @EqualsAndHashCode.Exclude
    @ToString.Exclude
    @ManyToOne
    @JoinColumn(name = &quot;question_id&quot;)
    private Question question;`enter code here`

    public Answer(int answerNumber, String text, boolean correct) {
        this.answerNumber = answerNumber;
        this.text = text;
        this.correct = correct;
    }
}

Does anyone know what cause that error and how can I solve it?

答案1

得分: 0

找到解决方案。

错误消息中最重要的信息是:
无法将类型为 'java.lang.String' 的值转换为所需类型 'com.easetest.website.model.Answer'
为了解决这个问题,我必须:

  • 创建实现 Converter<String, Answer> 接口的转换器

  • 在 WebConfig 中添加该转换器

下面是实现:

转换器类:

import com.easetest.website.model.Answer;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class StringToAnswerConverter implements Converter&lt;String, Answer&gt; {

    @Override
    public Answer convert(String s) {
        String[] data = s.split(&quot;,&quot;);
        return new Answer(
                Integer.parseInt(getSubstringAfterChar(data[1], &#39;=&#39;)),
                new String(getSubstringAfterChar(data[2], &#39;=&#39;)),
                Boolean.parseBoolean(getSubstringAfterChar(data[3], &#39;=&#39;)));
    }

    private String getSubstringAfterChar(String text, char character) {
        return text.substring(text.lastIndexOf(character) + 1);
    }
}

WebConfig 类

import com.easetest.website.converters.StringToAnswerConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToAnswerConverter());
    }
}

希望对某人有用。

英文:

Found solution.

The most important message of the error message is:
Cannot convert value of type 'java.lang.String' to required type 'com.easetest.website.model.Answer'.
To solve the problem I had to:

  • create converter that implements Converter<String, Answer>

  • add that converter in WebConfig

implementation below:

Converter class:

import com.easetest.website.model.Answer;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class StringToAnswerConverter implements Converter&lt;String, Answer&gt; {

    @Override
    public Answer convert(String s) {
        String[] data = s.split(&quot;,&quot;);
        return new Answer(
                Integer.parseInt(getSubstringAfterChar(data[1], &#39;=&#39;)),
                new String(getSubstringAfterChar(data[2], &#39;=&#39;)),
                Boolean.parseBoolean(getSubstringAfterChar(data[3], &#39;=&#39;)));
    }

    private String getSubstringAfterChar(String text, char character) {
        return text.substring(text.lastIndexOf(character) + 1);
    }
}

WebConfig class

import com.easetest.website.converters.StringToAnswerConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToAnswerConverter());
    }
}

Hope it will be usefull to somebody.

huangapple
  • 本文由 发表于 2020年6月29日 05:33:54
  • 转载请务必保留本文链接:https://java.coder-hub.com/62628448.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定