LazyDB

Introduction: 提供一种实现 Android ORM 框架的解决方案
More: Author   ReportBugs   DemoAPK   
Tags:
Android-ORM-SQLite-

Build Status License

一款简易使用的 orm 框架

1. 框架设计

1.1. 总体流程

  1. 对于数据库的“增删改”操作,将对象输入,通过 ORM 框架处理,构建 SQL 语句,然后写入数据库,如图 1 所示;
  2. 对于查询数据库的操作,输入要输出对象的 Class,通过 ORM 框架处理,构建 SQL 语句,查询数据库,返回后构建成 Object 输出,如图 2 所示;

1.2. ORM 内部实现流程

构建 SQL 语句的原理:通过 Java 反射机制,获取 Class 的字段和 Object 字段值,通过字符串拼接构建 SQL。

1.3. Java 数据类型与 SQLite 数据类型映射表

Java 数据类型 SQLite 数据类型
int、Integer、byte、Byte、short、Short、long、Long、Boolean、boolean INTEGER
float、Float、double、Double REAL
Date DATE
String TEXT

PS:这里简单起见,将所有的字符串都用 TEXT;整数都用 INTEGER;

2. 内部实现的几个重要实现规则

1. 对于 SQLite 表字段与 Java 对象字段映射规则:

(1)只映射非final和非static的 Java 字段; (2)Java 的字段名即 SQLite 表的字段名; (3)主键取 Java 对象中的名字为 ID(不区分大小写)或者加了@ID注解的字段,优先查找注解;

2. 保存数据类型规则:

(1)以 Java 基本数据类型以及其包装类为主,还有Date;对于其他类不做处理;

3. 表名称生成规则:

(1)以 Java 类的完全限定名来命名(.替换为_),例如:com.andy.Person的代表的表名为com_andy_Person,保证了表名的唯一性;

3. 代码实现

介绍一下几个重要的类 (1) 用于构造 SQL 语句的类SQLBuilder(主要用了 Java 反射机制) (2) 用于执行 SQL 语句的类SQLiteDBExecutor (3) 提供给外部使用的 ORM 主类LazyDB

3.1 SQLBuilder:SQL 语句构建器

该 Class 的主要作用是,将外部传进来的 Object(对象)或者 Class(类),通过 Java 反射机制,构建成 SQL 语句。

这里以创建表的 SQL 为例

代码步骤分解: (1)取出 Class 中所有 Field,即 Field 数组; (2)遍历 Field 数组,找出主键的 Field,拼到 SQL 的字符串里; (3)再次遍历 Field 数组,过滤掉主键的 Field、final、static,拼到 SQL 的字符串里;

具体代码如下:

    // SQLBuilder 类
    /**
     * 构建创建数据库表的 sql 语句
     *
     * @param clazz 类
     * @return 创建表的 sql 语句
     */
    public static String buildCreateTableSql(Class<?> clazz) {
        StringBuilder sb = new StringBuilder();

        Field[] fields = ReflectUtil.getDeclaredFields(clazz);
        if (fields == null || fields.length == 0) {
            throw new IllegalStateException("class'fields can not be empty");
        }

        // 开头
        sb.append("create table ")
                .append(TableUtil.getTableName(clazz)) // tableName
                .append("(");
        // 找到主键
        Field idField = IDUtil.getIDField(fields);
        if (idField != null) {
            String idColumn = idField.getName();
            ID id = idField.getAnnotation(ID.class);
            // 判断是有没有注解的
            if (id != null && !"".equals(id.column())) {
                idColumn = id.column();
            }
            DataType dataType = TableUtil.getDataType(idField.getType());
            sb.append(idColumn)
                    .append(" ")
                    .append(dataType.toString())
                    .append(" primary key")
                    .append(",");
        }
        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())) {// 移除是 final 和 static 的字段
                continue;
            }
            // 让不是 id 的 field 进来
            if (field != idField) {
                sb.append(field.getName())
                        .append(" ")
                        .append(TableUtil.getDataType(field.getType()))
                        .append(",");
            }
        }
        // 除去最后一个逗号
        if (sb.charAt(sb.length() - 1) == ',') {
            sb.deleteCharAt(sb.length() - 1);
        }
        sb.append(")");
        String sql = sb.toString();
        // debug log
        DeBugLogger.d("CreateTableSql", sql);
        return sql;
    }

上面代码中用到了几个工具类: ReflectUtil:用于 Java 反射的工具类,将所有反射调用的方法都放到这里来,统一管理,方便优化处理; IDUtil:用于主键 Field 查找的工具类; TableUtil:用于数据库表字段数据与 Java 数据类型转换、表名生成等;

3.2 SQLiteDBExecutor:SQL 语句执行器

该 Class 的主要作用是,用于执行 SQL 语句,或者通过SQLiteOpenHelper执行一些数据库操作,反正所有数据库操作都在这里面。

