5
\$\begingroup\$

I am trying to learn how to design and implement a layered Java web application with a presentation layer, service layer and the DAO layer. I have used the Struts 1.3 framework with Java JDK 1.6 for the below explained sample project.

Questions:

  1. How GOOD or BAD is the below implementation of the layered web application architecture? Is this the correct way to implement a layered architecture in a Java web application? Is this implementation following MVC strictly?

  2. Is this the correct way of propagating exceptions from one layer to another?

  3. Where should logging be done: in all 3 layers or for every request?

  4. Is the UserDTO object used correctly in this code? I have written the bean data copy code in the formbean, to be used when sending data from presentation layer to service layer and then from there to the DAO layer. Is it okay? Where should the corresponding code for copying User data from a resultset to the DTO object in DAO layer be implemented?

  5. Also, PostgreSQLDAOFactory.java contains 2 static getData(..) methods. Since they are DB independent, where should I put them so that any DB implementation of JDBC DAO code can access them? Can they be static methods? If yes, should they be synchronized methods or with synchronized code blocks?

  6. Since every request is processed by a new thread in Java web application should the service and DAO layer methods be synchronized so that there are no concurrency problems and lack of thread-safety?

  7. Is it standard to use different packages for the interfaces and concrete implementations? If not, what is the standard layout of the packages in a Java EE web app with such layered architecture?

  8. In UserServiceImpl.java file I am setting the datasource type to PostgreSQL. If the datasource type changes, doing this way I'll have to change this setting in many such ServiceImpl files (for example, ProductServiceImpl, CustomerServiceImpl, etc.). What is the standard way to set the datasource type such that if the datasource type changes I will have to change that at only one place in the project? In which layer and source file should that datasource property be read from such a settings file and set to be used by rest of the web app code?

Directory structure of this simple Eclipse project:

Directory Structure-1

Directory Structure-2

DAOFactory.java

package com.ex.dao;

import com.ex.dao.interfaces.UserDAO;

public abstract class DAOFactory {
    // List of DAO types supported by the factory
    public static final int POSTGRESQL = 1;
    public static final int ORACLE = 2;

    // There will be a method for each DAO that can be 
    // created. The concrete factories will have to 
    // implement these methods.
    public abstract UserDAO getUserDAO();

    public static DAOFactory getDAOFactory(int intWhichFactory) {
        switch (intWhichFactory) 
        {
            case POSTGRESQL : 
                return new PostgreSQLDAOFactory();
            case ORACLE : 
                return new OracleDAOFactory();
            default : 
                return null;
        }
    }
}

PostgreSQLDAOFactory.java

package com.ex.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import com.ex.dao.exceptions.DAOConfigurationException;
import com.ex.dao.impl.PostgreSQLUserDAOImpl;
import com.ex.dao.interfaces.UserDAO;

public class PostgreSQLDAOFactory extends DAOFactory {
    @Override
    public UserDAO getUserDAO() {
        // PostgreSQLUserDAOImpl implements UserDAO
        return new PostgreSQLUserDAOImpl();
    }

    // method to create POSTGRESQL DB connections
    public static Connection createConnection() {
        // Use DRIVER and DBURL to create a connection
        // Recommend connection pool implementation/usage
        Connection connection = null;
        String strDatasourceName="jdbc/sampleDS";
        try
        {
            Context initContext = new InitialContext();
            Context envContext = (Context) initContext.lookup("java:/comp/env");
            DataSource dataSource = (DataSource) envContext.lookup(strDatasourceName);
            connection = dataSource.getConnection();
        }
        catch(SQLException sqle){
            throw new DAOConfigurationException("Error while creating connection to the database server", sqle);
        }
        catch(NamingException ne){
            throw new DAOConfigurationException("DataSource '" + strDatasourceName + "' is missing in JNDI", ne);
        }

        return connection;
    }

    public static Collection<List<Object>> getData(String strQuery) {
        List<List<Object>> aLstQryResultData = new ArrayList<List<Object>>();
        Connection conn = null;
        PreparedStatement pStmt = null;
        ResultSet resultSet = null;
        try {
            conn = createConnection();
            pStmt = conn.prepareStatement(strQuery);
            resultSet = pStmt.executeQuery();

            if (resultSet != null) {
                while (resultSet.next()) {
                    List<Object> lstRowData = new ArrayList<Object>();
                    for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) {
                        lstRowData.add(resultSet.getObject(i));
                    }
                    aLstQryResultData.add(lstRowData);
                }
            }
        }
        catch (Exception e) {
            // Handle the error
        }
        finally {
            try {
                if (resultSet != null) {
                    resultSet.close();
                }
            } catch (Exception ex) {}

            try {
                if (pStmt != null) {
                    pStmt.close();
                }
            } catch (Exception ex) {}

            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (Exception ex) {}
        }

        return aLstQryResultData;
    }

    public static Collection<List<Object>> getData(String strQuery, Object[] objArrOfQryParams) {
        List<List<Object>> aLstQryResultData = new ArrayList<List<Object>>();
        Connection conn = null;
        PreparedStatement pStmt = null;
        ResultSet resultSet = null;
        try {
            conn = createConnection();
            pStmt = conn.prepareStatement(strQuery);
            for (int i = 0; i < objArrOfQryParams.length; i++) {
                pStmt.setObject(i + 1, objArrOfQryParams[i]);
            }
            resultSet = pStmt.executeQuery();

            if (resultSet != null) {
                while (resultSet.next()) {
                    List<Object> lstRowData = new ArrayList<Object>();
                    for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) {
                        lstRowData.add(resultSet.getObject(i));
                    }
                    aLstQryResultData.add(lstRowData);
                }
            }
        }
        catch (Exception e) {
            // Handle the error
        }
        finally {
            try {
                if (resultSet != null) {
                    resultSet.close();
                }
            } catch (Exception ex) {}

            try {
                if (pStmt != null) {
                    pStmt.close();
                }
            } catch (Exception ex) {}

            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (Exception ex) {}
        }

        return aLstQryResultData;
    }   
}

UserDAO.java

package com.ex.dao.interfaces;

import java.util.Collection;
import java.util.List;

import com.ex.dto.UserDTO;

public interface UserDAO {
    public boolean addUser(UserDTO userDTO);
    public UserDTO findUser();
    public Collection<List<Object>> fetchUserRolesList();
}

PostgreSQLUserDAOImpl.java

package com.ex.dao.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;

import com.ex.dao.PostgreSQLDAOFactory;
import com.ex.dao.exceptions.DAOException;
import com.ex.dao.interfaces.UserDAO;
import com.ex.dto.UserDTO;

public class PostgreSQLUserDAOImpl implements UserDAO {
    public PostgreSQLUserDAOImpl() {
        // initialization 
    }

    // The following methods can use
    // PostgreSQLDAOFactory.createConnection() 
    // to get a connection as required

    public boolean addUser(UserDTO userDTO) 
    {
        Connection conn = null;
        PreparedStatement pstmt = null;
        int intCount = 0;
        try {
            conn = PostgreSQLDAOFactory.createConnection();
            StringBuilder strBldr4Qry = new StringBuilder();
            strBldr4Qry.append("INSERT INTO ")
                        .append("USER_DETAILS_NEW ")
                        .append("(USER_ID,USER_NAME,ROLE_ID) ")
                        .append("VALUES ")
                        .append("(?,?,?)");
            pstmt = conn.prepareStatement(strBldr4Qry.toString());
            pstmt.setString(1, userDTO.getUserID());
            pstmt.setString(2, userDTO.getUserName());
            pstmt.setInt(3, userDTO.getUserRoleID());
            intCount = pstmt.executeUpdate();
        } catch (SQLException sqlEx) {
            intCount = -1;
            //System.out.println(ex);
            // should we log as close to the origin as possible? Should it be done here?
            throw new DAOException("Could not add a new user",sqlEx);
        } 
        catch (Exception e) {
            intCount = -1;
            //System.out.println(ex);
            // should we log as close to the origin as possible? Should it be done here?
            throw new DAOException("Could not add a new user.",e);
        }
        finally {
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
            } catch (Exception e) {}
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (Exception e) {}
        }

        if (intCount > 0)
            return true;
        else
            return false;
    }

    public UserDTO findUser() {
        // TODO
        // Implement find a user here using supplied
        // argument values as search criteria
        // Return a Transfer Object if found,
        // return null on error or if not found

        return null;
    }

    public Collection<List<Object>> fetchUserRolesList() {
        StringBuilder strBldr4Qry = new StringBuilder();
        strBldr4Qry.append("SELECT ")
                    .append("ROLE_ID,ROLE_NAME ")
                    .append("FROM ")
                    .append("USER_ROLES ")
                    .append("ORDER BY ROLE_NAME");

        return PostgreSQLDAOFactory.getData(strBldr4Qry.toString());
    }
}

DAOConfigurationException.java

package com.ex.dao.exceptions;
    public class DAOConfigurationException extends RuntimeException {

        private static final long serialVersionUID = 1L;

        public DAOConfigurationException(String message) {
            super(message);
        }

        public DAOConfigurationException(Throwable cause) {
            super(cause);
        }

        public DAOConfigurationException(String message, Throwable cause) {
            super(message, cause);
        }
    }

DAOException.java

package com.ex.dao.exceptions;

public class DAOException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public DAOException(String message) {
        super(message);
    }

    public DAOException(Throwable cause) {
        super(cause);
    }

    public DAOException(String message, Throwable cause) {
        super(message, cause);
    }
}

UserDTO.java

package com.ex.dto;

public class UserDTO implements java.io.Serializable {

    private static final long serialVersionUID = 1L;

    private String userID;
    private String userName;
    private Integer userRoleID;

    public String getUserID() {
        return userID;
    }
    public void setUserID(String userID) {
        this.userID = userID;
    }

    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Integer getUserRoleID() {
        return userRoleID;
    }
    public void setUserRoleID(Integer userRoleID) {
        this.userRoleID = userRoleID;
    }
}

UserService.java

package com.ex.service.interfaces;

import java.util.Collection;
import java.util.List;

import com.ex.dto.UserDTO;
import com.ex.service.exceptions.ServiceException;

public interface UserService {
    public boolean addUser(UserDTO userDTO) throws ServiceException;
    public Collection<List<Object>> fetchUserRolesList();
}

UserServiceImpl.java

package com.ex.service.impl;

import java.util.Collection;
import java.util.List;

import com.ex.dao.DAOFactory;
import com.ex.dao.exceptions.DAOException;
import com.ex.dao.interfaces.UserDAO;
import com.ex.dto.UserDTO;
import com.ex.service.exceptions.ServiceException;
import com.ex.service.interfaces.UserService;

public class UserServiceImpl implements UserService {
    private DAOFactory daoFactory=DAOFactory.getDAOFactory(DAOFactory.POSTGRESQL);
    // Create a DAO
    UserDAO userDAO = daoFactory.getUserDAO();

    public boolean addUser(UserDTO userDTO) throws ServiceException {
        boolean boolResult=false;

        try {
            boolResult=userDAO.addUser(userDTO);
        }
        catch (DAOException daoEx) {
            throw new ServiceException(daoEx);
        }

        return boolResult;
    }

    public Collection<List<Object>> fetchUserRolesList() {
        return userDAO.fetchUserRolesList();
    }
}

ServiceException.java

package com.ex.service.exceptions;

public class ServiceException extends Exception {
    private static final long serialVersionUID = 1L;

    public ServiceException(String message) {
        super(message);
    }

   public ServiceException(Throwable cause) {
        super(cause);
    }

    public ServiceException(String message, Throwable cause) {
        super(message, cause);
    }
}

UserAction.java

package com.ex.struts.action;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.actions.DispatchAction;
import org.apache.struts.util.LabelValueBean;

import com.ex.service.exceptions.ServiceException;
import com.ex.service.impl.UserServiceImpl;
import com.ex.service.interfaces.UserService;
import com.ex.struts.formbeans.UserForm;

public class UserAction extends DispatchAction {
    public ActionForward beginAddUser(ActionMapping mapping,ActionForm form,HttpServletRequest request,HttpServletResponse response)
    {
        try
        {
            UserForm userForm = (UserForm)form;
            userForm.setUserID(null);
            userForm.setUserName(null);
            userForm.setUserRoleID(null);

            HttpSession httpSession = request.getSession();
            UserService userService = new UserServiceImpl();
            Collection<LabelValueBean> colxnUserRoles = createLabelValueList(userService.fetchUserRolesList());
            httpSession.setAttribute("userRolesList",colxnUserRoles);
        }
        catch(Exception e)
        {
            System.out.println(e);
        }

        return mapping.findForward("fwdAddUser");
    }

    public ActionForward insertUser(ActionMapping mapping,ActionForm form,HttpServletRequest request,HttpServletResponse response)
    {
        try
        {
            UserService userService = new UserServiceImpl();
            UserForm userForm = (UserForm)form;
            if(userService.addUser(userForm.getUserDTO())) {
                System.out.println("New User created");
            }
            else {
                System.out.println("New User could not be created.");
            }
        }
        catch(ServiceException se)
        {
            System.out.println(se.getMessage());
        }

        return mapping.findForward("fwdAddUser");
    }

    protected Collection<LabelValueBean> createLabelValueList(Collection<List<Object>> inData)
    {
        List<LabelValueBean> outData = new ArrayList<LabelValueBean>();
        if(inData!=null && inData.size()>0)
        {
            Iterator<List<Object>> it = inData.iterator();
            while (it.hasNext())
            {
                List<Object> tmp = it.next();
                outData.add(new LabelValueBean(tmp.get(1).toString(), tmp .get(0).toString()));
            }
        }

        return outData;
    }
}

UserForm.java

package com.ex.struts.formbeans;

import org.apache.struts.action.ActionForm;

import com.ex.dto.UserDTO;

public class UserForm extends ActionForm {
    private static final long serialVersionUID = 1L;

    private String actionMethod;

    private String userID;
    private String userName;
    private Integer userRoleID;

    public String getActionMethod() {
        return actionMethod;
    }
    public void setActionMethod(String actionMethod) {
        this.actionMethod = actionMethod;
    }

    public String getUserID() {
        return userID;
    }
    public void setUserID(String userID) {
        this.userID = userID;
    }

    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Integer getUserRoleID() {
        return userRoleID;
    }
    public void setUserRoleID(Integer userRoleID) {
        this.userRoleID = userRoleID;
    }

    // Method to get Transfer Object for User data
    public UserDTO getUserDTO() {
        return createUserDTO();
    }

    // method to create a new Transfer Object and 
    // copy data from entity bean into the value 
    // object
    private UserDTO createUserDTO() {
        UserDTO userDTO = new UserDTO();
        userDTO.setUserID(this.userID);
        userDTO.setUserName(this.userName);
        userDTO.setUserRoleID(this.userRoleID);

        return userDTO;
    }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Welcome to CR! Are you on Java 8? Or at least Java 7? \$\endgroup\$ – h.j.k. Sep 10 '15 at 18:50
5
\$\begingroup\$

DAOFactory

public abstract class DAOFactory {
    // List of DAO types supported by the factory
    public static final int POSTGRESQL = 1;
    public static final int ORACLE = 2;

    ...
}

Woah, hold your horses! You should be using enums here, instead of plain int values. Plain int values for this scenario are ripe for abuse when you accidentally switch against 11 instead of 1, and you can say goodbye to an hour of additional sleep at 3 am while debugging furiously.

PostgreSQLUserDAOImpl

Under addUser(UserDTO):

    if (intCount > 0)
        return true;
    else
        return false;

The above block can be simplified as return intCount > 0. Slightly nitpicking on this, but the variable name strBldr4Qry in fetchUserRolesList() doesn't quite roll off the tongue... perhaps you are looking for queryBuilder?

edit: For the following method which is implemented by this class:

public interface UserDAO {
    public Collection<List<Object>> fetchUserRolesList();
}

Is there any reason why you are using Object as the generic type for the List? Maybe you need a domain class for it as well?


Other observations:

  • Your braces-style is extremely inconsistent, even across classes, not to say the entire codebase. I will strongly suggest standardizing the style as consistent code readability is beneficial in the long run.

  • If you are on Java 7, I think applying try-with-resources will greatly reduce the amount of boilerplate-like resource-handling code you have. If you are on Java 8, I foresee you might even be able to do some stream-based processing on the loops, or even use a library like jOOQ to simplify parts of your code.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.