The database framework has only 8 classes in total, which is ideal for beginners who want to understand how database framework encapsulation works. The framework was taken out of my ongoing CommonAndroid project and I decided to maintain it as a separate module. Welcome to star or Fork. CommonDao database address, Android base library CommonAndroid address.

Basic writing

Source code in the test/normal directory, there is the original database writing method, here will not paste. Now the requirement assumes that a user’s information is stored in the Android database, the normal process would look like this:

  1. Get the JavaBeen for Person ready
  2. Create databases, and colleagues create tables. This is done through the system-provided SQLiteOpenHelper class
  3. Create database operation class, complete add, delete, change and check function

At this point, we’re done. You will find that it is very simple, we only need a DBHelper and a Dao to operate the database. That’s true. This is good enough for projects that rarely use databases, but if there are a lot of database tables in your project, you’ll get sick of multiplying the code that is essentially repetitive…

thinking

Above we are operating on the database, ultimately through the Person JavaBeen object to interact with SQLite relational database. Increment – > map the value of Person to the SQLite database and look – > convert the value obtained from SQLite to Person.

Because JavaBeen corresponds to database fields, we can use this JavaBeen object when we encapsulate it. How do you use it? The Person class name is used as the database table name, and the fields in Person are used as the database table field names. Isn’t it perfect?

Person class name is easy to figure out, how to get properties? This is where reflection comes in. Also, because the database has primary keys and so on, some annotations need to be defined to distinguish between them.

We all know that reflection affects efficiency, so we need to save the JavaBeen (table and field) information after a reflection. Otherwise, every time you add, delete, change and check, you need to reflect.

Definition of annotations

There are many annotations that can be defined, such as table names, field names, field attributes (primaryKey, not NULL…). JavaBeen class name and field name are used to maintain consistency and make error finding easier. So here I’ve finally reduced the annotation to Column as a file that reads like this:

/** * */ @target (elementtype.field) @Retention(retentionPolicy.runtime) public @interface * Created by MJD on 2017/1/7. */ @target (elementtype.field) @Retention(retentionPolicy.runtime) @interface Column {/** * primaryKey not primaryKey */ Boolean primaryKey() default false; }Copy the code

Definition of entity

Here the entities need to be separated, including the JavaBeen and its member variables, which correspond to the tables and fields of the database. Our goal is to dynamically generate database table sentences using Java statements. This table clause is concatenated by the table name + field type + field name.

So our first step is to wrap a member variable so that it can provide a data name and a data name. The ColumnEntity class is named ColumnEntity because the member variable corresponds to the Column in the database. Do not misread the ColumnEntity class as it contains methods for member variables.

ColumnEntity constructor (ColumnEntity)

ColumnEntity(Field field) { this.field = field; field.setAccessible(true); This.name = field.getName(); this.primaryKey = field.isAnnotationPresent(Column.class) && field.getAnnotation(Column.class).primaryKey(); this.type = field.getType(); }Copy the code

JavaBeen retrieves all Fields of the class by using the class name.class.getDeclaredFields. Using this Field, we can obtain the type, name, and value of the member variable. If you use an annotated primary key on a field, you will get its value here as well. Ok, now you just need to provide some get methods to get the type + name. The database operation is inseparable from the add, delete, change, check, here also provides for the member variable set value (corresponding to the query) and get worth method (corresponding to add), here especially do not confuse with the general.

The next step is to wrap the JavaBeen, because the member variables inside are already wrapped above, so this is much easier. JavaBeen refers to a Table entity in a database, that is, Table, so I use TableEntity here. Again, don’t misread it, it refers to JavaBeen. Let’s look at the constructor:

public TableEntity(Class type) { tableName = type.getSimpleName(); fields = new ArrayList<>(); for (Field field : type.getDeclaredFields()) { fields.add(new ColumnEntity(field)); }}Copy the code

We accept a Class object, and with this Class, we can use its name as the table name and construct all the columnEntities of the field in the Class. So far the preparation of the table is almost done. The next step is to splice the construction statements. Select the corresponding database field type based on the field type.

