Spring – DAO and Service layer
In the previous part, we’ve defined GenericDao interface that tells us, what operations we will need to perform upon entities. Now we need to provide implementation. We will write class that performs these operations generically with Hibernate’s facilities (using SessionFactory). Therefore, any provided DAO automatically inherits these basic operations. We’ll talk about this later.
package org.timesheet.service.impl; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.timesheet.service.GenericDao; import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java.util.List; /** * Basic DAO operations dependent with Hibernate's specific classes * @see SessionFactory */ @Transactional(propagation= Propagation.REQUIRED, readOnly=false) public class HibernateDao<E, K extends Serializable> implements GenericDao<E, K> { private SessionFactory sessionFactory; protected Class<? extends E> daoType; public HibernateDao() { daoType = (Class<E>) ((ParameterizedType) getClass().getGenericSuperclass()) .getActualTypeArguments()[0]; } @Autowired public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } protected Session currentSession() { return sessionFactory.getCurrentSession(); } @Override public void add(E entity) { currentSession().save(entity); } @Override public void update(E entity) { currentSession().saveOrUpdate(entity); } @Override public void remove(E entity) { currentSession().delete(entity); } @Override public E find(K key) { return (E) currentSession().get(daoType, key); } @Override public List<E> list() { return currentSession().createCriteria(daoType).list(); } }
I want you to note couple of things about this code:
- We’re using @Transcational annotation at the top of the class. That basically means, that DAO methods will run within transcations. To make it work, we need to alter our persistence-beans.xml file and declare there transaction manager, which will be handling the transactions. Just add following lines (new bean definition):
<bean id='transactionManager' class='org.springframework.orm.hibernate3.HibernateTransactionManager'> <property name='sessionFactory' ref='sessionFactory' /> </bean>
- We’re autowiring (@Autowired) SessionFactory using setter injection. As you should know, there are more kinds of injection (field, setter, constructor). Field injection looks best with Spring, because annotation resides directly at field, not on constructor or setter method. On the other hand, field injection is most useless one, because we cannot manually set other dependencies to private fields (for example in unit test). I prefer constructor injection whenever I can, because I don’t have to use mutator (setter) for the dependency. Objects are therefore constructed in more safe way. In this specific case we will use setter injection, because we’re designing this class for extension. If we’d pick constructor injection, all extending classes would have to have constructor matching one from the superclass.
If you want to learn more about this, I recommend this brilliant book written by Dhanji R. Prasanna.
Also note, that first line of constructor is doing some reflective magic. That’s because Java doesn’t have generics at runtime, only at compile time, so it prevents us from writing something like E.class. Therefore we used this ugly hack.
Now we have some basic template for DAO operations. In the real systems, there usually is DAO for each entity. That’s because sometimes those inherited CRUD operations are not enough, and you need some additional business operations. We will define interfaces (sets of operations for each DAO) that are typesafe and we will only depend on those later in controllers. We will implement them with Hibernate and have them autowired. Create new package org.timesheet.service.dao and add there following interfaces – DAOs for each entity:
package org.timesheet.service.dao; import org.timesheet.domain.Employee; import org.timesheet.service.GenericDao; /** * DAO of employee. */ public interface EmployeeDao extends GenericDao<Employee, Long> { /** * Tries to remove employee from the system. * @param employee Employee to remove * @return {@code true} if employee is not assigned to any task * or timesheet. Else {@code false}. */ boolean removeEmployee(Employee employee); }
package org.timesheet.service.dao; import org.timesheet.domain.Manager; import org.timesheet.service.GenericDao; /** * DAO of Manager. */ public interface ManagerDao extends GenericDao<Manager, Long> { /** * Tries to remove manager from the system. * @param manager Manager to remove * @return {@code true} if manager is not assigned to any task. * Else {@code false}. */ boolean removeManager(Manager manager); }
package org.timesheet.service.dao; import org.timesheet.domain.Task; import org.timesheet.service.GenericDao; /** * DAO of Task. */ public interface TaskDao extends GenericDao<Task, Long> { /** * Tries to remove task from the system. * @param task Task to remove * @return {@code true} if there is no timesheet created on task. * Else {@code false}. */ boolean removeTask(Task task); }
package org.timesheet.service.dao; import org.timesheet.domain.Timesheet; import org.timesheet.service.GenericDao; /** * DAO of Timesheet. */ public interface TimesheetDao extends GenericDao<Timesheet, Long> { // no additional business operations atm }
Time for implementation. We will just extend HibernateDao and implement coresponding interface. We need those concrete classes, because this will be injected to the corresponding fields (which are declared by interface). Maybe you’ve heared something about this approach – it’s called programming to interfaces and it is something you definitelly want to embrace.
package org.timesheet.service.impl; import org.hibernate.Query; import org.springframework.stereotype.Repository; import org.timesheet.domain.Employee; import org.timesheet.service.dao.EmployeeDao; @Repository('employeeDao') public class EmployeeDaoImpl extends HibernateDao<Employee, Long> implements EmployeeDao { @Override public boolean removeEmployee(Employee employee) { Query employeeTaskQuery = currentSession().createQuery( 'from Task t where :id in elements(t.assignedEmployees)'); employeeTaskQuery.setParameter('id', employee.getId()); // employee mustn't be assigned on no task if (!employeeTaskQuery.list().isEmpty()) { return false; } Query employeeTimesheetQuery = currentSession().createQuery( 'from Timesheet t where t.who.id = :id'); employeeTimesheetQuery.setParameter('id', employee.getId()); // employee mustn't be assigned to any timesheet if (!employeeTimesheetQuery.list().isEmpty()) { return false; } // ok, remove as usual remove(employee); return true; } }
package org.timesheet.service.impl; import org.hibernate.Query; import org.springframework.stereotype.Repository; import org.timesheet.domain.Manager; import org.timesheet.service.dao.ManagerDao; @Repository('managerDao') public class ManagerDaoImpl extends HibernateDao<Manager, Long> implements ManagerDao { @Override public boolean removeManager(Manager manager) { Query managerQuery = currentSession().createQuery( 'from Task t where t.manager.id = :id'); managerQuery.setParameter('id', manager.getId()); // manager mustn't be assigned on no task if (!managerQuery.list().isEmpty()) { return false; } // ok, remove as usual remove(manager); return true; } }
package org.timesheet.service.impl; import org.hibernate.Criteria; import org.hibernate.Query; import org.springframework.stereotype.Repository; import org.timesheet.domain.Task; import org.timesheet.domain.Timesheet; import org.timesheet.service.dao.TaskDao; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @Repository('taskDao') public class TaskDaoImpl extends HibernateDao<Task, Long> implements TaskDao { @Override public boolean removeTask(Task task) { Query taskQuery = currentSession().createQuery( 'from Timesheet t where t.task.id = :id'); taskQuery.setParameter('id', task.getId()); // task mustn't be assigned to no timesheet if (!taskQuery.list().isEmpty()) { return false; } // ok, remove as usual remove(task); return true; } @Override public List<Task> list() { return currentSession().createCriteria(Task.class) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) .list(); } }
package org.timesheet.service.impl; import org.hibernate.Criteria; import org.springframework.stereotype.Repository; import org.timesheet.domain.Timesheet; import org.timesheet.service.dao.TimesheetDao; import java.util.List; @Repository('timesheetDao') public class TimesheetDaoImpl extends HibernateDao<Timesheet, Long> implements TimesheetDao { @Override public List<Timesheet> list() { return currentSession().createCriteria(Timesheet.class) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) .list(); } }
I want you to note that we have Spring’s @Repository annotation on every DAO class. That’s because we won’t create them by hand, but have them injected and managed by Spring’s IoC container. By the way – this is the newer annotation oriented approach. No XML configuration, Spring will figure it out for us We can use plenty of annotations that register classes as beans:
- @Component – autoscan component (Spring bean)
- @Repository – component in persistence layer (usually DAO)
- @Service – component in service layer
- @Controller – controller in MVC architecture
Another interseting thing is that we pass string value to @Repository annotation. I’ll quote Spring’s javadoc here, since it’s clearest explanation: “The value may indicate a suggestion for a logical component name, to be turned into a Spring bean in case of an autodetected component.”
Now – time for testing! Create new package: /src/test/java/org/timesheet/service/dao and put tests there.
We will be using some external SQL scripts for verifying database status. Under src/main/resources create folder sql. Now let’s add two scripts; cleanup.sql and create-data.sql. We’ll use now only cleanup.sql script, create-data.sql will be used later.
create-data.sql
-- delete old data delete from task_employee; delete from timesheet; delete from task; delete from employee; delete from manager; -- add few employees insert into employee values(1, 'management', 'Steve Jobs'); insert into employee values(2, 'management', 'Bill Gates'); insert into employee values(3, 'engineering', 'Steve Wozniak'); insert into employee values(4, 'engineering', 'Paul Allen'); -- add few managers insert into manager values(1, 'Eric Schmidt'); insert into manager values(2, 'Steve Ballmer'); -- add some tasks insert into task values(1, 0, 'task 1', 1); insert into task values(2, 0, 'task 2', 2); -- connect tasks to some employees insert into task_employee values (1, 1); insert into task_employee values (1, 3); insert into task_employee values (1, 4); insert into task_employee values (2, 2); insert into task_employee values (2, 1); -- create some timesheets on tasks insert into timesheet values(1, 5, -- hours 1, -- first task 1 -- employee steve jobs ); insert into timesheet values(2, 8, -- hours 2, -- second task 3 -- employee bill gates );
cleanup.sql
delete from task_employee; delete from timesheet; delete from task; delete from employee; delete from manager;
You don’t have to use my data; feel free to create some on your own. Just make sure they make somehow sense to you.
Before we write test we need new Spring bean. It’s called jdbcTemplate and it’s well known facility for working with JDBC in Spring. It’s basically wrapper upon plain JDBC that simplifies lot of things. Thanks to this we can run script with simple call as you’ll see later.
For now, add this bean to your persistence-beans.xml Spring Config file:
<bean id='jdbcTemplate' class='org.springframework.jdbc.core.simple.SimpleJdbcTemplate'> <constructor-arg type='javax.sql.DataSource' ref='dataSource'/> </bean>
I won’t be paying any special attention to every test, so let’s only briefly talk about what to test and how. We’re testing our DAOs and we need to make sure that basic CRUD operations work properly. We’re cleaning all the data after each test method and if necessary, we’re creating them before the test method runs. Basic idea of our tests is like so:
- If something was added, check if can find that
- If something was removed, check that we can’t find that anymore
- Add couple of items to database, count them and verify they’ve been added
- Update item, save it. Find it and check that it has been changed
I like to think of those tests like integration tests more like unit tests. In huge domain similar tests would require (unlike plain unit tests) quite large amount of time to run. This time we will create special base class called org.timesheet.DomainAwareBase. This extends AbstractJUnit4SpringContextTests so we can have our DAOs autowired, but it also deletes all data from database before any test method is executed using deleteScript.
package org.timesheet; import org.junit.Before; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.FileSystemResource; import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; import org.springframework.test.jdbc.SimpleJdbcTestUtils; /** * Base makes sure that before any test empty database is available. */ @ContextConfiguration(locations = {'/persistence-beans.xml'}) public abstract class DomainAwareBase extends AbstractJUnit4SpringContextTests { private final String deleteScript = 'src/main/resources/sql/cleanup.sql'; @Autowired private SimpleJdbcTemplate jdbcTemplate; @Before public void deleteAllDomainEntities() { SimpleJdbcTestUtils.executeSqlScript(jdbcTemplate, new FileSystemResource(deleteScript), false); } }
About autowiring and tooling:
For me, tooling is specially important when autowiring beans. It’s little hard to navigate through code withou any additional support. For example if you’re using ultimate edition of IntelliJ IDEA you can navigate directly from field to autowired dependency because IntelliJ will add little marker.
Or you can also see autowired depencies together with those declared in XML in dependencies view.
Let’s see the code for tests now:
package org.timesheet.service.dao; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.timesheet.DomainAwareBase; import org.timesheet.domain.Employee; import org.timesheet.domain.Manager; import org.timesheet.domain.Task; import org.timesheet.domain.Timesheet; import java.util.Arrays; import java.util.List; import static org.junit.Assert.*; @ContextConfiguration(locations = '/persistence-beans.xml') public class EmployeeDaoTest extends DomainAwareBase { @Autowired private EmployeeDao employeeDao; @Autowired private ManagerDao managerDao; @Autowired private TaskDao taskDao; @Autowired private TimesheetDao timesheetDao; @Test public void testAdd() { int size = employeeDao.list().size(); employeeDao.add(new Employee('test-employee', 'hackzorz')); // list should have one more employee now assertTrue (size < employeeDao.list().size()); } @Test public void testUpdate() { Employee employee = new Employee('test-employee', 'hackzorz'); employeeDao.add(employee); employee.setName('updated'); employeeDao.update(employee); Employee found = employeeDao.find(employee.getId()); assertEquals('updated', found.getName()); } @Test public void testFind() { Employee employee = new Employee('test-employee', 'hackzorz'); employeeDao.add(employee); Employee found = employeeDao.find(employee.getId()); assertEquals(found, employee); } @Test public void testList() { assertEquals(0, employeeDao.list().size()); List<Employee> employees = Arrays.asList( new Employee('test-1', 'testers'), new Employee('test-2', 'testers'), new Employee('test-3', 'testers')); for (Employee employee : employees) { employeeDao.add(employee); } List<Employee> found = employeeDao.list(); assertEquals(3, found.size()); for (Employee employee : found) { assertTrue(employees.contains(employee)); } } @Test public void testRemove() { Employee employee = new Employee('test-employee', 'hackzorz'); employeeDao.add(employee); // successfully added assertEquals(employee, employeeDao.find(employee.getId())); // try to remove employeeDao.remove(employee); assertNull(employeeDao.find(employee.getId())); } @Test public void testRemoveEmployee() { Manager manager = new Manager('task-manager'); managerDao.add(manager); Employee employee = new Employee('Jaromir', 'Hockey'); employeeDao.add(employee); Task task = new Task('test-task', manager, employee); taskDao.add(task); Timesheet timesheet = new Timesheet(employee, task, 100); timesheetDao.add(timesheet); // try to remove -> shouldn't work assertFalse(employeeDao.removeEmployee(employee)); // remove stuff timesheetDao.remove(timesheet); taskDao.remove(task); // should work -> employee is now free assertTrue(employeeDao.removeEmployee(employee)); } }
package org.timesheet.service.dao; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.timesheet.DomainAwareBase; import org.timesheet.domain.Employee; import org.timesheet.domain.Manager; import org.timesheet.domain.Task; import java.util.Arrays; import java.util.List; import static org.junit.Assert.*; @ContextConfiguration(locations = '/persistence-beans.xml') public class ManagerDaoTest extends DomainAwareBase { @Autowired private ManagerDao managerDao; @Autowired private EmployeeDao employeeDao; @Autowired private TaskDao taskDao; @Test public void testAdd() { int size = managerDao.list().size(); managerDao.add(new Manager('test-manager')); assertTrue (size < managerDao.list().size()); } @Test public void testUpdate() { Manager manager = new Manager('test-manager'); managerDao.add(manager); manager.setName('updated'); managerDao.update(manager); Manager found = managerDao.find(manager.getId()); assertEquals('updated', found.getName()); } @Test public void testFind() { Manager manager = new Manager('test-manager'); managerDao.add(manager); Manager found = managerDao.find(manager.getId()); assertEquals(found, manager); } @Test public void testList() { assertEquals(0, managerDao.list().size()); List<Manager> managers = Arrays.asList( new Manager('test-1'), new Manager('test-2'), new Manager('test-3') ); for (Manager manager : managers) { managerDao.add(manager); } List<Manager> found = managerDao.list(); assertEquals(3, found.size()); for (Manager manager : found) { assertTrue(managers.contains(manager)); } } @Test public void testRemove() { Manager manager = new Manager('test-manager'); managerDao.add(manager); // successfully added assertEquals(manager, managerDao.find(manager.getId())); // try to remove managerDao.remove(manager); assertNull(managerDao.find(manager.getId())); } @Test public void testRemoveManager() { Manager manager = new Manager('task-manager'); managerDao.add(manager); Employee employee = new Employee('Jaromir', 'Hockey'); employeeDao.add(employee); Task task = new Task('test-task', manager, employee); taskDao.add(task); // try to remove -> shouldn't work assertFalse(managerDao.removeManager(manager)); // remove task taskDao.remove(task); // should work -> no more tasks for manager assertTrue(managerDao.removeManager(manager)); } }
package org.timesheet.service.dao; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.timesheet.DomainAwareBase; import org.timesheet.domain.Employee; import org.timesheet.domain.Manager; import org.timesheet.domain.Task; import java.util.Arrays; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @ContextConfiguration(locations = '/persistence-beans.xml') public class TaskDaoTest extends DomainAwareBase { @Autowired private TaskDao taskDao; @Autowired private ManagerDao managerDao; @Autowired private EmployeeDao employeeDao; @Test public void testAdd() { int size = taskDao.list().size(); Task task = newSpringTask(); taskDao.add(task); assertTrue(size < taskDao.list().size()); } @Test public void testUpdate() { Task task = newSpringTask(); taskDao.add(task); // update task task.setDescription('Learn Spring 3.1'); taskDao.update(task); Task found = taskDao.find(task.getId()); assertEquals('Learn Spring 3.1', found.getDescription()); } @Test public void testFind() { Task task = newSpringTask(); taskDao.add(task); assertEquals(task, taskDao.find(task.getId())); } @Test public void testList() { assertEquals(0, taskDao.list().size()); Task templateTask = newSpringTask(); List<Task> tasks = Arrays.asList( newTaskFromTemplate(templateTask, '1'), newTaskFromTemplate(templateTask, '2'), newTaskFromTemplate(templateTask, '3') ); for (Task task : tasks) { taskDao.add(task); } List<Task> found = taskDao.list(); assertEquals(3, found.size()); for (Task task : found) { assertTrue(tasks.contains(task)); } } @Test public void testRemove() { Task task = newSpringTask(); taskDao.add(task); // successfully added assertEquals(task, taskDao.find(task.getId())); // try to remove taskDao.remove(task); assertNull(taskDao.find(task.getId())); } /** * @return Dummy task for testing */ private Task newSpringTask() { Manager bob = new Manager('Bob'); managerDao.add(bob); Employee steve = new Employee('Steve', 'Business'); Employee woz = new Employee('Woz', 'Engineering'); employeeDao.add(steve); employeeDao.add(woz); return new Task('Learn Spring', bob, steve, woz); } /** * Creates dummy task fo testing as copy of existing task and * adds aditional information to every field. * @param templateTask Task to copy * @param randomInfo Info to append everywhere * @return Random task for testing */ private Task newTaskFromTemplate(Task templateTask, String randomInfo) { String description = templateTask.getDescription() + randomInfo; Manager manager = new Manager( templateTask.getManager().getName()); managerDao.add(manager); List<Employee> templateEmployees = templateTask.getAssignedEmployees(); Employee[] employees = new Employee[templateEmployees.size()]; int idx = 0; for (Employee templateEmployee : templateEmployees) { Employee employee = new Employee( templateEmployee.getName() + randomInfo, templateEmployee.getDepartment() + randomInfo); employees[idx++] = employee; employeeDao.add(employee); } return new Task(description, manager, employees); } }
package org.timesheet.service.dao; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.timesheet.DomainAwareBase; import org.timesheet.domain.Employee; import org.timesheet.domain.Manager; import org.timesheet.domain.Task; import org.timesheet.domain.Timesheet; import java.util.Arrays; import java.util.List; import static org.junit.Assert.*; @ContextConfiguration(locations = '/persistence-beans.xml') public class TimesheetDaoTest extends DomainAwareBase { @Autowired private TimesheetDao timesheetDao; // daos needed for integration test of timesheetDao @Autowired private TaskDao taskDao; @Autowired private EmployeeDao employeeDao; @Autowired private ManagerDao managerDao; // common fields for timesheet creation private Task task; private Employee employee; @Override public void deleteAllDomainEntities() { super.deleteAllDomainEntities(); setUp(); } public void setUp() { employee = new Employee('Steve', 'Engineering'); employeeDao.add(employee); Manager manager = new Manager('Bob'); managerDao.add(manager); task = new Task('Learn Spring', manager, employee); taskDao.add(task); } @Test public void testAdd() { int size = timesheetDao.list().size(); Timesheet timesheet = newTimesheet(); timesheetDao.add(timesheet); assertTrue (size < timesheetDao.list().size()); } @Test public void testUpdate() { Timesheet timesheet = newTimesheet(); timesheetDao.add(timesheet); // update timesheet timesheet.setHours(6); taskDao.update(timesheet.getTask()); timesheetDao.update(timesheet); Timesheet found = timesheetDao.find(timesheet.getId()); assertTrue(6 == found.getHours()); } @Test public void testFind() { Timesheet timesheet = newTimesheet(); timesheetDao.add(timesheet); assertEquals(timesheet, timesheetDao.find(timesheet.getId())); } @Test public void testList() { assertEquals(0, timesheetDao.list().size()); Timesheet templateTimesheet = newTimesheet(); List<Timesheet> timesheets = Arrays.asList( newTimesheetFromTemplate(templateTimesheet, 4), newTimesheetFromTemplate(templateTimesheet, 7), newTimesheetFromTemplate(templateTimesheet, 10) ); for (Timesheet timesheet : timesheets) { timesheetDao.add(timesheet); } List<Timesheet> found = timesheetDao.list(); assertEquals(3, found.size()); for (Timesheet timesheet : found) { assertTrue (timesheets.contains(timesheet)); } } @Test public void testRemove() { Timesheet timesheet = newTimesheet(); timesheetDao.add(timesheet); // successfully added assertEquals(timesheet, timesheetDao.find(timesheet.getId())); // try to remoce timesheetDao.remove(timesheet); assertNull (timesheetDao.find(timesheet.getId())); } /** * @return Dummy timesheet for testing */ private Timesheet newTimesheet() { return new Timesheet(employee, task, 5); } private Timesheet newTimesheetFromTemplate(Timesheet template, Integer hours) { return new Timesheet( template.getWho(), template.getTask(), hours ); } }
You can run your tests as individual classes or alltogether from your IDE, or you can run them as “test” goal from Maven like so (switch to project directory):
$ mvn test
Tests are pretty much similar so if you can understand at least one of them, you’re probably fine just to copy-paste them to your own project. If you care to spend little more time, feel free to write them on your own and do little experimentation to get Hibernate know a little better.
As for DAOs, we’re pretty much done. One thing left though – our TimesheetService interface. That’s the set of business operations that we’re intersted in, so let’s implement it using Hibernate. We’ll put TimesheetServiceImpl class under org.timesheet.service.impl package:
package org.timesheet.service.impl; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.timesheet.domain.Employee; import org.timesheet.domain.Manager; import org.timesheet.domain.Task; import org.timesheet.service.TimesheetService; import org.timesheet.service.dao.TaskDao; import java.util.ArrayList; import java.util.List; import java.util.Random; @Transactional(propagation= Propagation.REQUIRED, readOnly=false) @Service('timesheetService') public class TimesheetServiceImpl implements TimesheetService { // dependencies private SessionFactory sessionFactory; private TaskDao taskDao; private Random random = new Random(); @Autowired public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } @Autowired public void setTaskDao(TaskDao taskDao) { this.taskDao = taskDao; } public SessionFactory getSessionFactory() { return sessionFactory; } public TaskDao getTaskDao() { return taskDao; } private Session currentSession() { return sessionFactory.getCurrentSession(); } @Override public Task busiestTask() { List<Task> tasks = taskDao.list(); if (tasks.isEmpty()) { return null; } Task busiest = tasks.get(0); for (Task task : tasks) { if (task.getAssignedEmployees().size() > busiest.getAssignedEmployees().size()) { busiest = task; } } return busiest; } @Override public List<Task> tasksForEmployee(Employee employee) { List<Task> allTasks = taskDao.list(); List<Task> tasksForEmployee = new ArrayList<Task>(); for (Task task : allTasks) { if (task.getAssignedEmployees().contains(employee)) { tasksForEmployee.add(task); } } return tasksForEmployee; } @Override public List<Task> tasksForManager(Manager manager) { Query query = currentSession() .createQuery('from Task t where t.manager.id = :id'); query.setParameter('id', manager.getId()); return query.list(); } }
Note that we use @Service annotation this time (we’ve talked about these before). Also we’re injecting some DAOs with setter injection. Some business method aren’t implemented most effectively, but we’re demonstrating that we can either mix generic DAO logic or create our own queries using HQL. We could have picked Criteria API, it doesn’t really matter now. The biggest downside about HQL is that it’s plain strings so it’s not refactoring friendly – unless you use proper tooling. For example, IntelliJ has autocompletion even for plain strings. It just figures out that you’re writing HQL. Pretty useful is also HQL console, IntelliJ has one and there’s plugin for Eclipse.
IntelliJ highlighting & autocompletion for HQL:
Now we should test this service. This time we don’t want to create instances of entities in Java, we’ll use external SQL scripts we created before – for setting up and cleaning the data.
Let’s put test class TimesheetServiceTest in package org.timesheet.service in src/test/java folder. In the following code, note how we’re using jdbcTemplate bean:
package org.timesheet.service; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.FileSystemResource; import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; import org.springframework.test.jdbc.SimpleJdbcTestUtils; import org.timesheet.domain.Employee; import org.timesheet.domain.Manager; import org.timesheet.domain.Task; import org.timesheet.service.dao.EmployeeDao; import org.timesheet.service.dao.ManagerDao; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @ContextConfiguration(locations = '/persistence-beans.xml') public class TimesheetServiceTest extends AbstractJUnit4SpringContextTests { @Autowired private TimesheetService timesheetService; // resources for accessing data during the testing @Autowired private SimpleJdbcTemplate jdbcTemplate; @Autowired private EmployeeDao employeeDao; @Autowired private ManagerDao managerDao; private final String createScript = 'src/main/resources/sql/create-data.sql'; private final String deleteScript = 'src/main/resources/sql/cleanup.sql'; @Before public void insertData() { SimpleJdbcTestUtils.executeSqlScript(jdbcTemplate, new FileSystemResource(createScript), false); } @After public void cleanUp() { SimpleJdbcTestUtils.executeSqlScript(jdbcTemplate, new FileSystemResource(deleteScript), false); } @Test public void testBusiestTask() { Task task = timesheetService.busiestTask(); assertTrue(1 == task.getId()); } @Test public void testTasksForEmployees() { Employee steve = employeeDao.find(1L); Employee bill = employeeDao.find(2L); assertEquals(2, timesheetService.tasksForEmployee(steve).size()); assertEquals(1, timesheetService.tasksForEmployee(bill).size()); } @Test public void testTasksForManagers() { Manager eric = managerDao.find(1L); assertEquals(1, timesheetService.tasksForManager(eric).size()); } }
Allright, that’s it. We’ve implemented DAO and service layer. This included quite lot of code, so before you continue make sure that you’re project structure looks like this:
Reference: Part 3 – DAO and Service layer from our JCG partner Michal Vrtiak at the vrtoonjava blog.
Can u provide mapping xml also here
yeah sure
Great post, thanks Michal.
I wonder that why don’t you public full project source here?
Hey guys, my original article is on my blog, JCG reposts posts like mine. Here you will find sources and everything :)
http://vrtoonjava.wordpress.com/2012/06/17/shall-we-do-some-spring-together/
Hello,
Is there any Git link through which I can download a whole project code, to make it easier to understand project’s logic?
Sure, here you go https://bitbucket.org/vrto/spring-tutorial
Michal Vrtiak, you are awesome dude!. This link https://bitbucket.org/vrto/spring-tutorial is very useful.
Good day,
I am trying to follow this tutorial but I am facing an issue right now, the error says:
no suitable method found for get(java.lang.Class,K)
method org.hibernate.Session.get(java.lang.Class,java.io.Serializable) is not applicable
(cannot infer type-variable(s) T
(argument mismatch; K cannot be converted to java.io.Serializable))
method org.hibernate.Session.get(java.lang.Class,java.io.Serializable,org.hibernate.LockMode) is not applicable
(cannot infer type-variable(s) T
(actual and formal argument lists differ in length))
method org.hibernate.Session.get(java.lang.Class,java.io.Serializable,org.hibernate.LockOptions) is not applicable
(cannot infer type-variable(s) T
(actual and formal argument lists differ in length))
method org.hibernate.Session.get(java.lang.String,java.io.Serializable) is not applicable
(argument mismatch; java.lang.Class cannot be converted to java.lang.String)
[INFO] 1 error
Repository annotation with single quote did not work in spring 4.4 , It should be replaced by double quote.