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