jdbc實戰-實現類Mybatis結果集解析

Jdbc 比較繁瑣的一個操作就是解析結果集ResultSet, 在實際開發時, 通常會將對結果集的解析封裝爲一個工具類. 需要注意的時, jdbc查詢出來的屬性可能不能直接轉換爲java的類型, 比如說java.sql.Date, 不能直接轉換爲java.util.Date 或LocalDate等類型, 需要自定義轉換器. 如果比較熟悉Mybatis的話, 會發現Mybatis底層也封裝了大量的類型轉換器.

1. 工具類源碼

筆者的工具類比較簡單, 只封裝了三個方法:
 

方法簽名 方法描述 參數說明
public static LinkedHashMap<String, Object> toPropertyMap(ResultSet resultSet) throws SQLException 轉換單行結果集爲Map結構. key爲列別名, value爲列值 resultSet: 查詢結果集
public static T toBean(ResultSet resultSet, Class clz) 轉換結果集爲單行對象 clz: 目標對象類型
resultSet: 結果集
public static List toBeans(ResultSet resultSet, Class clz) throws SQLException 轉換結果集爲java對象集合. clz: 要轉換的javaBean類
resultSet: 結果集

1.1 ResultSetUtil 源碼

/** 結果集解析工具類
 * @since 1.0
 * @author zongf
 * @created 2019-07-18
 */
public class ResultSetUtil {

    /** 轉換單行結果集爲Map結構. key爲列別名, value爲列值
     * @param resultSet 查詢結果集
     * @return 結果集中爲空時, 返回null
     * @since 1.0
     * @author zongf
     * @created 2019-07-18
     */
    public static LinkedHashMap<String, Object> toPropertyMap(ResultSet resultSet) throws SQLException {

        // 如果結果集爲空,則返回null
        if (!resultSet.next()) return null;

        LinkedHashMap<String, Object> cloumnMap = new LinkedHashMap<>();

        // 獲取結果集元信息
        ResultSetMetaData metaData = resultSet.getMetaData();

        // 獲取每一列列名與值
        for (int i = 1; i <= metaData.getColumnCount(); i++) {
            // 獲取列別名爲key
            String columnLabel = metaData.getColumnLabel(i);
            Object columnValue = resultSet.getObject(i);
            cloumnMap.put(columnLabel, columnValue);
        }
        return cloumnMap;
    }

    /** 轉換結果集爲單行對象
     * @param clz       目標對象類型
     * @param resultSet 結果集
     * @since 1.0
     * @return null
     * @author zongf
     * @created 2019-07-18
     */
    public static <T> T toBean(ResultSet resultSet, Class<T> clz) {
        try {
            LinkedHashMap<String, Object> propertyMap = toPropertyMap(resultSet);
            return ReflectUtil.newInstance(clz, propertyMap, new DateTypeConverter());
        } catch (SQLException e) {
            throw new RuntimeException("sql 執行異常!", e);
        }
    }

    /** 轉換結果集爲java對象集合.
     * @param clz       要轉換的javaBean類
     * @param resultSet 結果集
     * @return 結果集中沒有數據時, 返回null
     * @since 1.0
     * @author zongf
     * @created 2019-07-18
     */
    public static <T> List<T> toBeans(ResultSet resultSet, Class<T> clz) throws SQLException {

        List<T> list = new ArrayList<>();

        LinkedHashMap<String, Object> propertyMap = null;

        // 解析結果集
        while ((propertyMap = toPropertyMap(resultSet)) != null) {
            T t = (T) ReflectUtil.newInstance(clz, propertyMap, new DateTypeConverter());
            if(t != null) list.add(t);
        }
        return list.size() > 0 ? list : null;
    }

}

1.2 ReflectUtil 源碼

這是筆者對使用到的反射技術封裝的一個簡單工具類.


/** 反射工具類
 * @since 1.0
 * @author zongf
 * @created 2019-07-18
 */
public class ReflectUtil {

    /**爲對象屬性賦值
     * @param target 目標對象
     * @param property 屬性名
     * @param property 屬性名
     * @return value 屬性值
     * @since 1.0
     * @author zongf
     * @created 2019-07-18
     */
    public static void setPropertyValue(Object target, String property, Object value) {
        try {
            PropertyDescriptor descriptor = new PropertyDescriptor(property, target.getClass());
            Method writeMethod = descriptor.getWriteMethod();
            writeMethod.invoke(target, value);
        } catch (Exception e) {
            throw new RuntimeException("爲對象屬性賦值異常!",e);
        }
    }