(1)数据库非查询操作

    // SQLiteDBExecutor 类
    /**
     * 执行非查询操作事物
     *
     * @param operation 非查询操作
     */
    public void executeTransaction(NoQueryOperation operation) throws Exception {
        SQLiteDatabase db = helper.getWritableDatabase();
        try {
            db.beginTransaction();
            DeBugLogger.d("NoQuery", "beginTransaction");
            if (operation != null) {
                operation.onOperation(db);
            }
            db.setTransactionSuccessful();
            DeBugLogger.d("NoQuery", "transactionSuccessful");
        } finally {
            db.endTransaction();
            DeBugLogger.d("NoQuery", "endTransaction");
        }
    }

    public interface NoQueryOperation {
        void onOperation(SQLiteDatabase db) throws Exception;
    }

    /**
     * 执行 SQL,非查询操作
     * @param sql SQL 语句
     * @throws Exception
     */
    public void execSQL(final String sql) throws Exception {
        executeTransaction(new NoQueryOperation() {
            @Override
            public void onOperation(SQLiteDatabase db) throws Exception {
                db.execSQL(sql);
            }
        });
    }

(2)数据库查询操作

    // SQLiteDBExecutor 类
    public Cursor rawQuery(String sql, String[] selectionArgs) {
        SQLiteDatabase db = helper.getReadableDatabase();
        return db.rawQuery(sql, null);
    }

然后看一段比较好看的查询代码:

        List<Entity> entities = lazyDB
                .query(Entity.class)
                .selectAll()
                .where("id=? and name=? and age=? and birthday=? and sex=? and money=?",
                        entity.getId(),
                        entity.getName(),
                        entity.getAge() + "",
                        DateUtil.date2String(entity.getBirthday()),
                        entity.isSex() ? "1" : "0",
                        Double.toString(entity.getMoney())
                )
                .findAll();

个人比较钟意这种构建器模式下的代码;看下查询代码,基于 Builder 模式的实现。

public class SelectBuilder<T> {
    private final SQLiteDBExecutor executor;

    final Class<T> objectClass;
    String[] columns;
    String whereSection;
    String[] whereArgs;

    String having;
    String orderBy;
    String groupBy;
    String limit;

    public SelectBuilder(SQLiteDBExecutor executor, Class<T> clazz) {
        this.objectClass = clazz;
        this.executor = executor;
    }

    public SelectBuilder<T> selectAll() {
        return this;
    }

    public SelectBuilder<T> select(String... columns) {
        this.columns = columns;
        return this;
    }

    public SelectBuilder<T> where(String whereSection, String... whereArgs) {
        this.whereSection = whereSection;
        this.whereArgs = whereArgs;
        return this;
    }

    public SelectBuilder<T> having(String having) {
        this.having = having;
        return this;
    }

    public SelectBuilder<T> orderBy(String orderBy) {
        this.orderBy = orderBy;
        return this;
    }

    public SelectBuilder<T> groupBy(String groupBy) {
        this.groupBy = groupBy;
        return this;
    }

    public SelectBuilder<T> limit(String limit) {
        this.limit = limit;
        return this;
    }
    ......
}

提供了类似写 SQL 查询语句的字符串构建器; 最后,执行查询;

public class SelectBuilder<T> {
    ......

    /**
     * 执行查询操作,获取查询结果集
     *
     * @return 数据库游标 Cursor
     */
    public Cursor executeNative() {
        // 查询表是否存在
        String sql = SQLBuilder.buildQueryTableIsExistSql(objectClass);
        Cursor cursor = executor.rawQuery(sql, null);
        if (cursor != null) {
            try {
                if (cursor.moveToNext()) {
                    if (cursor.getInt(0) == 0) {
                        return null;
                    }
                }
            } finally {
                cursor.close();
            }
        } else {
            return null;
        }

        //String sql = SqlBuilder.buildQuerySql(TableUtil.getTableName(objectClass), columns, whereSection, whereArgs, groupBy, having, orderBy, limit);
        //Cursor cursor = db.rawQuery(sql, null);
        // 执行查询
        return executor.query(this);
    }

    /**
     * 执行查询操作,获取查询结果集
     *
     * @return 查询结果集,空集则查询不到
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws NoSuchFieldException
     * @throws ParseException
     */
    public List<T> findAll() throws InstantiationException, IllegalAccessException, NoSuchFieldException, ParseException {
        List<T> results = new ArrayList<>();

        // 执行查询
        Cursor cursor = executeNative();
        if (cursor != null) {
            try {
                while (cursor.moveToNext()) {
                    T object = ObjectUtil.buildObject(objectClass, cursor);
                    results.add(object);
                }
            } finally {
                cursor.close();
            }
        }
        return results;
    }

