本文隶属于专题系列: Android数据库操作通用化

这里写图片描述

概述

上一篇文章,已经解决了前两个问题,那么现在我们继续。

首先,我们回顾一下问题:
问题1:表名的获取
问题2:如何将实体中的数据,按照对应关系导入到数据库中
问题3: 明确实体中主键是谁?获取到主键中封装的值
问题4:如何将数据库中表的列的数据,按照对应关系,封装到实体中
问题5:实体的对象创建

明窗半掩小庭幽,夜静灯残未待留。

如何明确一个实体中的字段是主键,注解依旧是一个很好的方法。有关主键,有两个中很明确的性质是:获取值、是否自增长。

在编程,一种好习惯是先捋思路,再写类和方法,最后再自动创建,通过Eclipse的强大功能,很多东西就能够自动帮我们确定下来。

我们希望指定一个字段是主键,可以为它增加@Id注解,想要确定是否是自增长,可以为注解增加autoincrement属性。请看代码:

@TableName(DBHelper.TABLE_NEWS_NAME)
public class News {
    // 主键:如何明确给计算机这就是主键
    // 主键:值的获取
    // 主键:自增长
    @Id(autoincrement=true)
    // 指定了实体和数据库中表的对应关系
    @Column(DBHelper.TABLE_ID)
    private int id;
    @Column(DBHelper.TABLE_NEWS_TITLE)
    private String title;
    @Column(DBHelper.TABLE_NEWS_SUMMARY)
    private String summary;
}

此时,@Id注解还未创建,我们在@Id(autoincrement=true)上使用Ctrl+1的快捷键,可以快速度创建代码,随后我们再为注解设置存活时间和放置的位置等属性。代码如下:

/** * 用来标识主键 */
@Target(ElementType.FIELD)// 指定放置的位置
@Retention(RetentionPolicy.RUNTIME) // 指定存活时间
public @interface Id {
    /** * 主键是否自动增长 * @return */
    boolean autoincrement();
}

只要思路清晰,代码也就随之简单了。

接下来,我们填写BaseDaoSupport中的update方法,由于前两个问题的解决,这次我们的代码异常的简单,只在最后留下了一个未实现getId(m)方法。请看代码:

@Override
public int update(M m) {
    // 填充数据
    ContentValues values = new ContentValues();
    fillColumn(m, values);
    return db.update(getTableName(), values, DBHelper.TABLE_ID + "=?", new String[] { getId(m) });
}

万事俱备,只欠东风。由于向getId()方法中传入了m实体,那么,通过使用反射技术,拿到带有@Id注解的字段,并拿到相应的值,也就很简单了。请看代码:

/** * 问题3:明确实体中主键是谁?获取到主键中封装的值 */
public String getId(M m) {
    // 获取m实体的所有字段,看一看谁有@Id这个注解
    Field[] fields = m.getClass().getDeclaredFields();
    for (Field field : fields) {
        // 设置访问权限
        field.setAccessible(true);
        // 拿到有Id注解的字段,并获取值
        Id id = field.getAnnotation(Id.class);
        if (id != null) {
            try {
                return field.get(m).toString();
            } catch (IllegalArgumentException e) {
                throw new RuntimeException("字段不属于m实例");
            } catch (IllegalAccessException e) {
                throw new RuntimeException("没有访问字段域的权限");
            }
        }
    }
    return null;
}

总结:有了前两个问题的解决,问题的解决变得不是那么复杂了。

风冷结阴寒落叶,别离长倚望高楼。

接下来轮到填写BaseDaoSupport中的findAll()方法了,思路也是很简单,将游标中对应列的值拿到,并封装到实体的对应字段中,并将实体添加到指定集合中返回。代码如下:

@Override
public List<M> findAll() {
    List<M> result = null;
    Cursor cursor = db.query(getTableName(), null, null, null, null, null, null);
    if (cursor != null) {
        result = new ArrayList<M>();
        while (cursor.moveToNext()) {
            // 获取实体
            M m = getInstance();
            // 填充m实体中的字段
            fillField(cursor, m);
            result.add(m);
        }
    }
    return result;
}

