寫一個簡單的Android版本的ORM

幾年前剛剛涉及做Android開發(fā)時候畢竟頭大的是數(shù)據(jù)庫相關的增刪改查口渔,因為項目要對離線數(shù)據(jù)進行管理數(shù)據(jù)庫操作比較多帕翻,不斷通過查詢Cursor拼裝成對象,完了還要close cursor,重復工作太多太累筷狼,曾經(jīng)用過Hibernate只是Android平臺沒有罷了棍潘,那會兒也還沒有Android平臺的權威級的ORM恃鞋。
經(jīng)過項目實踐有意或無意做了一個如下操作DB的小lib,簡化了不少重復勞動.

想法起源:

最初的萌芽來自于Android Email App內(nèi)部一個叫EmailContent的class定義亦歉。每個Table類都手動實現(xiàn)toContentValues()和restore(Cursor cursor)恤浪,這樣可以避免重復通過Cursor拼裝表對象。隨后自己就想寫了一個Utils類似的工具類類來提供一系列增刪改查的API鳍徽,當然這些API的操作對象都是table對象资锰,隨著迭代慢慢衍化如今更加友好的light-dao了。

下面描述下如何使用此light-dao:

1. 得定義一個繼承BaseDBHelper的DBHelper阶祭,大家都懂的:

public class DBHelper extends BaseDBHelper {
    private static final String DATABASE_NAME = "school.db";
    private static final int VERSION = 1;

    @SuppressLint("StaticFieldLeak")
    private static DBHelper sSingleton;

    private DBHelper(Context context) {
        super(context, DATABASE_NAME, VERSION);
    }

    private static DBHelper getSingleton(Context context) {
        if (sSingleton == null) {
            synchronized (DBHelper.class) {
                sSingleton = new DBHelper(context.getApplicationContext());
            }
        }
        return sSingleton;
    }

    public static DBUtils with(Context context) {
        return DBUtils.create(getSingleton(context));
    }

    /**
     * all table classes should configured here
     *
     * @param tableClasses table classes
     */
    @Override
    protected void onClassLoad(List<Class<? extends Entity>> tableClasses) {
        tableClasses.add(Student.class);
        tableClasses.add(Teacher.class);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        super.onUpgrade(db, oldVersion, newVersion);

        /* upgrade db version by version
        if (oldVersion < VERSION) {
            SQL sql = SQLBuilder.buildTableCreateSQL(Student.class);
            db.execSQL(sql.getSql());
        }
        */
    }
}

2. 然后绷杜,定義你的各種表的類,如下為學生表和老師表濒募,且學生表里有老師表ID的外鍵:

@Table("teacher")
public class Teacher extends Entity {
    @Column(name = "name", notnull = true)
    public String name;
}  

@Table("student")
public class Student extends Entity {
    @Foreign(Teacher.class)
    @Column(name = "teacher_id", notnull = true)
    public long teacherId;

    @Column(name = "name", notnull = true)
    public String name;

    @Column(name = "age", notnull = true)
    public Integer age;
}

3. 最后就可以通過lightdao進行常見的數(shù)據(jù)庫增刪改查了:

3.1 單個保存

Teacher teacher = new Teacher();
teacher.name = "王老師";
long teacherId = DBHelper.with(mContext).save(teacher);

// 老師和學生關系是一對多鞭盟,因此學生表中有老師表的ID作為外鍵
Student student = new Student();
student.teacherId = teacherId;
student.name = "小學生";
student.age = 20;
long id = DBHelper.with(mContext).save(student);
assertTrue(id > 0);

3.2 批量保存

List<Student> students = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    Student student = new Student();
    student.name = "name " + i;
    student.age = i;
    student.teacherId = 1; // 假設都是ID為1的教師的學生
    students.add(student);
}
int count = DBHelper.with(mContext).saveAll(students);
assertTrue(count == students.size());

3.3 數(shù)量查詢

