JavaEE6 Weblogic exercice 2

De $1

Introduction

This time we will look at new features of Java EE6/JPA:
 

  1. Build an Enterprise project, not a "web profile project",
  2. Write an remote EJB and test a standalone remote client (not a Servlet),
  3. Write a bank account management web application that will generate and update the database schema as we update the code of the entity classes,
  4. Use JPA relationships

Create an EAR project, an EJB project and a Dynamic Web/JPA project

An Enterprise Application project (EAR) is a "composite" project, a container that will hold several other projects and deploy all of them at once in a .ear archive.

Typically, an EAR project will be attached with several other subprojects such as:

  • EJB projects,
  • Dynamic web projects (maybe with JPA facets),
  1. So, let's do it. Create a new project of type "enterprise application project" called DemoEAR. For the moment, do not set anything, just click finish. We create this project first as when we will create the subprojects, we will speficy that they belong to this EAR project.
  2. Create an EJB project, call it "DemoEJB", indicate under "EAR Membership" that you want to attach it to the EAR project from step 1, notice that a DemoEJBClient project is automatically added too. More on that later.
  3.  Add a JPA facet to the EJB project you just created, 
  4. Create a Dynamic Web project,  name it "DemoWeb", indicate  under "EAR Membership"  that you want to attach it to the EAR project from step 1, click next until the last screen and check "generate web.xml",
  5. Add the EJB project in the build path of the dynamic web project as our servlets will call EJBs from the EJB project.

So, you should get 4 projects: DemoEAR, demoEJB, demoEJBClient and demoWeb. You can check that everything is ok by looking at the properties of the DemoEAR projec, then look at "Deployment Assembly", you should see that the 3 other projects are in the assembly.

Experiment with Remote EJBs

Add a remote EJB to the EJB project

Add a session bean stateless but this tome check the "remote interface" option, and indicate for the name of the bean " DemoSessionEJB" and for the remote interface you should see that the dialog proposes automatically DemoSessionEJBRemote. Add an hello world method to the bean, like we did at the beginning of exercice one, yesterday.

The code should look like that: 

package sessions;

import javax.ejb.LocalBean;
import javax.ejb.Stateless;

/**
* Session Bean implementation class DemoSessionEJB
*/
@Stateless
@LocalBean
public class DemoSessionEJB implements DemoSessionEJBRemote {

public String helloWorld() {
return "hello world !";
}
}

Notice that the bean implements DemoSessionEJBRemote , and that this interface has been automatically added to the DemoEJBClient project. This "generated project" is useful for standalone clients, as we will see now...

Add the helloWord() method to the remote interface, in the file DemoSessionEJBRemote.java, from the DemoEJBClient project. The code should look like that:

package sessions;

import javax.ejb.Remote;

@Remote
public interface DemoSessionEJBRemote {
public String helloWorld();
}

At this stage, save all and deploy the EAR project to weblogic using right click on the server in the server tab of the Java EE perspective, and use the add/remote menu.

It is important to deploy this project as we will need the JNDI name of the EJB we created, and this JNDI name is available from the weblogic admin console.

Look for the JNDI name of the EJB in Weblogic console

 You can do that by looking at the weblogic admin console, on the left, click on "base_domain", then "servers", then in the main page click on the name of your server, then in the main window you should have something like "JNDI tree"... Click on that, a new tab opens in which you can explore the names objects on your server. In the small tree, click on "weblogic" then you will find your application and the EJBs deployed. The "liaison name" of the EJB is the JNDI name. It should look like this: " java:global.DemoEAR.DemoEJB.DemoSessionEJB!sessions.DemoSessionEJBRemote "

We will use this JNDI identifier in the standalone client... 
 

Write a standalone client for this EJB

A standalone client is just a Java project with a java class (with a main) that uses the remote EJB we created. 

The client project is just a Java project. Create a new project of type "Java project", name it "DemoEJBClientStandalone".   

You must add two things in the build path so that the client code can compile and run:

  1. Add the DemoEJBClient project in the build path (the project that contains the EJB remote interface you are going to call),
  2. Add wlfullclient.jar, a jar with all the client libs you need. This jar needs to be built once, using a command line interface. This is the procedure to build this jar: cd in the WEBLOGIC_INSTALL_LIB/server/lib, then run the command "java -jar wljarbuilder.jar". This will take 30s to build the client lib for your installation. At the end, you should find a wlfullclient.jar file. Add it to your build path.
  3. Add a new java class with a main(), name it  StandAloneClient, 
  4. Copy this source code provided below, check that the  JNDI_NAME is the same as the one you saw in the weblogic admin console/jndi tree. 

Then you should have such a source code for the standalone client:
 

package client;

import java.util.Hashtable;import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import sessions.DemoSessionEJBRemote;

public class StandAloneClient {
    private static String INITIAL_CONTEXT_NAME = "weblogic.jndi.WLInitialContextFactory";
    private static String PROVIDER_URL = "t3://localhost:7001";
    private static String JNDI_NAME = "java:global.DemoEAR.DemoEJB.DemoSessionEJB!sessions.DemoSessionEJBRemote";

