为什么 Java Server Faces 中的 h:dataTable 不在 table 行中呈现 h:inputText

Why h:dataTable in Java Server Faces doesn't render h:inputText in the table rows

我是 Java EE 和 JSF 的新手,我在这个问题上花了好几天都没有得出任何结论。现在我希望有人能给我一个 guidance.I 已经在这个论坛上搜索了这个问题,找到了一些有用的答案,但没有一个可以解决我的问题。

我有一个 JSF 页面,使用 h:dataTable 标签以表格形式显示“部件”(部件名称、部件编号、部件描述等)。这些行都有更新和删除 links。当我单击 link 以获取给定行的“更新”时,布尔标志永远不会从 'false' 切换到 'true' 因此要在行中呈现 h:inputText,我可以在哪里更新零件信息。我通过日志看到标志从 'false' 切换到 'true' 但是当控件 returns 从 bean 到 JSF 页面时,标志以某种方式(神秘地)切换回 'false',因此,用于更新的关联 h:inputText 不会呈现,我无法更新该行中的部件信息。
JSF 页面

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE html>
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets">

    <h:head>
        <title>This is a part number list service</title>
        <h:outputStylesheet library="css" name="cssLayout.css"  />
    </h:head>
    <h:body>
        <h:form>
            <h2> You are authorized to use the Part number List Service</h2>
            <p style="text-align: center">
                <h:dataTable value ="#{partList.parts}" var="p"
                             styleClass="part-table"
                             headerClass="part-table-header"
                             rowClasses="part-table-odd-row,part-table-even-row"
                             border="10"
                             >
                    <f:facet name="caption">
                        <h3> <h:outputText value="#{bundle.caption}" /> </h3>
                    </f:facet>
                    <p></p>    
                    <h:column>
                        <f:facet name="header">Part Name</f:facet>
                            #{p.name}
                        <h:inputText value = "#{p.name}"
                                     size = "5" rendered = "#{p.canUpdate}" />
                        <h:outputText value = "#{p.name}"
                                      rendered = "#{not p.canUpdate}" />
                    </h:column>
                    <h:column>
                        <f:facet name="header">Part Manufacture</f:facet>
                        <h:inputText value = "#{p.manufacture}"
                                     size = "10" rendered = "#{p.canUpdate}" />
                        <h:outputText value = "#{p.manufacture}"
                                      rendered = "#{not p.canUpdate}" />
                    </h:column>
                    <h:column>
                        <f:facet name="header">Part Number</f:facet>
                        <h:inputText value = "#{p.number}"
                                     size = "15" rendered = "#{p.canUpdate}" />
                        <h:outputText value = "#{p.number}"
                                      rendered = "#{not p.canUpdate}" />
                    </h:column>
                    <h:column>
                        <f:facet name="header">Part Description</f:facet>
                        <h:inputText value = "#{p.description}"
                                     size = "10" rendered = "#{p.canUpdate}" />
                        <h:outputText value = "#{p.description}"
                                      rendered = "#{not p.canUpdate}" />
                    </h:column>
                    <h:column>
                        <f:facet name="header">Price</f:facet>
                        <h:inputText value = "#{p.price}"
                                     size = "5" rendered = "#{p.canUpdate}" />
                        <h:outputText value = "#{p.price}"
                                      rendered = "#{not p.canUpdate}" />
                    </h:column>
                    <h:column>
                        <f:facet name = "header">Update</f:facet>
                        <h:commandLink value = "Update" 
                                       action = "#{partList.updateLinkAction(p)}" 
                                       rendered = "#{not p.canUpdate}">
                        </h:commandLink>
                    </h:column>
                    <h:column>
                        <f:facet name = "header">Delete</f:facet>
                        <h:commandLink value = "Delete" 
                                       action = "#{partList.deleteAction(p)}" 
                                       rendered = "#{not p.canUpdate}">
                        </h:commandLink>
                    </h:column>
                </h:dataTable>
            </p>
                <p></p>
                <h:commandButton id="back"
                                 value="Logout"
                                 action="auth" />
                <h:commandButton id="update"
                                 value="Save updates"
                                 action="#{partList.saveUpdate}" />
                

            <h2> Add new part</h2>
            <table>
                <tr>
                    <td>Part Name</td>
                    <td><h:inputText id="addpartname" size="10" value="#{partList.partName}" /></td>
                </tr>
                <tr>
                    <td>Part Manufacture</td>
                    <td><h:inputText id="addpartmanufacture" size="10" value="#{partList.partManufacture}" /></td>
                </tr>
                <tr>
                    <td>Part Number</td>
                    <td><h:inputText id="addpartnumbere" size="10" value="#{partList.partNumber}" /></td>
                </tr>
                <tr>
                    <td>Part Description</td>
                    <td><h:inputText id="addpartdescription" size="10" value="#{partList.partDescription}" /></td>
                </tr>
                <tr>
                    <td>$Price</td>
                    <td><h:inputText id="addpartprice" size="10" value="#{partList.partPrice}" /></td>
                </tr>
            </table>
            <p></p>
            <h:commandButton id="addparttolist" 
                             value="Add part"
                             action="#{partList.addAction}" />
        </h:form>

    </h:body>
