最初我以为这只是体系结构问题,所以我把它作为重构通用DAO的思想放在程序员身上。然后我在这里询问codereview meta并决定在这里放置代码。


如果有人想在这里看到一个github链接。
有四个DAO-DAODelete,DAOUpdate,DAORead,DAOInsert,但我只是将DAORead及其实现放在这里
我已经对代码进行了很多修整,因此如果出现问题,请告诉我,我会予以纠正。否则,您可以看到github链接,因为它具有完整的代码(kinda)。


我的体系结构是这样的


DAO的抽象层位于顶部
与数据库无关的DAO BUT特定于表的实现
表依赖性向下传递到较低的实用层
Enum向下传递到较低的实用层,通过它它给出表特定的结果
/>上一点提到的用于下层实用程序层的表特定的实用程序类。
我省略了一个Student pojo,因为它只是一个pojo。 private变量和所有。请注意,enrollmentDate属于java.sql.Date类型


我的担忧


我对DAO的上层感到满意,但我认为较低的水平,特别是在OracleSpecific.java处,应具有强耦合的物体具有弱耦合。例如有多种方法可以从结果集中获取pojo或获取主键。这些函数中的每一个都有switch的大小写,用于调用较低实用程序类中的函数。

尽管schema-specific方法在不同的实用程序类中捆绑在一起,但是OracleSpecifics.java中的方法本身没有任何耦合。 。我正在考虑特定于模式的最低级别实用程序类的状态。
枚举中包含的这些TableName可用于更改DAO的状态,然后DAO可以基于由所有此类状态实现的接口调用特定功能。因此,根据状态,行为可以自动更改。
此设计决策是否正确,或者是没有意义的?
我可能忽略了其他事情吗?
对设计本身有任何想法吗?
由于此更改,我是否会失去泛型引入的类型安全性?


枚举枚举

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;
    }
}



抽象层

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;
}




具体实现DAO抽象的实现

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;
        }
    }
}



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;
        }
    }
}


表特定实用工具类之一

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;
    }
}


评论

我强烈建议您在尝试推出自己的ORM之前,先检查一下Datanucleus和Hibernate,以解决任何严重的问题。尽管尝试编写ORM对于学生程序员来说是一种通过的仪式。

@abuzittingillifirca我知道这样的事情存在,但我想这样做是出于学习目的。这就是为什么我添加了重塑车轮标签的原因。

#1 楼

常规


if (<expr>) return true; else return false;替换为return <expr>;
将每个首字母缩写词的首字母以外的所有字母都改为小写:DaoFactory。虽然乍一看看起来很奇怪,但是它们的键入更容易,您会很快习惯它,并且像MySQLJDBCDAO中那样组合它们时,您不会感到困惑。这个简单,一致的规则消除了很多猜测。
与您的复数形式和单数形式命名保持一致。 QueryTypeTableName和。 。 。 Databases?代码完成显然可以防止您出错,但这会在一段时间后使您变慢。
拼写检查代码。是的,您必须手动执行此操作,但是没有理由让getPojoForPrimarKey使其成为公共接口。还有h.j.k.就在这里:将“ pojo”换成“实体”或更多解释性的信息。创建SQLException并使用实用程序类la Hibernate和Spring对其进行映射。Java包名称通常都是小写的,并形成树形结构: ,DaoExceptionaseem.enums。它们还倾向于避免使用复数形式,因此请使用aseem.dao而不是aseem.pojo。每个aseem.enum都会创建一个新的aseem.enums,附加字符串,并将结果打包在内部。在语句之间进行拆分会导致额外的临时生成器和不可变的字符串,从而导致垃圾回收器中的流失。
接口中的每个方法在定义上都是公共的和抽象的,因此您可以删除这些修饰符。这更多是个人喜好,在那些情况下,我倾向于在可行的地方倾向于懒惰。任何适用于所有事物的多余信息都是噪音。
除了非常高级的事件捕捉或线程的StringBuilder方法之外,不要捕获x + y + ... + zStringBuilder可以捕获Exception,以使无关的问题冒出来。原因是您可能会无意中捕获到一个不准备处理的异常(例如run)。可以安全地忽略DAOFactory.closeAll中的SQLException,但是可以忽略其他可能表示实际问题的异常类型。因此,将两个InvalidStateException子句都更改为SQLException。为什么也不要列名?我没有在表上看到任何要切换的地方[哦,请参阅下一项],如果是的话,应该将其移至具有多态行为的适当类中。枚举应建模一组真正固定的项目。 closeAll是一个很好的例子。
catch似乎随着它处理a)Oracle和b)每个表的发展而变成一个怪物。使用每个表的子类创建一个正确的SQLException类,或者从文件中读取配置,以便QueryType可以是100%Oracle特定的,而您不必在OracleSpecificsTable中重复它。意思?此查询是否查询数据库以查看主键是否已经存在? OracleSpecifics怎么样?与MySqlSpecifics类似;使名称更明确,例如SqlServerSpecifics,以将其与采用列/值映射的alreadyExisting区分开。我一直喜欢exists可能不返回任何结果的查询方法,以及getAllForInput那些返回单个结果或引发异常的方法。 />