    public static void main(String[] args) throws Exception {
        Context ctx = getInitialContext();
        
DemoSessionEJBRemote helloEJB = (DemoSessionEJBRemote) ctx.lookup(JNDI_NAME);
        System.out.println(helloEJB.helloWorld());
        }

    private static Context getInitialContext() throws NamingException {
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_NAME);
        env.put(Context.PROVIDER_URL, PROVIDER_URL);
        return new InitialContext(env);
    }
}

These info come from the Weblogic documentation (name of INITIAL_CONTEXT_FACTORY, etc.)

Redeploy the EAR project, just to be sure (right click on server, then "add and remove").

Run the standalone client project (as a "Java application").

Normally you should see in the console the "Hello World" message. As you saw, we could call the EJB from any external host, as long as we indicate the right URL for the weblogic server.

Write a bank account management application

This time, we will write a real application. A small bank account manager.

Create an empty MyBank database in javaDB (from Eclipse), connect it to Weblogic

Using the "Data Source Explorer" in Eclipse, use the "New" menu but this time to connect to a database named MyBank that does not exist. Put admin/admin as user/password, and check the "create database if needed" option. Once created Eclipse connects the database, that is, for the moment, empty.

Connect this database to Weblogic 12c, follow the same procedure as we did in Exercice 1.  If you do not remember, just look at the informations in this page:  Weblogic / Java EE6 FAQ and Guides.

In the EJB project, configure persistence.xml for working with MyBank database

This time, contrarily to Exo1, we are going to generate the tables from the source code of the entity beans. Furthermore, if we change the properties of any entity class in the project, the table schemas will be updated too...

  1. Specify the database we are going to use: like in Exo1 with the manufacturers, double click on the persistence.xml file, then in the designer, use the "connection" tab at bottom, change "transaction type" to JTA, and enter the JNDI name of the database (it should be "MyBank" or whatever JNDI name you used when you connected the database to weblogic).
  2. Specify that we want the tables to be created/updated automatically: go to the "schema generation" tab and change "DDL generation type" from "none" to "drop and create". This means that each time we are going to deploy a new version of the project, the tables will be dropped and re-created. This option is very useful when your entity classes (and consequently the schema of your tables) are subject to change. We will see further how to populate the database at deployment time automatically too.

In the DemoEJB Project, create an entity class for an Account

Use the New/JPA entity menu and add an empty entity class to the project. Put it in a package named "entities" and name it Account.

A bank account has a few properties:

  • name: Name of owner,
  • balance,

Remember: a property is defined by getters and setters, so do not forget to generate/write getters and setters for id, name, balance.

This time we will use an auto-generated primary key:

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
int id;

The @GeneratedValue annotation indicates that the PK is auto-generated, using any method, you do not care (AUTO). Other possible values = SEQUENCE, ID, etc. see the JPA course.

You may also add some convenience functions like add(int ammount) or remove(int ammont) methods that will add or remove money from the bank account.

In the end the basic Account.java entity class should look like that:

package entities;

import java.io.Serializable;
import javax.persistence.*;

/**
* Entity implementation class for Entity: Account
*
*/
@Entity
public class Account implements Serializable {
private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
int id;
String name;
int balance;

public Account() {
}

public Account(String name, int balance) {
this.name = name;
this.balance = balance;
}

public void add(int ammount) {
balance += ammount;
}

public void remove(int ammount) {
balance -= ammount;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getBalance() {
return balance;
}

public void setBalance(int balance) {
this.balance = balance;
}
}

Add a AccountFacade session bean to a package named "sessions" to the DemoEJB project

Create a stateless session bean. Implement some methods for the CRUD operations like we did yesterday with the ManufacturerFacade. It is not important to have all of them but at least a create(String name, int balance) method so that we can create new bank accounts and a getAllAccounts() that does a select *. This is very similar to yesterday's exercice.

Add also a method for creating some test accounts so that we will be able to play with a populated database:

 public Account create(String name, int balance) {
    Account a = new Account(name, balance);
    // insert in database
em.persist(a); return a; } public void createTestData() { create("John Lennon", 150000);
create("Paul Mac Cartney", 950000);
create("Ringo Starr", 20000);
create("Georges Harrisson", 100000); }

Add a singleton EJB that will populate the database at deployment time, when the project is run

package entities;

import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Singleton;
import javax.ejb.Startup;

import sessions.AccountFacade;

/**
* Session Bean implementation class InitEJB
*/
@Startup
@Singleton
@LocalBean
public class InitEJB {
@EJB
AccountFacade facade;

@PostConstruct
public void init() {
System.out.println("### Initialization of the database, adding some test accounts ###"); facade.createTestData();
}
}

The @Singleton annotation guarantees that only one instance of that bean will be created. The @Startup annotation indicates that this instance must be created at startup (deployment time), and the @PostConstruct annotation indicates that the annotated method should be called right after the instance of the bean has been created.

In other words, this is a best practice for doing some init stuff when  you deploy the application. As we are using a "drop and create" stategy table, this means that each time we deploy the application the tables are destroyed and re-generated. In order to work with some data, using the bean above, the database is always populated with some test data.

So, save and deploy the EAR project, after deployment go to the MyBank database in the Data Source Explorer tab, refresh the schemas, a new schemas named "Admin" appeared, with two tables in it: an ACCOUNT and a SEQUENCE table (for generating ids). Look at the data in the ACCOUNT table, the beatles should be there...

Add a Servlet and a JSP to manage the Accounts like we did yesterday for the manufacturers

Your turn to work ;-)

