This tutorial will show you how to create a Business Facade class (and a JUnit Test) to talk to the DAO you created in the Persistence Tutorial.
In the context of AppFuse, this is called a Manager class. Its main responsibility is to act as a bridge between the persistence (DAO) layer and the web layer. It's also useful for de-coupling your presentation layer from your database layer (i.e. for Swing apps and web services). Managers should also be where you put any business logic for your application.
You will learn two things:
- You don't need to write Managers if you just need generic CRUD functionality.
- How to write Managers when you need custom functionality.
Table of Contents
- Register a personManager bean definition
- Create a Manager Interface
- Create a Manager Test to test finder functionality
- Create a Manager Implementation
- Run the Manager Test
- Register your Manager Implementation
- Write the Manager Test using EasyMock
Source Code The code for this tutorial is located in the "tutorial-service" module of the appfuse-demos project on Google Code. Use the following command to check it out from Subversion: svn checkout http://appfuse-demos.googlecode.com/svn/trunk/tutorial-service |
Register a personManager bean definition
AppFuse 2.x doesn't require you to write a Manager or DAO to persist a POJO. You can use one of the pre-existing manager interfaces/implementations if all you need is CRUD on an object:
- GenericManager: A generics-based class that requires you to create a Spring bean definition.
- UniversalManager: A class that requires you to cast to the specific object type.
The UniversalManagerImpl class is already registered as a "manager" bean, so you can easily use it without any additional configuration. However, many developers prefer the generics-based Manager because it provides type safety. To register a personManager bean, its best to wrap the personDao bean.
Transactions All service.*Manager beans will automatically be configured by Spring to wrap PROPOGATION_REQUIRED transactions around their method executions. This is done by the following Spring AOP configuration in appfuse-service.jar. <aop:config> ... <aop:advisor id="managerTx" advice-ref="txAdvice" pointcut="execution(* *..service.*Manager.*(..))" order="2"/> </aop:config> |
Open your src/main/webapp/WEB-INF/applicationContext.xml and replace the personDao bean with the following:
Hibernate:
<script class="javascript" src="/download/resources/confluence.ext.code:code/shCore.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushCSharp.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushPhp.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushJScript.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushVb.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushSql.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushXml.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushShell.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushDelphi.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushPython.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushJava.js" type="text/javascript"></script><bean id="personManager" class="org.appfuse.service.impl.GenericManagerImpl"> |
<constructor-arg> |
<bean class="org.appfuse.dao.hibernate.GenericDaoHibernate" autowire="byType"> |
<constructor-arg value="org.appfuse.tutorial.model.Person"/> |
</bean> |
</constructor-arg> |
</bean> |
iBATIS and JPA Configuration For iBATIS and JPA, you'll need to change the nested DAO that gets injected into GenericManagerImpl:
|
If you wrote the PersonDao interface and implementation in the previous tutorial, you'll want to use the following for your personManager bean definition. If you don't, your PersonDaoTest will fail because there's no longer an exposed personDao bean.
<bean id="personManager" class="org.appfuse.service.impl.GenericManagerImpl"> |
<constructor-arg ref="personDao"/> |
</bean> |
Once you've created a personManager bean definition, you can use this bean on an object by adding the following setter method:
public void setPersonManager(GenericManager<Person, Long> personManager) { |
this.personManager = personManager; |
} |
If you'd prefer to use the UniversalManager, you simply need to declare your set method as follows:
public void setManager(UniversalManager manager) { |
this.manager = manager; |
} |
No futher XML configuration is required when using this class.
The Spring Framework To learn more about how Spring works, please see the Spring Reference Guide. |
That's it! To persist an object with AppFuse 2.x, all you need to do is:
- Create a POJO with annotations
- Register it with your persistence framework
- Write some XML to register a type-safe class to persist it, or write no XML to use the Universal* classes.
Not only did you write data access code, you also used interfaces to abstract your implementation. This means that it should be possible to replace your persistence framework at any time.
We hope to eliminate many of these steps in 2.2. For the final 2.0 release, you should be able to perform all 3 steps with the AppFuse Maven Plugin. |
If you need more than just CRUD functionality, you'll want to continue reading below. If not, you can continue to writing your web application.
Create a Manager Interface
First off, create a PersonManager interface (in the src/main/java/**/service directory - you may need to create this first) and specify the finder method for any implementation classes.
package org.appfuse.tutorial.service; |
import org.appfuse.service.GenericManager; |
import org.appfuse.tutorial.model.Person; |
import java.util.List; |
public interface PersonManager extends GenericManager<Person, Long> { |
public List<Person> findByLastName(String lastName); |
} |
Create a Manager Test to test finder functionality
Now you know what you want your manager to do, so it's time to write the tests. In this example, you will use mock objects to isolate your tests from external dependencies. This means you do not need a database set up to run these tests, and it will be easier to test different scenarios. In a large project, you could even test your Manager class before someone else finished writing the DAO implementation.
You can write your own mock objects, but here you are going to use jMock. Another widely use Mock Object library is EasyMock.
Create PersonManagerImplTest in src/test/java/**/service/impl:
package org.appfuse.tutorial.service.impl; |
import java.util.ArrayList; |
import java.util.List; |
import org.appfuse.tutorial.dao.PersonDao; |
import org.appfuse.tutorial.model.Person; |
import org.appfuse.service.impl.BaseManagerMockTestCase; |
import org.jmock.Mock; |
public class PersonManagerImplTest extends BaseManagerMockTestCase { |
private PersonManagerImpl manager = null; |
private Mock dao = null; |
private Person person = null; |
protected void setUp() throws Exception { |
dao = new Mock(PersonDao.class); |
manager = new PersonManagerImpl((PersonDao) dao.proxy()); |
} |
protected void tearDown() throws Exception { |
manager = null; |
} |
public void testGetPerson() { |
log.debug("testing getPerson"); |
Long id = 7L; |
person = new Person(); |
// set expected behavior on dao |
dao.expects(once()).method("get") |
.with(eq(id)) |
.will(returnValue(person)); |
Person result = manager.get(id); |
assertSame(person, result); |
dao.verify(); |
} |
public void testGetPersons() { |
log.debug("testing getPersons"); |
List people = new ArrayList(); |
// set expected behavior on dao |
dao.expects(once()).method("getAll") |
.will(returnValue(people)); |
List result = manager.getAll(); |
assertSame(people, result); |
dao.verify(); |
} |
public void testFindByLastName() { |
log.debug("testing findByLastName"); |
List people = new ArrayList(); |
String lastName = "Smith"; |
// set expected behavior on dao |
dao.expects(once()).method("findByLastName") |
.with(eq(lastName)) |
.will(returnValue(people)); |
List result = manager.findByLastName(lastName); |
assertSame(people, result); |
dao.verify(); |
} |
public void testSavePerson() { |
log.debug("testing savePerson"); |
person = new Person(); |
// set expected behavior on dao |
dao.expects(once()).method("save") |
.with(same(person)) |
.will(returnValue(person)); |
manager.save(person); |
dao.verify(); |
} |
public void testRemovePerson() { |
log.debug("testing removePerson"); |
Long id = 11L; |
person = new Person(); |
// set expected behavior on dao |
dao.expects(once()).method("remove") |
.with(eq(id)) |
.isVoid(); |
manager.remove(id); |
dao.verify(); |
} |
} |
This will not compile, as you have not created the PersonManagerImpl class it tests.
Create a Manager Implementation
The next step is to create a PersonManagerImpl class that implements the methods in PersonManager.
Create PersonManagerImpl.java in src/main/java/**/service/impl.
package org.appfuse.tutorial.service.impl; |
import org.appfuse.tutorial.dao.PersonDao; |
import org.appfuse.tutorial.model.Person; |
import org.appfuse.tutorial.service.PersonManager; |
import org.appfuse.service.impl.GenericManagerImpl; |
import java.util.List; |
public class PersonManagerImpl extends GenericManagerImpl<Person, Long> implements PersonManager { |
PersonDao personDao; |
public PersonManagerImpl(PersonDao personDao) { |
super(personDao); |
this.personDao = personDao; |
} |
public List<Person> findByLastName(String lastName) { |
return personDao.findByLastName(lastName); |
} |
} |
Now before you run your tests, review your test class to make sure that it will test all possible conditions.
I would put any complicated code in protected methods and go back and add extra test cases at this stage. |
Run the Manager Test
Save all your edited files and try running mvn test -Dtest=PersonManagerImplTest.
Register your Manager Implementation
Open your src/main/webapp/WEB-INF/applicationContext.xml file and replace the personDao bean with the following:
<bean id="personManager" class="org.appfuse.tutorial.service.impl.PersonManagerImpl"> |
<constructor-arg ref="personDao"/> |
</bean> |
The Web application tutorials assume that you will be using the GenericManager. If you follow them after making this change, you will need to change all the references in their code from the GenericManager to your new PersonManager interface. For example: private PersonManager personManager; public void setPersonManager(PersonManager personManager) { this.personManager = personManager; } |
That's it. If you want to see how to use EasyMock instead of JMock, then carry on reading. If not, you can continue to writing your web application.
Using EasyMock instead of JUnit in Unit Tests
Sooner or later, you will want to add extra dependencies into your project.
AppFuse currently ships with JMock, but not EasyMock, so this section will show you how to add EasyMock as a project dependency.
Edit your pom.xml file in your project's top level directory. Add EasyMock as a dependency in the <dependencies> element:
<dependencies> |
... |
<dependency> |
<groupId>org.easymock</groupId> |
<artifactId>easymock</artifactId> |
<version>2.2</version> |
<scope>test</scope> |
</dependency> |
</dependencies> |
EasyMock is only required during testing, and you don't want it packaged in your application, so it's restricted it to a scope of test.
To remove jMock and add EasyMock to references regenerate project with mvn install eclipse:eclipse for Eclipse or mvn idea:idea for IDEA. |
Edit the PersonManagerImplTest class you wrote above so it looks as follows:
package org.appfuse.tutorial.service.impl; |
import java.util.ArrayList; |
import java.util.List; |
import org.appfuse.tutorial.dao.PersonDao; |
import org.appfuse.tutorial.model.Person; |
import static org.easymock.EasyMock.*; |
import org.apache.commons.logging.LogFactory; |
import org.apache.commons.logging.Log; |
import junit.framework.TestCase; |
public class PersonManagerImplTest extends TestCase { |
private final Log log = LogFactory.getLog(PersonManagerImplTest.class); |
private PersonManagerImpl manager = null; |
private PersonDao dao = null; |
private Person person = null; |
protected void setUp() throws Exception { |
log.debug("setUpDao for PersonManagerImplTest"); |
dao = createStrictMock(PersonDao.class); |
manager = new PersonManagerImpl((PersonDao) dao); |
} |
public void testGetPerson() { |
log.debug("testing getPerson"); |
Long id = 7L; |
person = new Person(); |
// set expected behavior on dao |
expect(dao.get(id)).andReturn(person); |
replay(dao); |
Person result = manager.get(id); |
assertSame(person, result); |
verify(dao); |
} |
public void testGetPersons() { |
log.debug("testing getPersons"); |
List people = new ArrayList(); |
// set expected behavior on dao |
expect(dao.getAll()).andReturn(people); |
replay(dao); |
List result = manager.getAll(); |
assertSame(people, result); |
verify(dao); |
} |
public void testGetByLastName() { |
log.debug("testing getByLastName"); |
List people = new ArrayList(); |
String lastName = "Smith"; |
// set expected behavior on dao |
expect(dao.findByLastName(lastName)).andReturn(people); |
replay(dao); |
List result = manager.findByLastName(lastName); |
assertSame(people, result); |
verify(dao); |
} |
public void testSavePerson() { |
log.debug("testing savePerson"); |
person = new Person(); |
// set expected behavior on dao |
expect(dao.save(person)).andReturn(person); |
replay(dao); |
manager.save(person); |
verify(dao); |
} |
public void testRemovePerson() { |
log.debug("testing removePerson"); |
Long id = 11L; |
person = new Person(); |
// set expected behavior on dao |
dao.remove(id); |
replay(dao); |
manager.remove(id); |
verify(dao); |
} |
} |
Note that this class extends junit.framework.TestCase and not an EasyMock class. This makes EasyMock a good choice for annotation based test frameworks such as JUnit 4 and TestNG. Unfortunately (at the time of writing) Maven does not work properly with these frameworks.
Now check everything works by running mvn test -Dtest=PersonManagerImplTest again.
That's it, you can continue to writing your web application.