int count = DBHelper.with(mContext)
    .withTable(Student.class)
    .withWhere("age > ?", 5)
    .applyCount();
assertTrue(count > 0);

3.5 根據(jù)主鍵ID查找

Student student = DBHelper.with(mContext)
    .withTable(Student.class)
    .applySearchById(1);
assertTrue(student != null);

3.6 查詢所有并以list返回結(jié)果

List<Student> students = DBHelper.with(mContext)
    .withTable(Student.class)
    .applySearchAsList();
assertTrue(students.size() > 0);

3.7 帶有條件查詢并以list返回結(jié)果

// 類似的還有很多其他以“with”開頭的API,如:
// withColumns: 只查詢指定的column
// withGroupBy: 查詢分組
// withHaving: 分組后的條件篩選
// withOrderBy: 排序控制
// withLimit: 分頁控制
// withDistinct: 查詢?nèi)ブ?
List<Student> students = DBHelper.with(mContext)
    .withTable(Student.class)
    .withWhere("age>?", 5)
    .applySearchAsList();
assertTrue(students.size() > 0);

3.8 更新部分字段

ContentValues values = new ContentValues();
values.put("name", "hello baby");

int count = DBHelper.with(mContext)
        .withTable(Student.class)
        .withWhere("age<?", 5)
        .applyUpdate(values);
assertTrue(count > 0);

3.9 根據(jù)對象更新

DBUtils dbUtils = DBHelper.with(mContext);
Student student = dbUtils.withTable(Student.class).applySearchById(1);
assertTrue(student != null);

student.name = "testUpdateTable";
int count = dbUtils.withTable(Student.class).applyUpdate(student);
assertTrue(count > 0);

4.0 根據(jù)主鍵ID刪除

int count = DBHelper.with(mContext).withTable(Student.class).applyDeleteById(1);
assertTrue(count > 0);

4.1 刪除指定的對象

DBUtils dbUtils = DBHelper.with(mContext);
Student student = dbUtils.withTable(Student.class).applySearchById(2);
assertTrue(student != null);

int count = dbUtils.withTable(Student.class).applyDelete(student);
assertTrue(count > 0);

4.2 根據(jù)條件刪除

int count = DBHelper.with(mContext).withTable(Student.class).withWhere("age>=?", 9).applyDelete();
assertTrue(count > 0);

4.3 批處理(數(shù)據(jù)庫事務)

BatchJobs jobs = new BatchJobs();
Student student = new Student();
student.name = "insert from batch job";
student.age = 1;
jobs.addInsertJob(student);

// update with table object
student = DBHelper.with(mContext).withTable(Student.class).applySearchFirst();
student.name = "updated from batch job";
jobs.addUpdateJob(Student.class, student);

// update with id
jobs.addUpdateJob(Student.class, student.id, student.toContentValues());

// update with condition
jobs.addUpdateJob(Student.class, student.toContentValues(), "age=?", 6);

// delete with table object
jobs.addDeleteJob(student);

// delete with id
jobs.addDeleteJob(Student.class, 7);

// delete with condition
jobs.addDeleteJob(Student.class, "age<?", 3);

boolean success = DBHelper.with(mContext).applyBatchJobs(jobs);
assertTrue(success);

4.4 跨表查詢

// 因為跨表查詢的結(jié)果來自于多個表瑰剃,所以得重新定義返回結(jié)果的對象齿诉,并通過aliasName指定此字段來自于哪個表中的哪個字段
public class Relation extends Query {
    @Column(name = "teacher_id", aliasName = "student._id as teacher_id")
    public long teacherId;
    
    @Column(name = "teacher_name", aliasName = "teacher.name as teacher_name")
    public String teacherName;
    
    @Column(name = "student_id", aliasName = "student._id as student_id")
    public long studentId;
    
    @Column(name = "student_name", aliasName = "student.name as student_name")
    public String studentName;
    
    @Column(name = "age")
    public int studentAge;
}

