Defining the DAO implementations

The following DAO implementations will inherit the core CRUD operations from GenericDaoImpl and add their own class-specific methods as defined in the implemented interface. Each method will use the @Transactional annotation to define the appropriate transactional behavior.

The CompanyDaoImpl class

The full listing for our CompanyDaoImpl class is as follows:

package com.gieman.tttracker.dao;

import com.gieman.tttracker.domain.Company;
import java.util.List;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Repository("companyDao")
@Transactional
public class CompanyDaoImpl extends GenericDaoImpl<Company, Integer> 
    implements CompanyDao {

    public CompanyDaoImpl() {
        super(Company.class);
    }

    @Override
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public List<Company> findAll() {
        return em.createNamedQuery("Company.findAll")
                .getResultList();
    }    
}

The first thing to notice is the @Repository("companyDao") annotation. This annotation is used by Spring to automatically detect and process DAO objects when the application is loaded. The Spring API defines this annotation as follows:

Note

It indicates that an annotated class is a Repository, originally defined by Domain-Driven Design (Evans, 2003) as a mechanism for encapsulating storage, retrieval, and search behavior that emulate a collection of objects.

The purpose of the annotation is to allow Spring to auto detect implementing classes through the classpath scanning and to process this class for data access exception translation (used by Spring to abstract database exception messages from the underlying implementation). The Spring application will then hold a reference to the implementing class under the key companyDao. It is considered as the best practice to match the key value with the name of the implemented interface.

The CompanyDaoImpl class also introduces the use of the JPA named queries that were defined during the reverse engineering process in the previous chapter. The method call em.createNamedQuery("Company.findAll") creates the named query defined by the unique identifier "Company.findAll" in the persistence engine. This named query was defined in the Company class. Calling getResultList() executes the query against the database, returning a java.util.List of Company objects. Let's now review the named query definition in the Company class:

@NamedQuery(name = "Company.findAll", query = "SELECT c FROM Company c")

We will make a minor change to this named query to arrange the results by companyName in ascending order. This will require the addition of an ORDER BY clause in the query statement. The final named queries definition in the Company class will now look like the following code:

@NamedQueries({
    @NamedQuery(name = "Company.findAll", query = "SELECT c FROM Company c ORDER BY c.companyName ASC "),
    @NamedQuery(name = "Company.findByIdCompany", query = "SELECT c FROM Company c WHERE c.idCompany = :idCompany"),
    @NamedQuery(name = "Company.findByCompanyName", query = "SELECT c FROM Company c WHERE c.companyName = :companyName")})

The ProjectDaoImpl class

This implementation is defined as:

package com.gieman.tttracker.dao;

import com.gieman.tttracker.domain.Company;
import com.gieman.tttracker.domain.Project;
import java.util.List;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Repository("projectDao")
@Transactional
public class ProjectDaoImpl extends GenericDaoImpl<Project, Integer> 
    implements ProjectDao {
  
    public ProjectDaoImpl() {
        super(Project.class);
    }

    @Override
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public List<Project> findAll() {
        return em.createNamedQuery("Project.findAll")
                .getResultList();
    }    
}

Once again, we will add the ORDER BY clause to the Project.findAll named query in the Project class:

@NamedQuery(name = "Project.findAll", query = "SELECT p FROM Project p ORDER BY p.projectName")

The TaskDaoImpl class

This class is defined as:

package com.gieman.tttracker.dao;

import com.gieman.tttracker.domain.Project;
import com.gieman.tttracker.domain.Task;
import java.util.List;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Repository("taskDao")
@Transactional
public class TaskDaoImpl extends GenericDaoImpl<Task, Integer> implements TaskDao {

    public TaskDaoImpl() {
        super(Task.class);
    }

    @Override
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public List<Task> findAll() {
        return em.createNamedQuery("Task.findAll")
                .getResultList();
    }
}

Once again, we will add the ORDER BY clause to the Task.findAll named query in the Task class:

@NamedQuery(name = "Task.findAll", query = "SELECT t FROM Task t ORDER BY t.taskName")

The UserDaoImpl class

This UserDaoImpl class will require an additional named query in the User domain class to test a user's logon credentials (username/password combination). The UserDaoImpl class definition follows:

package com.gieman.tttracker.dao;

import com.gieman.tttracker.domain.User;
import java.util.List;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Repository("userDao")
@Transactional
public class UserDaoImpl extends GenericDaoImpl<User, String> implements UserDao {

    public UserDaoImpl() {
        super(User.class);
    }

    @Override
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public List<User> findAll() {
        return em.createNamedQuery("User.findAll")
                .getResultList();
    }

    @Override
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public User findByUsernamePassword(String username, String password) {
       
        List<User> users = em.createNamedQuery("User.findByUsernamePassword")
                .setParameter("username", username)
                .setParameter("password", password)
                .getResultList();
        
        return (users.size() == 1 ? users.get(0) : null);
    }    
    
    @Override
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public User findByUsername(String username) {
        List<User> users = em.createNamedQuery("User.findByUsername")
                .setParameter("username", username)
                .getResultList();
        
        return (users.size() == 1 ? users.get(0) : null);
    }    
    
    @Override
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public User findByEmail(String email) {
       
        List<User> users = em.createNamedQuery("User.findByEmail")
                .setParameter("email", email)
                .getResultList();
        
        return (users.size() == 1 ? users.get(0) : null);
    }    
}

The missing named query is User.findByUsernamePassword that is used to verify a user with the given username and password. The query definition must be added to the User class as follows:

@NamedQuery(name = "User.findByUsernamePassword", query = "SELECT u FROM User u WHERE u.password = :password AND (u.email = :username OR u.username = :username)")

Note that this definition allows a user to be matched by either the username or e-mail field. As is the common practice in web applications, a user may log on with either their unique logon name (username) or their e-mail address.

The findByEmail, findByUsername, and findByUsernamePassword methods can only ever return null (no match found) or a single result as there cannot be more than one record in the database with these unique fields. Instead of using the getResultList() method to retrieve a List of results and testing for a list size of one, we could have used the code that is similar to the following:

public User findByEmail(String email) {

  User user = (User) em.createNamedQuery("User.findByEmail")
      .setParameter("email", email)
      .getSingleResult();

  return user;
}

The getSingleResult() method returns exactly one result or throws an exception if a single result could not be found. You will also notice the need to cast the returned result to the required User type. The calling method would also need to catch any exceptions that would be thrown from the getSingleResult() method unless the sample code given previously is changed to catch the exception.

public User findByEmail(String email) {

  User user = null;

  try {
    user = (User) em.createNamedQuery("User.findByEmail")
      .setParameter("email", email)
      .getSingleResult();

  } catch(NoResultException nre){

  }
  return user;
}

We believe that the code in our UserDaoImpl interface is cleaner than the previous example that uses the try/catch function to wrap the getSingleResult() method. In both cases, however, the method returns null if the record cannot be found.

Note

Exceptions should be used judiciously in enterprise programming and only for truly exceptional circumstances. Throwing exceptions should be avoided unless the exception indicates a situation that the calling code cannot recover from. It is far cleaner to return null (or perhaps true/false in appropriate scenarios) to indicate that a situation is not as expected.

We do not consider being unable to find a record by ID, or by e-mail or by e-mail address as an exceptional circumstance; it is possible that a different user has deleted the record, or there is simply no record with the e-mail specified. Returning null clearly identifies that the record was not found without the need to throw an exception.

Regardless of whether you throw exceptions to indicate a record that cannot be found or use null as is our preference, your API should be documented to indicate the behavior. The UserDaoImpl.findByUsernamePassword method could, for example, be documented as follows:

/**
 * Find a User with the username/password combination or return null
 * if a valid user could not be found.
 * @param username
 * @param password
 * @return valid User object or null if not found.
 */

Users of your API will then understand the expected behavior and code their interactions accordingly.

The TaskLogDaoImpl class

The final DAO class in our application follows:

package com.gieman.tttracker.dao;

import com.gieman.tttracker.domain.Task;
import com.gieman.tttracker.domain.TaskLog;
import com.gieman.tttracker.domain.User;
import java.util.Date;
import java.util.List;
import javax.persistence.TemporalType;

public class TaskLogDaoImpl extends GenericDaoImpl<TaskLog, Integer> implements TaskLogDao {

    public TaskLogDaoImpl() {
        super(TaskLog.class);
    }

    @Override
    public List<TaskLog> findByUser(User user, Date startDate, Date endDate) {
        return em.createNamedQuery("TaskLog.findByUser")
                .setParameter("user", user)
                .setParameter("startDate", startDate, TemporalType.DATE)
                .setParameter("endDate", endDate, TemporalType.DATE)
                .getResultList();
    }

    @Override
    public long findTaskLogCountByTask(Task task) {
        Long count = (Long) em.createNamedQuery("TaskLog.findTaskLogCountByTask")
                .setParameter("task", task)
                .getSingleResult();
        return count;
    }

    @Override
    public long findTaskLogCountByUser(User user) {
        Long count = (Long) em.createNamedQuery("TaskLog.findTaskLogCountByUser")
                .setParameter("user", user)
                .getSingleResult();

        return count;
    }
}

This time, we will refactor the TaskLog named queries as follows:

@NamedQueries({
    @NamedQuery(name = "TaskLog.findByUser", query = "SELECT tl FROM TaskLog tl WHERE tl.user = :user AND tl.taskLogDate BETWEEN :startDate AND :endDate order by tl.taskLogDate ASC"),
    @NamedQuery(name = "TaskLog.findTaskLogCountByTask", query = "SELECT count(tl) FROM TaskLog tl WHERE tl.task = :task "),
    @NamedQuery(name = "TaskLog.findTaskLogCountByUser", query = "SELECT count(tl) FROM TaskLog tl WHERE tl.user = :user ")
})

We have removed several queries that will not be required and added three new ones as shown. The TaskLog.findByUser query will be used to list task logs assigned to a user for the given date range. Note the use of the BETWEEN key word to specify the date range. Also note the use of the TemporalType.DATE when setting the parameter in the TaskLogDaoImpl.findByUser method. This will ensure a strict date comparison, ignoring any time component, if present, in the arguments.

The TaskLog.findTaskLogCountByTask and TaskLog.findTaskLogCountByUser named queries will be used in our service layer to test if deletions are permitted. We will implement checks to ensure that a user or a task may not be deleted if valid task logs are assigned.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.35.193