结合Annotation类的学习写了一个SQLite的框架,对数据库的操作转化为对java对象的操作,摆脱开发过程中代码与表操作混在一起的问题,其实Annotation在框架设计当中用到的比较多,常见的就是我们在方法上加@注解,我们来看看如何自定义一个Annotation类来使用它
源码及DEMO地址:https://github.com/xiaoqi0716/LightSqlite
介绍框架之前先看来看它的用法:比如我们有一个数据库用来存储学生、老师的信息,那么我们在使用时可以这么来做
写一个类继承AbstractDBHelper,并实现相就的方法
public class PersonalDB extends AbstractDBHelper {
private final String DB_NAME = "MY_DB.db"; //数据库名
private final int DB_VERSION = 1; //数据库版本
public PersonalDB(Context context) {
super(context);
}
@Override
public String getDataBaseName() {
return DB_NAME;
}
@Override
public int getDataBaseVersion() {
return DB_VERSION;
}
//返回MY_DB.db数据库所有的表,后续增加表直接在这里添加
@Override
public List<AbstractTable<?>> getTables() {
List<AbstractTable<?>> list = new ArrayList<AbstractTable<?>>();
list.add(StudentTable.getInstance()); //学生表
list.add(TeacherTable.getInstance()); //老师表
return list;
}
}
然后建立一个学生表对应的数据类
public class StudentItem {
public static final String FIELD_S_ID = "s_id";
public static final String FIELD_S_NAME = "s_name";
public static final String FIELD_S_AGE = "age";
public static final String FIELD_S_CLASS = "class";
@Column(name = FIELD_S_ID, unique = true, index = true, notNull = true)
private int id;
@Column(name = FIELD_S_NAME, notNull = true)
private String name;
@Column(name = FIELD_S_AGE, notNull = true)
private int age;
@Column(name = FIELD_S_CLASS)
private String inClass;
//GET and SET
.....
}
public class StudentTable extends AbstractTable<StudentItem> {
private final static String TABLE_NAME = "student"; // 学生表
@Override
public String getTableName() {
return TABLE_NAME;
}
// 当数据库版本号更新时,对该表的操作
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
老师表就不写了,与StudentTable写法一样,完成上面几步操作就可以使用了,最后看盾如何使用:
PersonalDB db = new PersonalDB(context);
db.open();
上面两条语句就初始了一个数据库打开操作,接下来如何对表进行操作:
StudentTable.getInstance().add(......); //增
StudentTable.getInstance().find(......); //查询
StudentTable.getInstance().set(......); //修改
StudentTable.getInstance().remove(......); //删除
增、删、改、查各有多种API实现来使用,是不是很简单。
框架设计思路:
数据库概念大家都比较熟悉,一个数据库下面会有多张表,这属于一对多的情况,那么体现在框架中就是一个数据库类当中,需要为它配置表对应的类对象,由数据库这个类对管理所有表。
在处理数据时,按照我们平时的习惯,应该有model类去管理一个数据模型,比如上文用的学生、老师,他们都有自己的属性,其实model类中的属性是最能体现这个表中有哪些字段,比如StudentItem这个model类,它里面有学生ID、姓名、年龄、年级等,那么对应的表也是这些字段,那么怎么才能把这些类的属性关联到表中每一个字段上呢,这里我用到了java中一个特殊的类,Annotation类,这个类其它并不陌生,比如我们平时继承父类的一些方法,开发环境往往会加上@Overide这个注解,这个实际上就一个Annotation类,关于这个类的细节这里不做详细介绍,这里主要讲怎么使用它来完成我们的框架,代码献上
我们主要看一下AbstractDBHelper类中一个创建表的方法,其它逻辑请查看源码
private void createTable(SQLiteDatabase db, AbstractTable<?> table) {
StringBuilder sql = new StringBuilder();
sql.append("CREATE TABLE IF NOT EXISTS ");
String tableName = table.getTableName();
sql.append(tableName).append(" (");
Class<?> tableCls = null;
Type t = table.getClass().getGenericSuperclass();
if (t != null && t instanceof ParameterizedType) {
Type[] type = ((ParameterizedType) t).getActualTypeArguments();
tableCls = (Class<?>) type[0];
}
Field[] fields = tableCls.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (field.isAnnotationPresent(Column.class)) {
Column ano = field.getAnnotation(Column.class);
String fieldName = ano.name();
sql.append(fieldName).append(" ");
sql.append(getFieldType(field)).append(" ");
if (ano.primaryKey()) {
sql.append("PRIMARY KEY").append(" ");
}
if (ano.autoIncrement()) {
sql.append("AUTOINCREMENT").append(" ");
}
if (ano.unique()) {
sql.append("UNIQUE").append(" ");
}
if (ano.notNull()) {
sql.append("NOT NULL").append(" ");
}
if (!TextUtils.isEmpty(ano.defaultVal())) {
String fieldType = getFieldType(field);
if ("TEXT".equals(fieldType)) {
sql.append("default").append(" ").append("'").append(ano.defaultVal()).append("'").append(" ");
} else {
sql.append("default").append(" ").append(ano.defaultVal()).append(" ");
}
}
sql.append(", ");
}
}
sql.deleteCharAt(sql.length() - 2);
sql.append(")");
LogUtils.v(sql.toString());
try{
db.execSQL(sql.toString());
}catch (Exception e) {
e.printStackTrace();
}
for (Field field : fields) {
field.setAccessible(true);
if (field.isAnnotationPresent(Column.class)) {
Column an = field.getAnnotation(Column.class);
if (an.index()) {
String sqlStr = "CREATE INDEX IF NOT EXISTS " + table.getTableName() + "_" + an.name() + "_index ON " + table.getTableName() + "(" + an.name() + ")";
LogUtils.v(sqlStr);
try{
db.execSQL(sqlStr);
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
table.onCreateTrigger(db);
}
该方法接收两个参数,其中一个是继承AbstractTable类的实例,在方法内部通过Type t = table.getClass().getGenericSuperclass();,Type[] type = ((ParameterizedType) t).getActualTypeArguments(); Type是Java 中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。ParameterizedType参数化类型,就是所说的泛型,(Class<?>) type[0]拿到了传入泛型的真实类型,type它是一个数组,由于我们只用到一个参数,如上面看的StudentTable类,所以只取第0个,就是StudentItem这个类。当我们拿到了StudentItem我们就可以对它进行反射,field.isAnnotationPresent(Column.class);这句来判断某些属性是否加上了@Column注解,如果有才认为是字段相关的属性,Column ano = field.getAnnotation(Column.class);拿到了Column注解这个类的实例,后面就是对@Column中参数进行解析了。
我们来看看@Column这个是怎么定义的呢
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String name();
boolean primaryKey() default false;
boolean autoIncrement() default false;
boolean unique() default false;
boolean index() default false;
boolean notNull() default false;
String defaultVal() default "";
}
Column 是一个自定义Annotation类,@interface是自定义Annotation的写法,这个类本身也有两个注解修饰
@Target(ElementType.METHOD) 修饰的注解表示该注解只能用来修饰在方法上。
@Target(ElementType.FIELD)表示@Column这个注解只能用在属性上
@Retention(RetentionPolicy.CLASS)修饰的注解,表示注解的信息被保留在class文件(字节码文件)中当程序编译时,但不会被虚拟机读取在运行的时候;
@Retention(RetentionPolicy.SOURCE )修饰的注解,表示注解的信息会被编译器抛弃,不会留在class文件中,注解的信息只会留在源文件中;
@Retention(RetentionPolicy.RUNTIME )修饰的注解,表示注解的信息被保留在class文件(字节码文件)中当程序编译时,会被虚拟机保留在运行时,
本框架中的逻辑是在运行时使用,所以使用RetentionPolicy.RUNTIME
利用Annotation来实现表和类的属性关联,减少了在开发过程中不用刻意地去再定义一次字段的属性值了,充分利用现有的可用的数据
源码及DEMO地址:https://github.com/xiaoqi0716/LightSqlite