This time, we will create a generic DAO layer, a Service layer and a REST layer which exposes the Service layer to whoever wants to consume it. Unit tests and Integration tests will be created too. Everything is implemented with Spring 3.
Versions used:
- Spring 3.2.2.RELEASE
- JAXB
- Hibernate 4.2.1.Final
- Maven 3
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.2.1.Final</version>
</dependency>
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.10</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.2.2.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>test</scope>
</dependency>
</dependencies>
hsqldb will be our in-memory database. Let´s see the rest of the relevant pom info.
<!-- Jetty plugin -->
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.10</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
</configuration>
</plugin>
With the Maven Jetty Plugin you can manually run
mvn jetty:run in your terminal and the server will be started. Now that the server is up, it lets you run the REST services tests (those tests are considered integration tests). Below you can find the automated way to treat integration tests just like normal unit tests are treated.
<!-- This one is to run integration tests -->
<!-- By default runs all *IT.java tests -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.12</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
Similar to running
mvn test to run the unit tests, when you run
mvn verify, integration tests are executed. But what if you want to run unit tests and integration tests when you compile your project? Should you run mvn test verify in that case? Well, you must tell maven to start the jetty server right after unit tests finish but before integration tests run (no, you cannot use mvn test jetty:run verify)... see below:
<!-- Responsible for starting jetty before integration tests -->
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<version>1.2.0</version>
<configuration>
<container>
<containerId>jetty6x</containerId>
<type>embedded</type>
</container>
</configuration>
<executions>
<execution>
<id>start-jetty</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop-jetty</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
Pretty straightforward, right? Thanks to the Cargo plugin, whenever integration tests are executed, in the pre-integration-test maven phase the jetty server will be automatically started.
web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring-beans.xml</param-value>
</context-param>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath*:log4j.properties</param-value>
</context-param>
<servlet>
<servlet-name>applicationContext</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>applicationContext</servlet-name>
<url-pattern>/client-crud/*</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
That's a normal Spring setup.
applicationContext-servlet.xml
<!-- Enabling Spring beans auto-discovery -->
<context:component-scan base-package="ar.com.pabloExample" />
<!-- Enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="txManager" />
<!-- Enabling Spring MVC configuration through annotations -->
<mvc:annotation-driven />
In that XML we configure our beans to be scanned because we will use annotations. The transaction manager is enabled to work with annotations too.
GenericDao
public interface GenericDao<T extends Serializable> {
public long count();
public T create(T t);
public void delete(Object id);
public T find(Object id);
public List<T> getAll();
public T update(T t);
}
That's a generic DAO interface.
ClientDao
public interface ClientDao extends GenericDao<Client> {
}
For every entity we want to use the generic DAO, we must create the interface for that entity because the generic DAO is GenericDao<?> and we must tell it which is the entity it will accept, in this case GenericDao<Client>
GenericDaoImpl
public abstract class GenericDaoImpl<T extends Serializable> implements GenericDao<T> {
private Class<T> type;
@SuppressWarnings("unchecked")
public GenericDaoImpl() {
Type t = getClass().getGenericSuperclass();
ParameterizedType pt = (ParameterizedType) t;
type = (Class<T>) pt.getActualTypeArguments()[0];
}
@PersistenceContext
protected EntityManager em;
@Override
public long count() {
String entity = type.getSimpleName();
final StringBuffer queryString = new StringBuffer("select count(ent) from " + entity + " ent");
final Query query = this.em.createQuery(queryString.toString());
return (Long) query.getSingleResult();
}
@Override
public T create(final T t) {
em.persist(t);
return t;
}
@Override
public void delete(final Object id) {
em.remove(em.getReference(type, id));
}
@Override
public T find(final Object id) {
return em.find(type, id);
}
@Override
public T update(final T t) {
return em.merge(t);
}
@SuppressWarnings("unchecked")
@Override
public List<T> getAll() {
Query query = em.createQuery("from " + type.getName());
return query.getResultList();
}
}
This is the implementation of the generic DAO with Hibernate.
ClientDaoImpl
@Repository
public class ClientDaoImpl extends GenericDaoImpl<Client> implements ClientDao {
}
Similar to what we did with the ClientDao interface, we have to do it with the implementation too. We must create the ClientDao implementation and make it extend the generic Dao implementation.
The @Repository annotation only tells Spring this is a bean the "annotations way" of type "DAO" (see the end of the post to understand more of it).
ClientService
public interface ClientService {
public Client create(Client client);
public void delete(Integer id);
public Client update(Client client);
public Client find(Integer id);
public List<Client> getAll();
public Long count();
}
This is the Service layer. It can be generic too, altough in this example only the DAO layer is generic.
ClientServiceImpl
@Service
public class ClientServiceImpl implements ClientService {
@Autowired
private ClientDao clientDao;
@Override
@Transactional(readOnly = false)
public Client create(Client client) {
return clientDao.create(client);
}
@Override
@Transactional(readOnly = true)
public List<Client> getAll() {
return clientDao.getAll();
}
@Override
@Transactional(readOnly = false)
public void delete(Integer id) {
clientDao.delete(id);
}
@Override
@Transactional(readOnly = false)
public Client update(Client client) {
return clientDao.update(client);
}
@Override
@Transactional(readOnly = false)
public Client find(Integer id) {
return clientDao.find(id);
}
@Override
@Transactional(readOnly = true)
public Long count() {
return clientDao.count();
}
}
In this class, all of our methods are transactional, that's why we annotate them with @Transactional. With @Autorwired we inject the Client DAO implementation. The @Service does the same as @Repository; It tells Spring that this class is a bean too (check the end of the post for more information about it).
spring-beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Datasource configured in spring and not in persitence.xml -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:mem:testdb" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceXmlLocation" value="classpath:persistence.xml"/>
<property name="persistenceUnitName" value="spring-webapp-persistenceUnit" />
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
This file is a normal one, we define the datasource, the hibernate entity manager and the transaction manager associated with both.
ClientRestService
@Controller
@RequestMapping("/rest-services/clients")
public class ClientRestService {
private static final Logger LOGGER = Logger.getLogger(ClientService.class);
@Autowired
private ClientService clientService;
@RequestMapping(method = RequestMethod.GET)
@ResponseBody
public Clients loadClients() {
Clients clientsWrapper = new Clients();
clientsWrapper.setClients(clientService.getAll());
LOGGER.info("--- Client list retrieved ---");
return clientsWrapper;
}
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
public Client addClient(@RequestBody Client client) {
Client newClient = clientService.create(client);
LOGGER.info("--- New client saved ---");
return newClient;
}
@RequestMapping(value = "/{clientId}", method = RequestMethod.PUT)
@ResponseBody
public Client updateClient(@PathVariable(value = "clientId") Integer clientId, @RequestBody Client client) {
Client newClient = clientService.update(client);
LOGGER.info("--- Client updated ---");
return newClient;
}
@RequestMapping(value = "/{clientId}", method = RequestMethod.GET)
@ResponseBody
public Client findClient(@PathVariable(value = "clientId") Integer clientId) {
Client foundClient = clientService.find(clientId);
LOGGER.info("--- Client found ---");
return foundClient;
}
@RequestMapping(value = "/{clientId}", method = RequestMethod.DELETE)
@ResponseBody
public void deleteClient(@PathVariable(value = "clientId") Integer clientId) {
clientService.delete(clientId);
LOGGER.info("--- Client deleted ---");
}
}
Here we expose our service layer (in this case, our class annotated with @Service) via REST. To sum up the operations available, you can see this table:
URL | Method type | Operation |
http://localhost:8080/spring-webapp-example/client-crud/rest-services/clients | GET | get all clients |
http://localhost:8080/spring-webapp-example/client-crud/rest-services/clients | POST | create client |
http://localhost:8080/spring-webapp-example/client-crud/rest-services/clients/1 | PUT | update client id = 1 |
http://localhost:8080/spring-webapp-example/client-crud/rest-services/clients/1 | GET | find client id = 1 |
http://localhost:8080/spring-webapp-example/client-crud/rest-services/clients/1 | DELETE | delete client id = 1 |
@Controller does the same job as the previous @Repository and @Service annotations; It tells Spring that this class is a Spring bean.
Clients
@XmlRootElement
@XmlAccessorType(XmlAccessType.PROPERTY)
public class Clients {
private List<Client> clients;
@XmlElement(name = "client")
public List<Client> getClients() {
return clients;
}
public void setClients(List<Client> clients) {
this.clients = clients;
}
}
This class acts as a wrapper for the list of client class. That wrapper eases the process of consuming a REST service that returns a list of objects. The wrapper class is annotated with JAXB annotations
Client
@XmlRootElement
@XmlAccessorType(XmlAccessType.PROPERTY)
@Entity
@Table(name = "T_CLIENT")
public class Client implements Serializable {
private static final long serialVersionUID = -7682472386786656877L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
The Client JPA entity which is used by Hibernate, is annotated with JAXB elements too so that we can reuse the same entity when exposing it via REST. Of course you may create another Client class in order to expose only the information you want to expose.
Let's test this example!
ClientServiceTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/spring-beans-test.xml", "/applicationContext-servlet-test.xml" })
public class ClientServiceTest {
@Autowired
private ClientService clientService;
private Client createNewClient() {
Client client = new Client();
client.setName("Rupert");
return client;
}
@Test
public void createTest() {
Client client = createNewClient();
Client clientSaved = clientService.create(client);
Assert.assertNotNull(clientSaved);
Assert.assertNotNull(clientSaved.getId());
}
@Test
public void deleteTest() {
Client client = createNewClient();
Client clientSaved = clientService.create(client);
clientService.delete(clientSaved.getId());
Client clientDeleted = clientService.find(clientSaved.getId());
Assert.assertNull(clientDeleted);
}
@Test
public void findTest() {
Client client = createNewClient();
Client clientSaved = clientService.create(client);
Client clientDeleted = clientService.find(clientSaved.getId());
Assert.assertNotNull(clientDeleted);
}
@Test
public void countTest() {
Client client = createNewClient();
long countBefore = clientService.count();
clientService.create(client);
long countAfter = clientService.count();
Assert.assertEquals(countBefore, countAfter - 1);
}
@Test
public void getAllTest() {
this.createTest();
List<Client> all = clientService.getAll();
Assert.assertNotNull(all);
Assert.assertTrue(all.size() > 0);
}
}
This unit test is for the Client Service layer. You can run it with
mvn test command.
ClientRestServiceIT
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/spring-beans-test.xml", "/applicationContext-servlet-test.xml" })
public class ClientRestServiceIT {
private static final String REST_SERVICE_URL = "http://localhost:8080/spring-webapp-example/client-crud/rest-services/clients";
private RestTemplate restTemplate = new RestTemplate();
private Client createNewClient() {
Client client = new Client();
client.setName("Rupert");
return client;
}
@Test
public void createTest() {
Client newClient = createNewClient();
Client clientCreated = restTemplate.postForObject(REST_SERVICE_URL, newClient, Client.class);
Assert.assertNotNull(clientCreated);
Assert.assertNotNull(clientCreated.getId());
}
@Test
public void updateTest() {
Client newClient = createNewClient();
Client clientCreated = restTemplate.postForObject(REST_SERVICE_URL, newClient, Client.class);
clientCreated.setName("Stuart");
String restServiceUrl = REST_SERVICE_URL + "/" + clientCreated.getId();
restTemplate.put(restServiceUrl, clientCreated);
Client clientModified = restTemplate.getForObject(restServiceUrl, Client.class);
Assert.assertNotNull(clientModified);
Assert.assertTrue(clientModified.getName().equals("Stuart"));
}
@Test
public void findTest() {
Client newClient = createNewClient();
Client clientCreated = restTemplate.postForObject(REST_SERVICE_URL, newClient, Client.class);
String restServiceUrlFind = REST_SERVICE_URL + "/" + clientCreated.getId();
Client clientFound = restTemplate.getForObject(restServiceUrlFind, Client.class);
Assert.assertNotNull(clientFound);
Assert.assertEquals(clientFound.getId(), clientCreated.getId());
}
@Test
public void getAllTest() {
Client newClient = createNewClient();
restTemplate.postForObject(REST_SERVICE_URL, newClient, Client.class);
Clients clients = (Clients) restTemplate.getForObject(REST_SERVICE_URL, Clients.class);
Assert.assertNotNull(clients);
Assert.assertNotNull(clients.getClients());
Assert.assertTrue(clients.getClients().size() > 0);
}
@Test
public void deleteTest() {
Client newClient = createNewClient();
Client clientCreated = restTemplate.postForObject(REST_SERVICE_URL, newClient, Client.class);
Assert.assertNotNull(clientCreated);
Assert.assertNotNull(clientCreated.getId());
//It should be found
String restServiceUrlFind = REST_SERVICE_URL + "/" + clientCreated.getId();
Client clientFound = restTemplate.getForObject(restServiceUrlFind, Client.class);
Assert.assertNotNull(clientFound);
Assert.assertNotNull(clientFound.getId());
restTemplate.delete(restServiceUrlFind);
//It should not be found
Client clientDeleted = restTemplate.getForObject(restServiceUrlFind, Client.class);
Assert.assertNull(clientDeleted);
}
}
This test is the so called integration test we created. You can run it executing
mvn verify.
About @Controller @Repository and @Service annotations
All of them do the same! they tell Spring that the class annotated is a Spring bean. So, why don't we use something like "@Bean" for every bean? Because the idea goes like this:
- Every DAO should be @Repository
- Every Controller/Action should be @Controller
- Every Service should be @Service
Say, for instance, you have all of your beans annotated with @Controller (remember those 3 are the same) and you want to apply transactions via aspects with Spring to every Service class. Then, if you had all of your service beans annotated with @Service you could choose to apply transaction via aspects to every class annotated with @Service.
Download the complete example!
Checkout the project:
https://subversion.assembla.com/svn/pablo-examples/spring-webapp-example
Run
mvn test -> To run the unit tests only
Run
mvn verify -> To run the unit tests + integration tests
Run
mvn verify -Dmaven.test.skip=true -> To run integration tests only
Additionaly, you may run
mvn eclipse:eclipse to convert it to Eclipse project