Reputation: 55
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
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
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
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