为什么实体 bean 字段 (java.util.Date) 在更新后更改值

问题描述

我有这个实体 bean“用户”,它有一个 java.util.Date 字段。当我出于某种奇怪的原因更新 JSF 页面上的任何用户字段(即电话号码、公司名称等)时,数据库中日期字段的值会发生变化。我的开发环境是 Windows 10 上的 Netbeans 12.0、EclipseLink JPA、Apache Derby DB 和 GlassFish 5.1。

例如,当我添加出生日期为“1980-08-20”的用户 (Bob) 时,JSF 会为 Bob 的用户实体设置值“1/20/80 3:38 AM”,此外,无论如何一天中的时间,我向用户添加“3:38 AM”是始终附加到日期的固定时间。后来,当我从数据库中检索 Bob 并在 JSF 页面显示他的信息时,他的出生日期正如预期的那样是“1980-08-20”。如果我更新 Bob 的任何字段(即电话号码),“1/20/80 3:38 AM”的出生日期值发送到处理更新的会话 Bean LoginRequestSessionBean(我通过调试器验证了它)。在更新成功完成并且我从数据库中检索 Bob 并显示在 JSF 页面上之后,Bob 的出生日期是“1/20/80 12:00 AM”而不是“1/20/80 3:38 AM” ”,因此我看到的是“1980-30-19”而不是“1980-08-20”?任何的想法?

这是将 Bob 添加到系统时的 sql 日志:

  INSERT INTO PERSISTENCE_USER (ID,CELLPHONE,COMPANY,DATEOFBIRTH,FirsTNAME,LASTNAME,OFFICEPHONE,PASSWORD,USERID,USERROLE) VALUES (?,?,?)
    bind => [5,(910)-509-3924,IBM,1980-01-20,Bob,Nittelo,(818)-456-9012,password,Bobnitello,consumer]]]

这是更新 Bob 时的 sql 日志

  UPDATE PERSISTENCE_USER SET USERROLE = ?,CELLPHONE = ?,DATEOFBIRTH = ?,LASTNAME = ?,USERID = ?,FirsTNAME = ?,OFFICEPHONE = ?,COMPANY = ? WHERE (ID = ?)
    bind => [consumer,(818)-456-9011,5]]]

我有一个 JSF 页面显示所有用户并允许更新用户信息。还有另一个 JSF 页面允许向系统添加用户。这是源代码

@NamedQuery(
        name = "updateUser",query = "UPDATE User u SET u.userId=?1,u.userRole=?2,u.cellPhone=?3,u.company=?4,"
        + "u.dateOfBirth=?5,u.firstName=?6,u.lastName=?7,u.officePhone=?8 "
        + "WHERE u.id = ?9"
)
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @NotNull
    private String userId;
    @NotNull
    private String password;
    @NotNull
    private String userRole;
    @NotNull
    private String firstName;
    @NotNull
    private String lastName;
    @Temporal(TemporalType.DATE)
    private Date dateOfBirth;
    @NotNull
    private String officePhone;
    private String cellPhone;
    @NotNull
    private String company;
............
}

这是与数据库交互的类

Stateless
public class LoginRequestSessionBean {

public void addUser(User u) throws ListServiceException {
        try {
               em.persist(u);
              
        } catch (Exception e) {
            throw (ExceptionHandler.wrapException(
                                e,logger,"Error occured when adding a user "+u.getUserId()));
        }
    }
    public void removeUser(User u) throws ListServiceException{
        try{
              if(!em.contains(u)){
                  u = em.merge(u);
              }
              em.remove(u);
        } catch (Exception e) {
            throw (ExceptionHandler.wrapException(e,"Error occured while removing a user");
        }
    }
    public void updateUser(User u) throws ListServiceException{
        try {
              updatedRow = em.createNamedQuery("updateUser")
                          .setParameter(1,u.getUserId())
                          .setParameter(2,u.getUserRole())
                          .setParameter(3,u.getCellPhone())
                          .setParameter(4,u.getCompany())
                          .setParameter(5,u.getDateOfBirth())
                          .setParameter(6,u.getFirstName())
                          .setParameter(7,u.getLastName())
                          .setParameter(8,u.getofficePhone())
                          .setParameter(9,u.getId())
                          .executeUpdate();
        } catch (Exception e) {
            throw ExceptionHandler.wrapException(e,"Error occured while updating user");
        }
    }
   public User getUser(Long id) throws ListServiceException{
        User user;
        try {
               user = em.find(User.class,id);
        } catch (Exception e) {
            throw ExceptionHandler.wrapException(e,"Error occured in"+className+".getuser()");
        }
        return user;
   }
}

这是更新 JSF 页面处理日期列的相关部分

<h:column>
           <f:facet name="header">#{bundle.loginmanagementdob}</f:facet>
           <h:inputText 
                   p:type="date"
                   value = "#{l.dateOfBirth}"
                   size ="15" rendered = "#{l.canUpdate}" >
                   <f:convertDateTime type="date"
                         pattern = "yyyy-mm-dd" />
          </h:inputText>
          <h:outputText value = "#{l.dateOfBirth}"
                  rendered = "#{not l.canUpdate}" >
                  <f:convertDateTime 
                          type="date"
                          pattern = "yyyy-mm-dd" />
          </h:outputText>
 </h:column>
 <h:column>
         <f:facet name = "header">Update</f:facet>
                <h:commandLink value = "Update" 
                             disabled="#{login.currentUser.userRole == 'delete' or login.currentUser.userRole == 'consumer'}"
                             action = "#{loginManagment.updateLinkAction(l)}" 
                                           rendered = "#{not l.canUpdate}">
                            </h:commandLink>
  </h:column>
<f:facet name="footer">
                <h:panelGroup style="display: block; border-color: aquamarine;text-align: center;">
                            <h:commandButton id="update"
                                             tabindex="1"
                                             value="Save updates"
                                             action="#{loginManagment.saveUpdate}" />
                </h:panelGroup>
</f:facet>

这是 JSF 页面的托管 bean:

@Named
@SessionScoped
public class LoginManagment implements Serializable {

