Le printemps et/ou mise en veille prolongée: Sauver plusieurs-à-plusieurs relations d'un côté après la soumission du formulaire

Le contexte

J'ai une simple association entre les deux entités - Category et Email (NtoM). Je suis en train de créer l'interface web pour la navigation et de leur gestion. J'ai un simple e-mail d'abonnement formulaire d'édition de liste avec des cases à cocher qui représente catégories, à qui, compte tenu de l'e-mail appartient (je me suis inscrit à la propriété de l'éditeur pour Set<Category> type).

Le problème

Forme de l'affichage fonctionne bien, y compris le marquage actuellement assigné catégories (pour les e-mails). Mais pas de modifications sont enregistrées à EmailsCategories table (NtoM table de mappage, l'un défini avec @JoinTable - ni nouvellement vérifié catégories sont ajoutés, ni décoché catégories sont supprimés.

Le code

Email de l'entité:

@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();
}
}

Entité Category:

@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();
}
}

Contrôleur:

@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());
}
}

Formulaire:

<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>

Modifier ajoutée DAO code

(emailService.save() est juste un proxy appel à emailDao.save())

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

Edit 2 - un peu plus de débogage/logs

Un test simple extrait de:

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);

}

Et ce sont les logs:

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

Même avec ce connecte il me semble un peu strage - il tells que c'est l'insertion de la collection avec un seul élément, mais il dit ensuite qu'il était vide...

ajouter quel est le code de EmailService.save() ? Avez-vous mis un point d'arrêt à this.emailService.save(email); pour être sûr qu'il s'appelle ?
Vous pouvez poster votre dao?
Pour sûr il l'appelle, parce que l'e-mail d'enregistrement lui-même est enregistré. J'ai aussi vérifié toutes les propriétés - catégories de l'entité contient deux éléments avec la bonne Id de catégorie.
Avez-vous essayé d'utiliser la cascade = CascadeType.TOUS avec les annotations @JoinColumn
Je suppose que tu veux dire en cascade = CascadeType.TOUS sur @ManyToMany? Oui, essayé - je n'ai pas de travail.

OriginalL'auteur Rafał Wrzeszcz | 2013-10-09