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: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?Is this the correct way of propagating exceptions from one layer to another? Where should logging be done: in all 3 layers or for every request?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?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?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?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?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:DAOFactory.javapackage 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.javapackage 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.javapackage 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.javapackage 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.javapackage 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.javapackage 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.javapackage 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.javapackage 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.javapackage 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.javapackage 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.javapackage 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;    }}
Layered architecture implementation in a Java web application
java;mvc;error handling;thread safety;logging