提出问题
JDBC 中查询的数据存储在 ResultSet 中,一般来说,需要有一个实体类来承载 ResultSet 的数据。比如,数据库有一个 users 表,查询出来的结果肯定是要注入到 User 实体类中的。
一般的做法就是通过 while 遍历 ResultSet,通过getString()
(或其他对应类型的 getter)注入到实体类中(实体类的 setter)。难道每一个表的查询都要重新写一个重复的注入实体类的操作代码吗?通过反射机制我们可以简化这样的操作。
问题案例
public List selectAll() { List users = new ArrayList(); try { Connection connection = DriverManager.getConnection(config.getUrl(), config.getUsername(), config.getPassword()); PreparedStatement statement = connection.prepareStatement("select * from users"); ResultSet rs = statement.executeQuery(); while (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setAge(rs.getInt("age")); user.setAvatar(rs.getString("avatar")); user.setShow_name(rs.getString("show_name")); users.add(user); } } catch (SQLException e) { throw new RuntimeException(e); } return users;}
如果是 Student 实体类,那么 while 体内的代码又要修改,十个不同的实体类就修改十次,并且字段又多,太麻烦!
通过反射解决问题
最重要的是jnject
函数,它专门来处理 ResultSet 结果集。而且也需要传递一个实体类的反射对象,并且类型是泛型,也就是说没有规定死必须是哪种实体类。
private List inject(ResultSet rs, Class clz) { List list = new ArrayList(); try { while (rs.next()) { T t = clz.getDeclaredConstructor().newInstance(); for (Field field : clz.getDeclaredFields()) { field.setAccessible(true); if (field.getType().getName().equals(String.class.getName())) { field.set(t, rs.getString(field.getName())); } else if (field.getType().getName().equals(int.class.getName())) { field.set(t, rs.getInt(field.getName())); } else if (field.getType().getName().equals(java.util.Date.class.getName())) { field.set(t, rs.getDate(field.getName())); } } list.add(t); } } catch (SQLException | InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { throw new RuntimeException(e); } return list;}
通过反射创建一个新的实体类对象,再获取这个实体类对象的所有字段,不管你是 private、public、protected 修饰的字段都可以获取,所以必须是通过getDeclaredFields()
函数来获取对象的字段。在 for 循环体中,我做了一个判断,判断实体类字段的类型是哪一个,针对类型去从结果集中获取相应类型的值,然后再通过 Field 对象的set
函数给实体类的属性注入值。
public List selectAll(Class clz) { List list; try { Connection connection = DriverManager.getConnection(config.getUrl(), config.getUsername(), config.getPassword()); PreparedStatement statement = connection.prepareStatement("select * from users"); list = inject(statement.executeQuery(), clz); } catch (SQLException e) { throw new RuntimeException(e); } return list;}
测试函数
public static void main(String[] args) { MySQLConfig config = new LoadConfig(MySQLConfig.class).getConfig(); List users = new Simple(config).selectAll(User.class); System.out.println(Arrays.toString(users.toArray()));}
不管实体类字段有多少个,都不需要一个个去 setter 和 getter,这样做起来简直太方便了。而且,因为使用了泛型,实体类的类型也是可以改变的,比如说查询的表是 students,那么把类型换成 Student 就可以了。
补充说明
在测试函数中,new LoadConfig(MySQLConfig.class).getConfig()
不是什么官方的工具函数,而是我自己写的一个方便配置数据库的加载配置类。具体实践我在另一篇随笔中有详细阐述:认识注解带来的好处,如何通过注解简化程序。
理解本随笔中举的案例,最好是看源码,所以这里是?GitHub 仓库的源码地址。