Enterprise Java

Spring – DAO and Service layer

Welcome to the third part of Spring tutorial. In this part, we will continue in writing our Timesheet application and this time we’ll implement DAO layer, business services and write some tests.

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.

Michal Vrtiak

Michal is a freelancer currently located in Prague, Czech Republic with huge passion for Java platform. He is very enthusiastic about Dependency Injection, IntelliJ IDEA and loves to use both Spring and Java EE.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

9 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
roney
roney
11 years ago

Can u provide mapping xml also here

chirag
chirag
9 years ago
Reply to  roney

yeah sure

Terence
Terence
10 years ago

Great post, thanks Michal.
I wonder that why don’t you public full project source here?

Michal
10 years ago

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/

Prateek Ashtikar
Prateek Ashtikar
10 years ago

Hello,

Is there any Git link through which I can download a whole project code, to make it easier to understand project’s logic?

Michal
10 years ago
Suresh
Suresh
9 years ago

Michal Vrtiak, you are awesome dude!. This link https://bitbucket.org/vrto/spring-tutorial is very useful.

Phila Tshaka
Phila Tshaka
7 years ago

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

Mohammadreza Khatami
Mohammadreza Khatami
7 years ago

Repository annotation with single quote did not work in spring 4.4 , It should be replaced by double quote.

Back to top button