Created an Eclipse project that follows the '
Introduction to Spring 2 and JPA' pdf. This entails creating POJOs, the business objects of the application, which in this case are the
Employee class and the
Address class. The POJOs do not contain any business logic, possessing only their data fields and the associated getters and setters. Two constructors are added, a no-arg constructor and another constructor that sets all the fields with data.
The
POJOUnitTest class, a Junit test case, is coded to unit test the
Employee and
Address classes. Their objects are created, inserted with data (via setter methods and also constructor) and then the data is verified in the test.
A service layer containing the business logic to use these POJOs is visualized in the
EmployeeService interface, which contains abstract methods for dealing with the Employee class.
Up to now, it's been plain Java coding. Spring and JPA comes into play when one needs to add database support. JPA annotations are added to the POJOs, at the class and field names, to establish the mapping between objects and database counterparts. Spring's JPA support enables one to use its DAO API to code the implementation class which extends
JpaDaoSupport and implements the
EmployeeService interface, called
EmployeeDAO.
Now we use Spring to string these POJOs up as beans in
dwspring-service.xml. Here the provider is changed from TopLink to Hibernate and a database change to MySQL by modifying the
entityManagerFactory bean's properties as shown below:
From:property name="jpaVendorAdapter"
bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter"
property name="databasePlatform"
value="oracle.toplink.essentials.platform.database.HSQLPlatform"
To:
property name="jpaVendorAdapter"
bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
property name="databasePlatform"
value="org.hibernate.dialect.MySQLDialect"
Needless to say, the properties of the
dataSource bean (
driverClassName,
url,
username,
password) need to be changed to reflect the use of the MySQL database.
Another test, an integration test this time, verifies that the
EmployeeDAO implementation of
EmployeeService works against an actual database. The classname is
EmployeeServiceImplementationTest and it extends
AbstractJpaTests. This allows implementation of the
getConfigLocations() method, where one or more beans configuration files can be parsed by the Spring engine. This enables automatic dependency injection; when Spring loads the
EmployeeServiceImplementationTest class, it will discover an unfulfilled dependency - a property of type
EmployeeService. The engine looks through the
dwspring2-service.xml file for a configured bean of type
EmployeeService and injects it via the
setEmployeeService() method. The relevant code is below:
public class EmployeeServiceIntegrationTest extends AbstractJpaTests {
private EmployeeService employeeService;
private long JoeSmithId = 99999;
public void setEmployeeService(EmployeeService employeeService) {
this.employeeService = employeeService;
}
protected String[] getConfigLocations() {
return new String[] {"classpath:/com/ibm/dw/spring2/dwspring2-service.xml"};
}
After that, in order to run the test, one needs to include all the dependency JARs from Spring and Hibernate libraries, which includes the MySQL driver file. A
persistence.xml file is required by the JPA specification. It describes a
persistence unit, though in this case, it is only there to satisfy specs. The file is placed in the META-INF folder and must be accessible through the classpath. The code is below:
persistence-unit name="dwSpring2Jpa" type="RESOURCE_LOCAL"
Now the web application comes into play. The UI for this is Spring MVC, and the first controller class created is
MainController, which handles the initial incoming request. It obtains a list of all the employees in the system. Next the
dwspring2-servlet.xml (
dwspring2 being the name of the DispatcherServlet) file is configured with all the beans required by Spring MVC. The
web.xml file contains the configuration of the
DispatcherServlet and is located in the WEB-INF folder.
MainController does not display the data it holds, instead it hands the data over to a view which resolves to a jsp file,
home.jsp located in the jsp folder.
home.jsp displays the id and full name of each employee, it also made links out of the ids. When clicked, the links go to the controller
EmpDetailsController which gets all details about a particular employee (in other words, get the employee object). A command class,
EmployeeDetailsCommand, is used to parse the link arguments into an object. Here, the only arguments being passed is the employee id, so
EmployeeDetailsCommand has only one data field and a getter and setter.
EmpDetailsController passes the employee object it holds to the
empdetails view which as before is resolved by the
InternalResourceViewResolver by appending the appropriate prefix and suffix and returns
/jsp/empdetails.jsp as the view handler.
empdetails.jsp displays the employee's details.
home.jsp and empdetails.jsp use the css/dwstyles.css stylesheet to format their HTML. This only affect appearance.
The next step is the Eclipse WTP process, however it has been replaced with an Ant build. This step will compile the code, build a deployable WAR file (for deployment in a J2EE compatible Web tier container) and deploy that file to a Tomcat server. The WAR file consists of the compiled classes, the dependency library jars, the web content and the configuration files (
context.xml,
persistence.xml,
dwspring2-servlet.xml, dwspring-service.xml, web.xml)Before the WAR file can be deployed, a number of things had to be done. When a Spring JPA application runs on Tomcat, bytecode "weaving" during class loading is required for the JPA support to work properly. The standard Tomcat classloader does not support this. A Spring-specific classloader is needed.
Installing the Spring classloader:
1) Copy
spring-tomcat-weaver.jar into the Tomcat's server/lib subdirectory. The
spring-tomcat-weaver.jar library can be found in the dist/weaver of the Spring distribution.
2) Configure the
context.xml file (located in META-INF) to let Tomcat know to replace the standard classloader for this particular web application.
Spring needed to hook into Tomcat's context loading pipeline so a
ContextLoaderListener was added to
web.xml.
Datasources are managed by Tomcat and are available through a standard Java Naming and Directory Interface (JNDI) lookup mechanism. The employee system runs as a Web application inside Tomcat and should obtain its datasource through Tomcat's JNDI. To accomplish this, the MySQL driver needs to be copied to Tomcat's common/lib subdirectory. Then configure the
context.xml file, adding a JNDI resource. With a resource name of "
jdbc/dwspring2", the configuration makes the JNDI datasource available through the name
java:comp/env/jdbc/dwspring2. Next, add a resource reference in
web.xml, making it available for use within the web application. Finally,
dwspring2-service.xml must be modified to use the JNDI datasource.
The Spring engine needs to locate and process the bean configuration file (
dwspring2-service.xml) for the POJOs in order to wire them up with the web application. The context parameter in
web.xml must be configured with the location of the file.
The final step before deployment is to fill the database with data. The
FillTableWithEmployeeInfo class was coded to do this. This class extends
AbstractJpaTests. A great feature of tests based on
AbstractJpaTests is that all database changes are rolled back upon completion of a test, allowing the next test to run quickly. However calling the
setComplete() method within the test commits the transaction instead of rolling it back and makes the changes permanent. Which is what is done with
FillTableWithEmployeeInfo.
Finally the WAR file (
spring2web.war) can be deployed to Tomcat's webapps subdirectory and loaded. However the deployment was unsuccessful.
From Tomcat's Catalina log, it was a puzzling one-liner error about the context listener. Set up the web application logging by adding
log4j.properties in order to find out what was wrong. The error was more verbose, essentially a
ClassDefNotFoundException of a class in
jasper-compiler.jar, which is located in Tomcat's common/lib subdirectory. Adding that file to the web application library only made it a
ClassCastException. The project was stuck for a while.
Got hold of the
springjpa project from
MemeStorm, and tried to deploy that as well. Whilst doing that, changed the
loadTimeWeaver property in
dwspring2-service.xml from SimpleLoadTimeWeaver to
InstrumentationLoadTimeWeaver to follow the springjpa project. Read that SimpleLoadTimeWeaver was only suited for testing, so for real deployment,
InstrumentationLoadTimeWeaver or
ReflectiveLoadTimeWeaver should be used. Whatever the problem was, it wasn't the
loadTimeWeaver property as both applications still refused to deploy.
Eventually learned that it was the Spring class loader at fault. When Tomcat replaced the standard classloader with the Spring classloader, the Spring classloader does not have the classpath the standard classloader possess in order to access Tomcat's library jars. The solution was to add the attribute
useSystemClassLoaderAsParent="false" to the Loader in
context.xml. The error was hurdled but a new one popped up.
The new error was that no persistence units were parsed from META-INF/persistence.xml. This one was simple to solve, the META-INF/persistence.xml needed to be in the root of the class package. So a move to the web application's WEB-INF/classes and it is solved.
The next error was talking about a java agent and prior reading indicates this had to do with the
InstrumentationLoadTimeWeaver, basically it needs a Spring agent to be loaded into the JVM. To accomplish this in Tomcat, the line
set JAVA_OPTS=%JAVA_OPTS% -javaagent:"%CATALINA_BASE%\server\lib\spring-agent.jar" was inserted into
catalina.bat in Tomcat's bin subdirectory. The
spring-agent.jar mentioned in the line can be found in the dist/weaver of the Spring distribution. The file was copied to Tomcat's server/lib subdirectory.
Success! Deployment went without a hitch and the web application can be accessed, displaying the list of employees previously inserted into the database and their details when their link was clicked.
Messed around with the
loadTimeWeaver property, changing it back to
SimpleLoadTimeWeaver and even commenting it out as Hibernate apparently does not require it. The web application still runs fine.
One last change was the addition of
index.jsp and
include.jsp, which were from a previous project. A slight modification to the welcome file in
web.xml and one can now access the web application via
http://localhost:8080/spring2web without any need for a filename.
The file structure of the spring2web web application:
spring2web
|-css
| |-dwstyles.css
|
|-jsp
| |-empdetails.jsp
| |-home.jsp
| |-include.jsp
|
|-META-INF
| |-context.xml
|
|-WEB-INF
| |-classes
| | |-com.ibm.dw.spring2.*
| | |-com.ibm.dw.spring2.web.*
| | |
| | |-META-INF
| | | |-persistence.xml
| | |
| | |-log4j.properties
| |
| |-lib
| | |-*.jar
| |
| |-dwspring2-service.xml
| |-dwspring2-servlet.xml
| |-web.xml
|
|-index.jsp
The steps taken in Spring-JPA-Tomcat:
- POJOs
- Unit Test
- dwspring2-service.xml
- Integration Test
- META-INF/persistence.xml
- Spring MVC
- dwspring2-servlet.xml
- web.xml
- META-INF/context.xml (not at the same location as persistence.xml)
- (Tomcat)/common/lib <-- mysql-connector-java-5.0.4-bin.jar
- (Tomcat) /server/lib <-- spring-tomcat-weaver.jar, spring-agent.jar
- (Tomcat)/bin/catalina.bat <-- Java agent command