Skip to content

5. Version 1: Spring Architecture / JPA

We propose to write a console application as well as a graphical application to generate pay stubs for child care providers employed by the "Maison de la petite enfance" in a municipality. This application will have the following architecture:

5.1. BDLa database

The static data needed to generate the pay stub will be stored in a database that we will refer to as dbpam. This database could contain the following tables:

Table EMPLOYES: contains information about the various child care providers

Structure:

ID
primary key
VERSION
version – increments with each row modification
SS
Employee's Social Security number - unique
NOM
employee's last name
prenom
first name
ADRESSE
their address
VILLE
his/her city
CODEPOSTAL
his/her ZIP code
INDEMNITE_ID
Foreign key on field [ID] in table [INDEMNITES]

Its content could be as follows:

Image

Table COTISATIONS: contains the percentages needed to calculate social security contributions

Structure:

ID
Primary Key
VERSION
version number – increments with each modification of the row
CSGRDS
Percentage: General Social Contribution + Contribution to Social Debt Repayment
CSGD
percentage: deductible general social contribution
SECU
Percentage: Social Security, Widow's Benefits, Old-Age Benefits
RETRAITE
Percentage: supplemental pension + unemployment insurance

Its content could be as follows:

Image

Social security rates are independent of the employee. The previous table has only one row.

Table INDEMNITES: contains the elements used to calculate the salary to be paid.
ID
primary key
 
VERSION
version No. – increases with each modification of the row
 
INDICE
Processing index - unique
 
BASEHEURE
net price in euros for one hour of on-call duty
 
ENTRETIENJOUR
daily allowance in euros per day of care
 
REPASJOUR
Meal allowance in euros per day of care
 
INDEMNITESCP
Paid vacation allowance. This is a percentage applied to the base salary.
 
  

Its content could be as follows:

Image

Note that allowances may vary from one child care provider to another. They are linked to a specific child care provider via their pay grade. Thus, Ms. Marie Jouveinal, who has a pay grade of 2 (table EMPLOYES), has an hourly wage of 2.1 euros (table INDEMNITES).

5.2. Method for Calculating a Childcare Provider’s Salary

We will now present the method for calculating a childminder’s monthly salary. This is not intended to be the method actually used in practice. As an example, we will use the salary of Ms. Marie Jouveinal, who worked 150 hours over 20 days during the pay period.

The following factors are taken into account:

[TOTALHEURES]: total des heures
travaillées dans le mois

[TOTALJOURS]: total des jours travaillés
dans le mois
[TOTALHEURES]=150
[TOTALJOURS]= 20
The child care provider's base salary
is given by the following formula:
[SALAIREBASE]=([TOTALHEURES]
*[BASEHEURE])*(1+
[INDEMNITESCP]/100)
[SALAIREBASE]=
(150*[2.1])*(1+0.15)= 362,25
A number of social benefits
must be deducted from this base salary
:

Contribution sociale généralisée et
contribution au remboursement de la
dette sociale :
 [SALAIREBASE]*[CSGRDS/100]

Contribution sociale généralisée déductible :
 [SALAIREBASE]*[CSGD/100]

Sécurité sociale, veuvage, vieillesse :
 [SALAIREBASE]*[SECU/100]

Retraite Complémentaire + AGPF +
Assurance Chômage :
 [SALAIREBASE]*[RETRAITE/100]
CSGRDS : 12,64
CSGD : 22,28
Sécurité sociale : 34,02
Retraite : 28,55
Total social security contributions:
[COTISATIONSSOCIALES]=
[SALAIREBASE]*(CSGRDS+CSGD
+SECU+RETRAITE)/100
[COTISATIONSSOCIALES]=97,48
In addition, the child care provider is entitled to a daily allowance and a meal allowance for each day worked. As such, she receives the following allowances:

[Indemnités]=[TOTALJOURS]
*(ENTRETIENJOUR+REPASJOUR)
[INDEMNITES]=104
In total, the salary net to be paid to the childminder is as follows:
[SALAIREBASE]-[COTISATIONSSOCIALES]+[INDEMNITÉS]
[salaire NET]=368,77

5.3. How the console application works

Here is an example of running the console application in a DOS window:

dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150 20

Valeurs saisies :
N° de sécurité sociale de l'employee: 254104940426058
Nombre d'hours worked: 150
Nombre de jours travaillés : 20

Informations Employé :
Nom : Jouveinal
Prénom : Marie
Adresse : 5 rue des Oiseaux
Ville : St Corentin
Code Postal : 49203
Indice : 2

Informations Cotisations :
CSGRDS : 3.49 %
CSGD : 6.15 %
Retraite : 7.88 %
Sécurité sociale : 9.39 %

Informations Indemnités :
Salaire horaire : 2.1 euro
Entretien/jour : 2.1 euro
Repas/jour : 3.1 euro
Congés Payés : 15.0 %

Informations Salaire :
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnités d'maintenance: 42.0 euro
Indemnités de repas : 62.0 euro
Salaire net : 368.77 euro

We will write a program that will receive the following information:

  1. the child care provider’s Social Security number (254104940426058 in the example—line 1)
  2. total number of hours worked (150 in the example - line 1)
  3. total number of days worked (20 in the example - line 1)

We see that:

  • lines 9–14: display information about the employee whose Social Security number was provided
  • lines 17–20: display the rates for the various cotisations
  • lines 23–26: display the allowances associated with the employee’s pay grade (here, grade 2)
  • lines 29-33: display the components of the salary to be paid

The application reports any errors:

Call without parameters:


dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar
Syntaxe : pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés

Call with incorrect data:


dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar  254104940426058 150x 20x
Le nombre d'hours worked [150x] is incorrect
Le nombre de jours travaillés [20x] est erroné

Call with an incorrect social security number:


dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar  xx 150 20
L'the following error has occurred: Employee no. [xx] cannot be found

5.4. How the graphical application works

The graphical application calculates the salaries of child care providers using a Swing form:

  • The information passed as parameters to the console program is now entered using the [1, 2, 3] input fields.
  • The [4] button initiates the salary calculation
  • The form displays the various salary components up to the net salary to be paid [5]

The [1, 6] drop-down list does not display the employees’ ID numbers but their first and last names. We assume here that no two employees have the same first and last names.

5.5. Creating the database

We run WampServer and use the PhpMyAdmin tool [1]:

  • in [2], we use option and [Bases de données],
  • in [3], we create a database [dbpam_hibernate],
  • in [4], the database is created. We select it,
  • in [5], we want to import a script SQL,
  • in [6], use the button in [Parcourir] to select the file,
  • in [7,8], select the script SQL,
  • in [9], we run it,
  • In [10], the tables have been created. Their contents are as follows:

table EMPLOYES

Image

table INDEMNITES

Image

table COTISATIONS

Image

5.6. Implementation JPA

5.6.1. Layer JPA / Hibernate

We will configure the JPA layer in the following environment:

A console program will work with the database. To do this, you need:

  • have a database,
  • Obtain the JDBC driver from SGBD, here MySQL,
  • implement the JPA layer with Hibernate,
  • write the console program.

We create the Maven project [mv-pam-jpa-hibernate] [1]:

In our application architecture, we need the following components:

  • the database,
  • the JDBC driver for SGBD and MySQL,
  • the JPA / Hibernate layer (entities and configuration),
  • the test console program.

5.6.1.1. The database

First, let’s create the empty database. We launch WampServer and use the PhpMyAdmin and [1] tools:

  • in [2], we use option and [Bases de données],
  • in [3], we create a database [dbpam_hibernate],
  • in [4], the database is created.

5.6.1.2. Configuration of layer JPA

The connection between layer JDBC and the database is established in file [persistence.xml], which configures layer JPA. This file can be created using Netbeans:

  • In the [services] [1] tab, connect to the database using the JDBC driver from MySQL [2],
  • in [3], the name of the database to which you want to connect.
  • in [4], the database’s URL JDBC,
  • in [5], log in as root without a password,
  • In [6], you can test the connection,
  • in [7], the connection was successful.
  • The connection appears in [8] and [9],
  • in [10], add a new item to the project,
  • in [11], we select the category [Persistence] and in [12] the element [Persistence Unit],
  • in [13], we name this persistence unit,
  • in [14], select a Hibernate implementation,
  • in [15], we specify the connection we just created to the database MySQL,
  • in [16], we specify that when the JPA layer is instantiated, it must create the tables corresponding to the JPA entities in the project.

At the end of the wizard, the file [persistence.xml] is generated:

  • the file appears in a new branch of the project, in a folder named [META-INF] [1],
  • which corresponds to the [src/main/resources] folder in the [2,3] project.

Its content is as follows:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
      <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
    </properties>
  </persistence-unit>
</persistence>
  • Line 3: the name of the persistence unit and the transaction type. RESOURCE_LOCAL indicates that the project manages transactions itself. In this case, the console program will be responsible for doing so,
  • line 4: the implementation JPA used is Hibernate,
  • lines 6–9: the characteristics of the database connection,
  • line 11: requests the creation of tables corresponding to the JPA entities. In fact, Netbeans generates an incorrect configuration here. The configuration should be as follows:

      <property name="hibernate.hbm2ddl.auto" value="create"/>

With the option create, Hibernate, upon instantiation of the JPA layer, deletes and then creates the tables corresponding to the JPA entities. The option create-drop does the same thing, but at the end of the JPA layer’s lifecycle, it deletes all tables. There is another option:


      <property name="hibernate.hbm2ddl.auto" value="update"/>

This option creates the tables if they do not exist, but it does not delete them if they already exist.

We will add three more properties to the Hibernate configuration:


      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>

These instruct Hibernate to display the SQL statements it sends to the database. The complete file is therefore as follows:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
      <property name="hibernate.hbm2ddl.auto" value="create"/>
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>
      <property name="use_sql_comments" value="true"/>
    </properties>
  </persistence-unit>
</persistence>

5.6.1.3. Dependencies

Let’s return to the project architecture:

We configured the JPA layer via the [persistence.xml] file. The chosen implementation was Hibernate. This introduced dependencies into the project:

  

These dependencies are due to the inclusion of Hibernate in the project. We need to add another dependency: the JDBC driver for MySQL, which implements the JDBC layer of the architecture. We update the [pom.xml] file as follows:


<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>    
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.1.2</version>
    </dependency>
    ...
    <dependency>
      <groupId>org.hibernate.common</groupId>
      <artifactId>hibernate-commons-annotations</artifactId>
      <version>4.0.1.Final</version>
    </dependency>
  </dependencies>

Lines 8–12 add the dependency for the JDBC driver from MySQL.

5.6.1.4. The JPA entities


Question: Following the procedure in the example in section 4.4, generate the [Cotisation, Indemnite, Employe] entities.


Notes:

  • the entities will be part of a package named [jpa],
  • each entity will have the ID version,
  • if two entities are linked by a relationship, only the primary relationship @ManyToOne will be created. The inverse relationship @OneToMany will not be created.

5.6.1.5. The code for the main class

We include the previously developed entities JPA and [1] in the project:

then we add [2], the following [main.Main] class:


package main;
 
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
 
public class Main {
 
  public static void main(String[] args) {
    // creating the Entity Manager is enough to build the JPA layer
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-pam-jpa-hibernatePU");
    EntityManager em=emf.createEntityManager();
    // resource release
    em.close();
    emf.close();
  }
}
  • Line 10: We create the EntityManagerFactory persistence unit named [mv-pam-jpa-hibernatePU]. This name comes from the [persistence.xml] file:

  <persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    ...
  </persistence-unit>
  • line 12: EntityManager is created. This creation generates the JPA layer. The [persistence.xml] file will be processed, and thus the database tables will be created,
  • lines 14-15: resources are released.

5.6.1.6. Tests

Let’s return to our project’s architecture:

All layers have been implemented. We run the [2] project.

The console output is as follows:

------------------------------------------------------------------------
Building mv-pam-jpa-hibernate 1.0-SNAPSHOT
------------------------------------------------------------------------

[resources:resources]
[debug] execute contextualize
Using 'UTF-8' encoding to copy filtered resources.
Copying 1 resource

[compiler:compile]
Nothing to compile - all classes are up to date

[exec:exec]
juin 21, 2012 4:22:47 PM org.hibernate.annotations.common.Version <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.1.Final}
juin 21, 2012 4:22:47 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.1.2}
juin 21, 2012 4:22:47 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
juin 21, 2012 4:22:47 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000402: Using Hibernate built-in connection pool (not for production use!)
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000115: Hibernate connection pool size: 20
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000006: Autocommit mode: true
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000401: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/dbpam_hibernate]
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000046: Connection properties: {user=root, autocommit=true, release_mode=auto}
juin 21, 2012 4:22:48 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
juin 21, 2012 4:22:48 PM org.hibernate.engine.jdbc.internal.LobCreatorBuilder useContextualLobCreation
INFO: HHH000423: Disabling contextual LOB creation as JDBC driver reported JDBC version [3] less than 4
juin 21, 2012 4:22:48 PM org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
INFO: HHH000268: Transaction strategy: org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory
juin 21, 2012 4:22:48 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
juin 21, 2012 4:22:48 PM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: HHH000227: Running hbm2ddl schema export
Hibernate: 
    alter table EMPLOYES 
        drop 
        foreign key FK75C8D6BC73F24A67
juin 21, 2012 4:22:48 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: HHH000389: Unsuccessful: alter table EMPLOYES drop foreign key FK75C8D6BC73F24A67
juin 21, 2012 4:22:48 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: Table 'dbpam_hibernate.employes' doesn't exist
Hibernate: 
    drop table if exists COTISATIONS
Hibernate: 
    drop table if exists EMPLOYES
Hibernate: 
    drop table if exists INDEMNITES
Hibernate: 
    create table COTISATIONS (
        id bigint not null auto_increment,
        CSGD double precision not null,
        CSGRDS double precision not null,
        RETRAITE double precision not null,
        SECU double precision not null,
        VERSION integer not null,
        primary key (id)
    )
Hibernate: 
    create table EMPLOYES (
        id bigint not null auto_increment,
        SS varchar(15) not null unique,
        ADRESSE varchar(50) not null,
        CP varchar(5) not null,
        NOM varchar(30) not null,
        PRENOM varchar(20) not null,
        VERSION integer not null,
        VILLE varchar(30) not null,
        INDEMNITE_ID bigint not null,
        primary key (id)
    )
Hibernate: 
    create table INDEMNITES (
        id bigint not null auto_increment,
        BASE_HEURE double precision not null,
        ENTRETIEN_JOUR double precision not null,
        INDEMNITES_CP double precision not null,
        INDICE integer not null unique,
        REPAS_JOUR double precision not null,
        VERSION integer not null,
        primary key (id)
    )
Hibernate: 
    alter table EMPLOYES 
        add index FK75C8D6BC73F24A67 (INDEMNITE_ID), 
        add constraint FK75C8D6BC73F24A67 
        foreign key (INDEMNITE_ID) 
        references INDEMNITES (id)