public String getCreateTableStatement() {
    StringBuilder sb = new StringBuilder();
    sb.append(String.format("CREATE TABLE IF NOT EXISTS %s (", tableName));
    int index = 0;
    for (ColumnEntity field : fields) {
        sb.append(field.getName()).append(" ");
        sb.append(getSqlType(field)).append(" ");
        sb.append(index < fields.size() - 1 ? "," : ")");
        index++;
    }
    return sb.toString();
}
private String getSqlType(ColumnEntity field) {
    Class type = field.getType();
    if (field.isPrimaryKey()) {
        return "INTEGER PRIMARY KEY AUTOINCREMENT";
    } else if (type.equals(String.class)) {
        return "TEXT";
    } else if (type.equals(int.class) || type.equals(Integer.class)) {
        return "INT";
    } else if (type.equals(long.class) || type.equals(Long.class)) {
        return "INT";
    } else if (type.equals(boolean.class) || type.equals(Boolean.class)) {
        return "INT";
    } else if (type.equals(double.class) || type.equals(Double.class)) {
        return "FLOAT";
    }
    return null;
}Copy the code

Create a new TableEntity(Person.class).getCreateTableStatement(). Create a new TableEntity(Person.class).getCreateTableStatement() A SQL statement in which the fields in Person are the names of database fields. CREATE TABLE IF NOT EXISTS Person (age INT,name TEXT)

TableEntity also has a method for converting JavaBeen to ContentValues. The idea is to iterate through all the fields, get their values, and set them in ContentValues to make it easier to add and modify.

The definition of TableManager

Why define a TableManager? Reflection is inefficient, and every time we call a new TableEntity(Class<? > clazz), each member variable is retrieved by reflection, and each member variable is retrieved by reflection. So if we were to go to new TableEntity(Person.class) every time we add, delete, change, or search, it would be too inefficient, so we have TableManager, singleton mode, which uses HashMap to store table entities.

Table creation and lookup are all done by TableManager. There is a register() method that you can use at the beginning of the application to unregister the entities of the table. It has not been created yet, but it makes it more efficient when you need to create tables.

public void register(Class... types) { for (Class type : types) { if (find(type) ! = null) {logutils. d(TAG, "table registered "); continue; } TableEntity m = new TableEntity(type); entities.put(type, m); entityList.add(m); } } public TableEntity find(Class type) { return entities.get(type); }Copy the code

Create tables using TableManager

public void createTables(DbDao dao) { try { for (TableEntity tableEntity : entityList) { dao.execute(tableEntity.getCreateTableStatement(), null); }} catch (Exception ex) {logutils. e(TAG, "table creation failed :" + ex.getMessage()); }}Copy the code

Here you need to pass in a DbDao, in fact, is the database specific add, delete, change to check the class. As you can probably guess by now, to create a table in the database, you must first get a DbDao object, which must have SQLiteOpenHelper in it.

The definition of the Dao

As you can imagine, there are only ways to add, delete, change, and check, so let’s look at the constructor first.

public DbDao(Context context, DbParams params, DbUpdateListener dbUpdateListener) {
    this.mDbHelper = new DbHelper(context, params.dbName, null, params.dbVersion, dbUpdateListener);
}Copy the code

DbDao construction requires DbParams, which is the class that configures the database table name and version number. It is currently stored in DbManager. That will be explained later. Another parameter, DbUpdateListener, is the interface through which the onUpgrade() method in SQLiteOpenHelper calls back the processing of the upgrade to the caller.

The constructor initializes a DbHelper, which is actually SQLiteOpenHelper, and it doesn’t do anything different than SQLiteOpenHelper. The onCreate method in SQLiteOpenHelper does nothing. Since table creation is already done by TableManager, SQL statements are organized in TableManager and given to DbDao’s execute() method.

Public void execute(String SQL, String[] bindArgs) throws Exception {logutils. I (TAG, "prepare to execute SQL[" + SQL +"] statement "); mDb = mDbHelper.getWritableDatabase(); if (mDb.isOpen()) { if (! TextUtils.isEmpty(sql)) { if (bindArgs ! = null) { mDb.execSQL(sql, bindArgs); } else { mDb.execSQL(sql); } logutils. I (TAG, "Execute complete!") ); }} else {throw new Exception(" database not open!" ); }}Copy the code

If you want to research or modify it, you are welcome to fork or star on GitHub. The address is given at the beginning of this article.

At this point, the CommonDao framework core has been managed, the remaining DbManager, which is a unified database configuration management class, singleton mode, provides the default and configurable database name, version number, and upgrade listener. Remember to initialize the DbManager at Application creation time.

Look forward to your participation and making CommonDao more robust.