QYC
QYC

Reputation: 55

Thymeleaf th:each cannot find object

I'm using Thymeleaf with springboot 2.1.2, and I have a problem with Thymeleaf's th:each tag. Here's my code:

    <tr th:each="addr : ${user.addressList}">
        <td><input type="text" th:value="${addr.name}" th:field="${addr.name}"></td>
        <td><input type="text" th:value="${addr.phone}" th:field="${addr.phone}"></td>
        <td><input type="text" th:value="${addr.location}" th:field="${addr.location}"></td>
        <td><a th:href="@{/user/addr/del/{id}(id=${addr.id})}">del</a></td>
    </tr>

However, I encounter the following exception:

java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'addr' available as request attribute

The debugger says it can't find the variable, however, I'm sure that the view has a binding object named 'user' because I use it else where in the front.

Here's my controller and my entities:

@GetMapping("/profile")
public String getProfile(HttpServletRequest request, Model model) {
    HttpSession session = request.getSession(false);
    String email = (String) session.getAttribute("userId");
    User user = userService.getProfile(email);
    model.addAttribute("user", user);
    return "user/profile";
}

@Entity
public class User {

    @Id
    private String email;
    private String userName, password;
    private int pts;    //积分
    private int status= Flags.USER_EXIST;

    @OneToMany(targetEntity = Address.class
        , fetch = FetchType.LAZY, mappedBy = "user"
        , cascade = {CascadeType.PERSIST,CascadeType.REMOVE})
    private List<Address> addressList = new ArrayList<>();

    //constructors and getters and setters below...
}

@Entity
public class Address {
    @Id
    @GeneratedValue //去掉这个注解会导致一系列bug
    private long id;
    private String name, phone, location;
    @ManyToOne(targetEntity = User.class)
    private User user;

    //constructors and getters and setters below...
}

I follow the tutorial here and I can't find any difference between my th:each usage and the tutorial's. Could you help me?

UPDATE ============================================>

Here's my code for UserService.getProfile(String email):

@Override
public User getProfile(String email) {
    Optional<User> res = userRepository.findById(email);
    return res.isPresent() ? res.get() : null;
}

UPDATE again ==========================================================>

summerize what i've done so far:

1) use FetchType.EAGER in User class

2) force the repository to retrieve the relevant Address by printing the addressList:

user.getAddressList().stream().forEach(x -> System.out.println(x.getLocation()));

3) delete the th:each block and restart my app, there is no exception; when i add back the block, my app fails again...

All of these measures don't seem to help me....

Upvotes: 0

Views: 3426

Answers (3)

QYC
QYC

Reputation: 55

I thought I've found the problem...It's very tricky...I edit the th:each block, and now it's like :

    <tr th:each="addr : ${user.addressList}">
        <td><input type="text" th:value="${addr.name}"></td>
        <td><input type="text" th:value="${addr.phone}"></td>
        <td><input type="text" th:value="${addr.location}"></td>
        <td><a th:href="@{/user/addr/del/{id}(id=${addr.id})}">del</a></td>
    </tr> 

Have you found the difference? Yes i remove the th:field tag! In the origin code, what i did is binding the Address object's attribute in the list to a iterator! Maybe it's readonly i think...so the compiler would expect an object named 'addr' in the model, of course this would fail...

This bug alarms me to separate the modification function and the presentation function into respective view...

Upvotes: 0

wi2ard
wi2ard

Reputation: 1545

Problem not related to JPA after all. The problem is how you use th:field. See documentation about correct usage: https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#dynamic-fields

Try this:

<tr th:each="addr,addrStat : ${user.addressList}">
        <td><input type="text" th:value="${addr.name}" th:field="*{addressList[__${addrStat.index}__].name}"></td>

...

Upvotes: 1

Sumit
Sumit

Reputation: 957

You're having this error :-

java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'addr' available as request attribute

Because you're using @ModelAttribute annotation on your controller.
You're trying to send an object named addr to your controller after submit , However you're not sending any object by the exact name addr in/from your controller from where you're opening your requested html page.

Here's a quick example of how you send object using @ModelAttribute annotation :--

1.) Your requested controller from where you're opening the requested HTML page would be something like this: -

@RequestMapping("/test")
    public String test(HttpServletRequest request,Model model){

    //your code
  model.addAttribute("addr",new MyTest());
  return "yourRequestedPage";
  }

2.) And after submit your next controller would be something like this :-

@RequestMapping("/testAfterSubmit")
    public String someMethodName(@ModelAttribute("addr") MyTest myTestClass,HttpServletRequest request,Model model ) {
//your code . . 

 }

PS - For more information what @ModelAttribute does/is , here's a quick reference :-

What is @ModelAttribute in Spring MVC?

Upvotes: 0

Related Questions