juin 21, 2012 4:22:49 PM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: HHH000230: Schema export complete
juin 21, 2012 4:22:49 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH000030: Cleaning up connection pool [jdbc:mysql://localhost:3306/dbpam_hibernate]
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 2.637s
Finished at: Thu Jun 21 16:22:49 CEST 2012
Final Memory: 8M/153M

The console contains only Hibernate logs, since the executed program does nothing other than instantiate the JPA layer. Note the following points:

  • line 43: Hibernate attempts to delete the foreign key from the [EMPLOYES] table,
  • lines 51–55: deletion of the three tables,
  • line 57: creation of the [COTISATIONS] table,
  • line 67: creation of the table [EMPLOYES],
  • line 80: creation of table [INDEMNITES],
  • line 91: creation of the foreign key for table [EMPLOYES].

In Netbeans, you can see the tables in the connection that was created previously:

The tables created depend on both the implementation of the JPA layer used and the SGBD used. Thus, a JPA / EclipseLink implementation with the same database can generate different tables. This is what we will now examine.

We will build a new Maven project in the following environment:

We will follow the steps outlined in the previous section:

  1. create a database named MySQL [dbpam_eclipselink]. We will use the script [dbpam_eclipselink.sql] to generate it,
  2. create the project file [persistence.xml]. Take the JPA 2.0 implementation EclipseLink,
  3. add the JDBC driver dependency from MySQL to the generated dependencies,
  4. add the JPA entities and the console program,
  5. run the tests.

The [persistence.xml] file will be as follows:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="pam-jpa-eclipselinkPU" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <properties>
      <property name="eclipselink.target-database" value="MySQL"/>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="eclipselink.logging.level" value="FINE"/>
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
    </properties>
  </persistence-unit>
</persistence>
  • Properties 9–13 were generated by the Netbeans wizard,
  • Line 14: This property allows us to set the log level for EclipseLink. The FINE level allows us to see the SQL commands that EclipseLink will issue to the database,
  • Line 15: When the JPA / EclipseLink layer is instantiated, the JPA entity tables will be dropped and then created.

The console results obtained are as follows:

------------------------------------------------------------------------
Building mv-pam-jpa-eclipselink 1.0-SNAPSHOT
------------------------------------------------------------------------

[resources:resources]
[debug] execute contextualize
Using 'UTF-8' encoding to copy filtered resources.
Copying 1 resource

[compiler:compile]
Nothing to compile - all classes are up to date

[exec:exec]
[EL Config]: 2012-06-22 14:35:01.852--ServerSession(730572764)--Thread(Thread[main,5,main])--The access type for the persistent class [class jpa.Cotisation] is set to [FIELD].
[EL Config]: 2012-06-22 14:35:01.884--ServerSession(730572764)--Thread(Thread[main,5,main])--The access type for the persistent class [class jpa.Employe] is set to [FIELD].
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--Thread(Thread[main,5,main])--The target entity (reference) class for the many to one mapping element [field indemnite] is being defaulted to: class jpa.Indemnite.
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--Thread(Thread[main,5,main])--The access type for the persistent class [class jpa.Indemnite] is set to [FIELD].
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--Thread(Thread[main,5,main])--The alias name for the entity class [class jpa.Cotisation] is being defaulted to: Cotisation.
[EL Config]: 2012-06-22 14:35:01.915--ServerSession(730572764)--Thread(Thread[main,5,main])--The column name for element [id] is being defaulted to: ID.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--The alias name for the entity class [class jpa.Employe] is being defaulted to: Employe.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--The column name for element [id] is being defaulted to: ID.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--The alias name for the entity class [class jpa.Indemnite] is being defaulted to: Indemnite.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--The column name for element [id] is being defaulted to: ID.
[EL Config]: 2012-06-22 14:35:01.962--ServerSession(730572764)--Thread(Thread[main,5,main])--The primary key column name for the mapping element [field indemnite] is being defaulted to: ID.
[EL Info]: 2012-06-22 14:35:02.558--ServerSession(730572764)--Thread(Thread[main,5,main])--EclipseLink, version: Eclipse Persistence Services - 2.3.0.v20110604-r9504
[EL Config]: 2012-06-22 14:35:02.568--ServerSession(730572764)--Connection(1543921451)--Thread(Thread[main,5,main])--connecting(DatabaseLogin(
    platform=>MySQLPlatform
    user name=> "root"
    datasource URL=> "jdbc:mysql://localhost:3306/dbpam_eclipselink"
))
[EL Config]: 2012-06-22 14:35:02.738--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--Connected: jdbc:mysql://localhost:3306/dbpam_eclipselink
    User: root@localhost
    Database: MySQL  Version: 5.5.20-log
    Driver: MySQL-AB JDBC Driver  Version: mysql-connector-java-5.1.6 ( Revision: ${svn.Revision} )
[EL Info]: 2012-06-22 14:35:02.798--ServerSession(730572764)--Thread(Thread[main,5,main])--file:/D:/data/istia-1112/netbeans/glassfish/mv-pam/05/mv-pam-jpa-eclipselink/target/classes/_pam-jpa-eclipselinkPU login successful
[EL Fine]: 2012-06-22 14:35:02.818--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--ALTER TABLE EMPLOYES DROP FOREIGN KEY FK_EMPLOYES_INDEMNITE_ID
[EL Fine]: 2012-06-22 14:35:03.088--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--DROP TABLE COTISATIONS
[EL Fine]: 2012-06-22 14:35:03.118--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--CREATE TABLE COTISATIONS (ID BIGINT NOT NULL, CSGD DOUBLE NOT NULL, CSGRDS DOUBLE NOT NULL, RETRAITE DOUBLE NOT NULL, SECU DOUBLE NOT NULL, VERSION INTEGER NOT NULL, PRIMARY KEY (ID))
[EL Fine]: 2012-06-22 14:35:03.198--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--DROP TABLE EMPLOYES
[EL Fine]: 2012-06-22 14:35:03.238--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--CREATE TABLE EMPLOYES (ID BIGINT NOT NULL, SS VARCHAR(15) NOT NULL UNIQUE, ADRESSE VARCHAR(50) NOT NULL, CP VARCHAR(5) NOT NULL, NOM VARCHAR(30) NOT NULL, PRENOM VARCHAR(20) NOT NULL, VERSION INTEGER NOT NULL, VILLE VARCHAR(30) NOT NULL, INDEMNITE_ID BIGINT NOT NULL, PRIMARY KEY (ID))
[EL Fine]: 2012-06-22 14:35:03.318--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--DROP TABLE INDEMNITES
[EL Fine]: 2012-06-22 14:35:03.338--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--CREATE TABLE INDEMNITES (ID BIGINT NOT NULL, BASE_HEURE DOUBLE NOT NULL, ENTRETIEN_JOUR DOUBLE NOT NULL, INDEMNITES_CP DOUBLE NOT NULL, INDICE INTEGER NOT NULL UNIQUE, REPAS_JOUR DOUBLE NOT NULL, VERSION INTEGER NOT NULL, PRIMARY KEY (ID))
[EL Fine]: 2012-06-22 14:35:03.418--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--ALTER TABLE EMPLOYES ADD CONSTRAINT FK_EMPLOYES_INDEMNITE_ID FOREIGN KEY (INDEMNITE_ID) REFERENCES INDEMNITES (ID)
[EL Fine]: 2012-06-22 14:35:03.568--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
[EL Fine]: 2012-06-22 14:35:03.578--ServerSession(730572764)--Thread(Thread[main,5,main])--SELECT 1
[EL Warning]: 2012-06-22 14:35:03.578--ServerSession(730572764)--Thread(Thread[main,5,main])--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'sequence' already exists
Error Code: 1050
Call: CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
Query: DataModifyQuery(sql="CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))")
[EL Fine]: 2012-06-22 14:35:03.578--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--DELETE FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN
[EL Fine]: 2012-06-22 14:35:03.638--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--SELECT * FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN
[EL Fine]: 2012-06-22 14:35:03.638--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values (SEQ_GEN, 0)
[EL Config]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--disconnect
[EL Info]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Thread(Thread[main,5,main])--file:/D:/data/istia-1112/netbeans/glassfish/mv-pam/05/mv-pam-jpa-eclipselink/target/classes/_pam-jpa-eclipselinkPU logout successful
[EL Config]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Connection(1543921451)--Thread(Thread[main,5,main])--disconnect
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 3.503s
Finished at: Fri Jun 22 14:35:03 CEST 2012
Final Memory: 8M/153M
  • lines 26-30: connection to the MySQL database,
  • lines 31-34: confirmation that the connection was successful,
  • line 36: deletion of the foreign key from table [EMPLOYES],
  • line 37: deletion of table [COTISATIONS],
  • line 38: creation of table [COTISATIONS]. It is worth noting that the primary key ID does not have the attribute MySQL auto_increment. This means that it is not MySQL that generates the primary key values,
  • line 39: deletion of table [EMPLOYES],
  • line 40: creation of table [EMPLOYES]. Its primary key ID does not have the attribute MySQL auto_increment,
  • line 41: deletion of table [INDEMNITES],
  • line 42: creation of table [INDEMNITES]. Its primary key ID does not have the attribute MySQL auto_increment,
  • line 43: creation of the foreign key from table [EMPLOYES] to table [INDEMNITES],
  • line 44: creation of a table [SEQUENCE]. It will be used to generate the primary keys for the three previous tables,
  • line 47: an exception occurs because this table already existed,
  • lines 51–53: initialization of table [SEQUENCE].

The existence of the generated tables can be verified in Netbeans and [1]:

Therefore, based on the same JPA entities, the Hibernate implementation JPA and EclipseLink do not generate the same tables. In the rest of this document, when the JPA implementation used is:

  • Hibernate, the [dbpam_hibernate] database will be used;
  • EclipseLink, the [dbpam_eclipselink] database will be used.

5.6.3. Work to be done

Following the same procedure as before,

  1. create and test a [mv-pam-jpa-hibernate-oracle] project using a JPA Hibernate implementation and a SGBD Oracle implementation,
  2. create and test a [mv-pam-jpa-hibernate-mssql] project using a JPA Hibernate implementation and a SGBD SQL server,
  3. create and test a [mv-pam-jpa-eclipselink-oracle] project using a JPA EclipseLink implementation and an Oracle SGBD,
  4. create and test a [mv-pam-jpa-eclipselink-mssql] project using a JPA implementation, EclipseLink, and a SGBD, SQL server,

5.6.4. Lazy or Eager?

Let’s return to a possible definition of the [Employe] entity:


package jpa;
 
...
 
@Entity
@Table(name="EMPLOYES")
public class Employe implements Serializable {
 
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  @Version
  @Column(name="VERSION",nullable=false)
  private int version;
  @Column(name="SS", nullable=false, unique=true, length=15)
  private String SS;
  @Column(name="NOM", nullable=false, length=30)
  private String nom;
  @Column(name="PRENOM", nullable=false, length=20)
  private String prenom;
  @Column(name="ADRESSE", nullable=false, length=50)
  private String adresse;
  @Column(name="VILLE", nullable=false, length=30)
  private String ville;
  @Column(name="CP", nullable=false, length=5)
  private String codePostal;
  @ManyToOne(fetch= FetchType.LAZY)
  @JoinColumn(name="INDEMNITE_ID",nullable=false)
  private Indemnite indemnite;
  ...
}

Lines 27–29 define the foreign key from table [EMPLOYES] to table [INDEMNITES]. The fetch attribute on line 27 defines the retrieval strategy for the indemnite field on line 29. There are two modes:

  • FetchType.LAZY: when an employee is searched for, the corresponding allowance is not returned. It will be returned when the field [Employe].indemnite is referenced for the first time.
  • FetchType.EAGER: When an employee is searched for, the corresponding allowance is returned. This is the default mode when no mode is specified.

To understand the purpose of option and FetchType.LAZY, consider the following example. A list of employees without their allowances is displayed on a web page with a [Details] link. Clicking this link then displays the benefits for the selected employee. We see that:

  • to display the first page, we do not need the employees and their allowances. The FetchType.LAZY mode is therefore suitable;
  • to display the second page with details, an additional query must be made to the database to retrieve the selected employee’s allowances.

The FetchType.LAZY mode avoids retrieving too much data that the application does not need immediately. Let’s look at an example.

The project [mv-pam-jpa-hibernate] is duplicated:

  • in [1], the project is copied;
  • in [2], we specify the folder for the copy, and in [3], its name,
  • in [4], the new project has the same name as the old one. We change this:
  • In [1], rename the project,
  • in [2], we rename the project and its artifactId,
  • to [3], the new project.

We modify the program [Main.java] as follows:


package main;
 
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import jpa.Employe;
 
public class Main {
 
  // the JPQL query below brings back an employee
  // the foreign key [Employe].indemnite is in FetchType.LAZY
  public static void main(String[] args) {
    // creating the Entity Manager is enough to build the JPA layer
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("pam-jpa-hibernatePU");
    // first attempt
    EntityManager em = emf.createEntityManager();
    Employe employe = (Employe) em.createQuery("select e from Employe e where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
    em.close();
    // we display the employee
    try {
      System.out.println(employe);
    } catch (Exception ex) {
      System.out.println(ex);
    }
    // second test
    em = emf.createEntityManager();
    employe = (Employe) em.createQuery("select e from Employe e left join fetch e.indemnite where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
    // free up resources
    em.close();
    // we display the employee
    try {
      System.out.println(employe);
    } catch (Exception ex) {
      System.out.println(ex);
    }
    // resource release
    emf.close();
  }
}
  • line 15: we create the EntityManagerFactory from the JPA layer,
  • line 17: we obtain the EntityManager, which allows us to interact with the JPA layer,
  • line 18: we request the employee named Jouveinal,
  • line 19: we close EntityManager. This closes the persistence context.
  • Line 22: We display the retrieved employee.

The [Employe] class is as follows:


package jpa;
 
...
 
@Entity
@Table(name="EMPLOYES")
public class Employe implements Serializable {
 
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  @Version
  @Column(name="VERSION",nullable=false)
  private int version;
  @Column(name="SS", nullable=false, unique=true, length=15)
  private String SS;
  @Column(name="NOM", nullable=false, length=30)
  private String nom;
  @Column(name="PRENOM", nullable=false, length=20)
  private String prenom;
  @Column(name="ADRESSE", nullable=false, length=50)
  private String adresse;
  @Column(name="VILLE", nullable=false, length=30)
  private String ville;
  @Column(name="CP", nullable=false, length=5)
  private String codePostal;
  @ManyToOne(fetch= FetchType.LAZY)
  @JoinColumn(name="INDEMNITE_ID",nullable=false)
  private Indemnite indemnite;
 
 
  /**
   * Returns a string representation of the object.  This implementation constructs
   * that representation based on the id fields.
   * @return a string representation of the object.
   */
  @Override
  public String toString() {
    return "jpa.Employe[id=" + getId()
    + ",version="+getVersion()
    +",SS="+getSS()
    + ",nom="+getNom()
    + ",prenom="+getPrenom()
    + ",adresse="+getAdresse()
    +",ville="+getVille()
    +",code postal="+getCodePostal()
    +",indice="+getIndemnite().getIndice()
    +"]";
  }
  ...
}
  • line 27: the indemnite field is retrieved in LAZY mode,
  • line 47: uses the indemnite field. If the toString method is called before the indemnite field has been restored, it will be restored at that point. Unless the persistence context has been closed, as in the example.

Let’s return to the code for [Main]:

  • lines 21–25: an exception should occur. This is because the toString method will be called. It will use the indemnite field. This field will be looked up. Since the persistence context has been closed, the returned [Employe] entity no longer exists, hence the exception.
  • Line 27: A new EntityManager is created,
  • line 28: we query for employee Jouveinal by explicitly requesting the corresponding allowance in the JPQL query. This explicit request is necessary because the search mode for this allowance is LAZY,
  • line 30: close EntityManager,
  • lines 32–36: we display the employee again. There should be no exceptions.

To run the project, you need a populated database. You will create it by following the steps in section 5.5. Additionally, the file [persistence.xml] must be modified:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
    </properties>
  </persistence-unit>
</persistence>
  • We removed the option that created the tables. The database here already exists and is populated,
  • we have removed the options that caused Hibernate to log the SQL commands it sent to the database.

Running the project produces the following two messages in the console:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session
jpa.Employe[id=31,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]
  • Line 1: The exception that occurred when attempting to retrieve the missing compensation while the session was closed. We can see that the compensation was not retrieved due to the LAZY mode,
  • line 2: the employee with their allowance obtained via a query that bypassed the LAZY mode.

5.6.5. Work to be done

Following a procedure similar to the one just described, create a project named [mv-pam-pa-eclipselink-lazy] that demonstrates the behavior of EclipseLink when compared to mode LAZY.

The following results are obtained:

jpa.Employe[id=453,version=1,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]
jpa.Employe[id=453,version=1,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]

In LAZY mode, both queries returned the compensation along with the employee. When inquiring about net regarding this anomaly, we discover that the annotation [FetchType.LAZY] (line 1):


  @ManyToOne(fetch= FetchType.LAZY)
  @JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;

is not a requirement but a suggestion. The implementer JPA is not required to follow it. We can see, therefore, that the code sometimes becomes dependent on the JPA implementation used. It is possible to configure EclipseLink to behave as expected for the LAZY mode.

5.6.6. Moving forward

The architecture of the application to be built is as follows:

For the rest of this document, we will duplicate the Maven project [mv-pam-jpa-hibernate] into the project [mv-pam-spring-hibernate] [1, 2, 3]:

  • then we will rename the new project to [4, 5, 6].

We will change the dependencies of the new project. The [pom.xml] file becomes the following:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>istia.st</groupId>
  <artifactId>mv-pam-spring-hibernate</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
 
  <name>mv-pam-spring-hibernate</name>
  <url>http://maven.apache.org</url>
  <repositories>
    <repository>
      <url>http://repo1.maven.org/maven2/</url>
      <id>swing-layout</id>
      <layout>default</layout>
      <name>Repository for library Library[swing-layout]</name>
    </repository>
  </repositories>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
 
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>commons-pool</groupId>
      <artifactId>commons-pool</artifactId>
      <version>1.6</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.1.2</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
    <dependency>
      <groupId>org.swinglabs</groupId>
      <artifactId>swing-layout</artifactId>
      <version>1.0.3</version>
    </dependency>
  </dependencies>
</project>
  • lines 25–31: the dependency for the JUnit tests,
  • lines 32–41: the dependencies for the Apache connection pool DBCP,
  • lines 42–65: dependencies for the Spring framework,
  • lines 67-71: dependencies for the JPA / Hibernate implementation,
  • lines 72-76: the dependency for the JDBC driver of MySQL,
  • lines 77-81: the dependency for the Swing interface. This is automatically added by Netbeans when a Swing interface is added to the project.

In addition, the two databases MySQL will be generated:

  • [dbpam_hibernate] from the script [dbpam_hibernate.sql],
  • [dbpam_eclipselink] from the [dbpam_eclipselink.sql] script,

5.7. The [metier] and [DAO] layer interfaces

Let’s return to the application architecture:

In the architecture above, what interface should the [DAO] layer provide to the [metier] layer, and what interface should the [metier] layer provide to the [ui] layer? A first approach to defining the interfaces of the different layers is to examine the application’s various use cases. Here we have two, depending on the chosen user interface: console or graphical form.

Let’s examine how the console application is used:

dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150 20

Valeurs saisies :
N° de sécurité sociale de l'employee: 254104940426058
Nombre d'hours worked: 150
Nombre de jours travaillés : 20

Informations Employé :
Nom : Jouveinal
...

Informations Cotisations :
CSGRDS : 3.49 %
...

Informations Indemnités :
...

Informations Salaire :
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnités d'maintenance: 42.0 euro
Indemnités de repas : 62.0 euro
Salaire net : 368.77 euro

The app receives three pieces of information from the user (see line 1 above)

  • the child care provider’s Social Security number
  • the number of hours worked in the month
  • the number of days worked in the month

Based on this information and other data stored in configuration files, the application displays the following information:

  • lines 4–6: the entered values
  • lines 8-10: information related to the employee whose Social Security number was provided
  • lines 12–14: the rates for the various social security contributions
  • lines 16–17: the various allowances paid to the child care provider
  • lines 19–24: items on the child care provider’s pay stub

A certain amount of information must be provided by layer [metier] to layer [ui]:

  1. information related to a child care provider identified by their social security number. This information is found in table [EMPLOYES]. This allows lines 6–8 to be displayed.
  2. the amounts of the various social security rates to be deducted from the gross salary. This information is found in table [COTISATIONS]. This allows lines 10–12 to be displayed.
  3. the amounts of the various allowances related to the role of child care provider. This information is found in table [INDEMNITES]. This displays lines 14–15.
  4. the components of the salary displayed in lines 18–22.

Based on this, we could decide on an initial mapping from the [IMetier] interface presented by the [metier] layer to the [ui] layer:

1
2
3
4
5
6
package metier;

public interface IMetier {
   // get your payslip
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
}
  • line 1: the elements of the [metier] layer are placed in the [metier] package
  • line 5: the [ calculerFeuilleSalaire ] method takes as parameters the three pieces of information acquired by the [ui] layer and returns an object of type [FeuilleSalaire] containing the information that the [ui] layer will display on the console. The [FeuilleSalaire] class could be as follows:
package metier;

import jpa.Cotisation;
import jpa.Employe;
import jpa.Indemnite;

public class FeuilleSalaire {
   // private fields
  private Employe employe;
  private Cotisation cotisation;
  private ElementsSalaire elementsSalaire;

  ...
}
  • line 9: the employee covered by the pay slip - information #1 displayed by layer [ui]
  • line 10: the various contribution rates - information no. 2 displayed by layer [ui]
  • line 11: the various allowances linked to the employee's index - information #3 displayed by layer [ui]
  • line 12: the components of their salary - information #4 displayed by layer [ui]

A second use case for layer [métier] appears in the graphical interface:

As shown above, the [1, 2] drop-down list displays all employees. This list must be requested from the [métier] layer. The interface for this layer then evolves as follows:

package metier;

import java.util.List;
import jpa.Employe;

public interface IMetier {
   // get your payslip
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // list of employees
  public List<Employe> findAllEmployes();
}
  • line [10]: the method that will allow the [ui] layer to request the list of all employees from the [métier] layer.

The [metier] layer can only initialize the [Employe, Cotisation, Indemnite] fields of the [FeuilleSalaire] object above by querying the [DAO] layer, since this information is stored in the database tables. The same applies to obtaining the list of all employees. We could create a single [DAO] interface managing access to the three [Employe, Cotisation, Indemnite] entities. Instead, we have decided here to create a [DAO] interface per entity.

The interface [DAO] for accessing the entities [Cotisation] in the table [COTISATIONS] will be as follows:

package dao;

import java.util.List;
import jpa.Cotisation;

public interface ICotisationDao {
       // create a new contribution
  public Cotisation create(Cotisation cotisation);
       // modify an existing contribution
  public Cotisation edit(Cotisation cotisation);
       // delete an existing contribution
  public void destroy(Cotisation cotisation);
       // search for a specific contribution
  public Cotisation find(Long id);
       // get all objects Contribution
  public List<Cotisation> findAll();

}
  • Line 6: The [ICotisationDao] interface manages access to the [Cotisation] entity and thus to the [COTISATIONS] table in the database. Our application only needs the [findAll] method from line 16, which retrieves all the content of the [COTISATIONS] table. Here, we wanted to consider a more general case where all CRUD operations (Create, Read, Update, Delete) are performed on the entity.
  • Line 8: The [create] method creates a new [Cotisation] entity
  • Line 10: The [edit] method modifies an existing [Cotisation] entity
  • Line 12: The [destroy] method deletes an existing [Cotisation] entity
  • line 14: the [find] method retrieves an existing [Cotisation] entity via its id identifier
  • line 16: the [findAll] method returns a list of all existing [Cotisation] entities

Let’s take a closer look at the signature of the [create] method:

       // create a new contribution
Cotisation create(Cotisation cotisation);

The create method has a contribution parameter of type Contribution. The contribution parameter must be persisted, c.a.d. Here, it is stored in the [COTISATIONS] table. Before this persistence, the contribution parameter has an identifier id with no value. After persistence, the field id has a value that is the primary key of the record added to the table [COTISATIONS]. The contribution parameter is therefore an input/output parameter of the create method. It does not seem necessary for the create method to additionally return the contribution parameter as a result. Since the calling method holds a reference to the object [Cotisation cotisation], if that object is modified, it has access to the modified object because it holds a reference to it. It can therefore determine the value that the create method assigned to the id field of the [Cotisation cotisation] object. The method signature could therefore be simplified to:

       // create a new contribution
void create(Cotisation cotisation);

When writing an interface, it is important to remember that it can be used in two different contexts: local and remote . In the local context, the calling method and the called method are executed within the same JVM:

If the [metier] layer calls the create method of the [DAO] layer, it does indeed have a reference to the [Cotisation cotisation] parameter that it passes to the method.

In the remote context, the calling method and the called method are executed in different JVMs:

Above, layer [metier] runs in JVM 1 and layer [DAO] runs in JVM 2 on two different machines. The two layers do not communicate directly. Between them is an intermediate layer that we will call the [1] communication layer. This layer consists of a transmission layer [2] and a reception layer [3]. The developer generally does not have to write these communication layers. They are generated automatically by software tools. The [metier] layer is written as if it were running in the same JVM as the [DAO] layer. Therefore, there is no code modification.

The communication mechanism between the [metier] layer and the [DAO] layer is as follows:

  • The [metier] layer calls the create method of the [DAO] layer, passing it the [Cotisation cotisation1] parameter
  • this parameter is actually passed to the transmission layer [2]. This layer will transmit the value of the cotisation1 parameter over the network, not its reference. The exact format of this value depends on the communication protocol used.
  • The receiving layer [3] will retrieve this value and use it to reconstruct a [Cotisation cotisation2] object that mirrors the initial parameter sent by the [metier] layer. We now have two identical objects (in terms of content) in two different JVM layers: contribution1 and contribution2.
  • The receiving layer will pass the contribution2 object to the create method of the [DAO] layer, which will persist it in the database. After this operation, the id field of the contribution2 object has been initialized with the primary key of the record added to the [COTISATIONS] table. This is not the case for the cotisation1 object, to which the [metier] layer has a reference. If we want the [metier] layer to have a reference to the cotisation2 object, we must send it to the layer. Therefore, we need to change the signature of the create method of the [DAO] layer:
       // create a new contribution
Cotisation create(Cotisation cotisation);
  • With this new signature, the create method will return the persisted object contribution2 as its result. This result is returned to the receiving layer [3], which had called the layer [DAO]. This layer will return the value (not the reference) of contribution2 to the sending layer [2].
  • The sending layer [2] will retrieve this value and use it to reconstruct a [Cotisation cotisation3] object that is an image of the result returned by the create method of the [DAO] layer.
  • The [Cotisation cotisation3] object is returned to the method of the [metier] layer, whose call to the create method of the [DAO] layer had initiated this entire mechanism. The [metier] layer can therefore determine the primary key value assigned to the [Cotisation cotisation1] object for which it requested persistence: this is the value of the id field in cotisation3.

The previous architecture is not the most common. The layers [metier] and [DAO] are more frequently found within the same JVM:

In this architecture, it is the methods of the [metier] layer that must return results, not those of the [DAO] layer. However, the following signature of the create method of the [DAO] layer:

       // create a new contribution
Cotisation create(Cotisation cotisation);

allows us to avoid making assumptions about the actual architecture in place. Using signatures that will work regardless of the chosen architecture—whether local or remote—means that if a called method modifies some of its parameters:

  • these must also be part of the result of the called method
  • the calling method must use the result of the called method and not the references to the modified parameters that it passed to the called method.

This allows us to transition from a local architecture to a remote architecture without modifying the code. Let’s revisit, in this light, the [ICotisationDao] interface:

package dao;

import java.util.List;
import jpa.Cotisation;

public interface ICotisationDao {
       // create a new contribution
  public Cotisation create(Cotisation cotisation);
       // modify an existing contribution
  public Cotisation edit(Cotisation cotisation);
       // delete an existing contribution
  public void destroy(Cotisation cotisation);
       // search for a specific contribution
  public Cotisation find(Long id);
       // get all objects Contribution
  public List<Cotisation> findAll();

}
  • line 8: the case for the create method has been handled
  • Line 10: The edit method uses its parameter [Cotisation cotisation1] to update the record in table [COTISATIONS] that has the same primary key as the contribution object. It returns the contribution2 object, which is a representation of the modified record. The contribution1 parameter is not modified. The method must return contribution2 as the result, whether in a remote or local architecture.
  • Line 12: The destroy method deletes the record from table [COTISATIONS] that has the same primary key as the contribution object passed as a parameter. The contribution object is not modified. Therefore, it does not need to be returned.
  • Line 14: The parameter id of the find method is not modified by the method. It does not need to be included in the result.
  • Line 16: The findAll method has no parameters. Therefore, it does not need to be examined.

Ultimately, only the signature of the create method needs to be adapted to be usable within a remote architecture. The reasoning above applies to the other [DAO] interfaces. We will not repeat it here and will instead use signatures that are usable in both remote and local architectures.

The [DAO] interface for accessing the [Indemnite] entities in the [INDEMNITES] table will be as follows:

package dao;

import java.util.List;
import jpa.Indemnite;

public interface IIndemniteDao {
     // create an Indemnity entity
  public Indemnite create(Indemnite indemnite);
     // modify an Indemnite entity
  public Indemnite edit(Indemnite indemnite);
     // delete an Indemnity entity
  public void destroy(Indemnite indemnite);
     // search for an Indemnite entity via its identifier
  public Indemnite find(Long id);
     // get all Indemnite entities
  public List<Indemnite> findAll();

}
  • Line 6: The [IIndemniteDao] interface manages access to the [Indemnite] entity and thus to the [INDEMNITES] table in the database. Our application only needs the [findAll] method from line 16, which retrieves all the content of the [INDEMNITES] table. Here, we wanted to consider a more general case where all CRUD operations (Create, Read, Update, Delete) are performed on the entity.
  • Line 8: The [create] method creates a new [Indemnite] entity
  • Line 10: The [edit] method modifies an existing [Indemnite] entity
  • Line 12: The [destroy] method deletes an existing [Indemnite] entity
  • line 14: the [find] method retrieves an existing [Indemnite] entity via its identifier id
  • line 16: the [findAll] method returns a list of all existing [Indemnite] entities

The [DAO] interface for accessing [Employe] entities in the [EMPLOYES] table will be as follows:

package dao;

import java.util.List;
import jpa.Employe;

public interface IEmployeDao {
     // create a new Employ entity
  public Employe create(Employe employe);
     // modify an existing Employe entity
  public Employe edit(Employe employe);
     // delete an Employ entity
  public void destroy(Employe employe);
     // search for an Employe entity via its id identifier
  public Employe find(Long id);
     // search for an Employe entity via its SS number
  public Employe find(String SS);
     // get all Employe entities
  public List<Employe> findAll();
}
  • Line 6: The [IEmployeDao] interface manages access to the [Employe] entity and, consequently, to the [EMPLOYES] table in the database. Our application only needs the [findAll] method on line 16, which retrieves all the content of the [EMPLOYES] table. Here, we wanted to consider a more general case where all CRUD operations (Create, Read, Update, Delete) are performed on the entity.
  • Line 8: The [create] method creates a new [Employe] entity
  • Line 10: The [edit] method modifies an existing [Employe] entity
  • Line 12: The [destroy] method deletes an existing [Employe] entity
  • line 14: the [find] method retrieves an existing [Employe] entity via its identifier id
  • line 16: the [find(String SS)] method allows you to retrieve an existing [Employe] entity via its ID SS. We have seen that this method is necessary for the console application.
  • Line 18: The [findAll] method returns a list of all existing [Employe] entities. We have seen that this method is required by the graphical application.

5.8. The [PamException] class

The [DAO] layer will work with the Java API and JDBC classes. This API throws controlled exceptions of type [SQLException], which have two drawbacks:

  • they bloat the code, which must handle these exceptions using try/catch blocks.
  • they must be declared in the method signatures of the [IDao] interface using "throws SQLException". This prevents classes that throw a checked exception of a type other than [SQLException] from implementing this interface.

To resolve this issue, the [DAO] layer will only "propagate" unchecked exceptions of type [PamException].

  • The [JDBC] layer throws exceptions of type [SQLException]
  • The [JPA] layer throws exceptions specific to the JPA implementation used
  • The [DAO] layer throws uncaught exceptions of type [PamException]

This has two consequences:

  • The [metier] layer will not be required to handle exceptions from the [DAO] layer using try/catch blocks. It can simply let them propagate up to the [ui] layer.
  • the methods of the [IDao] interface do not need to include the nature of the [PamException] exception in their signature, which leaves open the possibility of implementing this interface with classes that would throw another type of unhandled exception.

The class [PamException] will be placed in the package [exception] of the project Netbeans:

Its code is as follows:

package exception;

@SuppressWarnings("serial")
public class PamException extends RuntimeException {

   // error code
  private int code;

  public PamException(int code) {
    super();
    this.code = code;
  }

  public PamException(String message, int code) {
    super(message);
    this.code = code;
  }

  public PamException(Throwable cause, int code) {
    super(cause);
    this.code = code;
  }

  public PamException(String message, Throwable cause, int code) {
    super(message, cause);
    this.code = code;
  }

   // getter and setter

  public int getCode() {
    return code;
  }

  public void setCode(int code) {
    this.code = code;
  }

}
  • Line 4: [PamException] derives from [RuntimeException]. It is therefore a type of exception that the compiler does not require us to handle with a try/catch block or include in method signatures. For this reason, [PamException] is not included in the method signatures of the [IDao] interface. This allows this interface to be implemented by a class that throws a different type of exception, provided that the class also derives from [RuntimeException].
  • To distinguish between the errors that may occur, the error code on line 7 is used. The three constructors on lines 14, 19, and 24 are those of the parent class [RuntimeException], to which a parameter has been added: the error code to be assigned to the exception.

From an exception handling perspective, the application will function as follows:

  • The [DAO] layer will encapsulate any exception encountered within a [PamException] exception and re-throw it to the [métier] layer.
  • The [métier] layer will allow exceptions thrown by the [DAO] layer to propagate upward. It will wrap any exception occurring in the [métier] layer into a [PamException] exception and rethrow the latter to the [ui] layer.
  • The [ui] layer intercepts all exceptions that are propagated from the [métier] and [DAO] layers. It will simply display the exception on the console or the graphical interface.

Let’s now examine the implementation of the [DAO] and [metier] layers in turn.

5.9. The [DAO] layer of the [PAM] application

We are working within the following architecture:

5.9.1. Implementation

Recommended reading: Section 3.1.3 of [ref1]


Question: Using the Spring / JPA integration, write the [CotisationDao, IndemniteDao, EmployeDao] classes that implement the [ICotisationDao, IIndemniteDao, IEmployeDao] interfaces. Each class method will catch any exception and wrap it in a [PamException] exception with an error code specific to the caught exception.


The implementation classes will be part of the [dao] package:

  

5.9.2. Configuration

Recommended reading: section 3.1.5 of [ref1]

The DAO / JPA integration is configured by the Spring file [spring-config-dao.xml] and the JPA [persistence.xml] file:


Question: Write the contents of these two files. We will assume that the database used is the MySQL5 [dbpam_hibernate] database generated by the SQL [dbpam_hibernate.sql] script. The Spring file will define the following three beans: employeDao of type EmployeDao, indemniteDao of type IndemniteDao, cotisationDao of type CotisationDao. Furthermore, the JPA implementation used will be Hibernate.


5.9.3. Tests

Recommended reading: sections 3.1.6 and 3.1.7 of [ref1]

Now that the [DAO] layer has been written and configured, we can test it. The test architecture will be as follows:

5.9.4. InitDB

We will create two test programs for the [DAO] layer. These will be placed in the [dao] [2] package of the [Test Packages] [1] branch of the Netbeans project. This branch is not included in the project generated by option [Build project], which ensures that the test programs we place there will not be included in the final .jar of the project.

The classes placed in the [Test Packages] branch have access to the classes present in the [Source Packages] branch as well as the project’s class libraries. If the tests require libraries other than those in the project, these must be declared in the [Test Libraries] and [2] branches.

The test classes use the unit testing tool JUnit:

  • [JUnitInitDB] does not perform any tests. It populates the database with a few records and then displays them on the console.
  • [JUnitDao] performs a series of tests and verifies the results.

The skeleton of the [JUnitInitDB] class is as follows:

package dao;

...

public class JUnitInitDB {

  private IEmployeDao employeDao = null;
  private ICotisationDao cotisationDao = null;
  private IIndemniteDao indemniteDao = null;

  @BeforeClass
  public void init(){
     // application configuration
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
     // layers DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
  }

  @Test
  public void initDB(){
     // fill the base
...
     // displays the contents of the database
...
  }

  @Before()
  public void clean(){
     // empty the base
...
  }
}
  • The [init] method is executed before the test suite begins (annotation @BeforeClass). It instantiates the [DAO] layer.
  • The [clean] method is executed before each test (annotation @Before). It clears the database.
  • The [initDB] method is a test (annotation @Test). It is the only one. A test must contain Assert.assertCondition assertion statements. Here, there will be none. The method is therefore a dummy test. Its purpose is to populate the database with a few rows and then display the database contents on the console. The create and findAll methods of the [DAO] layers are used here.

Question: Complete the code for the [JUnitInitDB] class. Use the example from section 3.1.6 of [ref1] as a guide. The code will generate the content shown in section 5.1.


5.9.5. Implementati ation of tests

We are now ready to run [InitDB]. We describe the procedure using SGBD and MySQL5:

  • the classes [1], the configuration files [2], and the test classes for the layer [DAO] and [3] are set up,
  • the project is built [4]
  • the class [JUnitInitDB] is executed [5]. SGBD MySQL5 is launched with an existing database [dbpam_hibernate],
  • the [Test Results] and [6] windows indicate that the tests were successful. This message is not significant here, as the [JUnitInitDB] program contains no Assert.assertCondition assertion statements that could cause the test to fail. Nevertheless, it shows that no exceptions occurred during test execution.

The [Output] window contains the execution logs, the Spring logs, and the test logs. The output generated by the [JUnitInitDB] class is as follows:

------------- Standard Output ---------------
Employés ----------------------
jpa.Employe[id=5,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]
jpa.Employe[id=6,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La brûlerie,ville=St Marcel,code postal=49014,indice=1]
Indemnités ----------------------
jpa.Indemnite[id=5,version=0,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités CP=12.0]
jpa.Indemnite[id=6,version=0,indice=2,base heure=2.1,entretien jour2.1,repas jour=3.1,indemnités CP=15.0]
Cotisations ----------------------
jpa.Cotisation[id=3,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88]
------------- ---------------- ---------------

The [EMPLOYES, INDEMNITES, COTISATIONS] tables have been populated. This can be verified by connecting to the [dbpam_hibernate] database using Netbeans.

  • In [1], on the [services] tab, you can view the data from table [employes] of connection [dbpam_hibernate] [2],
  • in [3], the result.

5.9.6. JUnitDao

We will now look at a second test class, [JUnitDao]:

The class skeleton will be as follows:

package dao;

import exception.PamException;
...

public class JUnitDao {

// layers DAO
  static private IEmployeDao employeDao;
  static private IIndemniteDao indemniteDao;
  static private ICotisationDao cotisationDao;

  @BeforeClass
  public static void init() {
     // log
    log("init");
     // application configuration
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
     // layers DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
  }

  @AfterClass
  public static void terminate() {
  }

  @Before()
  public void clean() {
...
  }

   // logs
  private static void log(String message) {
    System.out.println("----------- " + message);
  }

   // tests
  @Test
  public void test01() {
    log("test01");
     // cotisations list
    List<Cotisation> cotisations = cotisationDao.findAll();
    int nbCotisations = cotisations.size();
     // we add a contribution
    Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
     // on demand
    cotisation = cotisationDao.find(cotisation.getId());
     // check
    Assert.assertNotNull(cotisation);
    Assert.assertEquals(3.49, cotisation.getCsgrds(), 1e-6);
    Assert.assertEquals(6.15, cotisation.getCsgd(), 1e-6);
    Assert.assertEquals(9.39, cotisation.getSecu(), 1e-6);
    Assert.assertEquals(7.88, cotisation.getRetraite(), 1e-6);
     // we modify it
    cotisation.setCsgrds(-1);
    cotisation.setCsgd(-1);
    cotisation.setRetraite(-1);
    cotisation.setSecu(-1);
    Cotisation cotisation2 = cotisationDao.edit(cotisation);
     // checks
    Assert.assertEquals(cotisation.getVersion() + 1, cotisation2.getVersion());
    Assert.assertEquals(-1, cotisation2.getCsgrds(), 1e-6);
    Assert.assertEquals(-1, cotisation2.getCsgd(), 1e-6);
    Assert.assertEquals(-1, cotisation2.getRetraite(), 1e-6);
    Assert.assertEquals(-1, cotisation2.getSecu(), 1e-6);
     // the modified element is requested
    Cotisation cotisation3 = cotisationDao.find(cotisation2.getId());
     // checks
    Assert.assertEquals(cotisation3.getVersion(), cotisation2.getVersion());
    Assert.assertEquals(-1, cotisation3.getCsgrds(), 1e-6);
    Assert.assertEquals(-1, cotisation3.getCsgd(), 1e-6);
    Assert.assertEquals(-1, cotisation3.getRetraite(), 1e-6);
    Assert.assertEquals(-1, cotisation3.getSecu(), 1e-6);
     // we delete the
    cotisationDao.destroy(cotisation3);
     // checks
    Cotisation cotisation4 = cotisationDao.find(cotisation3.getId());
    Assert.assertNull(cotisation4);
    cotisations = cotisationDao.findAll();
    Assert.assertEquals(nbCotisations, cotisations.size());
  }


  @Test
  public void test02(){
    log("test02");
     // we ask for the list of allowances
...
     // we add an Indemnite indemnite
..
     // fetch indemnity from base - recover indemnity1
..
     // we check that indemnity1 = indemnity
...
     // modify the indemnity obtained and persist the modification in BD. The result is indemnity2
 ...
     // check the version of indemnite2
    ...
     // search for indemnity2 in base - obtain indemnity3
    ...
     // check that compensation3 = compensation2
    ...
     // delete indemnite3 image in base
    ...
     // we'll search for indemnite3 in base
    ...
     // check that a null reference has been obtained
 ...
  }

  @Test
  public void test03(){
    log("test03");
     // we repeat a test similar to the previous ones for Employe
 ...
  }

  @Test
  public void test04(){
    log("test04");
     // test method [IEmployeDao].find(String SS)
     // first with an existing employee
     // then with a non-existent employee
...
  }

  @Test
  public void test05(){
    log("test05");
     // we create two allowances with the same index
     // violates index uniqueness constraint
     // check for the occurrence of a PamException exception
     // and has the expected error no
...
  }

  @Test
  public void test06(){
    log("test06");
     // create two employees with the same SS number
     // violates the uniqueness constraint on n° SS
     // check for the occurrence of a PamException exception
     // and has the expected error no
...

  }

  @Test
  public void test07(){
    log("test07");
    // on crée deux employés avec le même n° SS, le 1er avec create, le 2ème avec edit
     // violates the uniqueness constraint on n° SS
     // check for the occurrence of a PamException exception
     // and has the expected error no
...
  }

  @Test
  public void test08(){
    log("test08");
     // deleting a non-existent employee does not trigger an exception
     // it is added and then destroyed - it is checked
...
  }

  @Test
  public void test09(){
    log("test09");
     // modifying an employee without having the correct version should trigger an exception
     // we check it
...
  }

  @Test
  public void test10(){
    log("test10");
     // deleting an employee without the correct version should trigger an exception
     // we check it
...

  }

   // getters and setters
  ...
}

In the previous test class, the database is cleared before each test.


Question: Write the following methods:

1 - test02: based on test01

2 - test03: an employee has a field of type Indemnite. You must therefore create an Indemnite entity and an Employe entity

3 - test04.


By proceeding in the same way as for the test class [JUnitInitDB], we obtain the following results:

  • In [1], run the test class
  • In [2], the test results appear in the [Test Results] window

Let’s trigger an error to see how it is reported on the results page:

  @Test
  public void test01() {
    log("test01");
     // cotisations list
    List<Cotisation> cotisations = cotisationDao.findAll();
    int nbCotisations = cotisations.size();
     // we add a contribution
    Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
     // on demand
    cotisation = cotisationDao.find(cotisation.getId());
     // check
    Assert.assertNotNull(cotisation);
    Assert.assertEquals(0, cotisation.getCsgrds(), 1e-6);
    Assert.assertEquals(6.15, cotisation.getCsgd(), 1e-6);
    Assert.assertEquals(9.39, cotisation.getSecu(), 1e-6);
    Assert.assertEquals(7.88, cotisation.getRetraite(), 1e-6);
     // we modify it
....
}

Line 13: the assertion will cause an error, since the value of Csgrds is 3.49 (line 8). Running the test class yields the following results:

  • The results page [1] now shows that some tests failed.
  • In [2], a summary of the exception that caused the test to fail. It includes the line number in the Java code where the exception occurred.

5.10. The [metier] layer of the [PAM] application

Now that the [DAO] layer has been written, we move on to examining the business layer [2]:

5.10.1. The Java interface [IMetier]

This was described in Section 5.7. We summarize it below:

package metier;

import java.util.List;
import jpa.Employe;

public interface IMetier {
   // get your payslip
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // list of employees
  public List<Employe> findAllEmployes();
}

The [metier] layer will be implemented in the [metier] package:

 

The [metier] package will include, in addition to the [IMetier] interface and its implementation [Metier], two other classes, [FeuilleSalaire] and [ElementsSalaire]. The class [FeuilleSalaire] was briefly introduced in Section 5.7. We will now revisit it.

5.10.2. The [FeuilleSalaire] class

The [calculerFeuilleSalaire] method of the [IMetier] interface returns an object of type [FeuilleSalaire] that represents the various elements of a pay stub. Its definition is as follows:

package metier;

import jpa.Cotisation;
import jpa.Employe;
import jpa.Indemnite;

public class FeuilleSalaire implements Serializable{
   // private fields
  private Employe employe;
  private Cotisation cotisation;
  private ElementsSalaire elementsSalaire;

   // manufacturers
  public FeuilleSalaire() {

  }

  public FeuilleSalaire(Employe employe, Cotisation cotisation, ElementsSalaire elementsSalaire) {
    setEmploye(employe);
    setCotisation(cotisation);
    setElementsSalaire(elementsSalaire);
  }

   // toString
  public String toString() {
    return "[" + employe + "," + cotisation + "," + elementsSalaire + "]";
  }

   // accessors
...  
}
  • line 7: the class implements the Serializable interface because its instances may be exchanged over the network.
  • line 9: the employee covered by the pay stub
  • line 10: the various contribution rates
  • line 11: the various allowances linked to the employee's index
  • line 12: the components of their salary
  • lines 14–22: the class’s two constructors
  • lines 25–27: method [toString] identifying a specific object [FeuilleSalaire]
  • Lines 29 and beyond: public accessors to the class’s private fields

The class [ElementsSalaire] referenced in line 11 of the class [FeuilleSalaire] above contains the elements that make up a pay stub. Its definition is as follows:

package metier;

public class ElementsSalaire implements Serializable{

   // private fields
  private double salaireBase;
  private double cotisationsSociales;
  private double indemnitesEntretien;
  private double indemnitesRepas;
  private double salaireNet;

   // manufacturers
  public ElementsSalaire() {

  }

  public ElementsSalaire(double salaireBase, double cotisationsSociales,
    double indemnitesEntretien, double indemnitesRepas,
    double salaireNet) {
    setSalaireBase(salaireBase);
    setCotisationsSociales(cotisationsSociales);
    setIndemnitesEntretien(indemnitesEntretien);
    setIndemnitesRepas(indemnitesRepas);
  }

   // toString
  public String toString() {
    return "[salaire base=" + salaireBase + ",cotisations sociales=" + cotisationsSociales + ",indemnités d'entretien="
      + indemnitesEntretien + ",indemnités de repas=" + indemnitesRepas + ",salaire net="
      + salaireNet + "]";
  }

   // public accessors
...  
}
  • line 3: the class implements the Serializable interface because it is a component of the FeuilleSalaire class, which must be serializable.
  • line 6: the base salary
  • line 7: the social security contributions paid on this base salary
  • line 8: daily child support payments
  • line 9: daily child meal allowances
  • line 10: the net salary to be paid to the child care provider
  • lines 12–24: class constructors
  • lines 27–31: method [toString] identifying a specific object [ElementsSalaire]
  • lines 34 and beyond: the public accessors to the class's private fields

5.10.3. The implementation class [Metier] of the layer [metier]

The implementation class [Metier] of the layer [metier] could be as follows:

package metier;

...

@Transactional
public class Metier implements IMetier {

   // reference on the [DAO] layer
  private ICotisationDao cotisationDao = null;
  private IEmployeDao employeDao=null;


   // get your payslip
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
    double nbHeuresTravaillées, int nbJoursTravaillés) {
...
  }

   // list of employees
   public List<Employe> findAllEmployes() {
     ...
  }

   // getters and setters
...
 }
  • line 5: the Spring @Transactional annotation ensures that every method in the class will run within a transaction.
  • lines 9-10: references to the [DAO] layers of the [Cotisation, Employe, Indemnite] entities
  • lines 14–17: the [calculerFeuilleSalaire] method
  • lines 20–22: the [findAllEmployes] method
  • line 24 and beyond: the public accessors for the class's private fields

Question: Write the code for the [findAllEmployes] method.



Question: Write the code for the [calculerFeuilleSalaire] method.


Note the following points:

  • The method for calculating salary was explained in Section 5.2.
  • If the parameter [SS] does not correspond to any employee (the layer [DAO] returned a null pointer), the method will throw an exception of type [PamException] with an appropriate error code.

5.10.4. Testing the [metier] layer

We create two test programs:

The [3] test classes are created in the [metier] [2] package of the [Test Packages] [1] branch of the project.

The [JUnitMetier_1] class could be as follows:

package metier;

...

public class JUnitMetier_1 {

// business layer
  private IMetier metier;

  @BeforeClass
  public void init(){
     // log
    log("init");
     // application configuration
     // instantiation layer [metier]
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
    metier = (IMetier) ctx.getBean("metier");
     // layers DAO
    IEmployeDao employeDao=(IEmployeDao) ctx.getBean("employeDao");
    ICotisationDao cotisationDao=(ICotisationDao) ctx.getBean("cotisationDao");
    IIndemniteDao indemniteDao=(IIndemniteDao) ctx.getBean("indemniteDao");
     // empty the base
    for(Employe employe:employeDao.findAll()){
      employeDao.destroy(employe);
    }
    for(Cotisation cotisation:cotisationDao.findAll()){
      cotisationDao.destroy(cotisation);
    }
    for(Indemnite indemnite : indemniteDao.findAll()){
      indemniteDao.destroy(indemnite);
    }
     // fill it
    Indemnite indemnite1=indemniteDao.create(new Indemnite(1,1.93,2,3,12));
    Indemnite indemnite2=indemniteDao.create(new Indemnite(2,2.1,2.1,3.1,15));
    Employe employe2=employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",indemnite2));
    Employe employe1=employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",indemnite1));
    Cotisation cotisation1=cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
  }

   // logs
  private void log(String message) {
    System.out.println("----------- " + message);
  }

   // test
  @Test
  public void test01(){
     // wage sheet calculation
    System.out.println(metier.calculerFeuilleSalaire("260124402111742",30, 5));
    System.out.println(metier.calculerFeuilleSalaire("254104940426058",150, 20));
    try {
      System.out.println(metier.calculerFeuilleSalaire("xx", 150, 20));
    } catch (PamException ex) {
      System.err.println(String.format("PamException[Code=%d, message=%s]",ex.getCode(), ex.getMessage()));
    }
  }
}

There is no Assert.assertCondition assertion in the class. We are simply trying to calculate a few salaries so that we can then verify them manually. The screen output obtained by running the previous class is as follows:

1
2
3
4
5
6
7
Testsuite: metier.JUnitMetier_1
----------- init
....
[jpa.Employe[id=22,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La brûlerie,ville=St Marcel,code postal=49014,indice=1],jpa.Cotisation[id=6,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88],jpa.Indemnite[id=29,version=0,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités CP=12.0],[salaire base=64.85,cotisations sociales=17.45,indemnités d'maintenance=10.0,meal allowance=15.0,salary net=72.4]]
[jpa.Employe[id=21,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2],jpa.Cotisation[id=6,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88],jpa.Indemnite[id=30,version=0,indice=2,base heure=2.1,entretien jour2.1,repas jour=3.1,indemnités CP=15.0],[salaire base=362.25,cotisations sociales=97.48,indemnités d'maintenance=42.0,meal allowance=62.0,salary net=368.77]]
PamException[Code=101, message=L'employee of n°[xx] cannot be found]
Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 3,234 sec
  • line 4: Justine Laverti's pay stub
  • line 5: Marie Jouveinal's pay stub
  • line 6: the exception due to the fact that employee no. SS 'xx' does not exist.

Question: Line 17 of [JUnitMetier_1] uses the Spring bean named metier. Provide the definition of this bean in the file [spring-config-metier-dao.xml].


The class [JUnitMetier_2] could be as follows:

package metier;

...
public class JUnitMetier_2 {

// business layer
  private IMetier metier;

  @BeforeClass
  public void init(){
...
  }

   // logs
  private void log(String message) {
    System.out.println("----------- " + message);
  }

   // test
  @Test
  public void test01(){
...
  }
}

The class [JUnitMetier_2] is a copy of the class [JUnitMetier_1], except that this time, assertions have been added to the test01 method.


Question: Write the test01 method.


When executing the [JUnitMetier_2] class, the following results are obtained if everything goes well:

Image

5.11. The [ui] layer of the [PAM] application – version console

Now that the [metier] layer has been written, we still need to write the [ui] and [1] layers:

We will create two different implementations of the [ui] layer: a version console version and a version Swing GUI version:

5.11.1. The [ui.console.Main] class

We will first focus on the console application implemented by the [ui.console.Main] class above. Its operation was described in Section 5.3. The skeleton of the [Main] class could be as follows:

package ui.console;

import exception.PamException;
import metier.FeuilleSalaire;
import metier.IMetier;

import java.util.ArrayList;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

  /**
   * @param args
   */
  public static void main(String[] args) {
     // local data
    final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
     // check the number of parameters
...
     // error list
    ArrayList erreurs = new ArrayList();
     // the second parameter must be a real number >0
...
     // mistake?
    if (...) {
      erreurs.add("Le nombre d'heures travaillées [" + args[1]
        + "] est erroné");
    }
     // the third parameter must be an integer >0
...
     // mistake?
    if (...) {
      erreurs.add("Le nombre de jours travaillés [" + args[2]
        + "] est erroné");
    }
     // mistakes?
    if (erreurs.size() != 0) {
      for (int i = 0; i < erreurs.size(); i++) {
        System.err.println(erreurs.get(i));
      }
      return;
    }
     // it's OK - we can ask for the payslip
    FeuilleSalaire feuilleSalaire = null;
    try {
       // instantiation layer [metier]
      ...
       // wage sheet calculation
      ...
    } catch (PamException ex) {
      System.err.println("L'erreur suivante s'est produite : "+ ex.getMessage());
      return;
    } catch (Exception ex) {
      System.err.println("L'erreur suivante s'est produite : "+ ex.toString());
      return;
    }

     // detailed display
    String output = "Valeurs saisies :\n";
    output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
    output += ajouteInfo("Nombre d'heures travaillées", args[1]);
    output += ajouteInfo("Nombre de jours travaillés", args[2]);
    output += ajouteInfo("\nInformations Employé", "");
    output += ajouteInfo("Nom", feuilleSalaire.getEmploye().getNom());
    output += ajouteInfo("Prénom", feuilleSalaire.getEmploye().getPrenom());
    output += ajouteInfo("Adresse", feuilleSalaire.getEmploye().getAdresse());
    output += ajouteInfo("Ville", feuilleSalaire.getEmploye().getVille());
    output += ajouteInfo("Code Postal", feuilleSalaire.getEmploye().getCodePostal());
    output += ajouteInfo("Indice", ""+ feuilleSalaire.getEmploye().getIndemnite().getIndice());
    output += ajouteInfo("\nInformations Cotisations", "");
    output += ajouteInfo("CSGRDS", ""+ feuilleSalaire.getCotisation().getCsgrds() + " %");
    output += ajouteInfo("CSGD", ""+ feuilleSalaire.getCotisation().getCsgd() + " %");
    output += ajouteInfo("Retraite", ""+ feuilleSalaire.getCotisation().getRetraite() + " %");
    output += ajouteInfo("Sécurité sociale", ""+ feuilleSalaire.getCotisation().getSecu() + " %");
    output += ajouteInfo("\nInformations Indemnités", "");
    output += ajouteInfo("Salaire horaire", ""+ feuilleSalaire.getEmploye().getIndemnite().getBaseHeure() + " euro");
    output += ajouteInfo("Entretien/jour", ""+ feuilleSalaire.getEmploye().getIndemnite().getEntretienJour() + " euro");
    output += ajouteInfo("Repas/jour", ""+ feuilleSalaire.getEmploye().getIndemnite().getRepasJour() + " euro");
    output += ajouteInfo("Congés Payés", ""+ feuilleSalaire.getEmploye().getIndemnite().getIndemnitesCP()+ " %");
    output += ajouteInfo("\nInformations Salaire", "");
    output += ajouteInfo("Salaire de base", ""+ feuilleSalaire.getElementsSalaire().getSalaireBase()+ " euro");
    output += ajouteInfo("Cotisations sociales", ""+ feuilleSalaire.getElementsSalaire().getCotisationsSociales()+ " euro");
    output += ajouteInfo("Indemnités d'entretien", ""+ feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+ " euro");
    output += ajouteInfo("Indemnités de repas", ""+ feuilleSalaire.getElementsSalaire().getIndemnitesRepas()+ " euro");
    output += ajouteInfo("Salaire net", ""+ feuilleSalaire.getElementsSalaire().getSalaireNet() + " euro");

    System.out.println(output);
  }

  static String ajouteInfo(String message, String valeur) {
    return message + " : " + valeur + "\n";
  }
}

Question: Complete the code above.


5.11.2. Execution

To execute the [ui.console.Main] class, proceed as follows:

  • In [1], select the project properties,
  • In [2], select the [Run] property of the project,
  • use the [3] button to specify the class (known as the main class) to be executed,
  • select the class [4],
  • The class appears as [5]. This class requires three arguments to run (SS, number of hours worked, number of days worked). These arguments are placed in [6],
  • once this is done, the project [7] can be executed. The previous configuration means that the class [ui.console.Main] will be executed.

The results of the execution are displayed in the [output] window:

5.12. The [ui] layer of the [PAM] – version graphical application

We now implement the [ui] layer with a graphical user interface:

  • in [1], the [PamJFrame] class of the graphical interface
  • in [2]: the graphical interface

5.12.1. A quick tutorial

To create the graphical interface, proceed as follows:

  • [1]: create a new file using the [1] button [New File...]
  • [2]: Select the file category [Swing GUI Forms], c.a.d. graphical forms
  • [3]: Select the type [JFrame Form], an empty form type
  • [5]: Name the form; this name will also be its class
  • [6]: Place the form in a package
  • [8]: the form is added to the project tree
  • [9]: The form is accessible through two perspectives: [Design] and [9], which allow you to design the form’s various components, and [Source] and [10 ci-dessous], which provide access to the form’s Java code. Ultimately, a form is a Java class like any other. The [Design] view is a tool for designing the form. Each time a component is added in [Design] mode, Java code is added in the [Source] view to account for it.
  • [11]: The list of Swing components available for a form is found in the [Palette] window.
  • [12]: The [Inspector] window displays the form component tree. Components with a visual representation will be found in the [JFrame] branch, while the others are in the [Other Components] branch.
  • In [13], we select a [JLabel] component with a single click
  • In [14], we drop it onto the form in [Design] mode
  • In [15], we define the properties of JLabel (text, font).
  • in [16], the resulting output.
  • In [17], we request a preview of the form
  • In [18], the result
  • In [19], the label [JLabel1] has been added to the component tree in the [Inspector] window
  • in [20] and [21]: in the [Source] view of the form, Java code has been added to handle the added JLabel.

A tutorial on building forms with Netbeans is available at url [http://www.netbeans.org/kb/trails/matisse.html].

5.12.2. The [PamJFrame] GUI

We will build the following graphical interface:

  • in [1], the graphical interface
  • in [2], the tree structure of its components: one JLabel and six JPanel containers

JLabel1

JPanel1

JPanel2

JPanel3

JPanel4

JPanel5


Practical exercise: Build the previous graphical interface using the tutorial [http://www.netbeans.org/kb/trails/matisse.html].


5.12.3. Graphical Interface Events

Recommended reading: Chapter [Interfaces graphiques] of [ref2].

We will handle the click on the [jButtonSalaire] button. To create the method for handling this event, proceed as follows:

The click handler for the [JButtonSalaire] button is generated:

1
2
3
    private void jButtonSalaireActionPerformed(java.awt.event.ActionEvent evt) {
       // TODO add your handling code here:
}

The Java code that associates the previous method with the click on the [JButtonSalaire] button is also generated:

1
2
3
4
5
6
    jButtonSalaire.setText("Salaire");
    jButtonSalaire.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        jButtonSalaireActionPerformed(evt);
      }
});

Lines 2–5 specify that the click (event type ActionPerformed) on the button [jButtonSalaire] (line 2) must be handled by the method [jButtonSalaireActionPerformed] (line 4).

We will also handle the [caretUpdate] event (input cursor movement) on the [jTextFieldHT] input field. To create the handler for this event, we proceed as before:

The handler for the [caretUpdate] event on the [jTextFieldHT] input field is generated:

  private void jTextFieldHTCaretUpdate(javax.swing.event.CaretEvent evt) {                                         
 ...
  }

The Java code that associates the previous method with the [caretUpdate] event on the [jTextFieldHT] input field is also generated:

1
2
3
4
5
    jTextFieldHT.addCaretListener(new javax.swing.event.CaretListener() {
      public void caretUpdate(javax.swing.event.CaretEvent evt) {
        jTextFieldHTCaretUpdate(evt);
      }
});

Lines 1–4 indicate that the event [caretUpdate] (line 2) on the button [jTextFieldHT] (line 1) must be handled by the method [ jTextFieldHTCaretUpdate] (line 3).

5.12.4. Initializing the GUI

Let’s return to our application’s architecture:

The [ui] layer requires a reference to the [metier] layer. Recall how this reference was obtained in the console application:

1
2
3
    // instantiation layer [metier]
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
IMetier metier = (IMetier) ctx.getBean("metier");

The method is the same in the GUI application. When the application initializes, the reference [IMetier metier] from line 3 above must also be initialized. The code generated for the GUI is currently as follows:

package ui.swing;

...
public class PamJFrame extends javax.swing.JFrame {

  /** Creates new form PamJFrame */
  public PamJFrame() {
    initComponents();
  }

  /** This method is called from within the constructor to
   * initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is
   * always regenerated by the Form Editor.
   */
   // <editor-fold defaultstate="collapsed" desc=" Generated Code ">
  private void initComponents() {
...
  }// </editor-fold>

  private void jTextFieldHTCaretUpdate(javax.swing.event.CaretEvent evt) {                                         
 ...
  }                                        

  private void jButtonSalaireActionPerformed(java.awt.event.ActionEvent evt) {                                               
...
  }                                              

  public static void main(String args[]) {
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        new PamJFrame().setVisible(true);
      }
    });
  }

   // Variables declaration - do not modify
  private javax.swing.JButton jButtonSalaire;
...
   // End of variables declaration

}
  • lines 29–35: the static method [main] that launches the application
  • line 32: an instance of the [PamJFrame] GUI is created and made visible.
  • lines 7-9: the GUI constructor.
  • Line 8: Call to the [initComponents] method defined on line 17. This method is auto-generated based on the work done in [Design] mode. Do not modify it.
  • Line 21: The method that will handle moving the input cursor in the [jTextFieldHT] field
  • Line 25: the method that will handle the click on the [jButtonSalaire] button

To add our own initializations to the previous code, we can proceed as follows:

 /** Creates new form PamJFrame */
  public PamJFrame() {
    initComponents();
    doMyInit();
  }

...

   // instance variables
  private IMetier metier=null;
  private List<Employe> employes=null;
  private String[] employesCombo=null;
  private double heuresTravaillées=0;

   // proprietary initializations
  public void doMyInit(){
     // init context
    try{
       // instantiation layer [metier]
...
     // list of employees
...
    }catch (PamException ex){
     // the exception message is placed in [jTextAreaStatus]
...
     // return
      return;
    }
     // salary button disabled
...
     // jScrollPane1 hidden
...
     // spinner days worked
    jSpinnerJT.setModel(new SpinnerNumberModel(0,0,31,1));
     // combobox employees
    employesCombo=new String[employes.size()];
    int i=0;
    for(Employe employe : employes){
      employesCombo[i++]=employe.getPrenom()+" "+employe.getNom();
    }
    jComboBoxEmployes.setModel(new DefaultComboBoxModel(employesCombo));
}
  • Line 4: We call a custom method to perform our own initializations. These are defined by the code in lines 10–42

Question: Using the comments as a guide, complete the code for procedure [doMyInit].


5.12.5. Event Handlers


Question: Write the method [jTextFieldHTCaretUpdate]. This method must ensure that if the data in the field [jTextFieldHT] is not a real number >=0, then the button [jButtonSalaire] must be disabled.



Question: Write the method [jButtonSalaireActionPerformed], which must display the pay stub for the employee selected in [jComboBoxEmployes].


5.12.6. Running the GUI

To run the GUI, modify the [Run] configuration of the project:

  • In [1], set the GUI class

The project must be complete with its configuration files (persistence.xml, spring-config-business-dao.xml) and the GUI class. The SGBD target will be launched before running the project.

We are interested in the following architecture where the JPA layer is now implemented by EclipseLink:

5.13.1. The Netbeans project

The new project Netbeans is created by copying the previous project:

  • to [1]: after right-clicking on the Hibernate project, select Copy
  • using the [2] button, select the parent folder for the new project. The folder name appears in [3].
  • in [4], name the new project
  • in [5], the project folder name
  • in [1], the new project has been created. It has the same name as the original,
  • in [2] and [3], we rename it to [mv-pam-spring-eclipselink].

The project must be modified in two places to adapt it to the new layer JPA / EclipseLink:

  1. In [4], the Spring configuration files must be modified. This is because they currently contain the configuration for the JPA layer.
  2. In [5], the project libraries must be modified: the Hibernate libraries must be replaced with those from EclipseLink.

Let’s start with this last point. The [pom.xml] file for the new project will be as follows:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>istia.st</groupId>
  <artifactId>mv-pam-spring-eclipselink</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
 
  <name>mv-pam-spring-eclipselink</name>
  <url>http://maven.apache.org</url>
  <repositories>
    <repository>
      <url>http://repo1.maven.org/maven2/</url>
      <id>swing-layout</id>
      <layout>default</layout>
      <name>Repository for library Library[swing-layout]</name>
    </repository>
    <repository>
      <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
      <id>eclipselink</id>
      <layout>default</layout>
      <name>Repository for library Library[eclipselink]</name>
    </repository>    
  </repositories>
 
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
 
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>commons-pool</groupId>
      <artifactId>commons-pool</artifactId>
      <version>1.6</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>eclipselink</artifactId>
      <version>2.3.0</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>javax.persistence</artifactId>
      <version>2.0.3</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
    <dependency>
      <groupId>org.swinglabs</groupId>
      <artifactId>swing-layout</artifactId>
      <version>1.0.3</version>
    </dependency>
  </dependencies>
</project>
  • lines 73-82: dependencies for the JPA and EclipseLink implementations,
  • lines 19-24: the Maven repository for EclipseLink.

The Spring configuration files must be modified to indicate that the JPA implementation has changed. In both files, only the section configuring the JPA layer changes. For example, in [spring-config-metier-dao.xml] we have:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

   <!-- application layers -->
   <!- - DAO -->
  <bean id="employeDao" class="dao.EmployeDao" />
  <bean id="indemniteDao" class="dao.IndemniteDao" />
  <bean id="cotisationDao" class="dao.CotisationDao" />
   <!-- business -->
  <bean id="metier" class="metier.Metier">
    <property name="employeDao" ref="employeDao"/>
    <property name="indemniteDao" ref="indemniteDao"/>
    <property name="cotisationDao" ref="cotisationDao"/>  
  </bean>

   <!-- configuration JPA -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <!--
          <property name="showSql" value="true" />
    -->
        <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
        <property name="generateDdl" value="true" />
   <!--
        <property name="generateDdl" value="true" />
        -->
      </bean>
    </property>
    <property name="loadTimeWeaver">
      <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
  </bean>

   <!-- data source DBCP -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/dbpam_hibernate" />
    <property name="username" value="root" />
<!--
    <property name="password" value="" />
-->
  </bean>
....  
</beans>

Lines 19–36 configure the JPA layer. The JPA implementation used is Hibernate (line 22). Additionally, the target database is [dbpam_hibernate] (line 41).

To switch to a JPA / EclipseLink implementation, lines 19–35 above are replaced by the lines below:

   <!-- configuration JPA -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
        <!--
          <property name="showSql" value="true" />
  -->
        <property name="databasePlatform" value="org.eclipse.persistence.platform.database.MySQLPlatform" />
        <!--
        <property name="generateDdl" value="true" />
        -->
      </bean>
    </property>
    <property name="loadTimeWeaver">
      <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
</bean>
  • Line 5: The JPA implementation used is EclipseLink
  • line 9: the databasePlatform property sets the target SGBD, here MySQL
  • line 11: to generate the database tables when the JPA layer is instantiated. Here, the property is commented out.
  • Line 7: to display the SQL commands issued by the JPA layer on the console. Here, the property is commented out.

Additionally, the target database becomes [dbpam_eclipselink] (line 4 below):

1
2
3
4
5
6
7
8
9
<!-- data source DBCP -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink" />
    <property name="username" value="root" />
<!--
    <property name="password" value="" />
-->
  </bean>

5.13.2. Running the tests

Before testing the entire application, it is a good idea to verify that the JUnit tests pass with the new JPA implementation. Before running them, we will start by deleting the tables from the database. To do this, in the [Runtime] tab of Netbeans, if necessary, create a connection to the dbpam_eclipselink / MySQL5 database. Once connected to the dbpam_eclipselink / MySQL5 database, you can proceed to delete the tables as shown below:

  • [1]: before deletion
  • [2]: after deletion

Once this is done, you can run the first test on the [DAO]:InitDB layer, which populates the database. To ensure that the tables previously deleted are recreated by the application, you must verify that in the Spring configuration for JPA / EclipseLink, the line:

        <property name="generateDdl" value="true" />

exists and is not commented out.

We build the project and then run the [JUnitInitDB] test :

  • In [1], the InitDB test is executed.
  • In [2], it fails. The exception is thrown by Spring, not by a test that failed.

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [spring-config-DAO.xml]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation.

Spring indicates that there is a configuration issue. The message is unclear. The reason for the exception was explained in section 3.1.9 of [ref1]. For the Spring / EclipseLink configuration to work, the JVM that runs the application must be launched with a specific parameter, a Java agent. The format of this parameter is as follows:

-javaagent:C:\...\spring-agent.jar

[spring-agent.jar] is the Java agent required by JVM to manage the Spring / EclipseLink configuration.

When running a project, it is possible to pass arguments to JVM:

  • in [1], you can access the project properties
  • in [2], the Run properties
  • in [3], you pass the -javaagent parameter to JVM

5.13.3. InitDB

Now we are ready to test again [InitDB]. This time, the results are as follows:

  • In [1], the test was successful
  • In [2], in the [Services] tab, we refresh the connection that Netbeans has with the [dbpam_eclipselink] database
  • in [3], four tables were created
  • In [5], the contents of the [employes] table are displayed
  • in [6], the result.

5.13.4. JUnitDao

The execution of the [JUnitDao] test class may fail, even though it succeeded with the JPA / Hibernate implementation. To understand why, let’s analyze an example.

The method being tested is the following IndemniteDao.create method:

package dao;

...
@Transactional(propagation=Propagation.REQUIRED)
public class IndemniteDao implements IIndemniteDao{

  @PersistenceContext
  private EntityManager em;

   // manufacturer
  public IndemniteDao() {
  }

   // create an allowance
  public Indemnite create(Indemnite indemnite) {
    try{
      em.persist(indemnite);
    }catch(Throwable th){
      throw new PamException(th,31);
    }
    return indemnite;
  }

...
}
  • lines 15–22: the method being tested

The test method is as follows:


package dao;
 
...
 
public class JUnitDao {
 
// layers DAO
  static private IEmployeDao employeDao;
  static private IIndemniteDao indemniteDao;
  static private ICotisationDao cotisationDao;
 
  @BeforeClass
  public static void init() {
    // log
    log("init");
    // application configuration
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-DAO.xml");
    // layers DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
  }
 
  @Before()
  public void clean() {
    // empty the base
    for (Employe employe : employeDao.findAll()) {
      employeDao.destroy(employe);
    }
    for (Cotisation cotisation : cotisationDao.findAll()) {
      cotisationDao.destroy(cotisation);
    }
    for (Indemnite indemnite : indemniteDao.findAll()) {
      indemniteDao.destroy(indemnite);
    }
  }
 
  // logs
  private static void log(String message) {
    System.out.println("----------- " + message);
  }
 
  // tests
….
  @Test
  public void test05() {
    log("test05");
    // we create two allowances with the same index
    // violates index uniqueness constraint
    boolean erreur = true;
    Indemnite indemnite1 = null;
    Indemnite indemnite2 = null;
    Throwable th = null;
    try {
      indemnite1 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
      indemnite2 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
      erreur = false;
    } catch (PamException ex) {
      th = ex;
      // checks
      Assert.assertEquals(31, ex.getCode());
    } catch (Throwable th1) {
      th = th1;
    }
    // checks
    Assert.assertTrue(erreur);
    // exception chain
    System.out.println("Chaîne des exceptions --------------------------------------");
    System.out.println(th.getClass().getName());
    while (th.getCause() != null) {
      th = th.getCause();
      System.out.println(th.getClass().getName());
    }
    // the 1st allowance had to be continued
    Indemnite indemnite = indemniteDao.find(indemnite1.getId());
    // check
    Assert.assertNotNull(indemnite);
    Assert.assertEquals(1, indemnite.getIndice());
    Assert.assertEquals(1.93, indemnite.getBaseHeure(), 1e-6);
    Assert.assertEquals(2, indemnite.getEntretienJour(), 1e-6);
    Assert.assertEquals(3, indemnite.getRepasJour(), 1e-6);
    Assert.assertEquals(12, indemnite.getIndemnitesCP(), 1e-6);
    // the second indemnity should not have persisted
    List<Indemnite> indemnites = indemniteDao.findAll();
    int nbIndemnites = indemnites.size();
    Assert.assertEquals(nbIndemnites, 1);
  }
 
...
}

Question: Explain what the test05 test does and indicate the expected results.


The results obtained with a JPA / Hibernate layer are as follows:

----------- test05
4 juin 2010 16:45:43 org.hibernate.util.JDBCExceptionReporter logExceptions
ATTENTION: SQL Error: 1062, SQLState: 23000
4 juin 2010 16:45:43 org.hibernate.util.JDBCExceptionReporter logExceptions
GRAVE: Duplicate entry '1' for key 2
Chaîne des exceptions --------------------------------------
exception.PamException
javax.persistence.EntityExistsException
org.hibernate.exception.ConstraintViolationException
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException

The test passes, c.a.d. The assertions are verified, and no exceptions are thrown from the test method.


Question: Explain what happened.


The results obtained with a JPA / EclipseLink layer are as follows:

----------- test05
[EL Warning]: 2010-06-04 16:48:26.421--UnitOfWork(749304)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.0.0.v20091127-r5931): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 2
Error Code: 1062
Call: INSERT INTO INDEMNITES (ID, ENTRETIEN_JOUR, REPAS_JOUR, INDICE, INDEMNITES_CP, BASE_HEURE, VERSION) VALUES (?, ?, ?, ?, ?, ?, ?)
        bind => [108, 2.0, 3.0, 1, 12.0, 1.93, 1]
Query: InsertObjectQuery(jpa.Indemnite[id=108,version=1,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités CP=12.0])
Chaîne des exceptions --------------------------------------
org.springframework.transaction.TransactionSystemException
javax.persistence.RollbackException
org.eclipse.persistence.exceptions.DatabaseException
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException

As with Hibernate previously, the test passes, c.a.d. The assertions are verified, and no exceptions are thrown from the test method.


Question: Explain what happened.



Question: From these two examples, what can we conclude about the interchangeability of implementations JPA? Is it complete here?


5.13.5. The other tests

Once the [DAO] layer has been tested and deemed correct, we can move on to testing the [metier] layer and the project itself in its version console or graphical interface. The change in the JPA implementation has no effect on the [metier] and [ui] layers, so if these layers worked with Hibernate, they will work with EclipseLink with a few exceptions: the previous example shows that the exceptions thrown by the [DAO] layers may differ. Thus, when running the test, Spring / JPA / Hibernate throws an exception of type [PamException], an exception specific to the [pam] application, whereas Spring / JPA / EclipseLink throws an exception of type [TransactionSystemException], an exception from the Spring framework. If, in the test use case, the [ui] layer expects a [PamException] exception because it was built with Hibernate, it will no longer work when switching to EclipseLink.

5.13.6. Work to be done


Practical exercise: Retest the console and Swing applications with different SGBD versions: MySQL5, Oracle XE, SQL Server.