    private static final long serialVersionUID = 1009L;
    
    private LoginRequestSessionBean request;
    private final ResourceBundle bundle; //application resource bundle
    public LoginManagment() {
        //Get the application's resource bundle
        bundle = ResourceBundle.getBundle("webmessages");
    }
    public List<User> getUsers() {
        if ((users == null) || refresh) {
            try {
                users = request.getUsers();
            } catch (EJBException e) {
                FacesMessage errMsg = new FacesMessage(e.getMessage());
                FacesContext.getCurrentInstance().addMessage(null,errMsg);
            }
            refresh = false;
        }

        return users;
    }

    public void resetUpdateLink() {
        users
                .stream()
                .filter(e -> e.getCanUpdate() == true)
                .forEach(e -> e.setCanUpdate(false));
    }

    public String updateLinkAction(User u) {
        u.setCanUpdate(true);
        return null;
    }

    public String saveUpdate() {
        
        Function<User,User> update = n -> {
            request.updateUser(n);
            return n;
        };
        try {
            users
                    .stream()
                    .filter(e -> e.getCanUpdate() == true)
                    .forEach(update);
            resetUpdateLink();
            FacesMessage msg = new FacesMessage(bundle.getString("loginmanagementupdatesuccess"));
            FacesContext.getCurrentInstance().addMessage(null,msg);
        } catch (ListServiceException e) {
            FacesMessage errMsg = new FacesMessage(e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null,errMsg);
        }
        logger.exiting(className,"saveUpdate()");
        return null;
    }
}

这是处理日期字段的添加用户 JSF 页面的相关部分:

  <h:outputLabel id="adddoblabel"
              for="adduserdob"
              style="color: green; font: caption; font-size: large;
              font-family: cursive; border-color: aquamarine"
              value="#{bundle.adduserdob}" />

 <h:inputText id="adduserdob"
              p:type="date"
              label="Date Of Birth "
              title="Date Of Birth"
              style="border-color: aquamarine"
              value="#{addUser.dateOfBirth}"
              required="true"
              requiredMessage="#{bundle.adduserdoberror}"
              maxlength="30" >
                                 
              <f:convertDateTime type="date"
                     pattern = "yyyy-mm-dd" />
 </h:inputText>
 <f:facet name="footer">
           <h:panelGroup style="display: block; border-color: aquamarine;text-align: center;">
                      <h:commandButton id="addusercommandbutton"
                                 value="Add"
                                 immediate="false"
                                 style="font-size: large; font-family: cursive"
                                 action="#{addUser.addAction}">
                       </h:commandButton>
                       
           </h:panelGroup>
</f:facet>
</h:panelGrid>

这是 JSF 添加用户页面的托管 bean:

@Named
@SessionScoped
public class AddUser implements Serializable {

    private static final long serialVersionUID = 1100L;
    private String firstName;
    private String lastName;
    private Date dateOfBirth;
    @EJB
    private LoginRequestSessionBean request;
    private ResourceBundle bundle; //application resource bundle
    public AddUser() {
        bundle = ResourceBundle.getBundle("webmessages");
    }

    public Date getDateOfBirth() {
        return dateOfBirth;
    }
    public void setDateOfBirth(Date date){
        dateOfBirth=date;
    }
    private void cleardatafield() {
        this.password = null;
        this.userId = null;
        this.role = null;
        this.cellPhone = null;
        this.officePhone = null;
        this.company = null;
        this.dateOfBirth = null;
        this.firstName = null;
        this.lastName = null;
    }

    public void addAction() {
        try {
            User u = new User(getUserId(),getpassword(),getRole(),getFirstName(),getLastName(),getDateOfBirth(),getofficePhone(),getCellPhone(),getCompany());
            request.addUser(u);
            cleardatafield();
            FacesMessage successMsg = new FacesMessage(bundle.getString("addusersuccess"));
            FacesContext.getCurrentInstance().addMessage(null,successMsg);
        } catch (EJBException e) {
            FacesMessage successMsg = new FacesMessage(e.getMessage());
            FacesContext.getCurrentInstance().addMessage(null,successMsg);
        }
    }    
}

解决方法

这是因为您已将日期转换器配置为将连字符之间的部分解释为分钟。

<f:convertDateTime type="date" pattern="yyyy-mm-dd" />

根据 its documentation,模式符号在 java.text.SimpleDateFormat 的文档中指定。此文档says您应该使用 M 数月。

日期或时间组件 介绍 示例
M 月份(上下文敏感) 七月;七月; 07
一小时一分钟 数量 30

因此,相应地调整模式:

<f:convertDateTime type="date" pattern="yyyy-MM-dd" />

与具体问题无关:指定 type 属性时忽略 pattern 属性。以下声明同样适用。

<f:convertDateTime pattern="yyyy-MM-dd" />

这也在 its documentation 中指定。

如果指定了 pattern,其语法必须符合 java.text.SimpleDateFormat 指定的规则。这样的模式将用于解析,typedateStyletimeStyle 属性将被忽略。