spring-boot - 双向 OneToMany-ManyToOne 关系引用未保存的瞬态实例(Spring MVC - Thymeleaf)
问题描述
新来的。我是 Spring 和 Thymeleaf 的新手,我正在尝试通过观看视频来学习,但我不知道为什么会出现以下异常(org.hibernate.TransientPropertyValueException:对象引用了未保存的瞬态实例 - 之前保存瞬态实例刷新: org.launchcode.codingevents.models.Event.eventCategory -> org.launchcode.codingevents.models.EventCategory) 当我尝试创建一个事件时,给它一个 Thymeleaf 形式的 EventCategory。我尝试从一侧级联,然后从另一侧级联,然后从两侧级联,但没有奏效。
我会非常感谢任何帮助我的人。
这是我的代码。
@MappedSuperclass
public abstract class AbstractEntity {
@Id
@GeneratedValue
private int id;
public int getId() {
return id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
AbstractEntity entity = (AbstractEntity) obj;
return this.id == entity.id;
}
@Entity
public class Event extends AbstractEntity {
@NotBlank(message = "Name is required")
@Size(min = 3, max = 50, message = "Name must be between 3 and 50 characters")
private String name;
@Size(max = 500, message = "Description too long!")
private String description;
@NotBlank(message = "Email is required")
@Email(message = "Invalid email. Try again")
private String contactEmail;
@ManyToOne
@NotNull(message = "Category is required")
private EventCategory eventCategory;
public Event() {
}
public Event(String name, String description, String contactEmail, EventCategory eventCategory) {
this.name = name;
this.description = description;
this.contactEmail = contactEmail;
this.eventCategory = eventCategory;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getContactEmail() {
return contactEmail;
}
public void setContactEmail(String contactEmail) {
this.contactEmail = contactEmail;
}
public EventCategory getEventCategory() {
return eventCategory;
}
public void setEventCategory(EventCategory eventCategory) {
this.eventCategory = eventCategory;
}
@Override
public String toString() {
return name;
}
@Entity
public class EventCategory extends AbstractEntity implements Serializable {
@Size(min = 3, message = "Name must be at least 3 characters long")
private String name;
@OneToMany(mappedBy = "eventCategory")
private final List<Event> events = new ArrayList<>();
public EventCategory() {
}
public EventCategory(@Size(min = 3, message = "Name must be at least 3 characters long") String name) {
this.name = name;
}
public List<Event> getEvents() {
return events;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
@Controller
@RequestMapping("events")
public class EventController {
@Autowired
private EventRepository eventRepository;
@Autowired
private EventCategoryRepository eventCategoryRepository;
@GetMapping
public String displayAllEvents(@RequestParam(required = false) Integer categoryId, Model model) {
if (categoryId == null) {
model.addAttribute("title", "All Events");
model.addAttribute("events", eventRepository.findAll());
} else {
Optional<EventCategory> result = eventCategoryRepository.findById(categoryId);
if (!result.isPresent()) {
model.addAttribute("title", "Invalid Category Id: " + categoryId);
} else {
EventCategory category = result.get();
model.addAttribute("title", "Events in Category: " + category.getName());
model.addAttribute("events", category.getEvents());
}
}
return "events/index";
}
// Lives at /events/create
@GetMapping("create")
public String displayCreateEventForm(Model model) {
model.addAttribute("title", "Create Event");
model.addAttribute(new Event());
model.addAttribute("categories", eventCategoryRepository.findAll());
return "events/create";
}
// lives at /events/create
@PostMapping("create")
public String processCreateEventForm(@Valid @ModelAttribute("newEvent") Event newEvent, Errors errors, Model model) {
if (errors.hasErrors()) {
model.addAttribute("title", "Create Event");
return "events/create";
}
model.addAttribute("events", eventRepository.findAll());
eventRepository.save(newEvent);
return "redirect:";
}
// lives at /events/delete
@GetMapping("delete")
public String displayDeleteEventForm(Model model) {
model.addAttribute("title", "Delete Events");
model.addAttribute("events", eventRepository.findAll());
return "events/delete";
}
// lives at /events/delete
@PostMapping("delete")
public String processDeleteEventForm(@RequestParam(required = false) int[] eventIds) {
if (eventIds != null) {
for (int id : eventIds) {
eventRepository.deleteById(id);
}
}
return "redirect:";
}
}
创建活动
<nav th:replace="fragments :: navigation"></nav>
<form method="post" th:action="@{/events/create}" th:object="${event}">
<div class="form-group">
<label>Name
<input class="form-control" th:field="${event.name}">
</label>
<p class="error" th:errors="${event.name}"></p>
</div>
<div class="form-group">
<label>Description
<input class="form-control" th:field="${event.description}">
</label>
<p class="error" th:errors="${event.description}"></p>
</div>
<div class="form-group">
<label>Contact Email
<input class="form-control" th:field="${event.contactEmail}">
</label>
<p class="error" th:errors="${event.contactEmail}"></p>
</div>
<div class="form-group">
<label>Category
<select th:field="${event.eventCategory}">
<option th:each="eventCategory : ${categories}" th:value="${eventCategory.id}"
th:text="${eventCategory.name}">
</option>
</select>
<p class="error" th:errors="${event.eventCategory}"></p>
</label>
</div>
<div th:replace="fragments :: create-button"></div>
</form>
解决方案
根据您的代码,您只是尝试保存事件实体并忽略 EventCategory。您需要将 Event 设置为 EventCategory 以及将 EventCategory 设置为 Event 并进行级联保存。
首先cascade
在 Event 实体中添加属性,如下所示。
@ManyToOne(cascade = CascadeType.ALL)
@NotNull(message = "Category is required")
private EventCategory eventCategory;
然后在 Controller 中进行以下更改。
@PostMapping("create")
public String processCreateEventForm(@Valid @ModelAttribute("newEvent") Event newEvent, Errors errors, Model model) {
if (errors.hasErrors()) {
model.addAttribute("title", "Create Event");
return "events/create";
}
model.addAttribute("events", eventRepository.findAll());
EventCategory eventCategory = newEvent.getEventCategory();
eventCategory.setEvent(newEvent);
eventRepository.save(newEvent);
return "redirect:";
}
推荐阅读
- java - 单词倒数第二个字母
- html - 我的图像没有加载,无论我尝试了什么,它都不起作用,所以任何人都可以找到可能的解决方案吗?
- javascript - 以“AWS”开头的消息属性名称。或“亚马逊”。保留供亚马逊使用
- php - 如何在 PHP 中获取字符串的第一部分?
- python - Seaborn 热图颜色 - 值大小的相同颜色
- solr - 当查询词中有特殊字符时,范围查询如何工作?
- swiftui - 如何使用 SwiftUI 设置图像
- c++ - 有没有办法在它的孩子中修改嵌套类的实现?
- validation - 当用户选择有效选项时,需要清除 EnumDropDownListFor 的客户端验证
- windows - Windows - 本地远程桌面的替代方案