其中,最重要的两行代码是M m = getInstance();fillField(cursor, m);,前一个先暂时放下,最后解决。后面的一句,则是将由表中的列数据,封装到m实体中对应的字段中,依旧利用了反射技术。代码如下:

/** * 问题四:如何将数据库中表的列的数据,按照对应关系,封装到实体中 */
public void fillField(Cursor cursor, M m) {
    //
    Field[] fields = m.getClass().getDeclaredFields();
    for (Field field : fields) {
        field.setAccessible(true);
        Column column = field.getAnnotation(Column.class);
        if (column != null) {
            try {
                // 根据列名,拿到由表中该列的索引
                int columnIndex = cursor.getColumnIndex(column.value());
                // 根据索引拿到对应列的值
                String value = cursor.getString(columnIndex);
                // 为m实体的字段设置值
                field.set(m, value);
            } catch (IllegalArgumentException e) {
                throw new RuntimeException("字段不属于m实例");
            } catch (IllegalAccessException e) {
                throw new RuntimeException("没有访问字段域的权限");
            }
        }
    }
}

经过这一番操作,填充数据的操作也写完了,findAll方法也写完了。此刻,就剩下最后一个问题了:实体对象的建。只能解决这个,那么我们Android数据通用操作基本上也就完成了。

迟迟月影斜依竹,叠叠诗余赋旅愁。

如果不把问题5解决,那么之前的事情都白做了。

如果你想要获取到它的话,你要知道,这个实体是什么时候确定的。

BaseDaoSupport<M>它肯定是不知道的,这里放的是一个泛型,我们Ctrl+T一下。

这里写图片描述

点进去NewsDaoImpl,发现原来实在这个时候确定实体对象的。

这里写图片描述

思路已经说明白了,那么我们的操作步骤呢?

首先,知道是那个孩子调用的该方法-那个孩子在运行。BaseDaoSupport是作为很多孩子的父亲的,到底是哪个孩子执行(调用)的,需要知道这个信息。

// 1. 知道是那个孩子调用的该方法。
Class<?> clazz = getClass(); // 获取到了实际运行时的那个类

其次,获取该孩子的父类(支持泛型的父类)。但是我们要注意,泛型不是那么容易拿得到的,由于我们是在运行时去拿的,泛型被擦除了。我们需要使用一些特殊的手段去拿,支持泛型的父类,才能获取到泛型里面的东西。

这里写图片描述

// 2. 获取该孩子的父类(支持泛型的父类)
Type genericSuperclass = clazz.getGenericSuperclass();

然后,获取到泛型中的参数。

// 安全性检查
if (genericSuperclass != null && genericSuperclass instanceof ParameterizedType) {
    // 3. 获取到泛型中的参数。
    // 所有的泛型,JDK都会让泛型实现一个接口,叫做参数化的类型,规定了泛型的通用操作。
    // ParameterizedType叫做参数化类型
    // getActualTypeArguments()该方法,会返回此类型的Type对象的数组。
    // 为什么是数组? 可能会有多个泛型参数,例如:Map<K,V>
    Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments();
    // 这个type就是我们的泛型中的实际参数。
    Type type = actualTypeArguments[0];
    // 4. 就可以利用泛型的参数,创建实例了。
    try {
        return (M) ((Class<?>) type).newInstance();
    } catch (Exception e) {
        throw new RuntimeException("实例化异常");
    }
}

通过以上这些步骤,getInstance()方法就基本完成了,代码如下:

/** * 问题五:实体对象的创建 */
@SuppressWarnings("unchecked")
public M getInstance() {
    // 1. 知道是那个孩子调用的该方法。
    Class<?> clazz = getClass(); // 获取到了实际运行时的那个类
    // 2. 获取该孩子的父类(支持泛型的父类)
    Type genericSuperclass = clazz.getGenericSuperclass();
    // 安全性检查
    if (genericSuperclass != null && genericSuperclass instanceof ParameterizedType) {
        // 3. 获取到泛型中的参数。
        // 所有的泛型,JDK都会让泛型实现一个接口,叫做参数化的类型,规定了泛型的通用操作。
        // ParameterizedType叫做参数化类型
        // getActualTypeArguments()该方法,会返回此类型的Type对象的数组。
        // 为什么是数组? 可能会有多个泛型参数,例如:Map<K,V>
        Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments();
        // 这个type就是我们的泛型中的实际参数。
        Type type = actualTypeArguments[0];
        // 4. 就可以利用泛型的参数,创建实例了。
        try {
            return (M) ((Class<?>) type).newInstance();
        } catch (Exception e) {
            throw new RuntimeException("实例化异常");
        }
    }
    return null;
}

将欲断肠随断梦,雁飞连阵几声秋。

至此,整个如何将Android数据库操作通用化的博客就写完了,第一次写这么长篇的,也不知道表达如何,希望对大家有些帮助。

下面是整个BaseDaoSupport中的所有代码,同时写修复了fillColumn()方法中填充主键出错的BUG,也修复了fileField()中,为字段设置值时没有考虑到Integer类型的BUG。

