Custom Exploded Database Framework (5) The Final Practice

Keywords: SQL Database Android xml

Preface

After learning and accumulating the basic knowledge, we finally come to the final battle and customize the database framework. The framework is used by referring to some popular database frameworks, such as Litepal.

text

First, let's sort out the flow chart of our framework.

Starting with the initialization of the database, we first define the parsing format of xml:

<?xml version="1.0" encoding="utf-8"?>
<sqlframe>

    <!-- Database name -->
    <name>Test</name>
    <!-- version number -->
    <version>1</version>

    <!-- Here we fill in the classes that need to create tables -->
    <data>
        <item>com.lzp.sqlframedemo.bean.Student</item>
    </data>

</sqlframe>

It contains the name of the database, version number, and the path of the class to build the table.

Complete xml parsing and database creation in the framework of SQL Frame.

/**
     * Initialize the database
     */
    public static void initSQLite(Context context) throws SQLiteInitException {

        // Parse the xml file and get the database configuration (name, version number, table)
        String databaseName = null;
        String version = null;
        ArrayList<String> tables = new ArrayList<>();
        try {
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            XmlPullParser parser = factory.newPullParser();
            // Read the specified xml
            parser.setInput(context.getResources().getAssets().open("SQLFrame.xml"), "utf-8");

            // Reading xml in assets can only use open method, otherwise FileNotFound exception will appear.
//          parser = context.getResources().getAssets().openXmlResourceParser("SQLFrame.xml");

            int eventType = parser.getEventType();
            // If the parsed representation is not the end tag of the file, read the tags in a loop
            while (eventType != XmlPullParser.END_DOCUMENT) {
                // Resolve the name of the tag
                String nodeName = parser.getName();
                switch (eventType) {
                    // Start tag
                    case XmlPullParser.START_TAG:
                        // Database name
                        if (nodeName.equals("name")) {
                            databaseName = parser.nextText();
                        }
                        // Database Version Number
                        else if (nodeName.equals("version")) {
                            version = parser.nextText();
                        }
                        // Table of database
                        else if (nodeName.equals("item")) {
                            tables.add(parser.nextText());
                        }
                        break;
                    // End tag
                    case XmlPullParser.END_TAG:
                        break;

                    default:
                        break;
                }

                // Resolve the next label
                eventType = parser.next();
            }
        } catch (XmlPullParserException | IOException e) {
            e.printStackTrace();
            // Throw database initialization exception
            throw new SQLiteInitException("please check the SQLFrame.xml has defined right!!!");
        }

        // Check if the database name and version number are set, otherwise the database initialization exception is thrown
        if (databaseName == null || version == null) {
            throw new SQLiteInitException("please check the SQLFrame.xml has defined right!!!");
        }

        // Initialize the database
        SQLFrameOpenHelper sqlFrameOpenHelper = new SQLFrameOpenHelper(context, databaseName, null, Integer.parseInt(version));
        sqLiteDatabase = sqlFrameOpenHelper.getWritableDatabase();
        // Cycle table construction
        for (String className : tables) {
            try {
                createTable(Class.forName(className));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

Create tables by reflection:

/**
     * Create table
     *
     * @param clazz Create the specified table by annotating the parameter class
     */
    private static void createTable(Class<?> clazz) {
        try {
            checkSqlInit();

            StringBuilder sql = new StringBuilder("create table if not exists ");

            // Get the name of the table first
            Table table = clazz.getAnnotation(Table.class);
            sql.append(table.value());
            sql.append("(");

            // Get all table fields to create
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                Column column = field.getAnnotation(Column.class);
                if (column != null) {
                    sql.append(column.field());
                    sql.append(" ");
                    sql.append(column.type());
                    sql.append(",");
                }
            }
            // Remove the last comma
            sql.deleteCharAt(sql.length() - 1);
            sql.append(")");
            sqLiteDatabase.execSQL(sql.toString());
        } catch (SQLiteNotInitException e) {
            e.printStackTrace();
        }
    }

Get @Table by reflection, get the name of the table, and then reflect the annotated attributes of Class, get the fields to be created by the table and the attributes of the fields.

The notes are very detailed and there is nothing to say.

Initialization operations are generally recommended in Application, so customize the SQL Frame Application:

package com.lzp.sqlframe.application;

import android.app.Application;

import com.lzp.sqlframe.sqllite.SQLUtil;
import com.lzp.sqlframe.sqllite.SQLiteInitException;

/**
 * Created by li.zhipeng on 2017/3/15.
 *
 *      Custom Application
 */

public class SQLFrameApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        try {
            SQLUtil.initSQLite(this);
        } catch (SQLiteInitException e) {
            e.printStackTrace();
        }
    }
}