</html>

这里是用@SessionScope注解的托管bean

@FacesConfig
@Named
@SessionScoped

public class PartList implements Serializable {

    @EJB
    RequestSessionBean request;

    private List<Part> parts;
    private String partName;
    private String partNumber;
    private String partManufacture;
    private String partDescription;
    private float price;
    private static final Logger logger = LoggerUtil.getLogger(PartList.class.getName(),
            "C:\Logs\log.txt");

    private static final long serialVersionUID = 1000L;

    private void update(Part p) {
        logger.entering(PartList.class.getName(), "update");

        if (p.getCanUpdate()) {
            try {
                logger.log(Level.INFO, "NB: Updating part. Part number:{0} Part name: {1} can update: {2}",
                        new Object[]{p.getNumber(), p.getName(), p.getCanUpdate()});
                request.removePart(p.getId());
                request.addPart(p);
                parts = request.getAllParts();
            } catch (Exception e) {
                logger.log(Level.SEVERE, "NB: something went wrong", e);
                throw (new EJBException(e));
            }
            logger.exiting(PartList.class.getName(), "NB: leaving update()");
        }
    }

    public PartList() {
        logger.log(Level.INFO, "NB PartList default constructor called");
    }

    public void setPartName(String n) {
        partName = n;
    }

    public String getPartName() {
        return partName;
    }

    public void setPartNumber(String n) {
        partNumber = n;
    }

    public String getPartNumber() {
        return partNumber;
    }

    public String getPartManufacture() {
        return partManufacture;
    }

    public void setPartManufacture(String n) {
        partManufacture = n;
    }

    public void setPartDescription(String p) {
        partDescription = p;
    }

    public String getPartDescription() {
        return partDescription;
    }

    public void setPartPrice(float p) {
        price = p;
    }

    public float getPartPrice() {
        return price;
    }

    public List getParts() {
        try {
            parts = request.getAllParts();
        } catch (Exception e) {
        }
        return parts;
    }

    public String deleteAction(Part p) {
        request.removePart(p.getId());
        parts = request.getAllParts();
        return null;
    }

    public String addAction() {
        Part p = new Part(partName, partManufacture, partNumber, partDescription, price);
        request.addPart(p);
        parts = request.getAllParts();
        clearDataField(); //clear the form data after add operation
        return null;
    }

    private void clearDataField() {
        this.partDescription = null;
        this.partManufacture = null;
        this.partName = null;
        this.partNumber = null;
        this.price = 0;
    }

    public String updateLinkAction(Part p) { //update link clicked
        logger.entering(PartList.class.getName(), "updateLinkAction");
        logger.log(Level.INFO, "NB: Updating part. Part number:{0} Part name: {1} can update: {2}",
                new Object[]{p.getNumber(), p.getName(), p.getCanUpdate()});
        p.setCanUpdate(true);
        logger.log(Level.INFO, "Can update is now :{0}",
                p.getCanUpdate());
        logger.exiting(PartList.class.getName(), "updateLinkAction");
        return null; //This allows the page flow remain on the same page
    }

    public String saveUpdate() {
        parts.stream()
                .forEach(e -> e.setCanUpdate(false));
        parts.stream()
                .filter(e -> e.getCanUpdate())
                .forEach(e -> update(e));

        return null;
    }

}

这是一个单例会话 bean,用于使用 JAXB 从 XML 文件初始填充数据库中的 table。