package com.bzh.db.dao.base;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import android.R.id;
import android.R.integer;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.bzh.db.DBHelper;
import com.bzh.db.dao.annocation.Column;
import com.bzh.db.dao.annocation.Id;
import com.bzh.db.dao.annocation.TableName;
@SuppressWarnings("unused")
public abstract class BaseDaoSupport<M> implements BaseDao<M> {
    // * 抽取公共部分应该解决的问题:<br>
    // * 问题1:表名的获取<br>
    // * 问题2:如何将实体中的数据,按照对应关系导入到数据库中<br>
    // * 问题3:明确实体中主键是谁?获取到主键中封装的值<br>
    // * 问题4:实体的对象创建<br>
    // * 问题5:如何将数据库中表的列的数据,按照对应关系,封装到实体中<br>
    private Context context;
    private DBHelper helper;
    private SQLiteDatabase db;
    public BaseDaoSupport(Context context) {
        super();
        this.context = context;
        this.helper = new DBHelper(context);
        this.db = helper.getWritableDatabase();
    }
    @Override
    public long insert(M m) {
        ContentValues values = new ContentValues();
        // m代表数据源,vlaues是数据导入的目标
        fillColumn(m, values);
        return db.insert(getTableName(), null, values);
    }
    @Override
    public int delete(Serializable id) {
        return db.delete(getTableName(), DBHelper.TABLE_ID + "=?", new String[] { id.toString() });
    }
    @Override
    public int update(M m) {
        // 填充数据
        ContentValues values = new ContentValues();
        fillColumn(m, values);
        return db.update(getTableName(), values, DBHelper.TABLE_ID + "=?", new String[] { getId(m) });
    }
    @Override
    public List<M> findAll() {
        List<M> result = null;
        Cursor cursor = db.query(getTableName(), null, null, null, null, null, null);
        if (cursor != null) {
            result = new ArrayList<M>();
            while (cursor.moveToNext()) {
                // 获取实体
                M m = getInstance();
                // 填充m实体中的字段
                fillField(cursor, m);
                result.add(m);
            }
        }
        return result;
    }
    /********************** 解决五个问题 ***********************/
    /** * 问题一:表名的获取 */
    public String getTableName() {
        // 每一个数据库表,都会对应着一个具体的实体
        // 比如:news表对应着News类
        // 如果我们能够通过“实体”拿到表名就好办了
        // 方案一:如果能够获取到实体,就能够获取到实体的简单名称,首字母小写后就是表名。
        // 缺点:数据库定义表的名称和实体名称要基本一致。定义实体的名称会受限制。
        // 方案二:利用注解,实体的名称和数据库表的名称,可以不一致,关系脱离。
        // 伪代码:
        // ① 问题五:获取到对象的实体
        M m = getInstance();
        // ② 获取实体头上的注解,依据value的设置值,确定操作的数据库表
        // 需要注意的,想要在“运行时”获取到注解的信息,给注解设置存活时间。
        TableName tableName = m.getClass().getAnnotation(TableName.class); // annotationType
                                                                            // =
                                                                            // 注解的类型
        // 为了安全起见,判断注解的合法性;合法则返回value值
        if (tableName != null) {
            return tableName.value();
        }
        return null;
    }
    /** * 问题二:如何将实体中的数据,按照对应关系导入到数据库中 * * @param m 数据源 * @param values 是数据导入的目标 */
    public void fillColumn(M m, ContentValues values) {
        // 获取m上所有的字段
        Field[] fields = m.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 设置访问权限
            field.setAccessible(true);
            // 获取字段头上的注解
            Column column = field.getAnnotation(Column.class);
            if (column != null) {
                try {
                    String key = column.value(); // 获取注解中,指定的列名
                    String value = field.get(m).toString(); // 获取字段值
                    // 如果该field是主键,并且是自增长的,不能够添加到集合中
                    Id id = field.getAnnotation(Id.class);
                    if (id != null && id.autoincrement()) {
                        continue;
                    }
                    // 填写数据
                    values.put(key, value);
                } catch (IllegalArgumentException e) {
                    throw new RuntimeException("字段不属于m实例");
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("没有访问字段域的权限");
                }
            }
        }
    }
    /** * 问题3:明确实体中主键是谁?获取到主键中封装的值 */
    public String getId(M m) {
        // 获取m实体的所有字段,看一看谁有@Id这个注解
        Field[] fields = m.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 设置访问权限
            field.setAccessible(true);
            // 拿到有Id注解的字段,并获取值
            Id id = field.getAnnotation(Id.class);
            if (id != null) {
                try {
                    return field.get(m).toString();
                } catch (IllegalArgumentException e) {
                    throw new RuntimeException("字段不属于m实例");
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("没有访问字段域的权限");
                }
            }
        }
        return null;
    }
    /** * 问题四:如何将数据库中表的列的数据,按照对应关系,封装到实体中 */
    public void fillField(Cursor cursor, M m) {
        //
        Field[] fields = m.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Column column = field.getAnnotation(Column.class);
            if (column != null) {
                try {
                    // 根据列名,拿到由表中该列的索引
                    int columnIndex = cursor.getColumnIndex(column.value());
                    // 根据索引拿到对应列的值
                    String value = cursor.getString(columnIndex);
                    // 为m实体的字段设置值
                    if (field.getType() == Integer.class) {
                        field.set(m, Integer.parseInt(value));
                    } else if (field.getType() == String.class) {
                        field.set(m, value);
                    }
                } catch (IllegalArgumentException e) {
                    throw new RuntimeException("字段不属于m实例");
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("没有访问字段域的权限");
                }
            }
        }
    }
    /** * 问题五:实体对象的创建 */
    @SuppressWarnings("unchecked")
    public M getInstance() {
        // 1. 知道是那个孩子调用的该方法。
        Class<?> clazz = getClass(); // 获取到了实际运行时的那个类
        // 2. 获取该孩子的父类(支持泛型的父类)
        Type genericSuperclass = clazz.getGenericSuperclass();
        // 安全性检查
        if (genericSuperclass != null && genericSuperclass instanceof ParameterizedType) {
            // 3. 获取到泛型中的参数。
            // 所有的泛型,JDK都会让泛型实现一个接口,叫做参数化的类型,规定了泛型的通用操作。
            // ParameterizedType叫做参数化类型
            // getActualTypeArguments()该方法,会返回此类型的Type对象的数组。
            // 为什么是数组? 可能会有多个泛型参数,例如:Map<K,V>
            Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments();
            // 这个type就是我们的泛型中的实际参数。
            Type type = actualTypeArguments[0];
            // 4. 就可以利用泛型的参数,创建实例了。
            try {
                return (M) ((Class<?>) type).newInstance();
            } catch (Exception e) {
                throw new RuntimeException("实例化异常");
            }
        }
        return null;
    }
}

这里写图片描述

你可能感兴趣的内容
Android中远程Service浅析 收藏,4234 浏览
0条评论

dexcoder

这家伙太懒了 <( ̄ ﹌  ̄)>
Owner