【问题标题】:Bidirectional OneToMany-ManyToOne Relationship referencing unsaved transient instance (Spring MVC - Thymeleaf)双向 OneToMany-ManyToOne 关系引用未保存的瞬态实例(Spring MVC - Thymeleaf)
【发布时间】:2020-10-31 21:32:16
【问题描述】:

这里是新的。我是 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>

【问题讨论】:

    标签: spring-boot hibernate thymeleaf


    【解决方案1】:

    根据您的代码,您只是尝试保存事件实体并忽略 EventCategory。 您需要将 Event 设置为 EventCategory 以及将 EventCategory 设置为 Event 并进行级联保存。

    首先在 Event 实体中添加cascade 属性,如下所示。

        @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:";
        }
    

    【讨论】:

    • 感谢您的回答。我尝试了您的建议,但是当我将 CascadeType.ALL 添加到 eventCategory 时,它会在数据库中保留一个新的 EventCategory 对象,而理论上它已经分配了一个。
    • 您是否传递了事件类别的 id。如果事件类别对象已经存在,您需要传递它的 id。您还需要调用saveOrUpdate 方法而不是save。希望对你有帮助
    猜你喜欢
    • 2023-04-09
    • 1970-01-01
    • 2022-01-02
    • 1970-01-01
    • 2015-04-07
    • 2018-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多