概述
上一篇文章,已经解决了前两个问题,那么现在我们继续。
- 首先,我们回顾一下问题:
- 问题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; } }