This short article describes my experiences using Maven on a Spring/JPA development. The source code given is an example and not from the original application. There are lot of resources for learning about Maven and I don't intend to reproduce that information but have given some links at the end of this document.
Maven comes as an archive. Installation is simply a case of extracting the directory to your disk. No messy set-up splatting files across your hard disk. You should then add the ./maven/bin directory to your PATH. To create a project that will be packaged into a Jar file enter the following command:
$> mvn archetype:create -DgroupId=com.abcseo -DartifactId=myapp
This creates a directory called myapp with the base package of com.abcseo with the following structure
myapp
|-- pom.xml
`-- src
|-- main
| `-- java
| `-- com
| `-- abcseo
| `-- app
| `-- App.java
`-- test
`-- java
`-- com
`-- abcseo
`-- app
`-- AppTest.java
This is the default quickstart archetype. There are other Maven Archetypes available, for example for a J2EE project. They provide a fast way to bootstrap your project. Myapp has two branches, one for your application code and the other for test cases. By default JUnit is used for tests.
A great deal happens under the cover with Maven. Much of the functionality is provided by plugins (written using MOJOs - Maven-old-Java-object) and if other libraries are required these can be downloaded from an external repository (assuming an internet connection and a correct firewall or proxy configuration). These plugins and libraries are then cached in a local repository:
~/.m2
Where tilde (~) is the User's local directory (/home/david or c:\Documents and Settings\david). The repository lets you easily manage and share libraries between projects and it could be located on a network drive for collaborative development. The project administrator can populate this with all the packages required for development.
Java Persistence API (JPA) is part of the EJB 3.0 specification. It provides a simpler model for mapping Java Persistent Entities onto relational databases. JPA draws on experiences with persistence frameworks such as Hibernate, Oracle's TopLink as well as the old EJB container-managed persistence. What is cool is that it doesn't need a full blown Java Enterprise Edition applicaton server such as WebLogic to run but will also integrate with frameworks such as Spring 2.0.
Our example application create's two entities called Address and Customer. These are main/java/com/abcseo directory tree. To make configuration easier we'll use annotations, another feature introduced with Java 5.0.
... @Entity public class Customer { @Id @GeneratedValue(strategy = GenerationType.TABLE) private long custid; ... @OneToOne(cascade = CascadeType.ALL) private Address addr; @Temporal(TemporalType.DATE) private Date regdate; ... }
The Primary Key of the Customer object is custid. This is annotated with @Id, the value will be retrieved from a database table that is used to store autoincrementing sequence numbers for all entities with the same strategy. There is also a One to One mapping between the Customer and Address class (not shown). The cascade type of All means that when Customer is loaded/updated or deleted from the database the operation will also cascade to the Address object.
Our CustomerDAO (see the Data Access Object Pattern in Sun's Core J2EE Patterns book) provides a level of abstration above our Customer entity. It supports operations such as finding all the Customers given a last name etc.
... public class JPACustomerDAO extends JpaDaoSupport { public Customer findById(long id) { return getJpaTemplate().find(Customer.class, id); } public List<Customer> findAll() { return getJpaTemplate().find("select c from Customer c"); } ... }
Our DAO extends JpaDaoSupport which is a Spring convenience class for data access objects. Given an EntityManagerFactory or EntityManager it creates a JpaTemplate object. If you don't need JPA exceptions mapped to Spring’s runtime exception hierarchy then you can inject a shared EntityManager reference via the JPA PersistenceContext annotation. Note in our example code we have abstracted JPACustomerDAO with an interface to hide the Spring implementation details from other Classes.
Using Spring and JPA means you will have to specify a whole bunch of dependencies in your pom.xml file. Some are obvious. Some you will see when you try to compile your application:
$ mvn compile ... [INFO] Compilation failure c:\usr\eclipse\workspace\spring\src\main\java\com\abcseo\CustomerDAO.java:[13,43] package org.springframework.orm.jpa.support does not exist
we need to add a dependency on the Spring framework to our pom.xml
<dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.0.6</version> </dependency>
1) javax.transaction:jta:jar:1.0.1B
Try downloading the file manually from:
http://java.sun.com/products/jta
Then, install it using the command:
mvn install:install-file -DgroupId=javax.transaction -DartifactId=jta \
-Dversion=1.0.1B -Dpackaging=jar -Dfile=/path/to/file
Alternatively, if you host your own repository you can deploy the file there:
mvn deploy:deploy-file -DgroupId=javax.transaction -DartifactId=jta \
-Dversion=1.0.1B -Dpackaging=jar -Dfile=/path/to/file \
-Durl=[url] -DrepositoryId=[id]
$ mvn install:install-file -DgroupId=javax.transaction -DartifactId=jta -Dversi on=1.0.1B -Dpackaging=jar -Dfile=jta-1_1-classes.zip
AND
$ mvn install:install-file -DgroupId=jboss -DartifactId=jboss-common-core -Dver sion=2.0.4.GA -Dpackaging=jar -Dfile=jboss-common-core-2.0.4.GA.jar
Test Driven Development (TDD) is one of the tennets of eXtreme Programming that has achieved widespread acceptance. The idea is not new or radical. When I started commercial programming in 1986 one of the rules was that you would never check code into the version control system (rcs back then) that didn't compile. Of course the code should also work, or at least not break other code so it should also pass regression tests. The problem was most testing was done as a separate process by a QA department. Developers hate testing, but if the tests and code are written hand in hand rather than as some final chore this would greatly improve the chances that code would also be unit tested.
Maven uses the Surefire plugin during the test phase of the build lifecycle to run unit tests. Surefire generates reports in text and xml format in the target/surefire-reports directory, more about these later. The Maven Surefire plugin did not like the latest 5.5 version of TestNG and complained about a missing method.
org.apache.maven.surefire.booter.SurefireExecutionException: org.testng.xml.XmlSuite.setParallel(Z)V; nested exception is java.lang.NoSuchMethodError: org.testng.xml.XmlSuite.setParallel(Z)V
This can be fixed by using the 2.4 Snapshot release of Surefire. Add the following lines to your pom.xml file:
<plugin> <artifactId>maven-surefire-plugin</artifactId> <groupId>org.apache.maven.plugins</groupId> <version>2.4-collab-SNAPSHOT</version> </plugin>
You will need to tell Maven where to find the Apache repository. Do this in your settings.xml file found in the ~/.m2 repository directory:
<settings> <profiles> <profile> <id>apache</id> <repositories> <repository> <id>apache.org</id> <name>Maven Snapshots</name> <url>http://people.apache.org/repo/m2-snapshot-repository</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>apache.org</id> <name>Maven Plugin Snapshots</name> <url>http://people.apache.org/repo/m2-snapshot-repository</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> </pluginRepositories> </profile> </profiles> </settings>
then run maven like this:
mvn -Papache test
You will only need to use the -P flag once, the 2.4 Snapshot will be installed in the local repository.
TestNG (Next Generation) is a development of the JUnit test suite and overcomes some of the limitations with JUnit. It can use annotations introduced with Java 5.0. For example whe may have two POJO Entity classes: Customer and Address, representing customer information in a database. We want to check that the constructors and accessors work as specified and that we can create, modify and delete information. We will do this with two test classes:
Our POJOUnitTest class will live in the test/java/com/abcseo branch of the directory tree created by Maven. You will notice that we don't have to extend TestCase as we did for JUnit. We tag the setUp() method with the @BeforeClass annotation. Unlike JUnit, which calls its setup() and teardown() methods before each test case this method will be called once per class, so any test data or other resources created here will persist for all the test methods. Each method annotated with @Test will be called once and any results will be written into the target/surefire-reports directory as described earlier.
... import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import static org.testng.AssertJUnit.assertEquals; public class POJOUnitTest { @BeforeClass protected void setUp() throws Exception { } @Test public void testCustomer() throws java.text.ParseException { } @Test public void testAddress() { } }
POJOUnitTest does nothing more than test the constructors, getters and setters. We are just testing the Plain Old Java Object part of the class. We also want to test the wiring of Spring contects and database access via the persistence layer - object creation, merge (update) and delete. We do this using a new test class that extends Spring's AbstractJpaTests. This helper class has the same contract as Spring's AbstractTransactionalDataSourceSpringContextTests and lets you perform Unit tests without deploying an application server. AbstractJpaTests provides the following services:
Our test class implementes getConfigLocations(). This gives the location(s) of the Spring context. This will be the same, or nearly, as the list of config locations specified in web.xml or other deployment configuration. Once loaded, the configuration will be reused for each test case. AbstractJpaTests will create and roll back a transaction for each test case.
... import org.springframework.test.jpa.AbstractJpaTests; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; public class CustomerDAOIntegrationTest extends AbstractJpaTests { private CustomerDAO customerDAO; public void setCustomerService(CustomerDAO customerDAO) { this.customerDAO = customerDAO; } @Override protected String[] getConfigLocations() { return new String[] { "classpath:/applicationContext.xml" }; } @BeforeClass protected final void onSetUpInTransaction() throws Exception { ... } @Test public void testFindByCustomerId() { ... } ... }
The application object that is being tested, CustomerDAO is set through dependency injection via the method setCustomerService().
Part II: Spring MVC