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

Why Entity bean field (java.util.Date) changes value after an update

我有一个实体 bean“用户”,它有一个 java.util.Date 字段。当我出于某种奇怪的原因更新 JSF 页面上的任何用户字段(即 phone 号码、公司名称等)时,数据库中日期字段的值发生变化。我的开发环境是 Windows10.

上的 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 的任何字段(即 phone 数字),出生日期值“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, (910)-509-3924, 1980-01-20, Nittelo, Bobnitello, Bob, (818)-456-9011, IBM, 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;
............
}

这是class与数据库交互

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, logger,"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, logger, "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,logger,"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 the pattern symbols are specified in the documentation of java.text.SimpleDateFormat. This documentation says,您应该使用 M 几个月。

Letter Date or time component Presentation Examples
M Month in year (context sensitive) Month July; Jul; 07
m Minute in hour Number 30

因此,相应地调整模式:

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

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

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

这在its documentation中也有规定。

If a pattern has been specified, its syntax must conform the rules specified by java.text.SimpleDateFormat. Such a pattern will be used to parse, and the type, dateStyle, and timeStyle properties will be ignored.