是否可以在不使用反射的情况下用 Java 编写一个简单的通用 DAO?

Posted

技术标签:

【中文标题】是否可以在不使用反射的情况下用 Java 编写一个简单的通用 DAO?【英文标题】:Is it possible to write a simple generic DAO in Java without using reflection? 【发布时间】:2012-08-28 03:40:50 【问题描述】:

我正在 Java 6(及更高版本)中开发一种玩具数据访问机制。每个模型类都应该有一个 findById 静态方法,该方法应该从具有指定 ID 的行实例化一个对象。我想出了下面显示的方法。我的方法被认为是好的做法吗?如果没有,有什么可以改进的?

数据库(mysql)引导脚本:

create database test;
create user test identified by 'test';
grant all on test.* to test;
use test;
create table products(id integer,name varchar(10));
insert into products values(1,'led');

源代码:

import java.sql.*;

class Test 
    public static void main(String[] args) throws SQLException, ClassNotFoundException 
        Class.forName("com.mysql.jdbc.Driver");
        Product p = Product.findById(1);
        System.out.println(p.id + " " + p.name);
    


class Database 
    static <T extends Model<T>> T findById(T m, String sql, int id) throws SQLException 
        try (Connection conn = DriverManager.getConnection("jdbc:mysql:///test", "test", "test");
                PreparedStatement stmt = conn.prepareStatement(sql)) 
            stmt.setInt(1, id);
            try (ResultSet rs = stmt.executeQuery()) 
                rs.next();
                m.load(rs);
            
        
        return m;
    


abstract class Model<T> 
    abstract void load(ResultSet rs) throws SQLException;


class Product extends Model<Product> 
    int id;
    String name;

    static Product findById(int id) throws SQLException 
        return Database.findById(new Product(), "select * from products where id=?", id);
    

    @Override
    void load(ResultSet rs) throws SQLException 
        this.id = rs.getInt("id");
        this.name = rs.getString("name");
    

【问题讨论】:

为什么每次都需要tp pass"select * from products where id=?" 查询?它可以移动到 findById 方法。 Database.findById 是通用的,因为它可以被任何模型使用。 【参考方案1】:

您将关注点和责任混为一谈,在您的实体 (Product) 和您的数据访问层之间引入了紧密耦合。

你应该分开

实体(只有 getter/setter 和可能的内部业务逻辑,根据您的整体模型,您可能也想将其分开) 数据访问层:我将为您的每个实体 (ProductDao) 提供接口,其中包含您要执行的方法来检索/存储/删除您的实体。然后,您可以使用您选择的技术(在您的情况下为 JDBC)具体实现这些。因此,如果稍后您想更改数据访问技术,您可以使用这些技术的另一种实现方式(JdbcProductDaoHibernateProductDao)。

您甚至可能想更进一步,将 DAO 层与实际存储库层分离,但这可能被视为过早的优化,具体取决于您系统中不同实体类的数量。

这有很多好处:

光耦合是更好的整体设计 更好的可测试性等

此外,尝试在任何地方都使用泛型方法不一定是个好主意:通常您会发现每个实体所需的 findById 略有不同,其中一些不适合泛型方法您在Database 中进行了描述(我什至没有提到它是一种静态方法,这是一种难闻的气味)。在我目前的团队中,我们使用三法则:仅当您编写系统的第三个将从中受益的元素时才引入重构的泛型类/方法。否则我们认为这是过早的优化。

【讨论】:

非常感谢您的回复。以防万一,我最终决定使 findById 通用,我可以使用反射。有没有更好的办法? 反射很慢。如果您的应用程序需要良好的性能,它可能会扼杀它。在您实际在代码中使用它之前对其进行测试(连续运行相同的查询 100000 次,测量时间) 感谢您的建议。实际上这段代码不会在生产环境中使用。唯一的目的是亲自研究在不使用反射的情况下用Java编写通用DAO机制的可能性。你觉得有可能吗? 只要付出努力,一切皆有可能:) 值得吗?不确定。仔细考虑你想做什么,然后去做,只做必要的事情。过早的优化是编程中最大的浪费时间。你最终会构建一个非常复杂的系统,甚至将来可能都不会使用。【参考方案2】:

我宁愿使用基于 DAO 的方法。您需要使用基本 CRUD 方法创建一个 GenericDao&lt;T&gt; 类,并且所有派生的 DAO 类都将具有针对指定实体类的开箱即用的 CRUD 功能。

这里有两篇文章演示了所描述的技术: http://www.codeproject.com/Articles/251166/The-Generic-DAO-pattern-in-Java-with-Spring-3-and http://www.ibm.com/developerworks/java/library/j-genericdao/index.html

【讨论】:

您能否概述一下您的提案? 感谢您的链接。似乎编写通用 DAO 需要使用反射,这是我一开始就试图避免的事情。【参考方案3】:

我喜欢基本的设计。但对于一个真正的生产项目,我会做出 3 处更改:

    在数据库和任何资源代码中添加一个 finally 块并在单独的 try-catch 中关闭每个连接(您只有 1 个),否则您将获得 con 泄漏 在数据库中:使用共享连接池 在产品 getById 中,如果产品正在被重用,将它们缓存在 HashMap 中,如果已经加载,则返回它,而不是每次都创建一个新对象。这取决于使用情况,但我们会为许多具有 100 到 5,000 行的表执行此操作,并且偶尔会更改但会读取多次。

【讨论】:

【参考方案4】:

您正在重新发明对象关系映射 (ORM) 和数据访问对象 (DAO) 方法。有许多 Java 库可以完全按照您在此处尝试执行的操作,例如 Hibernate。我想你可能会发现这个库比得到这个问题的正确答案更有用。

【讨论】:

我知道我应该使用 Hibernate!但这是一个主要用于实验的玩具项目,没有任何扩展前景。我完全同意你的观点,在生产环境中我应该使用 Hibernate。

以上是关于是否可以在不使用反射的情况下用 Java 编写一个简单的通用 DAO?的主要内容,如果未能解决你的问题,请参考以下文章

在没有 BouncyCastle 的情况下用 Java 创建 X509 证书?

在不重启linux的情况下用systemd启动nacos

java反射性能与优化

是否可以在不使用 Spring Boot JPA 的情况下测试基于 java 的 CRUD?

如何在不使用 3rd-party API 的情况下用 C# 压缩文件?

我可以在不使用反射的情况下获取类中字段或属性的 PropertyInfo 吗?