The next step is to customize annotations and define a base class, which has inherited methods of adding, deleting and modifying, to implement the operation of objects of a class.

ok, first define two annotations, @Table and @Column.

package com.lzp.sqlframe.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by li.zhipeng on 2017/3/10.
 *
 *  Annotations to database tables
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value(); // No default value, if not the default Bean class name
}
package com.lzp.sqlframe.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by li.zhipeng on 2017/3/10.
 *
 *      Annotations for database table fields
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    /**
     * Field name
     * */
    String field();

    /**
     * Field type
     * */
    String type();
}

Annotations have been written, the next step is to find ways to implement the base class just mentioned, we named SQL FrameBaseBean, because it is just reference learning, so I only wrote here save method, other methods (modification, deletion) as exercises, left to you. Look at the code:

package com.lzp.sqlframe.sqllite;

import com.lzp.sqlframe.annotation.Column;
import com.lzp.sqlframe.annotation.Table;

import java.lang.reflect.Field;
import java.util.ArrayList;

import static com.lzp.sqlframe.sqllite.SQLUtil.checkSqlInit;

/**
 * Created by li.zhipeng on 2017/3/13.
 * <p>
 * Base classes of bean s to be saved to the database
 */

public class SQLFrameBaseBean {

    /**
     * Save an object
     */
    public void save() {
        try {
            checkSqlInit();

            StringBuilder sql = new StringBuilder("insert into ");

            // Get the name of the table first
            Table table = getClass().getAnnotation(Table.class);
            sql.append(table.value());
            sql.append("(");

            // Get all the attributes in the class
            Field[] fields = getClass().getDeclaredFields();

            StringBuilder fieldStr = new StringBuilder();
            StringBuilder valueStr = new StringBuilder();

            ArrayList<Object> values = new ArrayList<>();
            for (Field field : fields) {
                // Set method permissions on this property, otherwise private member variables cannot be accessed and exceptions occur
                field.setAccessible(true);
                Column column = field.getAnnotation(Column.class);
                // Determine whether this property is annotated
                if (column != null) {
                    String f = column.field();

                    fieldStr.append(f);
                    fieldStr.append(",");

                    values.add(String.valueOf(field.get(this)));
                    valueStr.append("?,");
                }
            }
            // Remove the last comma
            fieldStr.deleteCharAt(fieldStr.length() - 1);
            valueStr.deleteCharAt(valueStr.length() - 1);

            sql.append(fieldStr);
            sql.append(") values (");
            sql.append(valueStr);
            sql.append(")");
            SQLUtil.getInstance().execSQL(sql.toString(), values.toArray());
        } catch (SQLiteNotInitException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

Or the database fields with table names and annotations are obtained by reflection, and then sql operations are performed so that the object can call its own save method and save itself.

Now there's the last item left. Define the selectAll method to query all the data of a table and change the soup without changing the dressing. It's no different from the previous reflex usage.

/**
     * All information in the query table
     */
    public static <T> List<T> selectAll(Class<T> clazz) {
        Cursor cursor = null;
        try {
            // Check if initialization has been made
            checkSqlInit();

            // Query sql statements
            StringBuilder sql = new StringBuilder("select * from ");

            // Get the name of the table first
            Table table = clazz.getAnnotation(Table.class);
            sql.append(table.value());
            cursor = SQLUtil.getInstance().rawQuery(sql.toString(), null);
            List<T> result = new ArrayList<>();
            // Loop to get the cursor query data
            if (cursor != null) {
                // Get all table fields to create
                Field[] fields = clazz.getDeclaredFields();
                while (cursor.moveToNext()) {
                    // Create an object of a specified type by reflection
                    T t = clazz.newInstance();
                    for (Field field : fields) {
                        if (field != null) {
                            Column column = field.getAnnotation(Column.class);
                            if (column != null) {
                                // Private properties must be set to this method, otherwise there is no permission to manipulate this property.
                                field.setAccessible(true);
                                // Assigning attributes of objects by reflection
                                switch (column.type()) {
                                    case "varchar":
                                        field.set(t, cursor.getString(cursor.getColumnIndex(column.field())));
                                        break;
                                    case "Integer":
                                        field.set(t, cursor.getInt(cursor.getColumnIndex(column.field())));
                                        break;
                                }
                            }
                        }
                    }
                    result.add(t);
                }
            }
            return result;
        }
        // The recommended catch block writing for catching exceptions makes me feel uncomfortable when I see a yellow warning
        catch (InstantiationException | IllegalAccessException | SQLiteNotInitException e) {
            e.printStackTrace();
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        return null;
    }

ok, all the work has been done. Let's test it and see the structure of the new Module.

First of all, don't forget to create SQLFrame.xml in assets. The xml format is exactly the same as above.

Take a look at Student's code:

package com.lzp.sqlframedemo.bean;

import com.lzp.sqlframe.annotation.Column;
import com.lzp.sqlframe.annotation.Table;
import com.lzp.sqlframe.sqllite.SQLFrameBaseBean;

/**
 * Created by li.zhipeng on 2017/3/10.
 * <p>
 * Student Information
 */

@Table("Student")
public class Student extends SQLFrameBaseBean{

    /**
     * Reflections are used to create objects
     * */
    public Student(){}

    /**
     * Create a constructor
     */
    public Student(String id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    @Column(field = "student_id", type = "varchar")
    private String id;

    @Column(field = "student_name", type = "varchar")
    private String name;

    @Column(field = "student_age", type = "Integer")
    private int age;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

MainActivity code:

package com.lzp.sqlframedemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;

import com.lzp.sqlframe.sqllite.SQLUtil;
import com.lzp.sqlframedemo.bean.Student;

import java.util.List;

public class MainActivity extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.content);
        // query data base
        refreshView();
        // Click to save a new Student data and re-query the refresh interface
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               new Student("001", "lisi", 18).save();
                refreshView();
            }
        });
    }

    /**
     * Query the database and refresh the interface
     * */
    private void refreshView(){
        StringBuilder sb = new StringBuilder();
        List<Student> list = SQLUtil.selectAll(Student.class);
        // Stitching the attributes of all classes
        if(list != null && list.size() > 0){
            for (Student student : list){
                sb.append(student.getId()).append(",").append(student.getName()).append(",").append(student.getAge()).append(";");
            }
            textView.setText(sb.toString());
        }
    }
}

Is this over? Think about it carefully. We haven't set up our application yet.

<application
        android:name="com.lzp.sqlframe.application.SQLFrameApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

If you need to customize the application, you can inherit the SQL Frame Application or manually call the SQLUtil.initSQLite(this) in onCreate.

Take a look at the results of the operation:

After clicking the button, the following TextView will add a Student value, and the test passes!!!

summary

The tense battle is over. We use all the previous lessons in demo. The rudiment of a blown-out database framework has emerged, but it is still far from the commercial level, such as the database upgrade has not been completed, the operation of modification and deletion has not been completed, and the lack of a lot of code robustness judgment, etc. All of these need a long-term accumulation. The process, but we have mastered the core database operation part, has been a huge harvest.

The original Litepal is the masterpiece of Guo Lin. It has been maintained for three years. Its function is very stable and its efficiency is very high. This battle imitates the design ideas of Litepal and pays tribute to the dedication of its predecessors. He has a Wechat Public Number and a blog, and he has published books that everyone can learn from.

Source code has been uploaded, interested friends can practice their own revision.

Guo Linda's blog link: http://blog.csdn.net/guolin_blog

Litepal's address link in github: https://github.com/LitePalFramework/LitePal

Source download link, which contains the previous exercise code, do not need to ignore directly

Posted by FredFredrickson2 on Fri, 19 Apr 2019 10:39:33 -0700