评论


\ $ \ begingroup \ $
为了澄清起见,我应该手动进行拼写检查,以便处理命名中的不一致之处?我是对的还是错过了吗? getPojoForPrimaryKey必须在公共接口中,以便查询特定记录。还是我可以在没有该行的情况下获得特定行?仅供参考,我不是一个有经验的人。说了这么多其他类型的数据库呢?您说不要捕获Exception,因为它可能导致不相关的问题。什么样的问题我该怎么办?将用户定义的Exeption传播到呼叫层以向呼叫者提供信息?
\ $ \ endgroup \ $
– Aseem Bansal
2013年12月3日14:32

\ $ \ begingroup \ $
我真的很关心DAO设计。具体来说,在各种转换情况下,OracleSpecifics将如何发展并减少相关术语之间的衔接。关于如何安排表格特定内容的任何建议?到目前为止,我对反射有了一些了解。还有其他事吗关于名称和其他所有内容的好的建议。我会照顾他们的非常感激。
\ $ \ endgroup \ $
– Aseem Bansal
2013年12月3日14:37



\ $ \ begingroup \ $
@AseemBansal是的,(很遗憾)拼写检查是对类/变量名的手动处理。我已经阐明了Java下的第3和第4个项目符号。
\ $ \ endgroup \ $
– David Harkness
13年12月3日在18:01

#2 楼

我暂时将实际的通用DAO讨论排除在这个答案之外,只提供我两分钱的编码风格...


您可以使用更好的方法名称代替getPojo。 .. Pojo易于理解,但仍然是一个非正式术语,无论是否保留全部上限,您都可能会受到平等的评论-毕竟是首字母缩写。 Spring框架使用mapRow方法调用其帮助程序类RowMappers,所以也许您想遵循类似的方法。 http://docs.spring.io/spring/docs/3.0.x/api/org/springframework/jdbc/core/RowMapper.html

您可以将return语句中的条件检查内联为这样:

@Override
public <T> boolean alreadyExisting(Connection con, TableName tableName,
        String primaryKey) throws SQLException {
    return getPojoForPrimarKey(con, tableName, primaryKey) != null;
}

@Override
public <T> boolean alreadyExisting(Connection con, TableName tableName,
        T currentPojo) throws SQLException {
    return alreadyExisting(con, tableName, OracleSpecifics.<T> getPrimaryKey(tableName, currentPojo));
}
实际上,我不确定您是否真的需要映射Pojo才能确定是否存在具有主键的行。 ..也许有一种更简单的方法?

编辑:这是因为事情仍在进行中。还是意味着您将使用自己的方言为其他RDBMS软件(如MySQL,MariaDB等)创建新类?
DAO不需要保持“状态”超出正在为其进行映射的当前Oracle*对象。您可能需要重新考虑ResultSet枚举的实现以包含特定状态。在这里需要更多说明。
在快速阅读之后,您可能需要考虑方法的“通用性”,因为添加新类(又名实体)传递给DAO框架似乎需要TableName。类(或更新您现有的Spec<Table>)以实现实际实现(例如,调用SpecStudent类上的所有getter方法),以及更新您的Student枚举。也许您也可以考虑将反射用于实现,这样从长远来看,需要更新的代码更少了?

评论


\ $ \ begingroup \ $
+1排名第三!将第一种方法更改为仅查找PK而无需加载POJO。
\ $ \ endgroup \ $
– David Harkness
2013年12月3日,下午5:30

\ $ \ begingroup \ $
关于内联return语句的好处。关于没有什么是Oracle特定的。是的,完全正确。目前没有任何东西。我不得不与Oracle打交道,因此只制作了Oracle类。我认为,但是DAO本身的结构允许它被其他人使用。为了扩展性,我将其分开。关于包含特定状态的TableName枚举,我将更新问题。我知道当前的实现实际上不是通用的(纯英语是预期的)。 Oracle规范和下层阶级是一个大问题。
\ $ \ endgroup \ $
– Aseem Bansal
2013年12月3日14:20在

\ $ \ begingroup \ $
我来看一下反射。我对程序员也有同样的建议。目前,整个事情还很小,但是从长远来看,它将至少是Oracle Specifics成为一个巨大的怪物。非常感谢您的回复。
\ $ \ endgroup \ $
– Aseem Bansal
2013年12月3日14:23



\ $ \ begingroup \ $
关于映射pojo以确定是否存在具有主键的行,我这样做是为了使DAO的通用性保持一致。假设一个表的主键存储为Timestamp,则需要传递一个Timestamp对象。如果我为主键的数据类型添加了一个类型参数,那么在架构更改的情况下将存在巨大的可维护性问题,即必须更改来自其他架构层的所有函数调用。 Pojo aka DTO是我唯一的限制。那是我认为人们可以维持的最小分母。
\ $ \ endgroup \ $
– Aseem Bansal
13年12月3日,16:02

\ $ \ begingroup \ $
@AseemBansal ORM常用的方法是要求可序列化的主键。它处理整数,时间戳甚至复合键,例如VehicleKey {String state,String plate}。
\ $ \ endgroup \ $
– David Harkness
13年12月3日在17:55