是否可以在不使用反射的情况下用 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)具体实现这些。因此,如果稍后您想更改数据访问技术,您可以使用这些技术的另一种实现方式(JdbcProductDao
和 HibernateProductDao
)。
您甚至可能想更进一步,将 DAO 层与实际存储库层分离,但这可能被视为过早的优化,具体取决于您系统中不同实体类的数量。
这有很多好处:
光耦合是更好的整体设计 更好的可测试性等此外,尝试在任何地方都使用泛型方法不一定是个好主意:通常您会发现每个实体所需的 findById
略有不同,其中一些不适合泛型方法您在Database
中进行了描述(我什至没有提到它是一种静态方法,这是一种难闻的气味)。在我目前的团队中,我们使用三法则:仅当您编写系统的第三个将从中受益的元素时才引入重构的泛型类/方法。否则我们认为这是过早的优化。
【讨论】:
非常感谢您的回复。以防万一,我最终决定使 findById 通用,我可以使用反射。有没有更好的办法? 反射很慢。如果您的应用程序需要良好的性能,它可能会扼杀它。在您实际在代码中使用它之前对其进行测试(连续运行相同的查询 100000 次,测量时间) 感谢您的建议。实际上这段代码不会在生产环境中使用。唯一的目的是亲自研究在不使用反射的情况下用Java编写通用DAO机制的可能性。你觉得有可能吗? 只要付出努力,一切皆有可能:) 值得吗?不确定。仔细考虑你想做什么,然后去做,只做必要的事情。过早的优化是编程中最大的浪费时间。你最终会构建一个非常复杂的系统,甚至将来可能都不会使用。【参考方案2】:我宁愿使用基于 DAO 的方法。您需要使用基本 CRUD 方法创建一个 GenericDao<T>
类,并且所有派生的 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 证书?
是否可以在不使用 Spring Boot JPA 的情况下测试基于 java 的 CRUD?