手写Android ORM库

ORM介绍

对象关系映射(Object Relational Mapping)简称ORM[1],用于实现面向对象编程语言里不同类型系统的数据之间的转换。简单的说,就是把数据库的表映射为类,列映射为类的属性,每一条数据映射为对象(类的实例)。

目标

重复造轮子,实现一个简单友好的Android SQLite ORM Library

  1. 支持根据类型自动建表与升级
  2. 支持执行自定义的初始化SQL脚本与升级SQL脚本
  3. 支持通过对方访问的方式进行表数据增删改查
  4. 支持注解方式配置表的属性与约束

分析、设计与实现

接口设计

SQL语法

先根据SQLite官方文档[2]定义用于自动建表与升级表的SQL语法

  • 自动建表语句格式
CREATE TABLE IF NOT EXIST  (
    ID INTEGER PRIMARY KEY AUTOINCREMENT,
     TEXT [NOT] NULL [UNIQUE],
     REAL [NOT] NULL [UNIQUE],
     BLOB [NOT] NULL,
     INTEGER [NOT] NULL [UNIQUE],
    [UNIQUE (field1, field2) ON CONFLICT REPLACE]
)
  • 自动升级表结构
ALTER TABLE  ADD COLUMN  column_type [NOT] NULL

仅支持新增字段

自动建表

根据java模型创建数据库表,通过一系统映射规则生成建表语句后执行,同时也允许执行一段自定义的SQL脚本对数据库进行初始化。

  • 根据类型名或属性名生成表名或列名
  • 通过注解自定义表名或列名
  • 通过注解生成表约束
  • 自动根据java属性类型获取列类型

名字转换规则

类名和属性名中只允许包含字母、数字和_,建议采用驼峰命名法则,名字转换示例:

类或属性名 表名或列名
HelloWorld HELLO_WORLD
HElloWorld H_ELLO_WORLD
helloWorld HELLO_WORLD
helloWORld HELLO_WO_RLD
helloWorld0 HELLO_WORLD0

通过注解自定义名字

  • 类型注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface Table {

    /**
     * customized table name
     *
     * @return table name
     */
    String name() default "";

}
  • 属性注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface Column {

    /**
     * customized column name
     *
     * @return column name
     */
    String name() default "";

    /**
     * is column unique?
     *
     * @return unique or not
     */
    boolean unique() default false;

    /**
     * is column nullable?
     *
     * @return nullable or not?
     */
    boolean notNull() default false;

}

表约束

支持以下约束

  1. 必须包含id属性作为自增主键
  2. NULL or NOT NULL
  3. UNIQUE
  4. MULTI UNIQUE

类型映射

SQLite类型 Java类型
INTEGER Boolean,boolean,Short,short,Integer,int,Long,long,Date,Calendar
TEXT String,BigDecimal
REAL Double,double,Float,float
BLOB byte[]

生成建表语句

    static String createTableSQL(Class table) {
        List columnFields = ReflectionUtils.getTableFields(table);
        String tableName = NamingUtils.toTableName(table);
        if (KeywordUtils.isKeyword(tableName)) {
            throw new InvalidNameException("Table name is keyword: " + tableName);
        }

        StringBuilder sb = new StringBuilder("CREATE TABLE IF NOT EXISTS ");
        sb.append(tableName).append(" ( ID INTEGER PRIMARY KEY AUTOINCREMENT ");
        for (Field field : columnFields) {
            String columnName = NamingUtils.toColumnName(field);
            String columnType = TypeUtils.toColumnType(field.getType());

            if ("ID".equalsIgnoreCase(columnName)) {
                continue;
            }

            boolean notNull = false;
            boolean unique = false;
            if (field.isAnnotationPresent(Column.class)) {
                Column annotation = field.getAnnotation(Column.class);
                notNull = annotation.notNull();
                unique = annotation.unique();
            }
            if (field.isAnnotationPresent(NotNull.class)) {
                notNull = true;
            }
            if (field.isAnnotationPresent(Unique.class)) {
                unique = true;
            }

            sb.append(", ").append(columnName).append(" ").append(columnType);
            if (notNull) {
                sb.append(" NOT");
            }
            sb.append(" NULL");
            if (unique) {
                sb.append(" UNIQUE");
            }
        }

        if (table.isAnnotationPresent(MultiUnique.class)) {
            String[] constraint = table.getAnnotation(MultiUnique.class).value();
            if (constraint.length > 0) {
                sb.append(", UNIQUE(");
                for (String name : constraint) {
                    sb.append(NamingUtils.toSQLName(name)).append(",");
                }
                sb.delete(sb.length() - 1, sb.length());
                sb.append(") ON CONFLICT REPLACE");
            }
        }

        sb.append(" ) ");
        return sb.toString();
    }

