Monday 31 May 2010

Querying the database with JPA

When working with entity beans you can retrieve records from the database using Java Persistence Query Language (JPQL), a declarative language similar to SQL but which works with Java objects rather than relational tables. Both dynamic and static queries can be defined with JPQL. Static queries (named queries) are defined in a JPA entity nested within a @NamedQuery annotation. Dynamic queries are defined directly within an application's business logic. The EntityManager interface provides the methods createQuery and createNamedQuery used to create dynamic, respectively static queries.
The Java EE 6 Tutorial presents JPQL in detail. (http://java.sun.com/javaee/6/docs/tutorial/doc/bnbrg.html)

Wednesday 19 May 2010

Composite Primary Keys for JPA Entities

When the primary key of a JPA entity is composed from more than one attribute it is necessary to define a class corresponding to the primary key.

The JPA entity will use the @IdClass annotation to specify the name of the class corresponding to the composite primary key.

In the class corresponding to the JPA entity, the attributes included in the primary key will be marked with the @Id annotation.

Example using a compound primary key in JPA

1.Create and populate a table to be used by the example.

ij> CREATE TABLE Grades(
> studentID INTEGER NOT NULL,
> cursID INTEGER NOT NULL,
> semestrul1 INTEGER,
> semestrul2 INTEGER,
> laborator INTEGER,
> CONSTRAINT pk_SidCid PRIMARY KEY(studentID,cursID));
0 rows inserted/updated/deleted
...
ij> INSERT INTO Grades VALUES
> (1,1,9,10,10);
1 row inserted/updated/deleted
...
 ij> SELECT * FROM Grades;
STUDENTID  |CURSID     |SEMESTRUL1 |SEMESTRUL2 |LABORATOR
-----------------------------------------------------------
1          |1          |9          |10         |10
2          |1          |8          |7           |8
3          |1          |10         |10        |10
28 rows selected

2.Define the JPA entity

import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Column;
import javax.persistence.NamedQuery;
import java.io.Serializable;

@Entity
@Table(name="GRADES")
@IdClass(GradesPK.class)
@NamedQuery(name="Grades.forOneStudent",
query="select o from Grades o where o.studentId=:param")

public class Grades implements Serializable{

@Id
@Column(name="STUDENTID", nullable=false)
private int studentId;

@Id
@Column(name="CURSID", nullable=false)
private int cursId;

@Column(name="SEMESTRUL1")
private int semestrul1;

...
}

3.Define the class corresponding to the primary key.

Make sure that the names and types of the fields are the same with those in the entity bean!

import java.io.Serializable;

public class GradesPK implements Serializable{

public int studentId;
public int cursId;

public GradesPK(){
//constructor public fara parametri
}

public GradesPK(int studentId, int cursId){
this.studentId=studentId;
this.cursId=cursId;
}

public int hashCode(){
return super.hashCode();
}

public boolean equals(Object other){

if(!(other instanceof GradesPK)){
return false;}

GradesPK celalalt = (GradesPK)other;

return(celalalt.studentId==studentId
       &&
      celalalt.cursId==cursId);
}
}

4.Define a method in a session bean to use for testing a query.

public List getGradesforOneStudent(int idStud){
List note = (List)em.createNamedQuery("Grades.forOneStudent").
            setParameter("param",idStud).getResultList();

return note;
}


5.Write a test client to call the method.
 
import javax.naming.InitialContext;
import java.util.List;
import java.util.ListIterator;
...

public class Client1{
public static void main(String[] args) throws Exception
{
    InitialContext ctx = new InitialContext();
    FatadaCursuri beanInstance = (FatadaCursuri)
                                      ctx.lookup(FatadaCursuri.class.getName());

    List grades = beanInstance.getGradesforOneStudent(2);
    ListIterator listIterator2 = grades.listIterator();
    System.out.println("Notele studentului cu studentId=2");
    while(listIterator2.hasNext()){
    Grades linie = (Grades)listIterator2.next();
    System.out.print("ID Curs:"+linie.citesteCursId());
    System.out.print(" Nota 1:"+linie.citesteSemestrul1());
    System.out.print(" Nota 2:"+linie.citesteSemestrul2());
    System.out.println(" Nota lab.:"+linie.citesteLaborator());   
    }
}
}

Monday 17 May 2010

Ciclul de viaţă al componentelor de tip sesiune

O componentă de tip sesiune fără stare este creată la iniţiativa containerului EJB. Containerul menţine un rezervor (pool) de instanţe ale componentelor de acest tip, astfel încât atunci când un client apelează o metodă a unei componente de tip sesiune fără stare este aleasă o instanţă a componentei din rezervor şi invocată metoda acesteia. Acest lucru este posibil deoarece starea componentei nu trebuie menţinută după încheierea execuţiei metodei. Distrugerea componentelor de tip sesiune fără stare este deasemenea efectuată prin decizia containerului EJB, de exemplu pentru eliberarea unor resurse de memorie sau atunci când o anumită componentă nu este utilizată un timp mai îndelungat. Astfel, o componentă de tip sesiune fără stare se poate găsi în timpul ciclului său de viaţă doar într-una din două stări: inexistentă şi în aştepare(ready).

O metodă a unei componente de tip sesiune fără stare poate fi marcată cu una dintre adnotările @PostConstruct sau @PreDestroy, ceea ce va face ca ele să fie apelate imediat după instanţierea componentei, respectiv înainte de distrugerea ei.

Spre deosebire de cazul componentelor fără stare, ciclul de viaţă al componentelor de tip sesiune cu stare este iniţiat de către clienţi prin solicitarea unei referinţe la o instanţă a unei componente. Pe durata ciclului său de viaţă, o componentă de tip sesiune cu stare poate fi trecută de către container din starea ready, în care componenta se află în memoria principală, într-o stare pasivă(inactivă), în memoria secundară a serverului. Re-activarea are loc atunci când un client apelează o metodă a unei instanţe pasive. Reamintiţi-vă că o componentă de tip sesiune cu stare este asociată unui client pentru toată durata conversaţiei acestuia cu serverul, care poate conţine mai multe apeluri de metode, între care trebuie păstrată starea componentei. Prin urmare o componentă de tip sesiune cu stare nu va fi în general distrusă la iniţiativa containerului! (Se poate ca o componentă de tip sesiune cu stare, pasivă, să fie distrusă de container după o perioadă îndelungată de inactivitate a clientului, timeout.)

Adnotările care pot marca una dintre metodele unei componente de tip sesiune cu stare sunt:  @PostConstruct, @PreDestroy, @PrePassivate şi @PostActivate, cu semnificaţiile evidente date de numele lor.

Tuesday 4 May 2010

Componente bazate pe mesaje (Message Driven Beans)

O componentă bazată pe mesaje (message-driven bean, MDB) este o componentă fără stare care consumă mesaje dintr-o coadă JMS (Java Message Service). Standardul JMS este parte integrantă din Java EE şi implementează suportul pentru comunicaţii asincrone pe această platformă. Mai concret, este vorba despre dirijarea mesajelor către destinaţii denumite cozi şi distribuirea acestor mesaje către consumatori.

O componentă MDB defineşte o metodă, onMessage(), care este apelată de către containerul EJB ori de câte ori este primit un mesaj în coada la care este asociată componenta. O componentă de tip MDB nu poate fi apelată direct de către clienţi.

Resursele necesare transmiterii mesajelor folosind JMS sunt:
- un obiect de tip ConnectionFactory, care conţine parametrii pentru configurarea conexiunii. Acest obiect este folosit de clienţi la crearea conexiunilor cu providerul de JMS.
- un obiect de tip Queue, coada propriu-zisă de mesaje.
- o destinaţie fizică.

Cele trei resurse pot fi create folosind consola grafică de administrare a serverului de aplicaţii, ca în imaginile de mai jos.


Ca exerciţiu, am creat fişierul PrimaCBM.java, cu următorul conţinut:

import javax.ejb.MessageDriven;
import javax.jms.MessageListener;
import javax.jms.Message;
import javax.jms.TextMessage;
import java.util.logging.Logger;

@MessageDriven(name="PrimaCBM",mappedName="jms/Queue")
public class PrimaCBM implements MessageListener {
    static final Logger registru = Logger.getLogger("PrimaCBM");
   
    public PrimaCBM(){}

    public void onMessage(Message mesaj) {
        try {         
           String text = mesaj.getStringProperty("text");
           registru.info("CBM a primit mesajul:"+text);
            }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

La primirea unui mesaj containerul apelează metoda onMessage(). Vom înregistra primirea mesajului într-unul dintre fişierele de jurnalizare ale serverului de aplicaţii. În acest scop folosim un obiect de tipul Logger, definit în pachetul java.util.logging. Conţinutul fişierului de jurnalizare poate fi vizualizat şi prin intermediul consolei grafice de administrare a serverului.

Compilaţi componenta şi construiţi arhiva CBM.jar, folosind următoarele comenzi:

C:\EJB>javac -classpath .;C:\glassfishv3\glassfish\lib\javaee.jar PrimaCBM.java

C:\EJB>jar cvf CBM.jar PrimaCBM.class

Încărcaţi arhiva CBM.jar pe serverul de aplicaţii, folosind consola grafică de administrare şi specificând tipul arhivei ca fiind EJB Jar.

Pentru a trimite mesaje către componenta definită am creat programul CBMClient.java, a cărui sursă este redată mai jos.

import javax.jms.ConnectionFactory;
import javax.jms.Queue;
import javax.jms.Connection;
import javax.jms.Session;
import javax.jms.MessageProducer;
import javax.jms.TextMessage;
import javax.jms.Message;

import javax.naming.InitialContext;


public class CBMClient {

public static void main(String[] args) {

System.out.println("Hi!");

try {

    InitialContext ctx = new InitialContext();
    System.out.println("S-a creat InitialContext"); 
    ConnectionFactory connectionFactory =
(ConnectionFactory) ctx.lookup("firstJMS_pool");
    System.out.println("S-a creat ConnectionFactory");
    Queue coadaMesaje = (Queue) ctx.lookup("jms/Queue");
    System.out.println("S-a creat Queue"); 

    Connection conexiune = null;
    Session sesiune = null;
    MessageProducer expeditor = null;
    Message mesaj = null;

conexiune = connectionFactory.createConnection();
    System.out.println("S-a creat conexiunea");

sesiune = conexiune.createSession(false, Session.AUTO_ACKNOWLEDGE);
expeditor = sesiune.createProducer(coadaMesaje);
mesaj = sesiune.createMessage();

mesaj.setStringProperty("text","Testez CBM");

    expeditor.send(mesaj);

System.out.println("Am trimis mesajul:"+mesaj.getStringProperty("text"));

    expeditor.close();
    sesiune.close();
    conexiune.close();
}
catch(Exception e){System.out.println("Exceptie: " + e.toString());}
} // main
} // class


Compilaţi şi executaţi programul client folosind următoarele comenzi:

C:\EJB\clienti>javac -classpath .;C:\glassfishv3\glassfish\lib\javaee.jar CBMClient.java

C:\EJB\clienti>java -classpath .;C:\glassfishv3\glassfish\lib\appserv-rt.jar;C:\glassfishv3\glassfish\lib\install\applications\jmsra\imqjmsra.jar;C:\glassfishv3\glassfish\lib\appserv-admin.jar;C:\glassfishv3\glassfish\lib\appserv-ws.jar;C:\glassfishv3\glassfish\lib\javaee.jar CBMClient


Mesajul primit înregistrat în fişierul de jurnalizare poate fi văzut şi prin intermediul consolei grafice de administrare a serverului, aşa cum este ilustrat în imaginile de mai jos.