I initially thought that this was just an architecture issue so I placed it on programmers as Thoughts on refactoring a generic DAO. Then I asked on codereview meta here and decided to put up the code here.
- Here's a github link if someone wants to see there.
- There are four DAOs - DAODelete, DAOUpdate, DAORead, DAOInsert but I am just putting DAORead and its implementation here so that it is easier to review.
- I have trimmed the code a lot so if something is amiss please tell me and I'll correct it. Otherwise you can see github link as it has complete code(kinda).
My architecture is like this
- Abstraction layer of DAO at top
- DataBase specific implementation of DAO BUT Table-independent
- Table dependency is passed down to a lower utility layer
- A
Enum
is passed down to the lower utility layer by which it gives table specific results - Table-specific utility classes for the lower utility layer mentioned in the previous point.
- There is a
Student
pojo that I am omitting because well it is just a pojo.private
variables and all. Just note thatenrollmentDate
is of typejava.sql.Date
My concern
- I am satisfied with the upper layer of DAO but I think that at the the lower level specifically at the
OracleSpecific.java
the things which should have strong coupling have weak coupling. e.g. there are different methods for getting pojo from resultset or getting primary key. Each of these in turn haveswitch
case for calling the functions in lower utility class. - Although the
schema-specific
methods are tied together in different utility classes the method call themselves inOracleSpecifics.java
do not have any coupling. - I am thinking whether I should change the enum
TableName
to contain a specific state. The state I am thinking for the schema-specific lowest level utility classes. These
specific states
contained in enums can be used to change the state of DAO and the DAO can then call the specific functions based on an interface implemented by all such states. Thus depending on the state the behavior can change automatically.Is this design decision correct or would it be meaningless?
- Any other thing that I might have overlooked?
- Any thoughts about the design itself?
- Would I lose type safety introduced by generics due to this change?
Enums Used
QueryType.java
package aseemEnums;
public enum QueryType {
READ, DELETE;
}
Databases.java
package aseemEnums;
public enum Databases {
Oracle;
}
TableName.java
package aseemEnums;
public enum TableName {
STUDENT_TABLE("STUDENT_TABLE");
private final String tableName;
TableName(String tableName) {
this.tableName = tableName;
}
public String toString() {
return tableName;
}
}
The Abstraction layer
DAOFactory.java
package aseemDao;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.naming.NamingException;
import aseemEnums.Databases;
public abstract class DAOFactory {
// Abstract Instance methods
public abstract DAOInsert getDAOInsert() throws SQLException;
public abstract DAORead getDAORead() throws SQLException;
public abstract DAODelete getDAODelete();
public abstract DAOUpdate getDAOUpdate();
// Concrete Class Methods
public static DAOFactory factoryProducer(Databases db)
throws NamingException {
switch (db) {
case Oracle:
return new OracleFactory();
default:
return null;
}
}
static void closeAll(PreparedStatement ps, ResultSet rs) {
try {
rs.close();
} catch (Exception e) {
}
try {
ps.close();
} catch (Exception e) {
}
}
}
DAORead.java
package aseemDao;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import aseemEnums.TableName;
public interface DAORead {
public abstract <T> List<T> getAll(Connection con, TableName tableName)
throws SQLException;
public abstract <T> List<T> getAllForInput(Connection con,
TableName tableName, String columnName, String searchValue)
throws SQLException;
public abstract <T> T getPojoForPrimarKey(Connection con,
TableName tableName, String primaryKey) throws SQLException;
public abstract <T> boolean alreadyExisting(Connection con,
TableName tableName, String primaryKey) throws SQLException;
public abstract <T> boolean alreadyExisting(Connection con,
TableName tableName, T currentPojo) throws SQLException;
}
Concrete Implementation of DAO's abstraction
OracleFactory.java
package aseemDao;
import java.sql.SQLException;
public class OracleFactory extends DAOFactory {
@Override
public DAOInsert getDAOInsert() throws SQLException {
return new OracleInsert(this);
}
@Override
public DAORead getDAORead() throws SQLException {
return new OracleRead(this);
}
@Override
public DAODelete getDAODelete() {
return new OracleDelete(this);
}
@Override
public DAOUpdate getDAOUpdate() {
return new OracleUpdate(this);
}
}
OracleRead.java
package aseemDao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import aseemEnums.QueryType;
import aseemEnums.TableName;
public class OracleRead implements DAORead {
DAOFactory fac = null;
OracleRead(DAOFactory fac) throws SQLException {
this.fac = fac;
}
@SuppressWarnings("unchecked")
public <T> List<T> getAll(Connection con, TableName tableName)
throws SQLException {
List<T> list = new ArrayList<T>();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = con.prepareStatement("select * from " + tableName);
rs = ps.executeQuery();
while (rs.next()) {
list.add((T) OracleSpecifics
.getPojoFromResultSet(tableName, rs));
}
} finally {
DAOFactory.closeAll(ps, rs);
}
return list;
}
@SuppressWarnings("unchecked")
public <T> List<T> getAllForInput(Connection con, TableName tableName,
String columnName, String searchValue) throws SQLException {
List<T> list = new ArrayList<T>();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = con.prepareStatement("SELECT * FROM " + tableName + " WHERE "
+ columnName + " LIKE '%" + searchValue + "%'");
rs = ps.executeQuery();
while (rs.next()) {
list.add((T) OracleSpecifics
.getPojoFromResultSet(tableName, rs));
}
} finally {
DAOFactory.closeAll(ps, rs);
}
return list;
}
@Override
public <T> T getPojoForPrimarKey(Connection con, TableName tableName,
String primaryKey) throws SQLException {
T currentPojo = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
String queryString = OracleSpecifics.queryString(tableName,
primaryKey, QueryType.READ);
ps = con.prepareStatement(queryString);
rs = ps.executeQuery();
if (rs.next()) {
currentPojo = OracleSpecifics.getPojoFromResultSet(tableName,
rs);
}
} finally {
DAOFactory.closeAll(ps, rs);
}
return currentPojo;
}
@Override
public <T> boolean alreadyExisting(Connection con, TableName tableName,
String primaryKey) throws SQLException {
if (getPojoForPrimarKey(con, tableName, primaryKey) != null) {
return true;
} else {
return false;
}
}
@Override
public <T> boolean alreadyExisting(Connection con, TableName tableName,
T currentPojo) throws SQLException {
String primaryKey = OracleSpecifics.<T> getPrimaryKey(tableName,
currentPojo);
if (alreadyExisting(con, tableName, primaryKey) == false) {
return false;
} else {
return true;
}
}
}
The lower utility layer in DAO
OracleSpecifics.java
package aseemDao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import aseemEnums.QueryType;
import aseemEnums.TableName;
import aseemPojo.Student;
public class OracleSpecifics {
// These functions don't call any table-specific functions
static String queryString(TableName tableName, String keyValue,
QueryType type) {
String firstHalf = null;
switch (type) {
case READ:
firstHalf = "select * from " + tableName + " where ";
break;
case DELETE:
firstHalf = "DELETE from " + tableName + " where ";
break;
default:
}
switch (tableName) {
case STUDENT_TABLE:
return firstHalf + "STUDENT_ID='" + keyValue + "'";
default:
return null;
}
}
static <T> String getPrimaryKey(TableName tableName, T currentPojo) {
switch (tableName) {
case STUDENT_TABLE:
return ((Student) currentPojo).getStudentId();
default:
return null;
}
}
// These functions call table-specific functions
@SuppressWarnings("unchecked")
static <T> T getPojoFromResultSet(TableName tableName, ResultSet rs)
throws SQLException {
switch (tableName) {
case STUDENT_TABLE:
return (T) SpecStudent.getPojo(rs);
default:
return null;
}
}
}
One of the Table-Specifics utility Classes
SpecStudent.java
package aseemDao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import aseemEnums.TableName;
import aseemPojo.Student;
public class SpecStudent {
public static Student getPojo(ResultSet rs) throws SQLException {
Student currentStudent = new Student();
currentStudent.setStudentId(rs.getString("Student_Id"));
currentStudent.setRollNo(rs.getString("Roll_No"));
currentStudent.setStudentName(rs.getString("Student_Name"));
currentStudent.setAddress(rs.getString("Address"));
currentStudent.setEmail(rs.getString("Email"));
currentStudent.setContactNumber(rs.getString("Contact_Number"));
currentStudent.setGuardianName(rs.getString("Guardian_Name"));
currentStudent.setEnrollmentDate(rs.getDate("Enrollment_Date"));
return currentStudent;
}
}
reinventing-the-wheel
tag. \$\endgroup\$