执行自定义建表脚本

如果有自定义脚本,可以将脚本写在assets/scripts/create.sql文件中,当自动建表过程完成以后,执行此脚本完成自定义初始化过程。

升级数据库

自动升级表结构

通过对比java类型和已存在表结构,自动判断并将新增的列添加到数据库表中。

仅支持自动新增列,并且新增列不支持UNIQUE约束

    private static void addColumns(SQLiteDatabase db, Class table) {
        List columnFields = ReflectionUtils.getTableFields(table);
        String tableName = NamingUtils.toTableName(table);
        List existColumns = getColumnNames(db, tableName);
        List alterCommands = new ArrayList<>();

        for (Field field : columnFields) {
            String columnName = NamingUtils.toColumnName(field);
            String columnType = TypeUtils.toColumnType(field.getType());

            if (existColumns.contains(columnName)) {
                continue;
            }

            boolean notNull = false;
            if (field.isAnnotationPresent(Column.class)) {
                Column annotation = field.getAnnotation(Column.class);
                notNull = annotation.notNull();
            }
            if (field.isAnnotationPresent(NotNull.class)) {
                notNull = true;
            }

            StringBuilder sb = new StringBuilder("ALTER TABLE ");
            sb.append(tableName).append(" ADD COLUMN ").append(columnName).append(" ").append(columnType);
            if (notNull) {
                sb.append(" NOT");
            }
            sb.append(" NULL");
            alterCommands.add(sb.toString());
        }

        for (String command : alterCommands) {
            db.execSQL(command);
        }
    }

执行自定义升级脚本

如果有自定义升级需求,可以将升级写在assets/scripts/.sql文件中,version是数据库版本号。自定义脚本支持逐版本升级,比如数据库从版本1升级到版本10,在版本3,6,9有自定义升级脚本,那么assets/scripts目录下就存在3.sql,6.sql,9.sql这几个文件,数据库升级时会依次执行完成自定义升级。

增删改查

数据库创建好以后,通过调用系统提供的接口[3],进行增删改查这些最基本的数据访问操作。删除操作最简单,因为删除不涉及对象与表数据的映射,我们就先从删除开始。

删除数据

根据系统接口定义,再根据之前定义的主键约束,我们可以根据对象类型获取表名,根据id获取主键值,然后提供接系统接口就可以删除数据。我们提供2个删除接口:

  1. 根据主键删除
  2. 根据ORM对象删除。
public static boolean delete(Class table, Long id);
public static boolean delete(Object o)

增加修改

增加和修改最大的特点就是要将ORM对象转为数据库表记录,是从对象到记录的映射。关于支持的数据类型与映射关系,请参考类型映射
增加和修改最终各自实现一个借口:

public static long save(Object o);
public static long update(Object o);

查询聚合

查询最大的特点就是要将数据库表记录转为ORM对象,是从记录到对象的映射。关于支持的数据类型与映射关系,请参考类型映射

查询可以有各种各样的查询方式,暂时提供几个接口:

public static  T fetch(Class type, Long id);
public static  List find(Class type, String whereClause, String...args);

public static long count(Class table);
public static long count(Class table, String whereClause, String...whereArgs);

使用示例

暂无

性能优化

暂无

后续增强

通过前面实现的基本功能,已经可以满足大部分应用的需求了。当然还可以添加更多的功能以满足更多的需求:

  1. 实现类型自动扫描
  2. 支持事物管理
  3. 支持表之间的关系映射

以上只是随便说说,有兴趣可以自己动手实现

源码下载

https://github.com/hziee514/android-orm

参考资料


  1. ORM百科 ↩

  2. SQLite Query Language ↩

  3. SQLiteDatabase ↩

你可能感兴趣的:(手写Android ORM库)