    /**
     * 执行查询操作,获取查询结果集的第一个
     *
     * @return 查询结果集的第一个,null 则查询不到
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws NoSuchFieldException
     * @throws ParseException
     */
    public T findFirst() throws InstantiationException, IllegalAccessException, NoSuchFieldException, ParseException {
        T result = null;

        // 执行查询
        Cursor cursor = executeNative();
        if (cursor != null) {
            try {
                if (cursor.moveToNext()) {
                    result = ObjectUtil.buildObject(objectClass, cursor);
                }
            } finally {
                cursor.close();
            }
        }
        return result;
    }
}

PS:ObjectUtil 用于通过 Java 反射实例化对象,然后对对象进行赋值;

3.3 LazyDB:ORM 入口的主类

public final class LazyDB {
    /**
     * 阻止通过 new 来实例化 LazyDB
     * 应该使用 create 方法来创建 LazyDB
     */
    private LazyDB() {
    }

    private SQLiteDBExecutor executor;

    /**
     * 创建默认配置的数据库
     *
     * @param context 上下文
     */
    public static LazyDB create(Context context)  {}

 ......

    /**
     * 创建表
     *
     * @param clazz 类
     */
    public void createTable(Class<?> clazz) throws Exception  {}

    /**
     * 创建表
     *
     * @param tableName       表名
     * @param idColumn        id
     * @param idDataType      id 的类型
     * @param isAutoIncrement 是否自动增长
     * @param columns         其他列名,不包括 id
     */
    public void createTable(String tableName, String idColumn, String idDataType, boolean isAutoIncrement, String... columns) throws Exception  {}

    /**
     * 删除表
     *
     * @param clazz 类
     */
    public void dropTable(final Class<?> clazz) throws Exception  {}

    /**
     * 删除数据库中所有的表
     */
    public void dropAllTables() throws Exception  {}

    /**
     * 查询所有表名
     *
     * @return 所有表名的集合;若没有表,则是空集合
     */
    public List<String> queryAllTableNames()  {}

    /**
     * 从表中查找出所有字段名
     *
     * @param clazz class
     * @return 字段列表
     * @throws NoSuchFieldException
     * @throws InstantiationException
     * @throws ParseException
     * @throws IllegalAccessException
     */
    public List<ColumnInfo> queryAllColumnsFromTable(Class<?> clazz) throws NoSuchFieldException, InstantiationException, ParseException, IllegalAccessException {}

    /**
     * 表是否存在
     *
     * @param clazz 类
     * @return true 表存在,false 表不存在
     */
    public boolean isTableExist(Class<?> clazz)  {}

    /**
     * 判断对象是否存在数据库中
     *
     * @param object 对象
     * @return true 存在,false 不存在
     * @throws IllegalAccessException
     */
    public boolean isObjectExist(Object object) throws IllegalAccessException  {}

    /**
     * 插入对象
     *
     * @param object 对象
     */
    public void insert(@NonNull Object object) throws Exception  {}

    /**
     * 插入多个对象数据
     *
     * @param objectList 对象集合
     * @throws IllegalAccessException
     */
    public void insert(@NonNull final List objectList) throws Exception  {}

    /**
     * 更新对象
     *
     * @param object 对象
     * @throws IllegalAccessException
     */
    public void update(@NonNull final Object object) throws Exception {}

    /**
     * 插入或更新对象
     *
     * @param object 对象
     * @throws IllegalAccessException
     */
    public void insertOrUpdate(@NonNull Object object) throws Exception  {}

    /**
     * 删除对象
     *
     * @param object 对象
     * @throws IllegalAccessException
     */
    public void delete(@NonNull final Object object) throws Exception  {}

    /**
     * 删除多个 object 集合
     *
     * @param objectList object 集合
     * @throws IllegalAccessException
     */
    public void delete(@NonNull final List objectList) throws Exception  {}

    /**
     * 删除数据
     *
     * @param clazz       类
     * @param whereClause 例如:id=?
     * @param whereArgs   ?的值
     */
    public void delete(final Class<?> clazz, final String whereClause, final String[] whereArgs) throws Exception  {}

    /**
     * 执行查询操作
     *
     * @param clazz 类
     * @return select 操作的构建器
     */
    public <T> SelectBuilder<T> query(Class<T> clazz)  {}

    /**
     * 通过 id 查询
     *
     * @param clazz   类
     * @param idValue idValue
     * @return object
     * @throws NoSuchFieldException
     * @throws InstantiationException
     * @throws ParseException
     * @throws IllegalAccessException
     */
    public <T> T queryById(Class<T> clazz, Object idValue) throws Exception {}

4. Java 反射优化方案

Java 反射机制虽然好用,但是为此会牺牲性能,这里有个优化点,可以在取反射时,加一层内存缓存,先查缓存,找不到再取反射。

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools