侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 285 篇文章
  • 累计创建 125 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

jdbc查询ResultSet结果使用反射机制转为java对象的封装

孔子说JAVA
2022-07-29 / 0 评论 / 0 点赞 / 186 阅读 / 13,546 字 / 正在检测是否收录...

在很多传统的项目中使用的框架比较古老,没有使用持久层的框架(如hibernate、mybatis等),在读取数据库的时候还用的是基础的jdbc连接,查询到结果根据表中字段列表的顺序来一个个获取解析字段,这样不仅代码不够简洁,重复工作量大,且返回的结果不够直观,返回结果一旦改动就需要从数据库读取层面逐步向上修改,与数据库表的耦合度高,不利于程序的扩展。本文对jdbc返回结果ResultSet转化为javabean的方式做了封装,将我们从数据库读取出来的属性转化为javabean的属性,使开发工作更加直观高效。

1、反射机制加注解的方式

可以将以下代码放入数据库读取类的基类中,所有表的读取都可以直接调用下述封装好的方法而不做任何改动。如我们在BaseApiManager中增加如下方法:

1.1 根据条件查询列表

该方法供业务层直接调用,DbSearchDTO封装了相关查询参数。

/**
 * 根据条件查询列表
 *
 * @param dbSearchDTO 查询条件
 * @return
 */
public <T> List<T> listPojo(DbSearchDTO dbSearchDTO) throws BaseJdbcException {
    String tableName = dbSearchDTO.getTableName();
    String selectField = dbSearchDTO.getSelectField();
    Class<T> clazz = dbSearchDTO.getClazz();
    if (StringUtils.isBlank(tableName) || StringUtils.isBlank(selectField) || clazz == null) {
        return null;
    }
    StringBuilder selectSql = new StringBuilder();
    selectSql.append("select ");
    selectSql.append(selectField);
    selectSql.append(" from ");
    selectSql.append(tableName);
    String condition = dbSearchDTO.getCondition();
    Object[] params = dbSearchDTO.getParams();
    if (org.apache.commons.lang3.StringUtils.isNotBlank(condition)) {
        selectSql.append(" where ");
        selectSql.append(condition);
    } else {
        params = null;
        dbSearchDTO.setParams(null);
    }
    String orderby = dbSearchDTO.getOrderby();
    if (org.apache.commons.lang3.StringUtils.isNotBlank(orderby)) {
        selectSql.append(" order by ");
        selectSql.append(orderby);
    }
    Integer pageNum = dbSearchDTO.getPageNum();
    Integer pageSize = dbSearchDTO.getPageSize();
    int begin = 0;
    int offset = 0;
    if (pageNum != null && pageSize != null && pageNum != 0 && pageSize != 0) {
        begin = (pageNum - 1) * pageSize;
        offset = pageSize;
    }
    String sql = selectSql.toString();
    // 根据条件查询数据列表(分页/不分页)
    List<T> list = getDocList(begin, offset, sql, params, clazz);
    return list;
}

附DbSearchDTO类

/**
 * 数据库查询对象DTO
 *
 * @Author kongzi
 * @Date 2022/7/22 17:46
 * @Version 1.0
 */
public class DbSearchDTO<T> {
    /** 动态查询sql表名 */
    private String tableName;
    /** sql语句查询字段 */
    private String selectField;
    /** 动态查询sql条件 */
    private String condition;
    /** 动态查询sql条件参数 */
    private Object[] params;
    /** 动态查询sql排序条件 */
    private String orderby;
    /** 查询页码 **/
    private Integer pageNum;
    /** 每页条数 **/
    private Integer pageSize;
    /** 结果集对应的java类 **/
    private Class<T> clazz;

    public DbSearchDTO(String tableName, String selectField) {
        this.tableName = tableName;
        this.selectField = selectField;
    }

    // getter、setter方法略
}

1.2 数据列表查询

/**
 * 数据列表查询(分页/不分页)
 *
 * @param begin  开始
 * @param offset 查询数量
 * @param sql    查询脚本
 * @param params 查询条件
 * @return
 * @throws BaseJdbcException
 */
public <T> List<T> getDocList(int begin, int offset, String sql, Object[] params, Class<T> clazz) throws BaseJdbcException {
    DBSession db = null;
    IResultSet rs = null;
    List<T> list = null;
    try {
        // 获取数据库连接对象
        db = Context.getDBSession();
        if (offset != 0) { 
            // 查询数量不为0表示分页查询,否则不分页
            // 根据方言增加分页条件
            sql = db.getDialect().getLimitString(sql, begin, offset);
        }
        rs = db.executeQuery(sql, params);
        list = resultSetToBean(rs, clazz);
    } catch (Exception e) {
        throw new BaseJdbcException(e);
    } finally {
        // 关闭rs
        ResourceMgr.closeQuietly(rs);
        // 关闭Connection
        ResourceMgr.closeQuietly(db);
    }
return list;
}

对于上述代码中的几处封装做了简单的截图,不是我们讨论的重点:

  • 根据数据库相关配置参数获取连接
    image-1659055039881

  • 根据数据库类型获取数据库连接对象
    image-1659055092042

  • mysql的分页实现
    image-1659055199706

  • 关闭rs
    image-1659055632272

  • 关闭Connection
    image-1659055657288
    image-1659055671940

1.3 rs结果集转java对象

上述代码中的,将resultSet结果集转化为java对象的集合。

/**
 * resultSet结果集转化为java对象的集合
 *
 * @param rs    结果集对象
 * @param clazz 转化的class对象
 * @param <T>
 * @return
 */
private <T> List<T> resultSetToBean(IResultSet rs, Class<T> clazz) throws E5Exception {
    List<T> list = null;
    try {
        if (rs != null && clazz != null) {
            // 根据表实体注解TableEntity确认需转换的java对象正确
            if (clazz.isAnnotationPresent(TableEntity.class)) {
                ResultSetMetaData rsmd = rs.getMetaData();
                // 利用反射获取java类的属性
                Field[] fields = clazz.getDeclaredFields();
                while (rs.next()) {
                    T bean = (T) clazz.newInstance();
                    for (int _iterator = 0; _iterator < rsmd.getColumnCount(); _iterator++) {
                        String columnName = rsmd.getColumnName(_iterator + 1);
                        Object columnValue = rs.getObject(_iterator + 1);
                        for (Field field : fields) {
                            // 根据TableField注解确认要解析的数据库字段和java属性的映射关系
                            if (field.isAnnotationPresent(TableField.class)) {
                                TableField column = field.getAnnotation(TableField.class);
                                if (column != null && columnValue != null
                                            && column.value().equalsIgnoreCase(columnName)) {
                                    BeanUtils.setProperty(bean, field.getName(), columnValue);
                                    break;
                                }
                            }
                        }
                    }
                    if (list == null) {
                        list = new ArrayList<>();
                    }
                    list.add(bean);
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
        throw new E5Exception(e);
    }
    return list;
}

1.4 java类及注解的代码

这里的java类就是我们将rs结果集转化为java对象的目标类,根据需要自定义即可。下面举个例子。

@TableEntity
public class ArticleDO {
    @TableField("SYS_DOCUMENTID")
    private Long id;
    
    @TableField("SYS_TOPIC")
    private String title;
    
    @TableField("a_url")
    private String url;
    
    @TableField("a_realPubTime")
    private String publishTime;
    
    @TableField("a_picBig")
    private String picBig;
    
    @TableField("a_picMiddle")
    private String picMiddle;
    
    @TableField("a_picSmall")
    private String picSmall;
    
    @TableField("a_columnID")
    private Long columnId;
    
    @TableField("col_name")
    private String columnName;

    // getter、setter方法略
}

@TableEntity注解定义:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 数据表映射实体注解
 *
 * @Author kongzi
 * @Date 2022/7/27 10:13
 * @Version 1.0
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TableEntity {
    String value() default "";
}

@TableField注解定义:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 数据表字段映射实体属性注解
 *
 * @Author kongzi
 * @Date 2022/7/27 10:14
 * @Version 1.0
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TableField {
    String value() default "";
}

至此,我们就完成了数据库列表查询结果转化为javabean的操作,业务层只需要调用listPojo接口即可完成列表的分页/不分页的查询。

1.5 数据总数的查询

如果是分页查询,还涉及到数据的总条数,这里也一并附上代码。

/**
 * 查询数据条数
 *
 * @param dbSearchDTO 数据查询条件
 * @return
 * @throws BaseJdbcException
 */
public int getTotal(DbSearchDTO dbSearchDTO) throws BaseJdbcException {
    String tableName = dbSearchDTO.getTableName();
    String condition = dbSearchDTO.getCondition();
    Object[] params = dbSearchDTO.getParams();
    String sql = "select count(1) as count from ".concat(tableName);
    if (org.apache.commons.lang3.StringUtils.isNotBlank(condition)) {
        sql = sql.concat(" where ").concat(condition);
    } else {
        params = null;
        dbSearchDTO.setParams(null);
    }
    int count = getTotal(sql, params);
    return count;
}

1.6 调用接口示例

String tableName = "article a, column b";
StringBuilder selectField = new StringBuilder("a.SYS_DOCUMENTID , a.a_columnID, a.SYS_TOPIC");
selectField.append(", a.a_url, a.a_pubTime, a.a_realPubTime");
selectField.append(", a.a_picBig, a.a_picMiddle, a.a_picSmall");
selectField.append(", b.col_name");
// 设置查询条件
DbSearchDTO dbSearchDTO = new DbSearchDTO(tableName, selectField.toString());
dbSearchDTO.setCondition("a.a_columnID = b.SYS_DOCUMENTID and a.a_columnID = ? and a.a_status = 1 and a.SYS_DELETEFLAG=0");
dbSearchDTO.setParams(new Object[]{columnId});
dbSearchDTO.setOrderby("SYS_DOCUMENTID ASC");
dbSearchDTO.setPageNum(pageResult.getPageNum());
dbSearchDTO.setPageSize(pageResult.getPageSize());
dbSearchDTO.setClazz(ArticleDO.class);
try {
    // 获取总条数
    int total = apiManager.getTotal(dbSearchDTO);
    // 获取分页结果
    List list = apiManager.listPojo(dbSearchDTO);
} catch (E5Exception e) {
    throw new RuntimeException(e);
}

2、反射机制封装工具类

下面是一个简单的反射工具类,直接将rs结果集转化为对应的java类对象,要求数据库表的字段和java属性满足驼峰命名规范。

package util;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class RsToBeanUtil {
    // 根据类名将查询结果resultSet封装成对象
    // 属性名必须严格遵守驼峰命名,该类会将属性驼峰转为数据库_ 去与数据库字段进行匹配
    public static <T> T resultToBean(ResultSet rs, Class<T> t) {
        T result = null; // 封装数据完返回的Bean对象

        try {
            // 无参构造 创建bean对象
            result = t.newInstance();
            // 获得bean的属性数组
            Field[] fields = t.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                // 取出属性
                Field field = fields[i];

                // 获取类属性名
                String fieldName = field.getName();

                // 属性名转数据库字段,驼峰转下划线
                Pattern humpPattern = Pattern.compile("[A-Z]");
                Matcher matcher = humpPattern.matcher(fieldName);
                StringBuffer sb = new StringBuffer();
                while (matcher.find()) {
                    matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
                }
                matcher.appendTail(sb);
                String sqlName = sb.toString();
//                System.out.println(sqlName);

                // 用属性名得出set方法,set+将首字母大写
                String setMethodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);

                // 获取字段类型
                String type = field.getType().toString();

//                // 测试
//                System.out.println(setMethodName);
//                System.out.println(type);

                // 通过调用set方法进行属性注入
                if ("class java.lang.Integer".equals(type)) {
                    // 类型匹配 则获取对应属性的set方法    方法名,类型
                    Method method = t.getMethod(setMethodName, Integer.class);
                    // 执行set方法    被执行的对象,需要注入的数据
                    method.invoke(result, rs.getObject(sqlName));
                } else if ("class java.lang.String".equals(type)) {
                    Method method = t.getMethod(setMethodName, String.class);
                    method.invoke(result, rs.getObject(sqlName));
                } else if ("class java.lang.Double".equals(type)) {
                    Method method = t.getMethod(setMethodName, Double.class);
                    method.invoke(result, rs.getObject(sqlName));
                } else if ("class java.sql.Timestamp".equals(type)) {
                    Method method = t.getMethod(setMethodName, Timestamp.class);
                    method.invoke(result, rs.getObject(sqlName));
                } else {
                    throw new RuntimeException("类型异常");
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        // 返回该对象
        return result;
    }
}

使用方法示例:

Class<Cost> costClass = Cost.class;
while (rs.next()) {
	Cost cost = RsToBeanUtil.resultToBean(rs, costClass);
	list.add(cost);
}

3、反射机制封装,和上例类似

使用反射把ResultSet对象得到的单条结果放入到java对象中,返回Object对象

public static Object singleResultset(ResultSet rs,String str){
    try {
        //加载类
        Class clazz = Class.forName(str);
        //创建此 Class对象所表示的类的一个新实例。
        Object obj = clazz.newInstance();
        //获取此 ResultSet对象的列的编号、类型和属性。
        ResultSetMetaData rsmd = rs.getMetaData();
        //ResultSetMetaData.getColumnCount()方法获取此 ResultSet对象中的列数。
        int count = rsmd.getColumnCount();
    		
        //遍历该ResultSet对象的行数据
        while(rs.next()){
            //遍历ResultSet对象的列数据
            for(int i=1;i<count+1;i++){
                //ResultSetMetaData.getColumnName()方法获取指定列的名称。
                String columnName = rsmd.getColumnName(i);
                //将指定列的列名首字母转换成大写
                columnName = columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
                //根据指定列的名称获取指定的get方法
                Method methodGet = clazz.getMethod("get"+columnName, null);
                //Method.getGenericReturnType()获取该方法的返回类型。(获取该实例的get方法的返回类型)
                Class type = (Class)methodGet.getGenericReturnType();
                //根据指定列的名称获取指定的set方法
                Method methodSet = clazz.getMethod("set"+columnName, type);
                //判断并转换该实例中的set方法中参数的类型
                if(type.isAssignableFrom(int.class) || type.isAssignableFrom(Integer.class)){
                    //将传给该实例的set方法的参数值转换成int类型,然后执行该实例的set方法将数据放入Po类的字段中
                    methodSet.invoke(obj, Integer.parseInt(rs.getString(columnName)));
                }
                if(type.isAssignableFrom(String.class)){
                    methodSet.invoke(obj, rs.getString(columnName));
                }
                if(type.isAssignableFrom(double.class) || type.isAssignableFrom(Double.class)){
                    methodSet.invoke(obj, Double.parseDouble(rs.getString(columnName)));
                }
                if(type.isAssignableFrom(boolean.class) || type.isAssignableFrom(Boolean.class)){
                    methodSet.invoke(obj, Boolean.parseBoolean(rs.getString(columnName)));
                }
            }
        }
        //返回该Class对象的实例
        return obj;

    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    }
    return null;
}

调用该方法示例:

//参数一是查询到的ResultSet对象,参数二是反射得到的结果集放入的java对象(应返回的是一个Object对象所以需要强转一下)
AssetPo ap = (AssetPo)singleResultset(rs,"com.wlx.po.AssetPo");

使用反射把ResultSet对象得到的多条结果放入到List 集合中,和上面的方法没有什么区别,唯一不同的是下面的结果集放入的是一个集合,返回List 集合

public static List listResultset(ResultSet rs,String str){
    List list = new ArrayList();
    try {
        //加载类
        Class clazz = Class.forName(str);
        //获取此 ResultSet 对象的列的编号、类型和属性。
        ResultSetMetaData rsmd = rs.getMetaData();
        //返回此 ResultSet 对象中的列数。
        int count = rsmd.getColumnCount();
        while(rs.next()){
            //创建此 Class对象所表示的类的一个新实例。
            Object obj = clazz.newInstance();
            for(int i=1;i<count+1;i++){
                //获取指定列的名称。
                String columnName = rsmd.getColumnName(i);
                //将指定列的列名首字母转换成大写
                columnName = columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
                //获取该实例中的get方法
                Method methodGet = clazz.getMethod("get"+columnName, null);
                //获取该实例中get方法的返回类型
                Class type = methodGet.getReturnType();
                //获取该实例中的set方法
                Method methodSet = clazz.getMethod("set"+columnName, type);
                //判断并转换该实例中的set方法中参数的类型
                if(type.isAssignableFrom(int.class) || type.isAssignableFrom(Integer.class)){
                    //将传给该实例的set方法的参数值转换成int类型,然后执行该实例的set方法将数据放入Po类的字段中
                    methodSet.invoke(obj, Integer.parseInt(rs.getString(columnName)));
                }
                if(type.isAssignableFrom(String.class)){
                    methodSet.invoke(obj, rs.getString(columnName));
                }
                if(type.isAssignableFrom(double.class) || type.isAssignableFrom(Double.class)){
                    methodSet.invoke(obj, Double.parseDouble(rs.getString(columnName)));
                }
                if(type.isAssignableFrom(boolean.class) || type.isAssignableFrom(Boolean.class)){
                    methodSet.invoke(obj, Boolean.parseBoolean(rs.getString(columnName)));
                }
            }
            list.add(obj);
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (NumberFormatException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
    return list;
}

调用该方法示例:

//参数一是查询到的ResultSet对象,参数二是反射得到的结果集放入的java对象
List list = listResultset(rs, "com.wlx.po.AssetPo");

本例中使用java的反射机制来获得Po类中get方法的返回类型,来设置set的参数类型,并把查询得到的ResultSet对象中的结果通过执行set方法设入Po类中。

0

评论区