Spring和/或Hibernate:提交表单后从一侧保存多对多关系 [英] Spring and/or Hibernate: Saving many-to-many relations from one side after form submission

查看:77
本文介绍了Spring和/或Hibernate:提交表单后从一侧保存多对多关系的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在两个实体-CategoryEmail(NtoM)之间建立了简单的关联.我正在尝试创建用于浏览和管理它们的Web界面.我有一个简单的电子邮件订阅编辑表单,其中包含代表给定电子邮件所属类别的复选框列表(我为Set<Category>类型注册了属性编辑器).

I have a simple association between two entities - Category and Email (NtoM). I'm trying to create web interface for browsing and managing them. I have a simple e-mail subscription edit form with list of checkboxes that represents categories, to which given e-mail belongs (I registered property editor for Set<Category> type).

表单显示效果很好,包括标记当前分配的类别(对于现有电子邮件).但是不会将任何更改保存到EmailsCategories表(NtoM映射表,该表是用@JoinTable定义的-不添加新检查的类别,也不会删除未检查的类别.

Form displaying works well, including marking currently assigned categories (for existing e-mails). But no changes are saved to EmailsCategories table (NtoM mapping table, the one defined with @JoinTable - neither newly checked categories are added, nor unchecked categories are removed.

电子邮件实体:

@Entity
@Table(name = "Emails")
public class Email
{
    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid2")
    @Column(length = User.UUID_LENGTH)
    protected UUID id;

    @NaturalId
    @Column(nullable = false)
    @NotEmpty
    @org.hibernate.validator.constraints.Email
    protected String name;

    @Column(nullable = false)
    @Temporal(TemporalType.TIMESTAMP)
    protected Date createdAt;

    @Column
    protected String realName;

    @Column(nullable = false)
    protected boolean isActive = true;

    @ManyToMany(mappedBy = "emails", fetch = FetchType.EAGER)
    protected Set<Category> categories = new HashSet<Category>();

    public UUID getId()
    {
        return this.id;
    }

    public Email setId(UUID value)
    {
        this.id = value;

        return this;
    }

    public String getName()
    {
        return this.name;
    }

    public Email setName(String value)
    {
        this.name = value;

        return this;
    }

    public Date getCreatedAt()
    {
        return this.createdAt;
    }

    public String getRealName()
    {
        return this.realName;
    }

    public Email setRealName(String value)
    {
        this.realName = value;

        return this;
    }

    public boolean isActive()
    {
        return this.isActive;
    }

    public Email setActive(boolean value)
    {
        this.isActive = value;

        return this;
    }

    public Set<Category> getCategories()
    {
        return this.categories;
    }

    public Email setCategories(Set<Category> value)
    {
        this.categories = value;

        return this;
    }

    @PrePersist
    protected void onCreate()
    {
        this.createdAt = new Date();
    }
}

类别实体:

@Entity
@Table(name = "Categories")
public class Category
{
    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid2")
    @Column(length = User.UUID_LENGTH)
    protected UUID id;

    @NaturalId(mutable = true)
    @Column(nullable = false)
    @NotEmpty
    protected String name;

    @ManyToMany
    @JoinTable(
        name = "EmailsCategories",
        joinColumns = {
            @JoinColumn(name = "idCategory", nullable = false, updatable = false)
        },
        inverseJoinColumns = {
            @JoinColumn(name = "idEmail", nullable = false, updatable = false)
        }
    )
    protected Set<Email> emails = new HashSet<Email>();

    public UUID getId()
    {
        return this.id;
    }

    public Category setId(UUID value)
    {
        this.id = value;

        return this;
    }

    public String getName()
    {
        return this.name;
    }

    public Category setName(String value)
    {
        this.name = value;

        return this;
    }

    public Set<Email> getEmails()
    {
        return this.emails;
    }

    public Category setEmails(Set<Email> value)
    {
        this.emails = value;

        return this;
    }

    @Override
    public boolean equals(Object object)
    {
        return object != null
            && object.getClass().equals(this.getClass())
            && ((Category) object).getId().equals(this.id);
    }

    @Override
    public int hashCode()
    {
        return this.id.hashCode();
    }
}

控制器:

@Controller
@RequestMapping("/emails/{categoryId}")
public class EmailsController
{
    @Autowired
    protected CategoryService categoryService;

    @Autowired
    protected EmailService emailService;

    @ModelAttribute
    public Email addEmail(@RequestParam(required = false) UUID id)
    {
        Email email = null;

        if (id != null) {
            email = this.emailService.getEmail(id);
        }
        return email == null ? new Email() : email;
    }

    @InitBinder
    public void initBinder(WebDataBinder binder)
    {
        binder.registerCustomEditor(Set.class, "categories", new CategoriesSetEditor(this.categoryService));
    }

    @RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
    public String editForm(Model model, @PathVariable UUID id)
    {
        model.addAttribute("email", this.emailService.getEmail(id));

        model.addAttribute("categories", this.categoryService.getCategoriesList());

        return "emails/form";
    }

    @RequestMapping(value = "/save", method = RequestMethod.POST)
    public String save(@PathVariable UUID categoryId, @ModelAttribute @Valid Email email, BindingResult result, Model model)
    {
        if (result.hasErrors()) {
            model.addAttribute("categories", this.categoryService.getCategoriesList());
            return "emails/form";
        }

        this.emailService.save(email);

        return String.format("redirect:/emails/%s/", categoryId.toString());
    }
}

表单视图:

<form:form action="${pageContext.request.contextPath}/emails/${category.id}/save" method="post" modelAttribute="email">
    <form:hidden path="id"/>
    <fieldset>
        <label for="emailName"><spring:message code="email.form.label.Name" text="E-mail address"/>:</label>
        <form:input path="name" id="emailName" required="required"/>
        <form:errors path="name" cssClass="error"/>

        <label for="emailRealName"><spring:message code="email.form.label.RealName" text="Recipient display name"/>:</label>
        <form:input path="realName" id="emailRealName"/>
        <form:errors path="realName" cssClass="error"/>

        <label for="emailIsActive"><spring:message code="email.form.label.IsActive" text="Activation status"/>:</label>
        <form:checkbox path="active" id="emailIsActive"/>
        <form:errors path="active" cssClass="error"/>

        <form:checkboxes path="categories" element="div" items="${categories}" itemValue="id" itemLabel="name"/>
        <form:errors path="categories" cssClass="error"/>

        <button type="submit"><spring:message code="_common.form.Submit" text="Save"/></button>
    </fieldset>
</form:form>

编辑-添加了DAO代码

(emailService.save()只是对emailDao.save()的代理调用)

Edit - added DAO code

(emailService.save() is just a proxy call to emailDao.save())

public void save(Email email)
{
    this.getSession().saveOrUpdate(email);
}

编辑2-调试/记录更多

一个简单的测试代码段:

Edit 2 - little more debug/logs

A simple test snippet:

public void test()
{
    Category category = new Category();
    category.setName("New category");
    this.categoryDao.save(category);

    Email email = new Email();
    email.setName("test@me")
        .setRealName("Test <at> me")
        .getCategories().add(category);
    this.emailDao.save(email);

}

这些是日志:

12:05:34.173 [http-bio-8080-exec-23] DEBUG org.hibernate.SQL - insert into Emails (createdAt, isActive, name, realName, id) values (?, ?, ?, ?, ?)
12:05:34.177 [http-bio-8080-exec-23] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Inserting collection: [pl.chilldev.mailer.web.entity.Category.emails#24d190e3-99db-4792-93ea-78c294297d2d]
12:05:34.177 [http-bio-8080-exec-23] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Collection was empty

即使使用此日志,它似乎也有点暂存-告诉它它正在插入具有一个元素的集合,但随后它告诉它是空的...

Even with this logs it seems a little strage - it tels that it's inserting collection with one element, but then it tells it was empty...

推荐答案

在这里我们再次进行.

双向关联具有两个方面:所有者方面和反方面.所有者端是没有的mappedBy属性.要知道实体之间存在哪个关联,JPA/Hibernate只关心所有者方面.您的代码只会修改反面,而不会修改所有者.

A bidirectional association has two sides: an owner side, and an insverse side. The owner side is the one without the mappedBy attribute. To know which association exists between entities, JPA/Hibernate only cares about the owner side. Your code only modifies the inverse side, and not the owner side.

维护对象图的一致性是您的工作.有时具有不连贯的对象图是可以接受的,但是不修改所有者端不会使更改持久化.

It's YOUR job to maintain the coherence of the object graph. It's sometimes acceptable to have an incoherent object graph, but not modifying the owner side won't make the changes persistent.

所以您需要添加

category.getEmails().add(email);

或选择电子邮件"作为所有者,而不是类别".

or to choose Email as the owner side rather than Category.

这篇关于Spring和/或Hibernate:提交表单后从一侧保存多对多关系的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