DAO设计模式:数据库访问应该在哪里实现?

DAO design pattern: where should the database access be implemented?

我正在构建一个使用数据库的 Java 应用程序并且我正在使用 DAO 设计模式:在我的代码中,所有对象 class 都有一个关联的 DAO class它实现了一个带有获取、保存和更新方法的接口。 例如,对于一个用户对象,我将有以下 class(ConnectionDB 实现与数据库的连接):

public class UserDAO implements Dao<User, String> {

    private final static String TABLE_NAME = "users";
    private final static UserDAO instance = new UserDAO();
    public static UserDAO getInstance() {
        return instance;
    }

    private UserDAO() {

    }

    @Override
    public User get(String username) throws SQLException {
        String query = "SELECT * FROM " + TABLE_NAME + " WHERE username = ?";

        PreparedStatement stmt = ConnectionDB.getInstance().prepareStatement(query);
        stmt.setString(1, username);
        ResultSet result = stmt.executeQuery();

        if (!result.next())
            return null;

        User user = new User(
            result.getInt("id"),
            username,
        );

        stmt.close();
        result.close();

        return user;
    }

    /* same thing for save and update */
}

这里是Dao接口供参考:

public interface Dao<T, S> {
    T get(S id) throws SQLException;

    ArrayList<T> getAll() throws SQLException;

    void save(T t) throws SQLException;

    void update(T t) throws SQLException;
}

这种方式工作得很好,但随着我的应用程序中有越来越多的 classes,并且每个 ES 都有一个 DAO class,我有很多重复的代码。例如,get 不同对象的实现之间的唯一区别是主键的名称和类型以及对构造函数的调用。

为了使代码更通用,我尝试在 ConnectionDB class 中实现一个 fetchItem 方法,该方法能够从数据库中查询一个项目:

public <T> HashMap<String, Object> fetchItem(String table_name, String pk, T id) throws SQLException {
    String query = "SELECT * FROM " + table_name + " WHERE " + pk + " = ?";

    PreparedStatement stmt = prepareStatement(query);
    stmt.setObject(1, id);
    ResultSet result = stmt.executeQuery();

    if (!result.next())
        return null;

    HashMap<String, Object> values = buildObject(result);

    stmt.close();
    result.close();

    return values;
}

public HashMap<String, Object> buildObject(ResultSet result) throws SQLException {
    ResultSetMetaData metadata = result.getMetaData();
    int columnCount = metadata.getColumnCount();
    HashMap<String, Object> values = new HashMap<>();
    for (int i = 1; i <= columnCount; i++) {
        values.put(metadata.getColumnName(i), result.getObject(i));
    }
    return values;
}

有了这个实现,我现在可以用下面的简化代码替换 UserDAO class 中的第一个 get 方法:

public User get(String username) throws SQLException {
    HashMap<String, Object> values = ConnectionDB.getInstance()
            .fetchItem(TABLE_NAME, "username", username);

    if (values == null || values.isEmpty())
        return null;


    return new User(
        id,
        (String) values.get("String")
    );
}

虽然这个新实现更简单并且允许 get 方法只做它们应该做的事情(在这里,使用来自数据库的正确参数创建一个用户对象),但我发现它有点危险,因为我必须做很多演员;因为我的代码中有很多对象变量,所以我不确定如果这些函数调用中的任何一个出现故障,调试代码是否容易。

所以这是我的问题:哪种实现更好、更易于维护且更安全?

Connection DB 是定义此类实现的一个非常糟糕的地方。它只是一个具有特定数据库的 link 而已。你违反了单一责任规则。最好为所有 DAO 实现基本泛型 class 并在那里放置通用逻辑。
此外,如果您将使用 Hibernate 框架,则无需使用查询字符串和对象变量转换。