    /** 獲取對象屬性值
     * @param target 目標對象
     * @param property 屬性
     * @return Object 返回對象屬性值
     * @since 1.0
     * @author zongf
     * @created 2019-07-18
     */
    public static Object getPropertyValue(Object target, String property) {
        try {
            PropertyDescriptor descriptor = new PropertyDescriptor(property, target.getClass());
            Method readMethod = descriptor.getReadMethod();
            return readMethod.invoke(target);
        } catch (Exception e) {
            throw new RuntimeException("獲取對象屬性異常!",e);
        }
    }

    /** 反射創建對象
     * @param clz 目標對象的類型
     * @return propertiesMap 目標對象的屬性與值
     * @since 1.0
     * @author zongf
     * @created 2019-07-18
     */
    public static <T> T newInstance(Class<T> clz, HashMap<String, Object> propertiesMap, DateTypeConverter typeConverter){

        // 如果屬性爲空, 則不進行創建, 返回null
        if (propertiesMap == null || propertiesMap.isEmpty()) {
            return null;
        }

        // 使用無參數構造方法創建對象
        T t = null;
        try {
            t = clz.newInstance();
            for (Map.Entry<String, Object> entry : propertiesMap.entrySet()) {

                // 獲取對象屬性與值
                String property = entry.getKey();
                Object value = entry.getValue();

                // 獲取屬性描述符
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(property, clz);
                // 獲取屬性類型
                Class<?> propertyType = propertyDescriptor.getPropertyType();

                // 使用類型轉換器轉換參數類型
                value = typeConverter.convert(value, propertyType);

                // 調用set方法, 賦值
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(t, value);
            }
        } catch (Exception e) {
            throw new RuntimeException("反射創建對象失敗!", e);
        }
        return t;
    }

}

1.3 DateTypeConverter 轉換器

筆者僅僅編寫了一個日期類型的轉換器, 在企業開發中, 可能需要用到的轉換器會更多.

/** 日期類型轉換器
 * @since 1.0
 * @author zongf
 * @created 2019-07-18
 */
public class DateTypeConverter {

    /** 轉換對象的類型
     * @param value 值
     * @param javaType java類型
     * @return 轉換後的類型
     * @since 1.0
     * @author zongf
     * @created 2019-07-18
     */
    public Object convert(Object value, Class javaType) {
        Object obj = value;

        // 如果是java 日期
        if(javaType.equals(Date.class)) {
            java.sql.Date date = (java.sql.Date) value;
            obj = date.toInstant().getEpochSecond();

            // 如果是java8 日期
        } else if(javaType.equals(LocalDate.class)){
            obj = ((java.sql.Date) value).toLocalDate();
        } else {
            obj = value;
        }
        return obj;
    }
}

2. 單元測試

2.1 創建javaBean

測試時, 需要創建t_user表和javabean, 筆者這邊僅給出javabean的定義.

public class UserPO {

    private Integer id;

    private String name;

    private String password;
    
    private LocalDate birthday;

    
    // 省略setter/getter/toString 方法
    
}

2.2 測試用例

// 測試轉換單個對象爲Map 結構
@Test
public void toPropertyMap() throws SQLException {

    String str = "select id uId, name , pwd, birthday from t_user where id = 1001";

    Connection connection = DbConnUtil.getConnection(true);

    Statement statement = connection.createStatement();

    ResultSet resultSet = statement.executeQuery(str);

    LinkedHashMap<String, Object> map = ResultSetUtil.toPropertyMap(resultSet);
    map.forEach((key, val) -> System.out.println(key + ":" + val));
}

// 測試轉換對象爲單個bean
@Test
public void toBean() throws SQLException {

    String str = "select * from t_user where id = 1002";

    Connection connection = DbConnUtil.getConnection(true);

    Statement statement = connection.createStatement();

    ResultSet resultSet = statement.executeQuery(str);

    UserPO userPO = ResultSetUtil.toBean(resultSet, UserPO.class);

    System.out.println(userPO);
}

// 測試轉換對象爲bean列表
@Test
public void toBeans() throws SQLException {

    String str = "select * from t_user ";

    Connection connection = DbConnUtil.getConnection(true);

    Statement statement = connection.createStatement();

    ResultSet resultSet = statement.executeQuery(str);

    List<UserPO> userPOList = ResultSetUtil.toBeans(resultSet, UserPO.class);

    userPOList.forEach(System.out::println);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章