In this section, we will run a simple entity bean test with the Arquillian framework.
We are using the setup of Gradle, Arquillian test framework, Apache Derby,and embedded Glassfish server. The project's Gradle build filebuild.gradle
is as follows:
apply plugin: 'java' apply plugin: 'maven' apply plugin: 'eclipse' apply plugin: 'idea' // Define equivalent Maven GAV coordinates. group = 'com.javaeehandbook.book1' archivesBaseName = 'ch04-jpa-simple' version = '1.0' repositories { mavenLocal() mavenCentral() maven { url 'https://maven.java.net/content/groups/ promoted' } maven { url 'http://repository.jboss.org/nexus/content/groups/public' } } dependencies { compile 'javax:javaee-api:7.0' runtime 'javax:javaee-api:7.0' testCompile 'junit:junit:4.11' testCompile 'org.jboss.arquillian.junit: arquillian-junit-container:1.0.3.Final' testCompile 'org.jboss.arquillian.container: arquillian-glassfish-embedded-3.1:1.0.0.CR4' runtime 'org.glassfish.main.extras: glassfish-embedded-all:4.0.1-b01' } task wrapper(type: Wrapper) { gradleVersion = '1.6' } // Override Gradle defaults - a force an exploded JAR view sourceSets { main { output.resourcesDir = 'build/classes/main' output.classesDir = 'build/classes/main' } test { resources { srcDir 'src/test/resources' } resources { srcDir 'src/test/resources-glassfish-embedded' } output.resourcesDir = 'build/classes/test' output.classesDir = 'build/classes/test' } }
We included a couple of extra dependencies in order to run the unit tests, which are the GlassFish embedded container and the Java EE 7 API. We also have two separate resource folders under the test tree src/test/resources
and src/test/resources-glassfish-embedded
. The reason why we have done this will become apparent.
First, we will define a session bean to act as a mediator to the database as follows:
packageje7hb.basic.jpa; import javax.ejb.Stateful; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; import javax.persistence.Query; import java.util.List; @Stateful public class SpyThrillerBookBean { @PersistenceContext(unitName = "testDatabase", type = PersistenceContextType.EXTENDED) private EntityManager entityManager; public void addBook(SpyThriller movie) throws Exception { entityManager.persist(movie); } public void deleteBook(SpyThriller movie) throws Exception { entityManager.remove(movie); } public List<SpyThriller> getBooks() throws Exception { Query query = entityManager.createQuery("SELECT m from SpyThriller as m"); return query.getResultList(); } }
The code shows a session EJB named SpyThrillerBookBean
of the stateful type, which we have already seen in Chapter 3, Enterprise Java Beans. The stateful session bean requires a javax.persistence.PersistenceContext
object that is injected into the service by the EJB container. The SpyThrillerBookBean
acts as the central processor for the SpyThriller
entity beans.
PersistenceContext
is annotated with an extended conversation scope, which allows the transaction context to live through multiple method requests. This is useful to support work flow in a web application. We also have to explicitly name the persistence unit, with which the persistence context used in this stateful EJB is associated. In this case, the persistent context refers to a unit named testDatabase
.
It is entirely possible to have multiple persistence contexts injected into a session bean.
There are methods on the stateful bean to add a book, addBook()
, remove a book, deleteBook()
, and retrieve a list of the books from the database, getBook()
.
In the listing method getBook()
, we make use of the JPA query facility from the entity manager. We create a query object that uses the Java Persistence API Query Language (JPQL), which looks like native SQL, but it is different.
JPQL is the query language for EJB and is designed to support portable entity bean with retrieving data, updating data, and insertion of new data, and JPQL also supports bulk update and delete operations. In the example code, we are using JPQL with a static String statement. There are other ways to define the JPQL statements with the annotations, and we will come to them in a later section of this chapter.
Let us now examine the unit test for this stateful EJB with the entity bean with Arquillian.
packageje7hb.basic.jpa; // other imports omitted import javax.ejb.EJB; import java.util.List; import static org.junit.Assert.*; @RunWith(Arquillian.class) public class SpyThrillerBookBeanTest { @Deployment public static JavaArchive createDeployment() { JavaArchive jar = ShrinkWrap.create(JavaArchive.class) .addPackage(SpyThrillerBookBean.class.getPackage()) .addAsResource("test-persistence.xml", "META-INF/persistence.xml") .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"); return jar; } @EJBSpyThrillerBookBeanbookService; @Test public void shouldPersistEntities() throws Exception { assertEquals(0, bookService.getBooks().size()); bookService.addBook(new SpyThriller("The Spy Who Came in from the Cold", 1963, "John Le Carre" )); bookService.addBook(new SpyThriller("Casino Royale", 1953, "Ian Fleming")); bookService.addBook(new SpyThriller("The Hunt for Red October ", 1984, "Tom Clancy")); bookService.addBook(new SpyThriller("Bravo Two Zero", 1993, "Andy McNab")); bookService.addBook(new SpyThriller("On Her Majesty's Secret Service", 1963, "Ian Fleming")); List<SpyThriller> list = bookService.getBooks(); assertEquals( 5, list.size()); for (SpyThriller movie : list) { System.out.printf("movie = %s ", movie); bookService.deleteBook(movie); } assertEquals(0, bookService.getBooks().size()); } }
The first difference between this test class, SpyThrillerBookBeanTest
, and the previous integration tests is the method named addPackage()
, which is part of the ShrinkWrap
builder. AddPackage()
registers all of the classes in the supplied Java package for deployment.
The second difference is that we supplied an additional XML configuration for deployment, which is named test-persistence.xml
in the source code. In the build tree, the file becomes the persistence context configuration META-INF/persistence.xml
. We will see this XML configuration in a bit.
The EJB container injects a reference to the stateful bean SpyThrillerBookBean
for the purpose of testing. We could also use @javax.inject.Inject
for future proofing.
In the actual test method, we invoke all the service methods of the stateful bean. First, we assert that the database table should be empty; there should be no SpyThriller
entities in it. Next, we insert a number of entities. We assert again the number of entities that should be stored in the database. After that, we retrieve a list collection of the entities, we iterate over this list in order to delete individual entities. Finally, we assert the database is empty, and if all things are right and good, then the test should pass. However, we need to set up and configure the persistence context.
Let us look at the persistence context file for the unit test as follows:
<persistence version = "2.1"xmlns = "http://xmlns.jcp.org/xml/ns/persistence"xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation = "http://xmlns.jcp.org/xml/ns/persistencehttp://java.sun.com/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name = "testDatabase" transaction-type = "JTA"> <provider> org.eclipse.persistence.jpa.PersistenceProvider </provider> <jta-data-source>jdbc/arquillian</jta-data-source> <properties> <property name = "eclipselink.ddl-generation" value = "drop-and-create-tables"/> <property name = "eclipselink.ddl-generation.output-mode" value = "both"/> <property name = "eclipselink.logging.level.sql" value = "FINE"/> <property name = "eclipselink.logging.parameters" value = "true"/> <property name = "eclipselink.create-ddl-jdbc-file-name" value = "createDDL.jdbc"/> </properties> </persistence-unit> </persistence>
The persistence configuration defines one persistence-unit
XML element and that is the session context, if you will, mapping EntityManager
to the database. It is possible to have more than one persistence-unit
inside the persistence configuration file.
There are two transaction types of persistence units: JTA
and RESOURCE_LOCAL
.
The JTA
variety is the type that can only run inside a Java EE application server; JTA stands for Java Transaction API. These persistence unit types have transactional support by default. The application server, via the EJB and JPA container, will provide a JTA data source to the entity manager.
The RESOURCE_LOCAL
variety is for standalone Java SE applications, and therefore allows JPA to be used outside of a Java EE application server. It is also possible to specify a non-JTA data source inside a Java EE application. In this case, using RESOURCE_LOCAL
in such an application means the data source is not going to be a JTA data source, and therefore will not have transactions.
When we declare a persistence unit with a JTA data source, then we also need to specify the name of the data source, which in this configuration is named jdbc/arquillian
.
A table of the EclipseLink configuration properties is as follows:
Since JPA 2.0, these EclipseLink JDBC configuration properties have been superseded by standard additional properties for the JPA providers.
Appendix B, Java EE 7 Persistence, has a full description of the standard JPA properties.
We configure specific properties for the JPA provider in order to allow it to connect to the database. In this case, we are using the embedded GlassFish server. We need to tell Arquillian how to set up the data source. We set this up in another configuration file named src/test/resources/arquillian.xml
.
<?xml version = "1.0" encoding = "UTF-8"?> <arquillian xmlns = "http://jboss.org/schema/arquillian" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd"> <container qualifier = "glassfish-embedded" default = "true"> <configuration> <property name = "resourcesXml"> src/test/resources-glassfish-embedded/ glassfish-resources.xml </property> </configuration> </container> </arquillian>
Since our example is using Apache Derby, we tell Arquillian where to find the additional setup of the GlassFish resources.
I know it looks a little complicated, this indirection, but it is useful when switching between different application servers in order to make a separate integration test.
Suppose next week, Monday, there was suddenly a requirement to make a test with the JBoss application server, then we would simply make a change, and add in extra configurations in a new folder, presumably named src/test/resources-jboss-as-embedded
. (Of course we would modify SourceSet
accordingly in the build.gradle
file and make sure we create the profile configuration, in order to anticipate every new application server request that arrives in the product backlog, going forward.)
The GlassFish resource configuration file, src/test/resources-glassfish-embedded/glassfish-resources.xml
, is as follows:
<?xml version = "1.0" encoding = "UTF-8"?> <!DOCTYPE resources PUBLIC"-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN""http://glassfish.org/dtds/glassfish-resources_1_5.dtd"> <resources> <jdbc-resource pool-name = "ArquillianEmbeddedDerbyPool"jndi-name = "jdbc/arquillian"/> <jdbc-connection-pool name = "ArquillianEmbeddedDerbyPool"res-type = "javax.sql.DataSource" datasource-classname = "org.apache.derby.jdbc.EmbeddedDataSource" is-isolation-level-guaranteed = "false"> <property name = "databaseName" value = "build/databases/derby"/> <property name = "createDatabase" value = "create"/> </jdbc-connection-pool> </resources>
The XML schema for this file is specific to the GlassFish application server. It configures a JDBC connection pool, and thus the JTA enabled data source. Notice how the Derby database is stored in the build folder, and is compatible with the Gradle build infrastructure build/databases/derby
.
Finally, we throw in a log configuration file so that developers can see the console output. The file, which is specific only to the GlassFish application server, and is named src/test/resources-glassfish-embedded/glassfish-resources.xml
is as follows:
handlers = java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter java.util.logging.SimpleFormatter.format = %4$s: %5$s%n java.util.logging.ConsoleHandler.level = FINEST
As we can see, GlassFish makes use of the standard JDK logging facility.
The output running the Arquillian integration unit test is as follows:
JUnitStarter -ideVersion5 je7hb.basic.jpa.SpyThrillerBookBeanTest sql: --SELECT BOOK_ID, TITLE, AUTHORS, BOOK_YEAR FROM SPY_THRILLER sql: --UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?bind => [50, SEQ_GEN] sql: --SELECT SEQ_COUNT FROM SEQUENCE WHERE SEQ_NAME = ?bind => [SEQ_GEN] sql: --INSERT INTO SPY_THRILLER (BOOK_ID, TITLE, AUTHORS, BOOK_YEAR) VALUES (?, ?, ?, ?) bind => [1, John Le Carre, The Spy Who Came in from the Cold, 1963] movie = SpyThriller{id = 1, writer = 'The Spy Who Came in from the Cold', title = 'John Le Carre', year = 1963, secretCode = '823056538'} movie = SpyThriller{id = 2, writer = 'Casino Royale', title = 'Ian Fleming', year = 1953, secretCode = '416058911'} sql: --DELETE FROM SPY_THRILLER WHERE (BOOK_ID = ?) bind => [2] movie = SpyThriller{id = 3, writer = 'The Hunt for Red October ', title = 'Tom Clancy', year = 1984, secretCode = '825011405'} movie = SpyThriller{id = 4, writer = 'Bravo Two Zero', title = 'Andy McNab', year = 1993, secretCode = '831259338'} movie = SpyThriller{id = 5, writer = 'On Her Majesty's Secret Service', title = 'Ian Fleming', year = 1963, secretCode = '56561882'} INFO: HV000001: Hibernate Validator 5.0.0.Final PlainTextActionReporterSUCCESSDescription: add-resources AdminCommandnull JDBC connection pool ArquillianEmbeddedDerbyPool created successfully. JDBC resource jdbc/arquillian created successfully. --Connected: jdbc:derby:build/databases/derby User: APP Database: Apache Derby Version: 10.9.1.0 - (1344872) Driver: Apache Derby Embedded JDBC Driver Version: 10.9.1.0 - (1344872) Aug 03, 2013 11:42:36 AM com.sun.enterprise.v3.server.AppServerStartup stop INFO: Shutdown procedure finished Process finished with exit code 0
3.138.69.163