利用Annotation实现Android sqlite框架

结合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
    .....
}


接下来,建立学生表对应的类,该类继承AbstractTable,使用泛型实现类StudentItem
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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值