美文网首页Android开发Android开发Android开发经验谈
Android数据库框架---注解加反射,构建简单Sql数据库框

Android数据库框架---注解加反射,构建简单Sql数据库框

作者: Androidgiao | 来源:发表于2019-08-08 16:15 被阅读10次

    有时公司不让使用三方数据库框架,自己又不想一遍一遍的写增删改查。于是出于懒惰,就基于反射加注解,写了一个简单的数据库框架。
    一 、先来看看使用。

    1) 首相要对要进行增删改查的数据对象进行注解配置,如下以Book类为例:

    @Table(name="booktwo")
    public class Book {
        
        @PrimaryKey
        @Column(name="b_id")
        public String bookid;
        @Column(name="b_name",length=500)
        public String bookname;
        @Column(name="time_tag",length=500)
        public long time;
        @Column(name="num")
        public Type type;
        @Column
        public boolean isRee;
        
        public Integer time3;
        @Override
        public String toString() {
            return "Book [bookid=" + bookid + ", bookname=" + bookname + ", time=" + time + ", type=" + type + ", isRee="
                    + isRee + ", time3=" + time3 + "]";
        }
        
    }
    

    注解就三个,@Table ,@Column,@PrimaryKey, 分别为表, 列, 还有主键。

    @Table 是设置表的,name 属性:设置表名;

    @Column 是设置列相关的属性, 有name 属性:设置列名; length 属性:设置数据长度;

    @PrimaryKey 是设置此字段为主key。唯一

    如果对注解不熟悉的朋友,可先看java注解基础。

    可以保存的数据类型包含java基础类型(int,long,String,float, short, enum (枚举)等等);

    2)然后再看看框架使用和增删改查的部分简单使用:

    初始化设置:

    SSLiteConfig config = new SSLiteConfig();
    config.setDbName("book.db");
    config.setDbVersion(1);
    SSLiteDB.getInstance().init(getApplicationContext(), config);
    

    增:

    // 单个插入
    boolean insert = SSLiteDB.getInstance().getSSlite().insert(book);
    //批量插入
    SSLiteDB.getInstance().getSSlite().batchInsert(books);
    

    删:

    //单个删除
    SSLiteDB.getInstance().getSSlite().delete(book);
    //批量删除
    SSLiteDB.getInstance().getSSlite().batchDelete(books);
    

    改:

    //单个修改
    SSLiteDB.getInstance().getSSlite().update(book);
    //批量修改
    SSLiteDB.getInstance().getSSlite().batchUpdate(books);
    

    查:

    // 查询
    List<Book> findAll = SSLiteDB.getInstance().getSSlite().findAll(Book.class);
    

    3) 再看看使用效率

    单条数据操作时间,如下:


    image.png

    批量10001条数据操作效率,如下:


    image.png
    4)看jar整体大小 image.png

    二 , 框架代码内部模块

    大体是下面的结构模块。


    image.png

    三,思路简单描述

    如果我们想实现上面便捷的调用方法,实现增删改查,首先你必须得到对应的数据信息。也就是类的信息。注解可以帮助我们解决这个问题。

    所以我们先编写Table ,Culomn, PrimaryPek,三个注解类。因为我们构建sql语句时,是需要知道表名是什么,列名是什么,哪个字段是主键等待。

    以下是注解类:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Table{
        String name();
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Column {
        String name() default "";
     
        int length() default 100;
     
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface PrimaryKey {
          String name() default "";
          int length() default 100;
    }
    

    其中@Target(ElementType.TYPE) 是表示使用在类上,@Target(ElementType.FIELD) 表示使用在字段上。具体的字段意思,上面已经说过了,对注解不太清楚的,可以去找一些java注解方面的东西。

    有了注解,然后就是怎样解析注解,然后获取类的相关信息了。

    这就需要用到java 的反射了。我们可以通过Book对象的Class,然后利用反射,得到其字段,注解,以及字段的值等等所有信息。

    假设Book的class 对象为clazz,则通过 clazz.getAnnotation(Table.class)就可以查看Book类是否有Table的注解。如果返回的是Null,则说明没有,如果不为空,则我们就难道了Book的Table注解类对象。就可以取Table的信息了。比如table.getName();就可以得到表名值了。

    对于Culomn注解的获取操作,就多了一步,说先得通过Field[] fields = clazz.getDeclaredFields();获取所有的Book类的字段,然后遍历,通过field.getAnnotation(Column.class);查看是否有Column注解。

    PrimaryKey 的获取通Column。

    如果对反射不熟悉的可以先看一下java 反射相关的东西。

    具体的这块操作已经封装到了AnnoParse类里面了,具体如下,

    class AnnoParse {
        private static final String TAG = "AnnoParse";
     
        /**
         * 初始化table 的信息
         * @param clazz
         */
        public static TableInfo initTableInfo(Class<?> clazz){
        
            Table table = (Table) clazz.getAnnotation(Table.class);
            if(table==null){
                return null;
            }
            TableInfo tableInfo = new TableInfo();
            tableInfo.clazzName = clazz.getName();
            if (TextUtils.isEmpty(table.name())) {
                tableInfo.tableName = clazz.getName();
            } else {
                tableInfo.tableName = table.name();
            }
            
            HashMap<String, ColumnInfo> columnMaps = null;
            HashMap<String, String> feildmaps =  null;
            Field[] fields = clazz.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                field.setAccessible(true);
                Column column = field.getAnnotation(Column.class);
                if(column!=null){
                    
                    // 列信息 
                    ColumnInfo columnInfo = new ColumnInfo();
                    columnInfo.fieldName = field.getName();
                    columnInfo.fieldtype = field.getType().getName();
                    
                    String columnname = column.name();
                    if(columnname==null||"".equals(columnname)){
                        columnInfo.columName = field.getName();
                    }else{
                        columnInfo.columName = columnname;
                    }
                    
                    columnInfo.columLength = column.length();
                    
                    //数据库 对应的列类型
                    columnInfo.dbtype = getType(field.getType(), columnInfo.fieldtype);
                    
                    if (columnMaps == null) {
                        columnMaps = new HashMap<String, ColumnInfo>();
                    }
                    if (feildmaps == null) {
                        feildmaps = new HashMap<String, String>();
                    }
                    
                    columnMaps.put(columnInfo.columName, columnInfo);
                    feildmaps.put(columnInfo.fieldName, columnInfo.columName);
                    
                    PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class);
                    if (primaryKey != null) {
                        tableInfo.primaryKey = columnInfo.columName;
                        columnInfo.isPrimaryKey = true;
                    }
                }
            }
            tableInfo.fieldMap = feildmaps;
            tableInfo.colunmMap = columnMaps;
            return tableInfo;
        }
        
        public static <T> String getTableName(Class<?> clazz) {
            Table table = (Table) clazz.getAnnotation(Table.class);
            if(table==null){
                return null;
            }
            String tableName = null;
            if (TextUtils.isEmpty(table.name())) {
                tableName = clazz.getName();
            } else {
                tableName = table.name();
            }
            return tableName;
        }
        
        
        /**
         * 通过field的type类型名字,得到存到数据库里对应的数据类型
         * @param typeName
         * @return
         */
        private static DataType getType(Class<?> clazz, String typeName) {
            if (clazz.isEnum()) {
                return DataType.ENUM;
            }else if (long.class.getName().equals(typeName) || Long.class.getName().equals(typeName)) {
                return DataType.LONG;
            } else if (Integer.class.getName().equals(typeName) || int.class.getName().equals(typeName)) {
                return DataType.INTEGER;
            } else if (String.class.getName() == typeName) {
                return DataType.STRING;
            } else if (float.class.getName().equals(typeName) || Float.class.getName().equals(typeName)) {
                return DataType.FLOAT;
            } else if (boolean.class.getName().equals(typeName) || Boolean.class.getName().equals(typeName)) {
                return DataType.BOOLEAN;
            } else if (short.class.getName().equals(typeName) || Short.class.getName().equals(typeName)) {
                return DataType.SHORT;
            } else {
                return DataType.UNKOWN;
            }
        }
    }
    

    得到注解的信息之后,就相当于得到了表的信息,所以现在需要检查是否有现在的表,如果没有则需要创建表。创建表就需要我们通过得到的表的信息去拼凑创建表的sql语句了。

    对于框架所需要的sql,都封装在了SQLCreator类中。检查 创建表的语句拼凑如下:

    private String createTableSql(TableInfo info) {
            if (info == null || info.colunmMap == null || info.colunmMap.keySet() == null) {
                return null;
            }
     
            StringBuilder sql = new StringBuilder();
            sql.append("CREATE TABLE IF NOT EXISTS ");
            sql.append(info.tableName);
            sql.append(" (");
     
            Set<String> keySet = info.colunmMap.keySet();
            for (String key : keySet) {
                ColumnInfo columnInfo = info.colunmMap.get(key);
                if (columnInfo == null) {
                    continue;
                }
                sql.append(" ");
                sql.append(columnInfo.columName);
                sql.append(" ");
                sql.append(columnInfo.dbtype.getSqlType());
     
                if (!DataType.INTEGER.getSqlType().equals(columnInfo.dbtype.getSqlType())) {
                    if (columnInfo.columLength <= 0) {
                        return null;
                    }
                    sql.append("(");
                    sql.append(columnInfo.columLength);
                    sql.append(")");
                }
     
                if (columnInfo.isPrimaryKey) {
                    sql.append(" PRIMARY KEY");
                }
                sql.append(",");
            }
            sql.deleteCharAt(sql.length() - 1);
            sql.append(");");
            LLog.d("Create tables sql -> " + sql.toString());
            return sql.toString();
     
        }
    

    得到创建表的sql语句后,使用sqlite的相关代码执行就可以了,源码里的一些接口设计以及预留扩展就先不说了。

    东西也算比较简单,是和练手,也可自己修改在代码中使用,减少代码冗余。

    这里为了支持多个表,并且为了表的灵活创建,以及为了操作方便,在做增删改查操作时,都需要执行创建表的sql语句,语句中已经做了判断表是否存在,如果不存在则创建表。
    最后
    如果你依然在编程的世界里迷茫,不知道自己的未来规划,可以加入高级程序员群:里面可以与大神一起交流并走出迷茫。小白可进群免费领取学习资料,看看前辈们是如何在编程的世界里傲然前行。
    1.LiveDataBus
    2.Google官方架构组件
    3.Jetpack架构
    4.饿了么通信技术
    5.OPenGL
    6.音视频
    7.人工智能
    8.Python
    9.性能优化
    10.Flutter等
    这些资料加群639986248领取


    image.png

    相关文章

      网友评论

        本文标题:Android数据库框架---注解加反射,构建简单Sql数据库框

        本文链接:https://www.haomeiwen.com/subject/cpixjctx.html