List<Relation> list = DBHelper.with(mContext)
        .withQuery(Relation.class)
        .applySearchAsList();
System.out.println(list.size());

4.5 數(shù)據(jù)庫升級

數(shù)據(jù)庫升級其實啥也不用做,因為是自動的,因為重寫了onUpgrade():

@Override
public final void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    autoMigrate(db, mTableClasses);
}

private void autoMigrate(SQLiteDatabase db, List<Class<? extends Entity>> tableClasses) {
    for (Class<? extends Entity> clazz : tableClasses) {
        String tableName = ReflectTools.getTableName(clazz);
        boolean exist = ReflectTools.isTableExist(db, tableName);
        if (exist) {
            Field[] fields = ReflectTools.getClassFields(clazz);
            for (Field field : fields) {
                Column column = field.getAnnotation(Column.class);
                if (column == null) {
                    continue;
                }

                String columnName = !TextUtils.isEmpty(column.name()) ? column.name() : field.getName();
                String dataType = ReflectTools.getDataTypeByField(field);
                boolean columnExist = ReflectTools.isColumnExist(db, tableName, columnName);
                if (!columnExist) {
                    db.execSQL("ALTER TABLE " + tableName + " ADD " + columnName + " " + dataType);
                }
            }
        } else {
            db.execSQL(SQLBuilder.buildCreateSQL(clazz).getSql());
        }
    }
}

static boolean isTableExist(SQLiteDatabase db, String tableName) {
    Cursor cursor = null;
    try {
        cursor = db.rawQuery("SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?", new String[]{tableName});
        boolean hasNext = cursor.moveToNext();
        return hasNext && cursor.getInt(0) > 0;
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
}

static boolean isColumnExist(SQLiteDatabase db, String tableName, String columnName) {
    Cursor cursor = null;
    try {
        cursor = db.rawQuery("SELECT count(*) FROM sqlite_master WHERE tbl_name = ? AND (sql LIKE ? OR sql LIKE ?);",
                new String[]{tableName, "%(" + columnName + "%", "%, " + columnName + " %"});
        boolean hasNext = cursor.moveToNext();
        return hasNext && cursor.getInt(0) > 0;
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
}

相關實現(xiàn)可查閱light-dao:

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末粤剧,一起剝皮案震驚了整個濱河市歇竟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抵恋,老刑警劉巖焕议,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異弧关,居然都是意外死亡盅安,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門世囊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來别瞭,“玉大人,你說我怎么就攤上這事株憾◎” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵号胚,是天一觀的道長籽慢。 經(jīng)常有香客問我,道長猫胁,這世上最難降的妖魔是什么箱亿? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮弃秆,結(jié)果婚禮上届惋,老公的妹妹穿的比我還像新娘。我一直安慰自己菠赚,他們只是感情好脑豹,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著衡查,像睡著了一般瘩欺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拌牲,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天俱饿,我揣著相機與錄音,去河邊找鬼塌忽。 笑死拍埠,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的土居。 我是一名探鬼主播枣购,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼嬉探,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了棉圈?” 一聲冷哼從身側(cè)響起涩堤,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎分瘾,沒想到半個月后定躏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡芹敌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了垮抗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氏捞。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖冒版,靈堂內(nèi)的尸體忽然破棺而出液茎,到底是詐尸還是另有隱情,我是刑警寧澤辞嗡,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布捆等,位于F島的核電站,受9級特大地震影響续室,放射性物質(zhì)發(fā)生泄漏栋烤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一挺狰、第九天 我趴在偏房一處隱蔽的房頂上張望明郭。 院中可真熱鬧,春花似錦丰泊、人聲如沸薯定。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽话侄。三九已至,卻和暖如春学赛,著一層夾襖步出監(jiān)牢的瞬間年堆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工罢屈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嘀韧,地道東北人。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓缠捌,卻偏偏與公主長得像锄贷,于是被迫代替她去往敵國和親译蒂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容