dbutils和反射实现ORM问题整理

来源:互联网 发布:怎样找淘宝客推广产品 编辑:程序博客网 时间:2024/06/06 04:26

背景

时间过的真快,用dbutils自定义ORM竟然已经是7月中旬的事情了,本周才真正用到那些代码,当初简单实现的MyBeanHandler类还有一些缺陷,整理修正流程如下。

实现流程

基本思路是这样的:需要将数据库的一条查询结果,转换成Java实体类的实例,自定义一个ResultHandler,接受一个类型Class。

由于select列可能不是Java全部的属性,所以不能遍历Java属性列表,逐个从ResultSet获取该列的值,一旦某一列没有被查出来,ResultSet的getObject(entityField)的时候直接报异常了。所以转换思路,以查询结果列,反着找到该列对应的Java类的属性,再用反射调用属性的setter方法,完成实例的组装。

此外,需要注意Java实体类属性的类型和数据库表列的类型,如果存在不一致的话,需要特殊处理。例如:Java中boolean类型的属性,对应到数据库中是tinyint。还有一个特别重要的问题就是ResultSet中获取的元数据信息中,所有的数值列都是Java的封装类型,即:tiny/int/bigint对应的Class是Integer.class的。

Java的封装类型和基本类型的Class是不同的,int/long/byte/char/float的Class是对应的类型名称.class的,如int.class,long.class。所以调用setter方法触发反射的类型一定要传递Java实体属性的类型。

反射获取类属性

开发过程中有些模块的实体有公共属性,所以有不少实体类是有基础父类的,这些实体类的表字段,除了有自己定义的属性外,还必须包括父类的属性。

反射获取实体字段的方法为:

    /**     * 判断某个属性是否是简单属性:所有的简单类型和对应的包装类型都是     *      * @param fieldType     * @return     */    public static boolean isSimpleTypeField(Class<?> fieldType) {        if (fieldType == Integer.class ||fieldType == int.class || fieldType == long.class ||fieldType == Long.class || fieldType == String.class                || fieldType == Short.class || fieldType == Character.class) {            return true;        }        return false;    }    /**     * 判断某个属性是否是boolean类型,有两种包装类型和原始boolean类型     *      * @param fieldType     * @return     */    public static boolean isBooleanTypeField(Class<?> fieldType) {        if (fieldType == Boolean.class ||fieldType==boolean.class) {            return true;        }        return false;    }/**     * 获取某一个类的fieleName对应的Field对象,如果当前类没有,从父类获取     * @comment 异常,不处理     * @param entityClass     * @param fieldName     * @return     */    public static Field getFieldOfEntity(Class<?> entityClass,String fieldName){        if(fieldName==null){            return null;        }        try {            return entityClass.getDeclaredField(fieldName);        } catch (NoSuchFieldException e) {            try {                return entityClass.getSuperclass().getDeclaredField(fieldName);            } catch (NoSuchFieldException e1) {            } catch (SecurityException e1) {            }        } catch (SecurityException e) {        }        return null;    }

此外,数据库表列名称和Java属性setter直接的映射关系,可以自定义的,通用的方法是二者名称一致,对应的就是setXXX,getXXX,isXXX(boolean类型)。如果数据库列表是下划线,那么就是另一种映射关系,这些都可以自定义映射规则的。

那么获取列的getter/setter方法为:

/**     * 获取某个属性的get方法名称     *      * @param name     * @return     */    public static String obtainGetterName(String name,boolean isBooleanType) {        //特殊处理boolean类型,统一为isXXX        if(isBooleanType){            return "is" + name.substring(0, 1).toUpperCase()+name.substring(1);        }        return "get" + name.substring(0, 1).toUpperCase()+name.substring(1);    }    /**     * 获取某个属性的set方法名称     *      * @param name     * @return     */    public static String obtainSetterName(String name) {        return "set" + name.substring(0, 1).toUpperCase()+name.substring(1);    }

自定义BeanHandler

按上述思路修正原来实现的BeanHandler如下:

public class MyBeanHandler<T> implements ResultSetHandler<T> {    private Logger logger = Logger.getLogger(MyBeanHandler.class);    private Class<T> entityClass;    public MyBeanHandler(Class<T> entityClass) {        this.entityClass = entityClass;    }    @Override    public T handle(ResultSet resultSet) {        try {            while (resultSet.next()) {                T result = null;                result = entityClass.newInstance();                ResultSetMetaData metaData = resultSet.getMetaData();                int columnCount = metaData.getColumnCount();                for(int col = 1 ; col <= columnCount ; col++){                    String name = metaData.getColumnName(col);                    Object oldValue = resultSet.getObject(col);                    Object columnValue = oldValue;                    if(columnValue==null){//空,直接过滤掉。。。。                        continue;                    }                    //获取该字段名称对应的Java实体类字段声明:updated on 2017-09-05                    Field field = IntrospectUtil.getFieldOfEntity(entityClass,name);                    if(field==null){                           continue;                    }                    Class<?> columnType = field.getType();                    if (ArrayList.class == columnType || List.class == columnType) {                        // 数据库存储的是JSON,回转成List对象                        List<Object> list = JSONObject.parseArray(columnValue.toString(), Object.class);                        columnValue = list;                    } else if (IntrospectUtil.isSimpleTypeField(columnType)) {                        // non op 普通属性,直接设置值                    } else if(IntrospectUtil.isBooleanTypeField(columnType)){                        columnValue = (Integer)oldValue==1 ? Boolean.TRUE:Boolean.FALSE;                    }else {                        // 复杂对象,且非List,通过JSON转换为对应类型的对象                        columnValue = JSONObject.parseObject(columnValue.toString(), columnType);                    }                    //找到对应简单字段的原始字段类型,作为setter的方法类型                    String setter = IntrospectUtil.obtainSetterName(name);                    Method method = entityClass.getMethod(setter, columnType);                    method.invoke(result, columnValue);                }                return result;            }        } catch (Exception e) {            logger.error("MyBeanHandler error", e);        }        return null;    }}

启示录

反射应用时,Class类的getDeclaredField能获取当前类的所有属性,静态、非静态的都能获取到。如果插入操作需要根据实体属性集合类定义insert后面的字段,需要排除掉静态成员变量。

此外,父类的成员变量即使定义成protected的,也不能直接被子类用getDeclaredField方法获取到;但是getMethod方法不仅能获取到自己的方法,同时也能获取父类继承来的方法,所以Method不存在需要特别注意的地方。

代码不用就不知道有坑啊,demo编写过程中还是太简单,所以发现不了问题。编程路漫漫呐!

而且生活时不时还给你一点小打击,毕小宝不到三岁半,刚上幼儿园一个月,昨天惊闻幼儿园缴费上的课程跟其他小朋友单独报的课程不一样,突然就感受到了深深的伤害,一整天心情都很沉重。我该怎么跟这个社会对抗,才能全身而退呢?我的孩子又该怎么面对将来的社会环境呢。又是一个大命题!

原创粉丝点击