我在更新对象时遇到问题。 我的对象具有这些属性(以及更多,但问题是相同的)
public String testString;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
public List<Mail> mails;
假设我有一个这种类型的实例,其中testString为NULL并且邮件列表中有一封电子邮件。这个对象在数据库中得到了很好的持久性。
现在尝试更新而不给它一个邮件列表。
final Map<String, String> values1 = new HashMap<String, String>();
values1.put("id", user.getId().toString());
Form<User> userForm1 = Form.form(User.class);
userForm1 = userForm1.bind(values1).get().update()
(我删除了不必要的代码以检查内容,是的,这是一种空的更新,但当我包含其他字段时也是如此) 在此更新后,邮件列表为空....但其他简单字段仍然有其值。所以我假设我做得对,并且play在“new”对象中看到一个空字段,而不是用null覆盖旧值,只是在updateobject中设置的字段。
但这不适用于OneToMany关系。这是打算还是Bug? 如果是这样的话,我应该如何更新具有这种关系的对象?
这是原始对象的system.out.println
User [username=Timmeey, testString=null, jobs=[], mails=[timmeey@xxx.xxx, isMainMail: true, id: 1], getCreated()=1401647537627, getModified()=1401647537627, getId()=1]
这是来自
的形式User [username=null, testString=null, jobs=[], mails=[], getCreated()=null, getModified()=null, getId()=1]
这是更新后的对象
User [username=Timmeey, testString=null, jobs=[], mails=[], getCreated()=1401647537627, getModified()=1401647537727, getId()=1]
正如我们所看到的,play并没有覆盖所有空字段,因为用户名仍然存在,只是邮件字段被覆盖
当然我知道我可以手动完成并迭代fieleds并检查它们是否为null然后设置应该自行更新的字段,但我无法想象这应该是要走的路。
谢谢
TL; DR Play / Ebean在更新已保存的对象时识别空值。因此它将执行部分更新,仅覆盖具有非空值的字段。这适用于简单的事情,比如String username。但是当谈到像@OneToMany这样的关系时,它会失败,并且总是用新对象的值覆盖存储的值,即使它为null也是如此。 我想要的是,关系字段也被视为普通字段,当字段为空时,旧对象上的字段不应被覆盖
整个模型类
package models;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import javax.naming.directory.InvalidAttributeValueException;
import javax.persistence.CascadeType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.validation.Valid;
import javax.validation.constraints.Null;
import com.avaje.ebean.Ebean;
import controllers.SettingsController;
import exceptions.InputValidationException;
import exceptions.NotYetInitializedException;
import play.Logger;
import play.data.validation.ValidationError;
@Entity
@Table(name = "userTable")
@DiscriminatorValue("aUser")
// User may be a reserved keyword in some sql databases
public class User extends AbstractSuperModel {
final static Logger.ALogger logger = Logger.of(User.class);
final private static String usernameRegexPattern = "[\\w_-]{3,}";
// This prevents binding of this value from forms
@Null
@javax.persistence.Column(unique = true)
private String username; // Must not be set by forms, only by controllers
// @OneToMany
// private Set<Ticket> responsibleForTickets;
// @ManyToMany
// private Set<Ticket> subscribedTickets;
// @OneToMany
// private Set<Ticket> reportedTickets;
// //public String secondaryEmails;
private String testString;
@ManyToMany(cascade = CascadeType.PERSIST)
private List<Job> jobs;
@Null
@OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
public List<Mail> mails;
public User(String username) throws InputValidationException {
logger.info("Adding a new User: " + username);
Pattern regex = Pattern.compile(usernameRegexPattern);
if (!regex.matcher(username).matches()) {
logger.error(username
+ " is not a valid username. In case you not just tried to troll the system, consider this as a serious Error and contact the maintainer (Timmeey@xxx.xxx (2014)");
throw new InputValidationException(username
+ " is not a valid username");
}
this.setUsername(username);
Mail mail = new Mail();
mail.setMailAddr(username + "@xxx.xxx");
mail.setIsMainMail(true);
this.addMail(mail);
}
public void addMail(Mail mail) {
this.getMails().add(mail);
}
public Mail getMainMail() {
for (Mail mail : this.getMails()) {
if (mail != null && mail.getIsMainMail()) {
return mail;
}
}
return null;
}
/**
* Removes a Mail from the User.
*
* @param mail
* The mail that should get removed
*/
public void removeMail(Mail mail) {
for (Iterator<Mail> iterator = this.getMails().iterator(); iterator
.hasNext();) {
Mail tmpMail = iterator.next();
if (tmpMail.getMailAddr().equalsIgnoreCase(mail.getMailAddr())) {
tmpMail.delete();
return;
}
}
}
public void addJob(Job job) {
this.jobs.add(job);
this.update();
}
public void removeJob(Job job) {
this.getJobs().remove(job);
this.update();
}
public static Finder<Long, User> find = new Finder<Long, User>(Long.class,
User.class);
public static List<User> findAll() {
return User.find.all();
}
public static User findById(final Long id) {
return User.find.byId(id);
}
public static User findByMail(final String address) {
final Mail mail = Mail.findByAddr(address);
if (mail != null) {
return mail.getUser();
}
return null;
}
public static User findByName(final String username) {
User resultUser = null;
resultUser = User.find.where().eq("username", username).findUnique();
return resultUser;
}
public static boolean isKnownUser(final String username) {
return findByName(username) != null;
}
/**
* Will be executed before a User is saved into the Database.
*/
@PreUpdate
public void processMailAddresses() {
}
public List<ValidationError> validate() {
List<ValidationError> errors = new ArrayList<ValidationError>();
if (errors.size() != 0) {
return errors;
}
return null;
}
public List<Job> getJobs() {
return this.jobs;
}
public List<Mail> getMails() {
if (this.mails == null) {
return null;
}
return this.mails;
}
public String getTestString() {
return this.testString;
}
public String getUsername() {
return this.username;
}
public void setJobs(final List<Job> jobs) {
this.jobs = jobs;
}
public void setMails(final List<Mail> mails) {
this.mails = mails;
}
public void setTestString(final String testString) {
this.testString = testString;
}
public void setUsername(final String username) {
this.username = username;
}
public boolean hasJob(Job job) {
if (getJobs() == null) {
return false;
}
return this.getJobs().contains(job);
}
@Override
public String toString() {
final List<String> jobs = new ArrayList<String>();
for (Job job : this.getJobs()) {
jobs.add(job.getJobName());
}
final List<String> mails = new ArrayList<String>();
for (final Mail mail : this.getMails()) {
mails.add(mail.getMailAddr() + ", isMainMail: "
+ mail.getIsMainMail() + ", id: " + mail.getId());
}
return String
.format("User [username=%s, testString=%s, jobs=%s, mails=%s, getCreated()=%s, getModified()=%s, getId()=%s]",
this.username, this.testString, jobs, mails,
this.getCreated(), this.getModified(), this.getId());
}
}
和Mail类
package models;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.validation.constraints.Null;
import play.Logger;
import play.Logger.ALogger;
import play.data.validation.Constraints.Email;
import play.data.validation.Constraints.Required;
import play.data.validation.ValidationError;
/**
* Just a container for Emails. Because Play! cannot store List<String> for the
* Email-Addresses
*
* @author timmeey
*
*/
@Entity
public class Mail extends AbstractSuperModel {
private static final ALogger logger = Logger.of(Mail.class);
public static Mail findByAddr(final String address) {
Mail mail = null;
mail = find.where().eq("mailAddr", address).findUnique();
return mail;
}
@Required
@Email
@javax.persistence.Column(unique = true)
private String mailAddr;
@Null
@ManyToOne
private User user;
@Null
Boolean isMainMail;
public static Finder<Long, Mail> find = new Finder<Long, Mail>(Long.class,
Mail.class);
public static Mail findById(final Long id) {
if (id == null) {
return null;
}
return find.byId(id);
}
public static List<Mail> findAll() {
return find.all();
}
public String getMailAddr() {
return this.mailAddr;
}
public User getUser() {
return this.user;
}
public void setMailAddr(final String mailAddr) {
this.mailAddr = mailAddr;
}
public void setUser(final User user) {
this.user = user;
}
@Override
public String toString() {
return String
.format("Mail [id=%s, mailAddr=%s, user=%s, getCreated()=%s, getModified()=%s, getId()=%s]",
this.id, this.mailAddr, this.user.getUsername(),
this.getCreated(), this.getModified(), this.getId());
}
public Boolean getIsMainMail() {
return isMainMail;
}
public void setIsMainMail(Boolean isMainMail) {
this.isMainMail = isMainMail;
}
public List<ValidationError> validate() {
List<ValidationError> errors = new ArrayList<ValidationError>();
if (find.where().eq("mailAddr", this.mailAddr).findRowCount() > 0) {
errors.add(new ValidationError(mailAddr, "Mailaddress already used"));
}
if (errors.size() != 0) {
return errors;
}
return null;
}
}
答案 0 :(得分:0)
问题实际上不在您的ORM注释中。游戏缺乏对表单内不同对象的渲染和获取信息的支持。我假设你想要一个对象给一个表单,在视图中呈现结果,然后用户键入一些除了邮件之外的其他字段的更改,然后更新更改的信息。表单本身自动使用getter和setter来生成字段,然后从请求中提取信息。在您的情况下,您的邮件列表和HTML字段没有正确的映射。我的意思是,如果你有一个带有getter和setter的字段String name;
,表单将自动创建与名称约定匹配的字段,然后使用setter将这些字段中的信息绑定到User对象,必须命名为setName(String name)
。但是,如果信息属于与基本类型不同的特定类型或String
,如Mail
,则表单没有正确的方法可以在HTML中为其创建字段。所以你基本上需要一些手工工作。您必须将您的电子邮件保存在Java代码中,然后将它们添加到必须更新的用户,如果您不愿意更改它们或使用表单以适当的形式将它们提供给视图,然后使用DynamicForm
无论您是否将render方法与form参数一起使用,视图本身都是一个纯粹的HTML效果。因此,您的Java对象和HTML文件之间没有直接映射。 bind
方法将简单地遍历请求的输入字段的所有id,并将尝试查找匹配的setter(因此setter的名称是关键的)。但是当您使用更具体的对象时,您需要手动完成这项工作。