全部博文(89)
分类: Java
2011-03-04 15:45:49
Imagine there is an inventory system, it requires some basic features, such as Add Product/Edit Product, List Product etc. I will guide you to implement such a simple Product CRUD application.
I will reuse the project created in last post, you can also create another new project for this tutorial.
Open Eclipse IDE
Expand the project created in the last post and expand the Java Resources node
Create a new package named inventory under the src/main/java node. Right click the Project node, select New->Package in the context menu, then fill the package name in the wizard panel. Click Finish button to close the wizard.
Create a domain object Product, for demonstration purpose, I do want make thing complex. The Product object includes three properties:id, name, inventoryAmount.
Right click the newly created inventory node, select New-> Class and open the “New Class” wizard.
Input the class name : Product
Click Finish button to close the wizard.
In the Editor, create three properties(id, name, inventoryAmount) in the Product class.
Right click the editor, select Source->Generate Getters and Setters. And click Select All button in the opened dialog. Click OK to generate getters and setters for all fields.
The final codes are like the following.
package inventory;
public class Product {
private Long id;
private String name;
private Integer inventoryAmount;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getInventoryAmount() {
return inventoryAmount;
}
public void setInventoryAmount(Integer inventoryAmount) {
this.inventoryAmount = inventoryAmount;
}
}
The ProductRepository is responsible for the database operation for Product domain object, here I use some dummy codes to demonstrate, I will replace it with real database operation in the next section.
Right click the newly created inventory node, select New-> Class and open the “New Class” wizard.
Input the class name : ProductRepository
Click Finish button to close the wizard.
public class ProductRepository {
private
static
List
static {
products.add(new Product(1L, "product 1", 101));
products.add(new Product(2L, "product 2", 102));
products.add(new Product(3L, "product 3", 103));
products.add(new Product(4L, "product 4", 104));
products.add(new Product(5L, "product 5", 105));
}
public void saveProduct(Product p) {
if (p.getId() == null) {
if (!products.isEmpty()) {
Product _lastProduct = products.get(products.size() - 1);
p.setId(new Long(_lastProduct.getId().longValue() + 1));
} else {
p.setId(new Long(1));
}
products.add(p);
} else {
Iterator
while (iter.hasNext()) {
Product _p = iter.next();
if (_p.getId().longValue() == p.getId()) {
_p.setName(p.getName());
_p.setInventoryAmount(p.getInventoryAmount());
break;
}
}
}
}
public void removeProduct(Product p) {
Iterator
while (iter.hasNext()) {
Product _p = iter.next();
if (_p.getId().longValue() == p.getId()) {
iter.remove();
break;
}
}
}
public
List
return products;
}
public Product findById(Long productId) {
Iterator
while (iter.hasNext()) {
Product _p = iter.next();
if (_p.getId().longValue() == productId) {
return _p;
}
}
return null;
}
This class includes some method, such as saveProduct is use for saving new product or editing product, and findById is use for fetch the product by product id, removeProduct provides an approach to remove Product from “database”(Here I use a prodcuts list as the data container, not a real database).
Product list is the entry of the application, it includes a table list of all products, and some links, such as Add Product/Edit Product/Delete Product. The Add/Edit link will open another template page for adding or editing product information.
DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "">
<html xmlns=""
xmlns:ui=""
xmlns:h=""
xmlns:f="">
<ui:composition template="template.xhtml">
<ui:define name="content">
<h1>Product Listh1>
<h:form>
<h:dataTable value="#{controller.products}" var="p" border="1">
<h:column>
<f:facet name="header">
ID
f:facet>
<h:outputText value="#{p.id}" />
h:column>
<h:column>
<f:facet name="header">
Name
f:facet>
<h:outputText value="#{p.name}" />
h:column>
<h:column>
<f:facet name="header">
Amount
f:facet>
<h:outputText value="#{p.inventoryAmount}" />
h:column>
<h:column>
<f:facet name="header">
Action
f:facet>
<h:link outcome="/addProduct.xhtml" value="Edit..." includeViewParams="true">
<f:param name="productId" value="#{p.id}"/>
h:link>
<h:commandLink value="Delete" action="#{controller.delete(p)}" />
h:column>
h:dataTable>
h:form>
<h:link outcome="/addProduct.xhtml">Add Producth:link>
ui:define>
ui:composition>
html>
JSF 2.0 adopted the effort of Sun facelets project and make it as the first citizen in JSF2. By default, facelet is the view handler, u can use it in project without any extra configuration.
You must notice here I use ui:composition and there is a template.xhtml specified to the “tempalte” attribute.
The h:link is also newly introduced in JSF 2.0, which provides GET method page navigation, it can be used without a form.
As the name means, the Controller class is the Controller of the MVC pattern here, it handles the request from pages and interact with Model(ProductRespository) to retrieve data or remove data from database, then response to the result.
Right click the newly created inventory node, select New-> Class and open the “New Class” wizard.
Input the class name : Controller
Click Finish button to close the wizard.
@SessionScoped
@ManagedBean(name = "controller")
public class Controller {
private ProductRepository inventory;
private Product product;
private Long productId;
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public Controller() {
}
@PostConstruct
public void init(){
inventory = new ProductRepository();
}
public void initProductForm() {
if (productId == null) {
product = new Product();
} else {
product = inventory.findById(productId);
}
}
public String delete(Product p) {
inventory.removeProduct(p);
return "products";
}
public
List
System.out.println("get products...");
return inventory.listProducts();
}
public String save() {
inventory.saveProduct(product);
return "products";
}
}
Here Controller class is a JSF ManagedBean, I put it into Session scope.
There is no faces-config.xml in this project, how JSF process page navigation.
JSF 2 introduce Convention over Configuration mechanism which will reduce the configuration , for example, the save method, it return a “products” string as result, JSF will determine if there is a outcome named “products” in configuration, obviously there is no such definition in this project, if not found, it will continue find if there exists a facelet page named “products”.
This page is use for adding or editing production information.
DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "">
<html xmlns=""
xmlns:ui=""
xmlns:h=""
xmlns:f="">
<ui:composition template="template.xhtml">
<ui:define name="content">
<f:metadata>
<f:viewParam name="productId" value="#{controller.productId}" />
<f:event type="preRenderView"
listener="#{controller.initProductForm}" />
f:metadata>
<h1>Add Producth1>
<h:messages />
<h:form id="productForm">
<h:panelGrid columns="2">
<h:outputLabel for="name">Name:h:outputLabel>
<h:inputText id="name" value="#{controller.product.name}"
required="true" />
<h:outputLabel for="amount">Inventory Amount:h:outputLabel>
<h:inputText id="amount"
value="#{controller.product.inventoryAmount}" />
h:panelGrid>
<h:commandButton id="saveProduct" value="Save Product"
action="#{controller.save()}" />
h:form>
<h:link outcome="/products.xhtml">Back to Products Listh:link>
ui:define>
ui:composition>
html>
The f:metadata is introduced in JSF2, and the f:viewParam make the page can accept request parameter. For adding product, the productId can be null and for editing product, I will transfer the productId of the edited product.
The f:event will fire the event preRenderView, here I use it to initialize the form.
Create the template page for the application page layout.
DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "">
<html xmlns=""
xmlns:ui=""
xmlns:h=""
xmlns:f="">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>JEE6 CRUD Applciationtitle>
head>
<body>
<div id="container">
<div id="header">div>
<div id="sidebar">div>
<div id="content">
<ui:insert name="content" />
div>
<br style="clear: both" />
div>
body>
html>
Now, u can run it on glassfish server and test it.
Right click the project node, select Run as->Run on Server, and select Glassfish Server instance in the popup dialog,and click Finish button to close the wizard.
Try to click the add, edit and remove link, and have a look what happens.
In last section, I have implemented the application with a faked data repository. Here I will replace it with real database.
I will use MySQL as database for demonstration.
Toplink/EclipseLink is the default JPA provider in Glassfish server, I will user Hibernate replace it.
For windows, go to website and download the latest mysql 5.1.x, and install it into your operation system, it is simple work.
For linux user, you can install from the distribution package repository, for example, Fedora, you can install by yum command.
yum install mysql-server mysql mysql-client
It will fetch mysql package and its dependent packages from the nearest remote repository and install it into your linux system automatically.
Create a database for this tutorial, named jee6testapp.
Open cmd in Windows, or Gnome terminal under Linux.
Execute mysql -uroot and enter the mysql console.
In the mysql console, execute create database jee6testapp, it will create a new database named jee6testapp.
Execute show databases to verify it is created.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
...
| jee6testapp |
...
+--------------------+
13 rows in set (0.35 sec)
Create a database user for this application.
mysql> grant all privileges on jee6testapp.* to jee6testapp@'localhost' identified by 'jee6testapp';
Download the mysql jdbc driver from http://dev.mysql.com/downloads/connector/j/.
Unzip the download file in your disk.
Copy the mysql-connector-java-5.1.xx-bin.jar into the Glassfish server domain lib/ext folder.
Start glassfish from command line, exexute asadmin start-domain domain1 in Glassfish bin folder.
Open browser, and go to and open Glassfish Server Administration Console page.
Select Resources->JDBC->JDBC Connection Pool, in the right panel, click the “New ” button to open “New JDBC Connection Pool” wizard.
In first step, fill the following values in the fields.
Pool Name: jee6testappPool
Resource Type : select javax.sql.DataSource
Database Driver Vendor: select MySQL
Fill the essential properties.
portNumber:3306
databaseName:jee6testapp
serverName:localhost
user:jee6testapp
password:jee6testapp
Use the ping button to test connection. If it is failed, check what is wrong.
Select Resources->JDBC->JDBC Resources, click “New” button to open wizard to create a new DataSource defintion.
JDNI Name:jdbc/jee6testappDS
Pool Name:jee6testappPool
Click OK button to save the configuration.
Open
pom.xml file in Eclipse IDE, and switch to pom.xml tab and append
the following codes in
<dependency>
<groupId>org.hibernate.javax.persistencegroupId>
<artifactId>hibernate-jpa-2.0-apiartifactId>
<version>1.0.0.Finalversion>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-entitymanagerartifactId>
<version>3.5.6-Finalversion>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-simpleartifactId>
<version>1.5.8version>
dependency>
Expand the src/main/resources folder(if not exist, create it), create a subfolder named META-INF.
Create a xml named persistence.xml under the new created META-INF folder.
Put the following content into this file
xml version="1.0" encoding="UTF-8"?>
<persistence xmlns=""
xmlns:xsi=""
xsi:schemaLocation=" /persistence_2_0.xsd"
version="2.0">
<persistence-unit name="jee6testappDatabase" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistenceprovider>
<jta-data-source>jdbc/jee6testappDSjta-data-source>
<exclude-unlisted-classes>falseexclude-unlisted-classes>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<property name="hibernate.show_sql" value="false" />
<property name="hibernate.transaction.flush_before_completion"
value="true" />
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.HashtableCacheProvider" />
properties>
persistence-unit>
persistence>
Here I specify Hibernate as JPA provider, and jdbc/jee6testappDS as jta datasource name which I have created it in the Glassfish server.
I use create-drop as hibernate.hbm2ddl.auto value which will create table when application server is starting up and drop the tables when application server is stopping.
It is time to replace the dummy database opertaion code to use JPA and EJB on real database.
The modified Product class is the following.
@Entity
public class Product implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private Integer inventoryAmount;
public Product() {
}
public Product(Long id, String name, Integer amount) {
this.id = id;
this.name = name;
this.inventoryAmount = amount;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getInventoryAmount() {
return inventoryAmount;
}
public void setInventoryAmount(Integer inventoryAmount) {
this.inventoryAmount = inventoryAmount;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result
+ ((inventoryAmount == null) ? 0 : inventoryAmount.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Product other = (Product) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (inventoryAmount == null) {
if (other.inventoryAmount != null)
return false;
} else if (!inventoryAmount.equals(other.inventoryAmount))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
I added a @Entity annotation to tell Hibernate this is a Entity class. The @Id specify the identity of the Entity class. I ser AUTO as the generatedValue strategy, it will create the table primary key automatically, which is against the database you are using.
@Stateless
@LocalBean
public class ProductRepository {
@PersistenceContext
EntityManager em;
public void saveProduct(Product p) {
if (p.getId() == null) {
em.persist(p);
} else {
em.merge(p);
}
}
public void removeProduct(Product p) {
em.remove(em.merge(p));
}
public
List
Query query = em.createQuery("select p from Product p");
return query.getResultList();
}
public Product findById(Long productId) {
return em.find(Product.class, productId);
}
}
I still use a @LocalBean here, which is no need an interface for the bean class. @PersistenceContext injects an EntityManager object, because there is only one persistence unit defined, there is no need specify the persistence unit name.
@SessionScoped
@ManagedBean(name = "controller")
public class Controller {
@EJB
private ProductRepository inventory;
private Product product;
private Long productId;
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public Controller() {
}
// @PostConstruct
// public void init(){
// inventory = new ProductRepository();
// }
public void initProductForm() {
if (productId == null) {
product = new Product();
} else {
product = inventory.findById(productId);
}
}
public String delete(Product p) {
inventory.removeProduct(p);
return "products";
}
public
List
System.out.println("get products...");
return inventory.listProducts();
}
public String save() {
inventory.saveProduct(product);
return "products";
}
}
I use a @EJB inject the ProductRepository object instead of the new operation in the PostConstruct phase.
No other modification, run it on Glassfish server and have a test.
Ajax is native support in JSF2. I will demonstrate how to use the f:ajax in the Product list page. I will add a filter box to the product list, and filter the result by keyword.
<div>
<h:inputText value="#{controller.keyword}">
h:inputText>
<h:commandButton value="Filter">
<f:ajax event="click" render="productsDataTable" />
h:commandButton>
div>
Put these codes in the beginning of h:form tag, also do not forget add id=”productsDataTable” the h:dataTable tag.
private String keyword;
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
public
List
System.out.println("get products...");
if (this.keyword == null || keyword.trim().length() == 0) {
return inventory.listProducts();
} else {
return inventory.listProducts(keyword);
}
}
Add a new property keyword in the Controller class, and modify the getProducts method.
Add a new method to process query by keyword.
public
List
Query query = em.createQuery("select p from Product p where p.name like :name");
query.setParameter("name", '%'+keyword+'%');
return query.getResultList();
}
Run the application on Glassfish Server.