segunda-feira, 27 de outubro de 2014

An Enterprise Application Repository to Access the Players Table

In my previous message, I explained how you could create a stand-alone JPA project using EclipseLink. Now, I'm going to pick this project and turn it into a part of a more complete Enterprise Application Repository. Overall, we are going to need four different projects, all of them interconnected:

  1. Java Persistence API
  2. Enterprise JavaBeans
  3. Dynamic Web Project
  4. Enterprise Application Repository
This is only a small tutorial with minimal implementation. Production code would require a better decoupling of layers (I will mention this below).

The Java Persistence API project

Let us start by the JPA project, which is almost the same as we had in the EclipseLink example. First you need to do File--> New--> JPA Project, to reach the following window:


After a couple of Next's you reach the following window, where you tell Eclipse that the JPA library (Hibernate) will be provided by the Runtime, i.e., WildFly:


Once we press the Finish button we may start copying (sorry for this redundancy) the Player and Team code:

package data;

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

import javax.persistence.*;

/**
 * Entity implementation class for Entity: Player
 *
 */
@Entity
public class Player implements Serializable {
private static final long serialVersionUID = 1L;
@Id @GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
@Temporal(TemporalType.DATE)
private Date birth;
private float height;
@ManyToOne
private Team team;
public Player() {
super();
}

public Player(String name, Date birthfloat height, Team team) {
super();
this.name = name;
this.birth = birth;
this.height = height;
this.team = team;
}

public String getName() {
return name;
}

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

public Date getBirth() {
return birth;
}

public void setBirth(Date birth) {
this.birth = birth;
}

public float getHeight() {
return height;
}

public void setHeight(float height) {
this.height = height;
}

public Team getTeam() {
return team;
}

public void setTeam(Team team) {
this.team = team;
}

public static long getSerialversionuid() {
return serialVersionUID;
}
@Override
public String toString() {
return this.name + " id = " + this.id + ", " + this.height + " plays for " + this.team.getName() + ". Born on " + this.birth;
}
   
}

And now the Team:



package data;

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

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;

@Entity
public class Team implements Serializable {
private static final long serialVersionUID = 1L;
@Id @GeneratedValue(strategy=GenerationType.AUTO)
int id;
private String name;
private String address;
private String presidentname;
@OneToMany(mappedBy="team")
private List<Player> players;
public Team() {
super();
}
public Team(String name, String address, String presidentname) {
super();
this.name = name;
this.address = address;
this.presidentname = presidentname;
}

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPresidentname() {
return presidentname;
}
public void setPresidentname(String presidentname) {
this.presidentname = presidentname;
}
public List<Player> getPlayers() {
return players;
}
public void setPlayers(List<Player> players) {
this.players = players;
}

}


I will not go into details on this code in this message, because I gave some in the previous one. You may simply copy the files from one project to the other, if you red the previous example. The one thing that is different is the persistence.xml file. It looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="Players" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/PlayersEAR</jta-data-source>
<class>data.Player</class>
<class>data.Team</class>
<properties>
<property name="javax.persistence.jdbc.user" value="artur" />
<property name="javax.persistence.jdbc.password" value="***" />
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/PlayersAndTeams" />
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />

<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
<property name="hibernate.hbm2ddl.auto" value="create" />
</properties>

</persistence-unit>
</persistence>


Notice the reference to the database in the javax.persistence.jdbc.url. You may use exactly the same database as in the previous example. It will be useful to fill data for us here. We are going to use the Java Transactions API (TPA) and we need to refer to the jta-data-source. The name we put here (java:/PlayersEAR) must exist in a configuration file of WildFly. It could be standalone.xml, or standalone-full.xml if you need Java Message Service. Look for the tag <datasources> to insert this piece of configuration:


                <datasource jta="true" jndi-name="java:/PlayersEAR" pool-name="my_pool2" enabled="true" use-java-context="true" use-ccm="true">
                    <connection-url>jdbc:mysql://localhost:3306/PlayersAndTeams</connection-url>
                    <driver>mysql</driver>
                    <security>
                        <user-name>artur</user-name>
                        <password>***</password>
                    </security>
                    <statement>
                        <prepared-statement-cache-size>100</prepared-statement-cache-size>
                        <share-prepared-statements>true</share-prepared-statements>
                    </statement>

                </datasource>


In fact, configuration of WildFly is more complicated than this. Look for the following messages on how to make WildFly work with MySQL:


I have this configured on WildFly, but I haven't read the specific messages on WildFly:


The EJB Project

Our next step is to create the Enterprise JavaBeans project. We will need a new specific "EJB Project" for that:



We need to add a reference to the previous ServerPlayers-JPA to refer the Player class in this project (see the source code, after this picture):



We will write a very simple Enterprise JavaBean (EJB). It contains two methods: one to populate the database, the other to search for players taller than a given value. This class refers an EJB remote interface, which we present afterwards. To create the bean and the respective interface, you need to do File-->New-->Session Bean, as in the figure.



The EJB implementation. You may want to take a longer more detailed look at the query:


package ejb;

import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import data.Player;
import data.Team;

/**
 * Session Bean implementation class PlayersEJB
 */
@Stateless
public class PlayersEJB implements PlayersEJBRemote {
@PersistenceContext(name="Players")
EntityManager em;
    /**
     * Default constructor. 
     */
    public PlayersEJB() {
        // TODO Auto-generated constructor stub
    }
    
public static Date getDate(int day, int month, int year) {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, month - 1);
cal.set(Calendar.DAY_OF_MONTH, day);

Date d = cal.getTime();
return d;
}


    public void populate() {
Team [] teams = { new Team("Sporting", "Alvalade", "Carvalho"), new Team("Academica", "Coimbra", "Simões"), new Team("Porto", "Antas", "Costa"), new Team("Benfica", "Luz", "Vieira") };
Player [] players = { 
new Player("Albino", getDate(23,4,1987), 1.87f, teams[0]), 
new Player("Bernardo", getDate(11,4,1987), 1.81f, teams[0]), 
new Player("Cesar", getDate(12,5,1983), 1.74f, teams[0]), 
new Player("Dionisio", getDate(3,12,1992), 1.67f, teams[0]), 
new Player("Eduardo", getDate(31,8,1985), 1.89f, teams[0]), 
new Player("Franco", getDate(6,1,1989), 1.95f, teams[1]), 
new Player("Gil", getDate(7,12,1986), 1.8f, teams[1]), 
new Player("Helder", getDate(14,5,1987), 1.81f, teams[1]), 
new Player("Ilidio", getDate(13,6,1991), 1.82f, teams[1]), 
new Player("Jacare", getDate(4,2,1993), 1.83f, teams[1]), 
new Player("Leandro", getDate(4,10,1984), 1.81f, teams[2]), 
new Player("Mauricio", getDate(3,6,1984), 1.8f, teams[2]), 
new Player("Nilton", getDate(11,3,1985), 1.88f, teams[2]), 
new Player("Oseias", getDate(23,11,1990), 1.74f, teams[2]), 
new Player("Paulino", getDate(14,9,1986), 1.75f, teams[2]), 
new Player("Quevedo", getDate(10,10,1987), 1.77f, teams[2]), 
new Player("Renato", getDate(7,7,1991), 1.71f, teams[3]), 
new Player("Saul", getDate(13,7,1992), 1.86f, teams[3]), 
new Player("Telmo", getDate(4,1,1981), 1.88f, teams[3]), 
new Player("Ulisses", getDate(29,8,1988), 1.84f, teams[3]), 
new Player("Vasco", getDate(16,5,1988), 1.83f, teams[3]), 
new Player("X", getDate(8,12,1990), 1.82f, teams[3]), 
new Player("Ze", getDate(13,5,1987), 1.93f, teams[3]), 
};
for (Team t : teams)
em.persist(t);
for (Player p : players)
em.persist(p);
    }
    
    
    public List<Player> playersTallerThan(float threshold) {
    System.out.println("In the EJB. Height = " + threshold);
Query q = em.createQuery("from Player p where p.height >= :t");
q.setParameter("t", threshold);
@SuppressWarnings("unchecked")
List<Player> result = q.getResultList();
return result;
    }
}


Here are the two methods that are visible from the outside:


package ejb;

import java.util.List;

import javax.ejb.Remote;

import data.Player;

@Remote
public interface PlayersEJBRemote {
public void populate();
public List<Player> playersTallerThan(float threshold);
}




The Dynamic Web Project

Now, we will provide a web access to the EJB, through a servlet. For this we must first create a Dynamic Web Project (via File-->New):



Again, we need to refer to the other two projects (more on this below):


 It would be better to use Data Transfer Objects between the EJB and the presentation layer, to ensure a strict separation between the presentation layer and the data layer, as they are not adjacent. A library like MapStruct may help here. This would make the example more complicated. Maybe in the future I will ensure this strict separation.

We now create the servlet (File-->New-->Servlet):



The source code next:


package servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import data.Player;
import ejb.PlayersEJBRemote;

/**
 * Servlet implementation class PlayersTallerThan
 */
// http://localhost:8080/ServerPlayers-Web/PlayersTallerThan?fill=1
// url = http://localhost:8080/ServerPlayers-Web/PlayersTallerThan?height=1.80
@WebServlet("/PlayersTallerThan")
public class PlayersTallerThan extends HttpServlet {
private static final long serialVersionUID = 1L;
@EJB
PlayersEJBRemote ejbremote;

/**
* @see HttpServlet#HttpServlet()
*/
public PlayersTallerThan() {
super();
}

/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
response.setContentType("text/html");

if (request.getParameter("fill") != null) {
ejbremote.populate();
out.println("<h1>Populate: OK!</h1>");
}
else {
float tall = Float.parseFloat(request.getParameter("height"));
List<Player> lp = ejbremote.playersTallerThan(tall);

out.println("<h1> Players </h1>");
for (Player p : lp)
out.println(p + "<br/>");
}
}

/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

}


I must say that this is not the most elegant code I've ever written, as I control the operation I want using two URLs and I have an HTTP GET command changing the state of the system (it should be a POST). Anyway, the get accepts two commands:

http://localhost:8080/ServerPlayers-Web/PlayersTallerThan?height=1.80, where 1.80 might be changed to the "height" threshold you want to set. But before we are able to do this, we must populate the database with:

http://localhost:8080/ServerPlayers-Web/PlayersTallerThan?fill=1

The parameter "fill" can actually have any value, the code will still work.

The Enterprise Application Repository (EAR) Project

Before we can put anything to run, we must create and deploy an EAR project. We start with File-->New--> Enterprise Application Project:


Once you push "Next" you are prompted to select the projects that compose the EAR. In our case we have the following three:


Once this step is finished, we may deploy the whole package, as follows (use the right button of the mouse):

Select the appropriate location in the deployments directory of the application server:

I assume that WildFly was running. Once you do the deployment, you should see the following output in WildFly, with the references to our project:


We can now go to a browser and enter the following URLs:

http://localhost:8080/ServerPlayers-Web/PlayersTallerThan?fill=1

to fill the database, and

http://localhost:8080/ServerPlayers-Web/PlayersTallerThan?height=1.80

should produce the following outcome:



1 comentário: