我是Java EE和JSF的新手,在这个问题上花了好几天时间,但没有得出任何结论。现在我希望有人能给我一个指导。我在这个论坛上搜索了这个问题,找到了一些有用的答案,但没有什么能解决我的问题。
我有一个JSF页面显示";部件";(零件名称、零件编号、零件描述等(。所有行都有更新和删除链接。当我点击";"更新";对于给定的行,布尔标志从不从"false"切换到"true",因此h:inputText将在行中呈现,我可以在行中更新零件信息。我从日志中看到,标志是从"false"切换到"true",但当控件从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文件初始填充数据库中的表。
@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编组/解编组的数据包装器
@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);
}
}
下面是一个用于记录的简单实用程序类
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 Collection将数据保存在内存中。它们都工作得很好,但是,一旦我创建了这个版本来使用数据库,更新行的问题就消失了
感谢的帮助
今天,我研究了您的代码,它看起来像是一个环境设置。我可以通过点击更新链接来更新零件。目前,您的代码有许多陷阱,这些陷阱可能会导致容器无法呈现应用程序的错误。您需要确保在您的单例中捕获并记录异常。它们是容器管理的,不应该抛出异常。此外,在向数据库中添加行之前,请确保该行不存在。例如,当您关闭Glassfish服务器时,它不会自动关闭DB服务器,因此在服务器重新启动时,上次运行的表仍在数据库中,您的代码将尝试再次从XML文件加载行,这将导致重复的键异常;因此,Singleton让这些异常冒泡到容器级别,您的应用程序将永远不会启动。
此外,在测试阶段,当您从XML文件手动将数据添加到DB中时,最好让应用程序为实体生成PK,以避免重复的主键错误。例如,当将Netbeans IDE与Apache Derby协同使用时,每次回收DB服务器时,pk生成都会在应用程序启动并运行后从一个设置的初始值开始。在您的情况下,假设您从PK=1的XML中加载了一行,然后在应用程序JSF页面的下一次添加中,将与该现有行发生PK冲突。