ResultSet 自动封装数据到实体对象

在传统的 JDBC 开发中,将 ResultSet 结果集手动映射到 Java 实体对象(Entity)往往需要编写大量重复的 setter 代码。本文将介绍一种基于 Java 反射(Reflection)机制的工具类 AntResult,它能够自动根据数据库列名匹配实体类的属性,并将数据封装到对象集合中。

核心工具类:AntResult

该类主要通过反射获取实体类的 setter/getter 方法,并根据 ResultSetMetaData 中的列名动态调用相应方法完成数据填充。

package com.daicy.util;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class AntResult {

    /**
     * 匹配 Bean 的 getter 和 setter 方法
     * @param clazz 类对象
     * @param beanProperty 属性名
     * @return [getter, setter]
     */
    private Object[] beanMatch(Class clazz, String beanProperty) {
        Object[] result = new Object[2];
        char beanPropertyChars[] = beanProperty.toCharArray();
        beanPropertyChars[0] = Character.toUpperCase(beanPropertyChars[0]);
        String s = new String(beanPropertyChars);
        
        // 构建可能的方法名
        String names[] = { ("set" + s).intern(), ("get" + s).intern(),
                ("is" + s).intern(), ("write" + s).intern(),
                ("read" + s).intern() };
        
        Method getter = null;
        Method setter = null;
        Method methods[] = clazz.getMethods();
        
        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            // 只取公共字段
            if (!Modifier.isPublic(method.getModifiers()))
                continue;
                
            String methodName = method.getName().intern();
            for (int j = 0; j < names.length; j++) {
                String name = names[j];
                if (!name.equals(methodName))
                    continue;
                if (methodName.startsWith("set") || methodName.startsWith("read"))
                    setter = method;
                else
                    getter = method;
            }
        }
        result[0] = getter;
        result[1] = setter;
        return result;
    }

    /**
     * 注册 Bean 属性值
     * @param object 对象实例
     * @param beanProperty 属性名
     * @param value 字符串值
     */
    private void beanRegister(Object object, String beanProperty, String value) {
        Object[] beanObject = beanMatch(object.getClass(), beanProperty);
        Object[] cache = new Object[1];
        Method getter = (Method) beanObject[0];
        Method setter = (Method) beanObject[1];
        
        try {
            // 通过 get 获得方法类型
            String methodType = getter.getReturnType().getName();
            if (methodType.equalsIgnoreCase("long")) {
                cache[0] = new Long(value);
                setter.invoke(object, cache);
            } else if (methodType.equalsIgnoreCase("int") || methodType.equalsIgnoreCase("integer")) {
                cache[0] = new Integer(value);
                setter.invoke(object, cache);
            } else if (methodType.equalsIgnoreCase("short")) {
                cache[0] = new Short(value);
                setter.invoke(object, cache);
            } else if (methodType.equalsIgnoreCase("float")) {
                cache[0] = new Float(value);
                setter.invoke(object, cache);
            } else if (methodType.equalsIgnoreCase("double")) {
                cache[0] = new Double(value);
                setter.invoke(object, cache);
            } else if (methodType.equalsIgnoreCase("boolean")) {
                cache[0] = new Boolean(value);
                setter.invoke(object, cache);
            } else if (methodType.equalsIgnoreCase("java.lang.String")) {
                cache[0] = value;
                setter.invoke(object, cache);
            } else if (methodType.equalsIgnoreCase("java.io.InputStream")) {
                // 暂不处理 InputStream
            } else if (methodType.equalsIgnoreCase("char")) {
                cache[0] = (Character.valueOf(value.charAt(0)));
                setter.invoke(object, cache);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据 Connection 和 SQL 获取对象集合
     */
    public Collection get(final Connection connection, final Class clazz, final String sql) {
        PreparedStatement ptmt = null;
        ResultSet rset = null;
        Collection collection = null;
        
        try {
            ptmt = connection.prepareStatement(sql);
            rset = ptmt.executeQuery();
            collection = get(rset, clazz);
        } catch (SQLException e) {
            System.err.println(e.getMessage());
        } finally {
            try {
                // 关闭 rs 并释放资源
                if (rset != null) {
                    rset.close();
                    rset = null;
                }
                // 关闭 ps 并释放资源
                if (ptmt != null) {
                    ptmt.close();
                    ptmt = null;
                }
            } catch (SQLException e) {
                System.err.println(e.getMessage());
            }
        }
        return collection;
    }

    /**
     * 根据 ResultSet 获取对象集合
     */
    public Collection get(final ResultSet result, final Class clazz) {
        Collection collection = null;
        try {
            ResultSetMetaData rsmd = result.getMetaData();
            // 获得数据列数
            int cols = rsmd.getColumnCount();
            // 创建等同数据列数的 arraylist 类型 collection 实例
            collection = new ArrayList(cols);
            
            // 遍历结果集
            while (result.next()) {
                Object object = null;
                try {
                    // 从 class 获得对象实体
                    object = clazz.newInstance();
                } catch (Exception e) {
                    // 忽略实例化异常
                }
                
                // 循环每条记录
                for (int i = 1; i <= cols; i++) {
                    beanRegister(object, rsmd.getColumnName(i), result.getString(i));
                }
                // 将数据插入 collection
                collection.add(object);
            }
        } catch (SQLException e) {
            System.err.println(e.getMessage());
        } finally {
            // 资源关闭由上层调用者处理或在此补充
        }
        return collection;
    }

    //===========================================//

    public static void main(String[] args) {
        // 加载驱动
        try {
            Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        
        // 连接字符串
        String url = "jdbc:sqlserver://localhost:1433;databaseName=test";
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        
        try {
            // 创建连接
            connection = DriverManager.getConnection(url, "sa", "javalife");
            AntResult test = new AntResult();
            
            // Ltest 是我测试用类,实际操作请注入相关对象,支持 set,get,is,read,writer 为前缀数据对,更多请继续添加。
            Collection collection = test.get(connection, com.ant.po.UserInfo.class, "select * from userinfo");
            
            for (Iterator it = collection.iterator(); it.hasNext();) {
                com.ant.po.UserInfo ltest = (com.ant.po.UserInfo) it.next();
                System.out.println(ltest.getUserid() + ":" + ltest.getUserName());
            }
        } catch (SQLException e) {
            // SQL 异常,用于抛出 SQL 语句处理中所引发的错误。
            System.err.println(e.getMessage());
        } finally {
            try {
                // 关闭 connection 并释放资源
                if (connection != null) {
                    connection.close();
                    connection = null;
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            // 如果关闭时产生异常将由此抛出
        }
    }
}

实体类示例:UserInfo

为了配合上述工具类,实体类的属性名需要与数据库列名保持一致(区分大小写取决于数据库配置),并提供标准的 public getter/setter 方法。

// UserInfo.java 实体类
package com.ant.entity;

public class UserInfo {
    private int userID;
    private String userName;
    private String userPass;

    public int getUserID() {
        return userID;
    }

    public void setUserID(int userID) {
        this.userID = userID;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPass() {
        return userPass;
    }

    public void setUserPass(String userPass) {
        this.userPass = userPass;
    }
}

实现原理与注意事项

  1. 命名规范:类中的属性名需要和数据库的列名保持一致。工具类通过 ResultSetMetaData.getColumnName() 获取列名,并尝试匹配实体类中 set + 首字母大写的属性名方法。
  2. 反射机制:该实现利用了 Java 反射机制动态调用方法。这与某些 MVC 框架(如 Struts 的 FormBean)封装数据的原理类似,都是通过属性名匹配进行值注入。
  3. 类型转换beanRegister 方法中手动处理了常见基本类型(int, long, boolean 等)的字符串转换。在实际生产中,直接调用 ResultSet 的 typed getters(如 getInt, getString)通常比通过字符串解析更安全且高效。
  4. 扩展性:当前实现支持以 set, get, is, read, write 为前缀的方法对。如需支持更多复杂类型(如 Date、BigDecimal 等),需在 beanRegister 中继续添加分支逻辑。

说明

版本与时效性说明
本文代码示例属于早期 Java 反射实践方案。

  1. 代码中使用的 Class.newInstance() 方法已在 Java 9 中被标记为 deprecated,现代开发建议使用 getDeclaredConstructor().newInstance()
  2. 基本类型包装类构造函数(如 new Integer(value))也已过时,建议使用 valueOf 或直接利用 ResultSet 的类型化方法。
  3. 在实际企业级开发中,建议直接使用成熟的 ORM 框架(如 MyBatis、Hibernate)或 Spring JDBC Template,以获得更好的性能、安全性及维护性。
  4. 本文代码主要用于理解反射与 JDBC 底层交互原理,不建议直接用于新版本 Java 的生产环境。