@Singleton
@Startup
//This bean will be managed by the container automatically since it is a singleton. The container calls 
//the method that has a @PostConstruct annotation. This is ideal to perform initial data load into the
// the data store
public class DataLoaderSessionBean {
    @EJB
    private RequestSessionBean request;
    private final static String logPath="C:\Logs\log.txt";
    private final static String dataPath="c:\Logs\Parts.xml";
    private final static Logger logger=LoggerUtil.getLogger(DataLoaderSessionBean.class.getName(), logPath);
    private void saveData(){
        logger.entering(DataLoaderSessionBean.class.getName(),"saveData()");
         try {
            List<Part> parts = request.getAllParts();
            JAXBContext context = JAXBContext
                    .newInstance(PartWrapper.class);
            Marshaller m = context.createMarshaller();
            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

            // Wrapping our person data.
            PartWrapper wrapper = new PartWrapper();
            wrapper.setParts(parts);

            // Marshalling and saving XML to the file.
            m.marshal(wrapper, new File(dataPath));
            m.marshal(parts, System.out);

        } catch (JAXBException e) {
            e.printStackTrace();
        }
        logger.exiting(DataLoaderSessionBean.class.getName(),"saveData()");
    }
     private List<Part> loadData(){
        logger.entering(DataLoaderSessionBean.class.getName(),"loadData()");
         List<Part> parts=null;
         try {
            JAXBContext context = JAXBContext
                    .newInstance(PartWrapper.class);
            Unmarshaller um = context.createUnmarshaller();
            PartWrapper wrapper = (PartWrapper) um.unmarshal(new File(dataPath));
            parts = wrapper.getParts();

        } catch (JAXBException e) {
            e.printStackTrace();
        }
        logger.exiting(DataLoaderSessionBean.class.getName(),"loadData()");
        return parts;
    }

    @PostConstruct
    public void createData(){
        request.addParts(loadData());
    }
    
     @PreDestroy
    public void deleteData() {
        saveData();
    }
    
}

这是模型的实体 bean

@Entity
@Table(name = "PERSISTENCE_PART")
@NamedQuery(
        name = "findAllParts",
        query = "SELECT p FROM Part p "
        + "ORDER BY p.number"
)
@SessionScoped

public class Part implements Serializable {

    private static final long serialVersionUID = 1001L;
    private static final String logPath="C:\Logs\log.txt";
    private static final Logger logger = LoggerUtil.getLogger(Part.class.getName()
             ,logPath);
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @NotNull
    private String name;
    @NotNull
    private String manufacture;
    @NotNull
    private String number;
    @NotNull
    private String description;
    @NotNull
    private float price;
    //@Transient
    // @XmlTransient
    private boolean canUpdate;

    public Part() {

    }

    public Part(String n, String m, String nu, String d, float p) {
        name = n;
        manufacture = m;
        number = nu;
        description = d;
        price = p;
    }

    public void setId(Long l) {
        id = l;
    }

    public Long getId() {
        return id;
    }

    public void setName(String n) {
        name = n;
    }

    public String getName() {
        return name;
    }

    public void setNumber(String n) {
        number = n;
    }

    //  @Id
    //  @Column(nullable = false)
    public String getNumber() {
        return number;
    }

    public void setManufacture(String n) {
        manufacture = n;
    }

    public String getManufacture() {
        return manufacture;
    }

    public void setDescription(String d) {
        description = d;
    }

    public String getDescription() {
        return description;
    }

    public void setPrice(float p) {
        price = p;
    }

    public float getPrice() {
        return price;
    }

    public void setCanUpdate(boolean b) {
        logger.log(Level.INFO, "NB: Part.setCanUpdate() for part name {0} with id {1} and canUpdate {2}",
                 new Object[]{this.name, this.id, b});
        canUpdate = b;
    }

    public boolean getCanUpdate() {
        logger.log(Level.INFO, "NB: Part.getCanUpdate() for Part name {0} with id {1} and CanUpate {2}",
                 new Object[]{this.name, this.id, this.canUpdate});
        return canUpdate;
    }
}

这里是与数据库交互的会话bean。

@Stateful
public class RequestSessionBean {

    // Add business logic below. (Right-click in editor and choose
    // "Insert Code > Add Business Method")
    @PersistenceContext
    private EntityManager em;
    
    public void createPart(String name,
            String manufacture,
            String partNumber,
            String description,
            float price) {
        try {
            Part part = new Part(name,
                    manufacture,
                    partNumber,
                    description,
                    price);
            em.persist(part);
        } catch (Exception ex) {
            throw new EJBException(ex.getMessage());
        }
    }
    public void addParts(List<Part> parts){
        parts.stream()
                .forEach(e->addPart(e));
    }
    public void addPart(Part p){
        try {
            em.persist(p);
        } catch (Exception ex) {
            throw new EJBException(ex.getMessage());
        }
    }
    public List<Part> getAllParts() {
        List<Part> parts = (List<Part>) em.createNamedQuery("findAllParts").getResultList();
        return parts;
    }
    public Part getPart(String partNumber){
        Part p=null;
        try {
            p  = em.find(Part.class, partNumber);
        } catch (Exception e) {
            throw new EJBException(e.getMessage());
        }
        return p;
    }
    public void removePart(Long id) {
        try {
            Part p = em.find(Part.class, id);
            em.remove(p);
        } catch (Exception e) {
            throw new EJBException(e.getMessage());
        }
    }
}

这是 JAXB 的数据包装器 marshalling/unmarshalling

@XmlRootElement(name="PartWrapper")
public class PartWrapper {
    List<Part> parts;
    public List<Part> getParts(){
        return parts;
    }
    @XmlElement(name="part")
    public void setParts(List<Part> p){
        parts=p;
    }
    public void add(Part p){
        if(parts==null){
            parts = new ArrayList();
        }
        parts.add(p);
    }
}

这是一个简单的实用程序 class 用于记录

public class LoggerUtil {

    public static Logger getLogger(String className, String pathToLogFile) {
        Logger logger = Logger.getLogger(className);
        for (Handler h : logger.getHandlers()) {
            logger.removeHandler(h);
        }
            logger.addHandler(getFileHandler(pathToLogFile));
            logger.addHandler(getConsoleHandler());
            logger.setLevel(Level.FINER);

        return logger;
    }
    private static Handler getConsoleHandler(){
         ConsoleHandler handle = new ConsoleHandler();
        handle.setLevel(Level.FINER);
        handle.setFormatter(new SimpleFormatter());
        return handle;
    }
    private static Handler getFileHandler(String pathToLogFile){
        Handler handle=null;
        try {
            handle = new FileHandler(pathToLogFile,10240,1, true);
            handle.setLevel(Level.FINER);
            handle.setFormatter(new SimpleFormatter());

        } catch (IOException | SecurityException e) {
        }
        return handle;
    }
}

这是数据库的初始数据加载

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<PartWrapper>
    <part>
        <canUpdate>false</canUpdate>
        <description>EGR Valve</description>
        <id>7</id>
        <manufacture>Emission Systems</manufacture>
        <name>EGR Valve</name>
        <number>EV4301766ES</number>
        <price>29.95</price>
    </part>
    <part>
        <canUpdate>false</canUpdate>
        <description>Carborator float</description>
        <id>6</id>
        <manufacture>Duke Carboration</manufacture>
        <name>Floater Element</name>
        <number>FE3511029DC</number>
        <price>3.99</price>
    </part>
</PartWrapper>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="4.0" 
         xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd">
    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
    </context-param>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>showpartnumer.xhtml</welcome-file>
    </welcome-file-list>
</web-app>

我有此应用程序的其他两个版本:一个版本使用 XML 文件作为数据存储。另一个版本使用 Java 集合将数据保存在内存中。它们都工作正常,但是,一旦我创建了这个版本来使用数据库,这个更新行的问题就被淹没了。
感谢您的帮助

今天,我查看了您的代码,它似乎是一个环境设置。我能够通过单击更新 link 来更新一部分。目前,您的代码有许多缺陷,可能会导致错误阻止容器呈现您的应用程序。您需要确保在单例中捕获并记录异常。它们是容器管理的,它们不应该抛出异常。此外,在向数据库中添加行之前,请确保该行不存在。例如,当您关闭 Glassfish 服务器时,它不会自动关闭数据库服务器,因此在服务器重新启动时,之前 运行 中的表仍在数据库中,您的代码将尝试加载行再次来自 XML 文件,这将导致重复键异常;因此,Singleton 让这些异常冒泡到容器级别,您的应用程序将永远不会启动。

此外,在测试阶段,当您手动将数据从XML文件添加到数据库时,最好让应用程序为实体生成PK以避免重复主键错误.例如,当将 Netbeans IDE 与 Apache Derby 一起使用时,每次回收 DB 服务器时,pk 生成都会在应用程序启动和 运行ning 后从设置的初始值开始。在您的情况下,假设您从 XML 加载了 PK=1 的行,然后在您从应用程序的 JSF 页面添加的下一次添加中,将与该现有行发生 PK 冲突。