我在两个实体之间有一个简单的关联- Category和Email(NtoM)。我正在尝试创建用于浏览和管理它们的Web界面。我有一个简单的电子邮件订阅编辑表单,其中包含代表类别的复选框列表,这些类别属于给定电子邮件所属的类别(我为Set<Category>类型注册了属性编辑器)。
Category
Email
Set<Category>
表单显示效果很好,包括标记当前分配的类别(对于现有电子邮件)。但不会将任何更改保存到EmailsCategories表(NtoM映射表,该表定义为@JoinTable-不添加新检查的类别,也不删除未检查的类别。
@JoinTable
电子邮件实体:
@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>
(emailService.save()只是对的代理调用emailDao.save())
emailService.save()
emailDao.save()
public void save(Email email) { this.getSession().saveOrUpdate(email); }
一个简单的测试片段:
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
即使使用此日志,它似乎也有点暂存-告诉它它正在插入具有一个元素的集合,但随后它告诉它是空的…
再来一次。
双向关联具有两个方面:所有者方面和反方面。拥有者一方是 没有 mappedBy属性的一方。要知道实体之间存在哪个关联,JPA / Hibernate仅关心所有者方面。您的代码只会修改反面,而不会修改所有者。
维护对象图的一致性是您的工作。有时具有不连贯的对象图是可以接受的,但是不修改所有者端不会使更改持久化。
所以你需要添加
category.getEmails().add(email);
或选择“电子邮件”作为所有者,而不是“类别”。