In the servlet :

  1. Add a reference to the facade,
  2. Call the getAllAccounts() method and using a foreach loop, print in the console the 4 accounts.
  3. Create a JSP (call it "DisplayAccounts.jsp" for example), modify the servlet and the JSP so that the accounts are displayed in a table, 
  4. You may add some forms for deleting/updating/creating new accounts.

Add some functionalities: transfert money from one account to another

Add a transfert(int sourceId, int destinationId, int ammount) method to the facade, that will remove some money from the first account, and add it to the other.

You may also add a form in the JSP for entering Ids of Accounts and the ammount.

Use JPA relationships

Add a new entity class for describing operations on an account

Now, we are going to improve the design of our models. We are going to use an entity class for describing  Operations that are done on a given by account. An operation can be an account creation, a deposit or a retrieval of money on an account.

An operation has the following properties: 

  • Auto generated id,
  • Description,
  • date,
  • ammount

And of course, we wil define a 1-n relationship between the Accounts and the Operations. An account has a list of operations associated.

Add a new entity class named  Operations in the "entities" package 

The code should look like this:

package entities;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.*;

@Entity
public class Operation implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String description;
@Temporal(javax.persistence.TemporalType.DATE)
private Date dateOperation;
private int ammount;

public Operation() { }

public Operation(String description, int ammount) {
this.description = description;
this.ammount = ammount;
dateOperation = new Date();
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public Date getDateOperation() {
return dateOperation;
}

public void setDateOperation(Date dateOperation) {
this.dateOperation = dateOperation;
}

public int getAmmount() {
return ammount;
}

public void setAmmount(int ammount) {
this.ammount = ammount;
}
}

Note that we are using the java.util.Date type, not the java.sql.Date. Here we do not have anything to do with SQL. We are working with Java objects !

Add a relationship between Account and Operation

An Account may have a list of Operations associated. The list starts always with the creation of the account. We add new operations to the list when an account is created, when money is added or removed from the account. See how we modified the add() and remove() methods, as well as the constructor. We also added a getter and a setter for the new "operations" property of Account.

Here is how we modify the Account entity class:

package entities;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.*;

/**
* Entity implementation class for Entity: Account
*
*/
@Entity
public class Account implements Serializable {
private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
int id;
String name;
int balance;
@OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER)
List<Operation> operations = new ArrayList<Operation>();

public List<Operation> getOperations() {
return operations;
}

public void setOperations(List<Operation> operations) {
this.operations = operations;
}

public Account() {
// update the list of operations
Operation o = new Operation("Account created", 0);
operations.add(o);
}

public Account(String name, int balance) {
this.name = name;
this.balance = balance;
// update the list of operations
Operation o = new Operation("Account created", balance);
operations.add(o);
}

public void add(int ammount) {
balance += ammount;
// update the list of operations
Operation o = new Operation("Deposit", ammount);
operations.add(o);
}

public void remove(int ammount) {
balance -= ammount;
// update the list of operations
Operation o = new Operation("Retrieval", ammount);
operations.add(o);
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getBalance() {
return balance;
}

public void setBalance(int balance) {
this.balance = balance;
}

}

Modify the JSP so that the "Actions" column proposes to remove the current account and to show details of operations

Try to modify the table that displays the list of accounts so that it look like this:

INSERT SCREENSHOT

WHen one click on the "Details of operations" link, we call the servlet that looks for the Account by id, and redirect to a second JSP page named DisplayOperations.jsp that receives the selected account in the request and display in a table like that the details of operations for this account:

INSERT SCREENSHOT

Advanced topics : sort operations by acending dates, use entity bean property validations

Sorting a relation maye done in several different ways:

Using the @OrderBy annotation, unfortunately it works only when the relation does not involve an association table, and it works only on integer elements

Using the @OrderColumn annotation on the entity's properties. Does work only with numeric elements, not dates...

Using encapsulation and sort the Collection using Collection.sort(...) like in this example:

@Entity
public class Actor {

private List<Credits> credits = new ArrayList<Credits>();

@OneToMany
@JoinColumn(name="ACTOR_ID")
public List<Credits> getCredits() {
return this.credits;
}

@Transient
public List<Credits> getCreditsOrderedByMovieYear() {
Collections.sort(credits, new Comparator<Credits>() {
public int compare(Credits o1, Credits o2) {
// Integer implements Comparable<T>
return o1.getMovie().getYear().compareTo(o2.getMovie().getYear());
}
});

return credits;
}

}

Using a JPQL request with an order by expression...

Modify the AccountFacade so that it can also be called remotely, call it from the stand alone client

This time we would like to call methods from the AccountFaceEJB, from a client. Add a remote interface to this EJB, add classes nessary to the client so that it can compile and run. Try to display for example the list of Accounts.