Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1199058
  • 博文数量: 89
  • 博客积分: 10546
  • 博客等级: 上将
  • 技术积分: 1510
  • 用 户 组: 普通用户
  • 注册时间: 2004-10-16 01:24
文章分类

全部博文(89)

文章存档

2012年(7)

2011年(4)

2010年(5)

2009年(52)

2008年(21)

分类: Java

2011-03-04 15:45:49

In the previous post , we have created a very simple JEE6 application with Maven and Eclipse, and finally we ran it on Glassfish application server successfully.

In this post, I will improve it and create a real world CRUD application with database operation support. This tutorial will cover some new APIs introduced in Java EE 6, such as EJB 3.1, JSF 2.0, JPA2.0.

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.


Create Product CRUD pages

I will reuse the project created in last post, you can also create another new project for this tutorial.


Create inventory package
  1. Open Eclipse IDE

  2. Expand the project created in the last post and expand the Java Resources node

  3. 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 Product class

Create a domain object Product, for demonstration purpose, I do want make thing complex. The Product object includes three properties:id, name, inventoryAmount.

  1. Right click the newly created inventory node, select New-> Class and open the “New Class” wizard.

  2. Input the class name : Product

  3. Click Finish button to close the wizard.

  4. In the Editor, create three properties(id, name, inventoryAmount) in the Product class.

  5. 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.

  6. 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;

}

}


Create ProductRepository class

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.

  1. Right click the newly created inventory node, select New-> Class and open the “New Class” wizard.

  2. Input the class name : ProductRepository

  3. Click Finish button to close the wizard.


public class ProductRepository {

private static List products = new ArrayList();

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 iter = products.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 iter = products.iterator();

while (iter.hasNext()) {

Product _p = iter.next();

if (_p.getId().longValue() == p.getId()) {

iter.remove();

break;

}

}

}


public List listProducts() {

return products;

}


public Product findById(Long productId) {

Iterator iter = products.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).


Create product list page( prodcuts.xhtml )

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.


Create the Controller class

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.


  1. Right click the newly created inventory node, select New-> Class and open the “New Class” wizard.

  2. Input the class name : Controller

  3. 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 getProducts() {

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”.


Create the prodcut form page(addProduct.xhtml)

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 template.xhtml page

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>


Run it on Glassfish server

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.


Implement the persistence layer


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.


Install MySQL database


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 database

Create a database for this tutorial, named jee6testapp.


  1. Open cmd in Windows, or Gnome terminal under Linux.

  2. Execute mysql -uroot and enter the mysql console.

  3. In the mysql console, execute create database jee6testapp, it will create a new database named jee6testapp.

  4. Execute show databases to verify it is created.

mysql> show databases;

+--------------------+

| Database |

+--------------------+

...

| jee6testapp |

...

+--------------------+

13 rows in set (0.35 sec)


  1. Create a database user for this application.


mysql> grant all privileges on jee6testapp.* to jee6testapp@'localhost' identified by 'jee6testapp';


Create a Datasource in Glassfish Server



  1. Download the mysql jdbc driver from http://dev.mysql.com/downloads/connector/j/.

  2. Unzip the download file in your disk.

  3. Copy the mysql-connector-java-5.1.xx-bin.jar into the Glassfish server domain lib/ext folder.

  4. Start glassfish from command line, exexute asadmin start-domain domain1 in Glassfish bin folder.

  5. Open browser, and go to and open Glassfish Server Administration Console page.

  6. Select Resources->JDBC->JDBC Connection Pool, in the right panel, click the “New ” button to open “New JDBC Connection Pool” wizard.

  7. In first step, fill the following values in the fields.

Pool Name: jee6testappPool

Resource Type : select javax.sql.DataSource

Database Driver Vendor: select MySQL


  1. Fill the essential properties.

portNumber:3306

databaseName:jee6testapp

serverName:localhost

user:jee6testapp

password:jee6testapp

  1. Use the ping button to test connection. If it is failed, check what is wrong.

  2. 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.

Add Hibernate dependency to pom.xml

Open pom.xml file in Eclipse IDE, and switch to pom.xml tab and append the following codes in pairs.


<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>


Create a Persistence Unit


  1. Expand the src/main/resources folder(if not exist, create it), create a subfolder named META-INF.

  2. Create a xml named persistence.xml under the new created META-INF folder.

  3. 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.


Update Product class

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.


Update ProductRepository class

@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 listProducts() {

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.


Update Controller class


@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 getProducts() {

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.


Ajaxify the page


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.


Update products.xhtml


<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.


Update Controller class

private String keyword;


public String getKeyword() {

return keyword;

}


public void setKeyword(String keyword) {

this.keyword = keyword;

}


public List getProducts() {

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.


Update the ProductRepository class


Add a new method to process query by keyword.


public List listProducts(String keyword) {

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.

阅读(2554) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~