我想动态创建一个类文件。在这里... 使用给定的 ResultSet,提取元数据我想使用 ResultSet 中存在的所有列的 getter 和 setter 方法动态构建一个类文件。此外,我应该能够在以后使用的任何地方使用生成的这个类文件。任何机构都可以建议我更好的方法来实现这一点。此外,如果有任何现有的 jar 文件可用于实现这一点,那将很有帮助。
8 回答
也许Apache Beanutils可能适合您的要求?
请参阅有关Dynabeans的部分
尤其:
3.3 ResultSetDynaClass(在DynaBeans中包装ResultSet)
DynaBean API 的一个非常常见的用例是包装其他通常不以 JavaBeans 呈现的“东西”集合。最好包装的最常见的集合之一是 java.sql.ResultSet,当您要求 JDBC 驱动程序执行 SQL SELECT 语句时,它会返回。Commons BeanUtils 提供了一种标准机制,可以使结果集的每一行都作为 DynaBean 可见,您可以使用它,如下例所示:
Connection conn = ...;
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery
("select account_id, name from customers");
Iterator rows = (new ResultSetDynaClass(rs)).iterator();
while (rows.hasNext()) {
DynaBean row = (DynaBean) rows.next();
System.out.println("Account number is " +
row.get("account_id") +
" and name is " + row.get("name"));
}
rs.close();
stmt.close();
3.4 RowSetDynaClass(断开连接的ResultSet作为DynaBeans)
尽管 ResultSetDynaClass 是将 SQL 查询的结果表示为一系列 DynaBean 的一种非常有用的技术,但一个重要的问题是底层 ResultSet 必须在应用程序处理行的整个时间段内保持打开状态。这阻碍了使用 ResultSetDynaClass 作为在模型-视图-控制器架构(例如 Struts 框架提供的架构)中将信息从模型层传递到视图层的能力,因为没有简单的机制来确保结果集最终关闭(并且底层连接返回到它的连接池,如果你正在使用一个)。
RowSetDynaClass 类代表了解决此问题的不同方法。当您构造这样一个实例时,底层数据被复制到一组表示结果的内存中 DynaBeans。当然,这种技术的优点是您可以立即关闭 ResultSet(和相应的语句),通常甚至在您处理返回的实际数据之前。当然,缺点是您必须支付复制结果数据的性能和内存成本,并且结果数据必须完全适合可用的堆内存。对于许多环境(尤其是在 Web 应用程序中),这种权衡通常是非常有益的。
作为一个额外的好处,定义了 RowSetDynaClass 类来实现 java.io.Serializable,这样它(以及与结果的每一行对应的 DynaBeans)可以方便地序列化和反序列化(只要底层的列值也是可序列化)。因此,RowSetDynaClass 代表了一种将 SQL 查询结果传输到远程基于 Java 的客户端应用程序(例如小程序)的非常方便的方法。
事情是这样的——从你的情况来看,我知道你想在运行时创建这个类,基于你刚刚从数据库查询中返回的 ResultSet 的内容。这一切都很好,可以通过字节码操作来完成。
但是,您认为您会从中获得什么好处?您的其他代码将无法调用此类上的任何方法(因为它们在编译时不存在),因此实际使用此生成的类的唯一方法是通过反射或通过其父类上的方法或实现的接口(我假设它会扩展 ResultSet)。您可以在没有字节码编织的情况下执行后者(查看接口的任意运行时实现的动态代理),如果您正在执行前者,我看不出拥有一个类并getFoo
通过反射机械地调用该方法比只是调用resultSet.getString("foo")
- 它会更慢,更笨重且类型安全性更低。
所以 - 你确定你真的想创建一个类来实现你的目标吗?
您可能想查看BCEL,尽管我相信还有其他可用的字节码操作库。
如果您使用的是 Java 6,您可以编写代码并直接调用 Java 编译器:
Files[] files1 = ... ; // input for first compilation task
Files[] files2 = ... ; // input for second compilation task
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> compilationUnits1 =
fileManager.getJavaFileObjectsFromFiles(Arrays.asList(files1));
compiler.getTask(null, fileManager, null, null, null, compilationUnits1).call();
Iterable<? extends JavaFileObject> compilationUnits2 =
fileManager.getJavaFileObjects(files2); // use alternative method
// reuse the same file manager to allow caching of jar files
compiler.getTask(null, fileManager, null, null, null, compilationUnits2).call();
fileManager.close();
然后您将不得不加载该类,但您可以使用类加载器轻松完成此操作。
可悲的是,这是您在 Java 中必须做的。
在 C# 中,您只需使用 'var' 类型。
我对它应该工作的方式感到困惑。而且我认为这是不可能的。原因如下:
如果您想在应用程序的其余部分中使用类代码,则需要一个接口(或大量使用反射),这意味着您事先知道列类型 - 违背了生成类的目的。
生成的类可能在运行时与另一个类发生冲突。如果您为每个 SQL 调用创建一个新类,您将拥有用于相同目的的不同类。这些可能甚至不会通过对“等于”的常规调用。您必须从以前执行的语句中查找类。你失去了灵活性和/或用类填充你的堆。
我做过可能类似的事情。但我不会创建动态类。我有一个名为 Schema 的对象,它可以加载我需要的每个表的数据。我有一个具有 Schema 类型的 Table 对象。每个 Schema 对象都将具有 columns 属性,而 While Table 对象具有具有值的属性和对 Schema 列属性的引用。
Schema 拥有您在数据库中插入、选择、删除、更新数据所需的一切。
我有一个调解器来处理数据库和 Table 对象之间的连接。
Table t = new Table('Dog');
t.randomValue(); // needed for the purpose of my project
t.save();
Table u = Table.get(t);
u.delete();
但它可以很容易地获得某些列名的价值。无论如何,原理很简单,我可以加载表 information_data 中包含的数据,它也可能与描述一起使用。
我能够动态加载anytable,因为表具有动态属性,结构没有硬编码。但是没有真正需要为每个表创建新的类。
还有一些重要的事情需要注意。每个表模式都加载一次。表只引用了模式,而模式引用了列。列引用了列类型等...
找到比它更好的用途可能会很有趣。我为数据库复制的单元案例做了这个。我没有真正的兴趣为 30 个表中的每一个编写一个类并进行插入/删除/更新和选择。这是我认为创建有关 sql 的动态内容很有用的唯一原因。如果您不需要了解有关表格的任何信息,而只想在其中插入/删除垃圾。
如果我不得不重做我的代码,我会使用更多的关联数组。
无论如何祝你好运
我赞同 dtsazza 和 Stroboskop 的评论;在这种情况下,在运行时生成一个新类可能不是您想要做的。
您还没有真正了解为什么要这样做,但听起来您正在尝试推出自己的Object-Relational mapper。这是一个比最初看起来更难解决的问题。
与其自下而上构建自己的系统,不如研究现有的解决方案,例如Hibernate(高级系统,为您管理大部分对象和查询)或iBatis(更底层;它处理对象映射,但您仍然可以编写自己的 SQL)。
我发现在 JSF 中 bean 和地图可以互换使用。因此,对于处理您不想构建完整的 get/setter 集而只创建 ah:table 的结果,为每一行创建一个带有映射的列表要容易得多,其中键是列名(或number),值为列内容。
如果您以后发现它相关以使其更加类型安全,则可以使用 bean 重新编写后端代码,并保持您的 JSF 代码不变。