Testing Java EE 6 components : JPA 2.0 With EclipseLink
Abstract
This tutorial shows you how to run and unit test a simple JPA Entity with Eclipse Link, Maven, JUnit 4 and DbUnit. It uses MySQL 5 to run in production mode and Derby embedded for testing. This tutorial assumes that you are familiar with Maven, JPA and JUnit.
Introduction
The next version of the java enterprise edition, ie. Java EE 6, will bring simplicity, modularity as well as extensibility. One complaint made to the previous versions was the difficulty to unit test J2EE components. Inspired by lightweight containers, Java EE 6 components (Servlets, Entities, EJBs...) can now be unit tested inside a JVM. No need to run a container in a separate JVM.
This is the first of a serie of tutorials that will show you how to use and unit test Java EE 6 components with their reference implementations.
Because the JPA 2.0 specification is not out there yet, this tutorial will evolve to fit the future changes
JPA 2.0 and Eclipse Link
This first tutorial shows you how to develop a very simple Artist entity, compile it with Maven 2, run it with Eclipse Link and test it with JUnit and DbUnit. Because usually projects have a different production and development environment, this tutorial uses MySQL as well as Derby and its embedded mode.
JPA 2.0 : originated as part of the work of the JSR 220 Expert Group, the Java Persistence API, is a framework that allows developers to manage relational data : object/relational mapping, CRUD operations and a Query Language. Still under construction (expected in Q1 2009), the second version on JPA will also bring queries by criterias, better locking mechanism, richer mapping and much more.
Eclipse Link : open source framework and reference implementation of JPA 2.0. Oracle donated the source code from the TopLink product and development resources to the open source Eclipse Foundation
Derby : Apache Derby is a relational database that can be embedded in Java programs with just a 2 MB disk-space footprint. Derby was previously distributed as IBM Cloudscape. It is currently distributed as Sun Java DB.
MySQL : relational database management system (RDBMS) which runs as a server providing multi-user access to a number of databases.
JUnit : unit testing framework for the Java programming language created by Kent Beck and Erich Gamma.
DbUnit : a JUnit extension to perform unit testing with database-driven programs
This tutorial is also a tribute to Richard Wright, keyboardist of the Pink Floyd. Wish you were still here
Installing the software
To develop, compile, run and test the following code you will need to install the following set of tools :
JDK 1.6 : During the process of installing the JDK, you can install the Derby database (aka JavaDB) if you want. You don't need to because we will only use the Emmbedded mode of Derby. Make sure you have set your JAVA_HOME variable.
Maven 2.0.9 : just unzip the file, set your MAVEN_HOME and add the %MAVEN_HOME%bin to your PATH
MySQL 5 : there are several ways to install MySQL, it's straight forward
This tutorial uses the JDK 1.6_07, MySQL 5.0.67, Derby 10.4.2, Maven 2.0.9, JUnit 4.5 and DBUnit 2.3.0
Once all theses tools are installed, the environment variables setup and your PATH set, make sure you can type the following :
C:\> java -version
java version "1.6.0_07"
Java(TM) SE Runtime Environment (build 1.6.0_07-b06)
Java HotSpot(TM) Client VM (build 10.0-b23, mixed mode, sharing)
C:\> mvn -version
Maven version: 2.0.9
Java version: 1.6.0_07
OS name: "windows xp" version: "5.1" arch: "x86" Family: "windows"
C:\> start mysqld-nt --verbose
C:\> mysql
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.0.67-community-nt MySQL Community Edition (GPL)
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql>
Now that everything is installed, we are ready to start the Ummagumma project.
Kicking off the Ummagumma project with Maven
To kick off the project, let's use the Maven Archetype Plugin which allows to create a project skeleton with Maven. Use the maven-archetype-quickstart like this :
The important parameters of this command are the package name of the project (-DgroupId=org.pink) and its name (-DartifactId=ummagumma).
Maven creates a project (ummagumma) with the following directory structure (package org.pink) :
src/main/java : for the business code. Note that Maven has created a dummy class called App
src/test/java : for unit testing (with a dummy class AppTest)
pom.xml : the Maven Project Object Model (POM) that describes the project being built, its dependencies on other external modules and components
Running with MySQL
Writing an Artist entity with JPA
Now that the skeleton of the project is created, let's add some code. Because the idea of this tutorial is not to explain JPA (check the references if needed), let's write a very simple Artist entity.
Rename the App class to Artist and add the following :
package org.pink;
import javax.persistence.*;
@Entity
@NamedQueries({
@NamedQuery(name = Artist.FIND_ALL, query = "SELECT a FROM Artist a")
})
public class Artist {
publicstaticfinalString FIND_ALL = "findAllArtists";
@Id
@GeneratedValue
privateLong id;
privateString name;
privateString surname;
public Artist() {
}
public Artist(String name, String surname) {
this.name = name;
this.surname = surname;
}
publicLong getId() {return id;}
publicString getName() {return name;}
public void setName(String name) {this.name = name;}
publicString getSurname() {return surname;}
public void setSurname(String surname) {this.surname = surname;}
}
The Artist entity has 3 attributes (id, name and surname), a default constructor, getters, setters and a few JPA annotations (@Entity, @Id and @GeneratedValue).
The @NamedQuery annotation is used to define a query in JPQL (Java Persistent Query Language) that selects all the artist (SELECT a FROM Artist a in JPQL is like select * from Artist is SQL).
Before compiling this class, let's change the pom.xml to add the JPA API dependency (persistence-api). We also need to inform Maven that we are using Java SE 6 (by configuring the maven-compiler-plugin).
That will be enough to compile the Artist class. For that, type the following Maven command : mvn compile. You should see the BUILD SUCCESSFUL message informing you that the compilation was successful.
Now that the entity is developed and compiled, let's write a Main class that will create an Artist object and persist it. For that, we need to initialise the EntityManager through a factory, start a transaction, create an instance of the object (using the new keyword), set the name and surname of the artist, use the EntityManager.persist() method to insert the data in the database, commit the transaction and close the EntityManager.
package org.pink;
import javax.persistence.*;
public class Main {
publicstatic void main(String[] args) {
// Initializes the Entity manager
EntityManagerFactory emf = Persistence.createEntityManagerFactory("floydPU");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
// Creates a new object and persists it
Artist artist = new Artist("Richard", "Wright");
tx.begin();
em.persist(artist);
tx.commit();
em.close();
emf.close();
}
}
As you can see, the Main class needs a persistence unit called floydPU. We need to define this persistence unit in the persistence.xml file under the src/main/resources/META-INF directory. This file, needed by the JPA specification, is important as it links the JPA provider (EclipseLink in our case) and the database (MySQL). It contains all the needed information to connect to MySQL (target, url, JDBC driver, user, password) and informs of the DDL generation mode (create-tables means that tables will be created if they do not exist).
As defined in the persistence.xml file, the floydPU persistence unit needs to connect to the floydDB database using the floydUser user and floydPwd password. We now need to start MySQL and create the database and the user using the mysql command line. Make sure MySQL is up and running (mysqld-nt on windows, mysqld-nt on Linux) and type the following :
C:\> mysql --user=root --password=<your mysql pwd> --execute="create database floydDB"
C:\> mysql --user=root --password=<your mysql pwd> --execute="grant all privileges on floydDB.* to floydUser@localhost identified by 'floydPwd'"
C:\> mysql --user=root --password=<your mysql pwd> --execute="show databases"
+--------------------+
| Database |
+--------------------+
| floyddb |
+--------------------+
By default, the root user has no password. If it's the case, avoid the --password or change it instead of your mysql pwd
As you can see, with the last command show databases, the floydDB database has been listed, meaning it as successfully been created. Now you can log on as floydUser and check if there are any table in the database (using the show tables command).
C:\> mysql --user=floydUser --password=floydPwd
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 15
Server version: 5.0.67-community-nt MySQL Community Edition (GPL)
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> use floydDB
Database changed
mysql> show tables;
Empty set (0.00 sec)
No tables are found. That's because we still need to run our Main class.
Running the Main class
Before running the Main class we need to add the JDBC Driver for MySQL and the Eclipse Link jars to our project. Let's add the following dependencies to the pom.xml file :
Note that we need to define the Eclipse Link Repository as it's not part of the default Maven ones.
Now let's use the Maven clean compile command to compile the code and the exec plugin to execute the Main class.
When you run the Main class, several things happen.
First of all, because in the persistence.xml file we've set the eclipselink.ddl-generation to create-tables, the Artist table has been created
An artist has been inserted into the table (with a automatic generated id)
Now let's use the MySQL commands to display the tables of the database (show tables), the description of the artist table (desc artist) and show its content (select * from artist).
C:\> mysql --user=floydUser --password=floydPwd
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.0.67-community-nt MySQL Community Edition (GPL)
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> use floydDB
Database changed
mysql> show tables;
+-------------------+
| Tables_in_floydDB |
+-------------------+
| ARTIST |
| SEQUENCE |
+-------------------+
mysql> desc ARTIST;
+---------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| ID | bigint(20) | NO | PRI | NULL | |
| NAME | varchar(255) | YES | | NULL | |
| SURNAME | varchar(255) | YES | | NULL | |
+---------+--------------+------+-----+---------+-------+
mysql> select * from ARTIST;
+----+---------+---------+
| ID | NAME | SURNAME |
+----+---------+---------+
| 1 | Richard | Wright |
+----+---------+---------+
Coming back to the code of the Artist entity, you can see that we've only used the @Entity and @Id annotations. That's the minimum a developer has to do to turn a POJO into a persistent object. JPA then does the object/relational mapping on its own, following certain conventions : table name = class name, column name = attribute name. We could customise these defaults using other annotations (such as @Column...) but that's not the topic of this tutorial (check these two good articles for more 12).
Because we've used the @GeneratedValue annotation (to automatically generate an id), Eclipse Link as created a sequence table to store the numbering.
Testing with Derby
We've seen how to write a simple entity and how to persist it in a running MySQL. When you unit test, you don't want to have a database running in a seperate process. So, you can either mock the database calls (using JMock or EasyMock), or use a in-memory database. This is a good compromise as you only have to add a jar file to your classpath, run your tests in the same JVM and use a fully fonctional RDBMS.
That's how we will run our unit tests : using the embedded mode of Derby.
Error: I.T. is never easy. I wanted to have a full in-memory database for testing, meaning no data would be stored in the file system. So I took hsqldb. But it doesn't work with EclipseLink yet. There is a nasty known bug that you can workaround but I didn't want to introduce a hack. So I switched to Derby. But Derby doesn't do full in-memory, it stores a folder and files into disk. Too bad for now. I'll have to wait for EclipseLink 1.1 and move back to hsqldb
Writing the ArtistTest class
Maven uses two different directory structure for the main and the test code. So, under the src/test/java/org/pink directory, rename the AppTest class to ArtistTest.
The first test that we'll do is to persist an artist (using the EntityManager.persist() method) and check that the id has been automatically generated by EclipseLink (assertNotNull). This method, called createArtist is annotated with the JUnit @Test annotation.
To initialize the needed components, let's use the JUnit 4 annotation fixtures. The @BeforeClass and @AfterClass annotations allow you to execute some code only once before and after the class is executed. That's the perfect place to create an EntityManager with the floydPU persistent unit (in the initEntityManager() method) and to close it (closeEntityManager()). The @Before annotation allows you to run code before each test, that's where we initiliaze the transaction (initTransaction() method).
package org.pink;
import javax.persistence.*;
import org.junit.*;
importstatic org.junit.Assert.*;
public class ArtistTest {
privatestatic EntityManagerFactory emf;
privatestatic EntityManager em;
privatestatic EntityTransaction tx;
@BeforeClass
publicstatic void initEntityManager() throws Exception {
emf = Persistence.createEntityManagerFactory("floydPU");
em = emf.createEntityManager();
}
@AfterClass
publicstatic void closeEntityManager() {
em.close();
emf.close();
}
@Before
public void initTransaction() {
tx = em.getTransaction();
}
@Test
public void createArtist() throws Exception {
Artist artist = new Artist("Richard", "Wright");
tx.begin();
em.persist(artist);
tx.commit();
assertNotNull("ID should not be null", artist.getId());
}
}
Type the mvn clean test-compile command to compile the test case.
Running the ArtistTest class
Before running the test case we need to setup a few things.
First of all, we need to create a new persistence.xml file just for unit testing (under src/test/resources/META-INF). This one is set up with Derby :
As you can see, there are different changes between both persistence.xml files. The database target of this one is Derby instead of MySQL and the DDL generation is drop-and-create-tables. That means that the tables will be dropped and then recreated before running our code. This behaviour is suitable for testing as you want to test with a fresh database structure. We also use the special Embedded JDBC driver that allows us to do in-memory database with Derby (EmbeddedDriver). Note also that the logging level is higher : INFO instead of SEVERE. Again, good for testing as you can get more information in case something goes wrong.
Now let's add the mission bits to the Maven pom.xml file, that is the Embedded Derby driver and a more recent version of JUnit (4.5 instead of the default 3.8). Notice that the scope of these dependencies is test.
C:\ummagumma> mvn test
...
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running org.pink.ArtistTest
[EL Info]: ServerSession(13905160)--EclipseLink, version: Eclipse Persistence Services - 1.0.1 (Build 20080905)
[EL Info]: ServerSession(13905160)--file:/C:/ummagumma/target/test-classes/-floydPU login successful
[EL Info]: ServerSession(13905160)--file:/C/ummagumma/target/test-classes/-floydPU logout successful
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.813 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
One test has been successfully executed. Now let's add a more complex one.
Adding DbUnit
Let's add another test. We want to make sure the find all query works. What we can do is a first find all, make sure it return a certain number of artists, add a new artist and check that the find all has inscread by one. Deleting the artist, do another find all, and check that we have the original number of artist. It would look something like that.
@Test
public void findAll() throws Exception {
// Gets all the objects from the database
Query query = em.createNamedQuery(Artist.FIND_ALL);
assertEquals("Should have 5 artists", query.getResultList().size(), 5);
// Creates a new object and persists it
Artist artist = new Artist("Richard", "Wright");
tx.begin();
em.persist(artist);
tx.commit();
// Gets all the objects from the database
assertEquals("Should have 6 artists", query.getResultList().size(), 6);
// Removes the object from the database
tx.begin();
em.remove(artist);
tx.commit();
// Gets all the objects from the database
assertEquals("Should have 5 artists", query.getResultList().size(), 5);
}
This test would fail because the tables are empty and the findAll is expecting 5 artists (remember that, as defined in the persistent.xml file, tables are dropped and created each time). What we need is to initialze the Artist table with data before each test. And that's where DbUnit comes into play.
First of all, DbUnit needs a seed file so it can add data to the database. Let's use an xml dataset file to list all the artists that we want to initialize our tests with (into src/test/resources/artist-dataset.xml) :
The root tag is dataset and then you have a tag that has the name of the table it wants to insert data to (in our case artist). The attributes id, name and surname are stored in the corresponding columns.
To initialize the table with this dataset we need to change the code of the ArtistTest class :
public class ArtistTest {
privatestatic EntityManagerFactory emf;
privatestatic EntityManager em;
privatestatic EntityTransaction tx;
privatestatic IDatabaseConnection connection;
privatestatic IDataSet dataset;
@BeforeClass
publicstatic void initEntityManager() throws Exception {
emf = Persistence.createEntityManagerFactory("floydPU");
em = emf.createEntityManager();
// Initializes DBUnit
connection = new DatabaseConnection(((EntityManagerImpl) (em.getDelegate())).getServerSession().getAccessor().getConnection());
dataset = new FlatXmlDataSet(Thread.currentThread().getContextClassLoader().getResourceAsStream("artist-dataset.xml"));
}
@AfterClass
publicstatic void closeEntityManager() throws SQLException {
em.close();
emf.close();
connection.close();
}
@Before
public void cleanDB() throws Exception {
// Cleans the database with DbUnit
DatabaseOperation.CLEAN_INSERT.execute(connection, dataset);
}
...
Notice that the initEntityManager() method now initializes a database connection and the dataset artist-dataset.xml. The cleanDB() method is called every time before a test to clean and initialize the database with the data from the dataset. This way, we know that before running each test, the Artist table contains 5 artists.
Before running the test we need to add DbUnit to our project. Let's add a new dependency to the pom.xml file :
This time, if you run mvn test it will work. DbUnit initializes the database with the 5 artists described in the artist-dataset.xml file and runs the findAll test.
As you can see of the image, the project has an Artist entity, a Main class to run it and a persistence.xml to connect to MySQL. Under the test directory, an ArtistTest test case and a persistence.xml to use with embedded Derby. The pom.xml allows Maven to do all the tasks of compiling, running and testing.
Conclusion
Unit testing is an important topic in projects. It allows you to safely refactor an application, avoid introduction of non-regression code and help you in debugging and keeping an application robust.
J2EE has suffered from a lack of testability and, clearly, got overtaken by light open source containers. Java EE 6 has taken that into account and you'll see in this series or tutorial, that unit testing an entity or an EBJ is as simple.
Comments: 0