一.主要讲解内容

1.基于运行时注解的ormlite的使用

2.基于反射的数据库工具封装

3.基于编译时注解的greendao的使用

二.github 的下载地址

https://github.com/MatrixSpring/OrmLiteDemo

三.视频讲解

 

四.原理分析

1.ormlite的使用

1.导入依赖

implementation 'com.j256.ormlite:ormlite-android:5.0'
 implementation 'com.j256.ormlite:ormlite-core:5.0'

2.使用注解建立bean类

  1. 创建表名         @DatabaseTable(tableName = "xxxx") //xxxx指用户自己定义的表名
  2. 绑定id字段      @DatabaseField(generatedId = true)   //设置表的id为主键且自动生成
  3. 绑定普通字段  @DatabaseField(columnName = "name")
  4. bean对象的例子
@DatabaseTable(tableName = "article")
public class ArticleBean {
    // article表中各个字段的名称
    public static final String COLUMNNAME_ID = "id";
    public static final String COLUMNNAME_TITLE = "title";
    public static final String COLUMNNAME_CONTENT = "content";
    public static final String COLUMNNAME_USER = "user_id";

    @DatabaseField(generatedId = true, columnName = COLUMNNAME_ID, useGetSet = true)
    private int id;
    @DatabaseField(columnName = COLUMNNAME_TITLE, useGetSet = true, canBeNull = false, unique = true)
    private String title;
    @DatabaseField(columnName = COLUMNNAME_CONTENT, useGetSet = true)
    private String content;
    @DatabaseField(columnName = COLUMNNAME_USER, foreign = true, foreignAutoRefresh = true, foreignAutoCreate = true,
            foreignColumnName = UserBean.COLUMNNAME_ID)
    private UserBean user_id;

    public ArticleBean() {
    }

    public ArticleBean(String title, String content, UserBean user) {
        this.title = title;
        this.content = content;
        this.user_id = user;
    }

    public int getId() {
        return id;
    }

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

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public UserBean getUser() {
        return user_id;
    }

    public void setUser(UserBean user) {
        this.user_id = user;
    }

    @Override
    public String toString() {
        return "ArticleBean{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", user=" + user_id +
                '}';
    }
}

3.使用OrmLiteSqliteOpenHelper类,建立OrmLiteHelper帮助类继承自OrmLiteSqliteOpenHelper类。

1.继承后系统提示会有几个重要的方法要实现   

@Override // 创建数据库时调用的方法
public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {
}

@Override // 数据库版本更新时调用的方法
public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) {
}

// 释放资源
@Override
public void close() {
    super.close();
}

2.对OrmLiteHelper实现单例化,采用Java中的双重检查锁(double checked locking)单例 传进来的上下文context需要使用Application的content不然会产生内存泄漏,当然里面也可以使用(软引用,弱引用)的activity。也可以使用内部静态类产生对象。

3.使用map存储APP中所有的DAO对象 private Map<String, Dao> daos = new HashMap<>();这样在使用时通过对象名获取到相应的操作表的对象。在close()方法中释放map中引用的对象

4.DatabaseHelper的实现

public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
    // 数据库名称
    public static final String DATABASE_NAME = "mydb.db";
    // 本类的单例实例
    private static DatabaseHelper instance;
    // 存储APP中所有的DAO对象的Map集合
    private Map<String, Dao> daos = new HashMap<>();

    // 获取本类单例对象的方法
    public static synchronized DatabaseHelper getInstance(Context context) {
        if (instance == null) {
            synchronized (DatabaseHelper.class) {
                if (instance == null) {
                    instance = new DatabaseHelper(context);
                }
            }
        }
        return instance;
    }

    // 私有的构造方法
    private DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, 1);
    }

    // 根据传入的DAO的路径获取到这个DAO的单例对象(要么从daos这个Map中获取,要么新创建一个并存入daos)
    public synchronized Dao getDao(Class clazz) throws SQLException {
        Dao dao = null;
        String className = clazz.getSimpleName();
        if (daos.containsKey(className)) {
            dao = daos.get(className);
        }
        if (dao == null) {
            dao = super.getDao(clazz);
            daos.put(className, dao);
        }
        return dao;
    }

    @Override // 创建数据库时调用的方法
    public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {
        try {
            TableUtils.createTable(connectionSource, UserBean.class);
            TableUtils.createTable(connectionSource, ArticleBean.class);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override // 数据库版本更新时调用的方法
    public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) {
        try {
            TableUtils.dropTable(connectionSource, UserBean.class, true);
            TableUtils.dropTable(connectionSource, ArticleBean.class, true);
            onCreate(database, connectionSource);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 释放资源
    @Override
    public void close() {
        super.close();
        for (String key : daos.keySet()) {
            Dao dao = daos.get(key);
            dao = null;
        }
    }
}

2.基于反射的数据库工具封装

1.思维设计

对于设计数据库封装工具。我们首先得把一些场景和条件或者说约束列出来

1.提供的一系列的数据接口,都需要提供统一的方法 增删改查批量的增加减少。于是就可以抽象出统一的接口来规范这些操作。

2.对于这个数据接口的实现类,我们想一般需要从外面传进来两个参数,一个是数据库操作的类,一个是这个数据接口封装的对象。

3.对外使用时我们需要调一个统一的入口,返回我们需要的带有上述增删改查统一接口方法的对象。这时我们想到了单例模式和工厂模式。

4.由于数据库的创建和连接销毁这个功能实现比较耗性能和消耗内存因此这个功能也需要用单例模式来实现。

5.工厂入口使用单例来实现了,里面的数据库操作的功能类也是单例实现了。那么这个工厂类需要根据传进去的类,初始化表的创建,根据传进去的类通过反射找到里面的参数。来实现接口增删改查功能。反射会有点耗性能。

6.sqlite中和Java中对于基本数据集的定义不同需要根据不同类型做转化。

7.我们可以将一个工厂实现的接口类里面定义一个缓存,将发射获取的参数加入到缓存中。下次其他方式操作这个类时就可以使用之前的缓存减少性能消耗。

8.查询支持类需要专门分装,由于查询的条件多方式多专门封装后功能清晰不凌乱。

9.在Android6.0以后在存储器上见文件需要权限,因此在数据库初始化时需要检测是否有存储器上的创建和读写的权限。

10.更高级的优化即把运行时的反射功能改成编译时自动生成代码功能。比如说greendao。

2.功能模块

1.接口定义

2.数据库操作工具单例实现

3.操作入口工厂工具类的实现 

4.转化工具类的实现

5.查询功能的封装

3.代码

1.接口定义代码

public interface IDaoSupport<T> {
    void init(SQLiteDatabase sqLiteDatabase, Class<T> tClass);
    //插入数据
    public long insert(T t);
    //批量插入数据
    public void insert(List<T> dataList);
    //获取专门的查询的支持类
    QuerySupport<T> querySupport();
    //
    int delete(String whereClause, String... whereArgs);

    int update(T obj, String whereClause, String... whereArgs);
}

2.数据库操作工具单例实现代码和工厂入口工具类的实现代码

public class DaoSupportFactory {
    // 持有外部数据库的引用
    private SQLiteDatabase mSqLiteDatabase;
    private static final String db_name = "yty";
    private static WeakReference<Context> weakReference;

    public static class DaoSupportInstance{
        public static DaoSupportFactory daoSupportFactory=new DaoSupportFactory();
    }

    public static DaoSupportFactory getInstance(Context context){
        weakReference = new WeakReference<>(context);
        return DaoSupportInstance.daoSupportFactory;
    }

    private DaoSupportFactory(){
        // 把数据库放到内存卡里面  判断是否有存储卡 6.0要动态申请权限
        File dbRoot = new File(weakReference.get().getFilesDir().getAbsolutePath() + File.separator + db_name + File.separator + "database");
        if (!dbRoot.exists()) {
            dbRoot.mkdirs();
        }
        File dbFile = new File(dbRoot, "test.db");

        // 打开或者创建一个数据库
        mSqLiteDatabase = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
    }

    public <T> IDaoSupport<T> getDao(Class<T> clazz) {
        IDaoSupport<T> daoSupport = new DaoSupport();
        daoSupport.init(mSqLiteDatabase, clazz);
        return daoSupport;
    }
}

3.数据库接口实现类

public class DaoSupport<T> implements IDaoSupport<T> {
    private String TAG = "DaoSupport";
    private SQLiteDatabase mSqLiteDatabase;
    //范型类
    private Class<T> mClass;
    private static final Object[] mPutMethodArgs = new Object[2];

    private static final Map<String, Method> mPutMethods = new ArrayMap<>();

    private QuerySupport<T> mQuerySupport;

    @Override
    public void init(SQLiteDatabase sqLiteDatabase, Class<T> tClass) {
        this.mSqLiteDatabase = sqLiteDatabase;
        this.mClass = tClass;

        //创建表
//        create table if not exists Student(
//                id integer primary key autoincrement,
//                name text,
//                age integer,
//                flag boolean
//        )

        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("create table if not exists ")
                .append(DaoUtils.getTableName(mClass))
                .append("(id integer primary key autoincrement, ");

        Field[] fields = mClass.getDeclaredFields();
        for(Field field: fields){
            field.setAccessible(true);
            String name = field.getName();
            String type = field.getType().getSimpleName();// int String boolean
            //  type需要进行转换 int --> integer, String text;
            stringBuffer.append(name).append(DaoUtils.getColumnType(type)).append(", ");
        }

        stringBuffer.replace(stringBuffer.length()-2, stringBuffer.length(),")");
        String createTableSql = stringBuffer.toString();

        // 创建表
        mSqLiteDatabase.execSQL(createTableSql);
    }

    @Override
    public long insert(T t) {
//        ContentValues values = new ContentValues();
//        values.put("name",person.getName());
//        values.put("age",person.getAge());
//        values.put("flag",person.getFlag());
//        db.insert("Person",null,values);

        // 使用的其实还是  原生的使用方式,只是我们是封装一下而已
        ContentValues values = contentValuesByObj(t);

        // null  速度比第三方的快一倍左右
        return mSqLiteDatabase.insert(DaoUtils.getTableName(mClass), null, values);
    }

    @Override
    public void insert(List<T> dataList) {
        //批量插入数据采用 采用事务 提高 性能
        mSqLiteDatabase.beginTransaction();
        for(T data: dataList){
            //采用单条插入
            insert(data);
        }
        mSqLiteDatabase.setTransactionSuccessful();
        mSqLiteDatabase.endTransaction();
    }

    @Override
    public QuerySupport<T> querySupport() {
        if (null == mQuerySupport){
            mQuerySupport = new QuerySupport<>(mSqLiteDatabase, mClass);
        }
        return mQuerySupport;
    }

    @Override
    public int delete(String whereClause, String... whereArgs) {
        return mSqLiteDatabase.delete(DaoUtils.getTableName(mClass), whereClause, whereArgs);
    }

    @Override
    public int update(T obj, String whereClause, String... whereArgs) {
        ContentValues values = contentValuesByObj(obj);
        return mSqLiteDatabase.update(DaoUtils.getTableName(mClass),values,whereClause,whereArgs);
    }

    private ContentValues contentValuesByObj(T obj){
        // 第三方的 使用比对一下 了解一下源码
        ContentValues values = new ContentValues();
        //封装values
        Field[] fields = mClass.getDeclaredFields();

        for(Field field: fields){
            try{
                //设置权限,私有和公有的都可以访问
                field.setAccessible(true);
                String key = field.getName();
                //获取value
                Object value = field.get(obj);

                //put第二个参数是类型 需要转化
                mPutMethodArgs[0] = key;
                mPutMethodArgs[1] = value;

                //方法使用反射,反射在一定程度上会影响性能
                //参考AppCompatViewInflater源码
                String filedTypeName = field.getType().getName();
                //还是使用反射  获取方法  put  缓存方法
                Method putMethod = mPutMethods.get(filedTypeName);
                if(putMethod == null){
                    putMethod = ContentValues.class.getDeclaredMethod("put", String.class, value.getClass());
                    mPutMethods.put(filedTypeName, putMethod);
                }
                //通过反射执行
                putMethod.invoke(values,mPutMethodArgs);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                mPutMethodArgs[0] = null;
                mPutMethodArgs[1] = null;
            }
        }
        return values;
    }
}

4.转化工具类的实现代码

public class DaoUtils {
    private DaoUtils() {
        throw new UnsupportedOperationException("cannot be instantiated");
    }

    /**
     * 获得类名
     */
    public static String getTableName(Class<?> clazz) {
        return clazz.getSimpleName();
    }

    /**
     * 数据库操作的时候根据类型进行转换
     */
    public static String getColumnType(String type) {
        String value = null;
        if (type.contains("String")) {
            value = " text";
        } else if (type.contains("int")) {
            value = " integer";
        } else if (type.contains("boolean")) {
            value = " boolean";
        } else if (type.contains("float")) {
            value = " float";
        } else if (type.contains("double")) {
            value = " double";
        } else if (type.contains("char")) {
            value = " varchar";
        } else if (type.contains("long")) {
            value = " long";
        }
        return value;
    }

    /**
     * 查询数据:cursor.getInt();将int第一个字符进行转换成大写
     */
    public static String capitalize(String string) {
        if (!TextUtils.isEmpty(string)) {
            return string.substring(0, 1).toUpperCase(Locale.US) + string.substring(1);
        }
        return string == null ? null : "";
    }
}

5.查询功能的封装代码

public class QuerySupport<T> {
    // 查询的列
    private String[] mQueryColumns;
    // 查询的条件
    private String mQuerySelection;
    // 查询的参数
    private String[] mQuerySelectionArgs;
    // 查询分组
    private String mQueryGroupBy;
    // 查询对结果集进行过滤
    private String mQueryHaving;
    // 查询排序
    private String mQueryOrderBy;
    // 查询可用于分页
    private String mQueryLimit;

    private Class<T> mClass;
    private SQLiteDatabase mSQLiteDatabase;

    public QuerySupport(SQLiteDatabase sqLiteDatabase, Class<T> clazz) {
        this.mClass = clazz;
        this.mSQLiteDatabase = sqLiteDatabase;
    }

    public QuerySupport columns(String... columns) {
        this.mQueryColumns = columns;
        return this;
    }

    public QuerySupport selectionArgs(String... selectionArgs) {
        this.mQuerySelectionArgs = selectionArgs;
        return this;
    }

    public QuerySupport having(String having) {
        this.mQueryHaving = having;
        return this;
    }

    public QuerySupport orderBy(String orderBy) {
        this.mQueryOrderBy = orderBy;
        return this;
    }

    public QuerySupport limit(String limit) {
        this.mQueryLimit = limit;
        return this;
    }

    public QuerySupport groupBy(String groupBy) {
        this.mQueryGroupBy = groupBy;
        return this;
    }

    public QuerySupport selection(String selection) {
        this.mQuerySelection = selection;
        return this;
    }

    public List<T> query() {
        Cursor cursor = mSQLiteDatabase.query(DaoUtils.getTableName(mClass), mQueryColumns, mQuerySelection,
                mQuerySelectionArgs, mQueryGroupBy, mQueryHaving, mQueryOrderBy, mQueryLimit);
        clearQueryParams();
        return cursorToList(cursor);
    }

    public List<T> queryAll() {
        Cursor cursor = mSQLiteDatabase.query(DaoUtils.getTableName(mClass), null, null, null, null, null, null);
        return cursorToList(cursor);
    }

    /**
     * 清空参数
     */
    private void clearQueryParams() {
        mQueryColumns = null;
        mQuerySelection = null;
        mQuerySelectionArgs = null;
        mQueryGroupBy = null;
        mQueryHaving = null;
        mQueryOrderBy = null;
        mQueryLimit = null;
    }

    /**
     * 通过Cursor封装成查找对象
     *
     * @return 对象集合列表
     */
    private List<T> cursorToList(Cursor cursor) {
        List<T> list = new ArrayList<>();
        if (cursor != null && cursor.moveToFirst()) {
            do {
                try {
                    T instance = mClass.newInstance();
                    Field[] fields = mClass.getDeclaredFields();
                    for (Field field : fields) {
                        // 遍历属性
                        field.setAccessible(true);
                        String name = field.getName();
                        //cursor.getInt(0);
                        // 获取角标
                        int index = cursor.getColumnIndex(name);
                        if (index == -1) {
                            continue;
                        }
                        // 通过反射获取 游标的方法
                        Method cursorMethod = cursorMethod(field.getType());
                        if (cursorMethod != null) {
                            Object value = cursorMethod.invoke(cursor, index);
                            if (value == null) {
                                continue;
                            }

                            // 处理一些特殊的部分
                            if (field.getType() == boolean.class || field.getType() == Boolean.class) {
                                if ("0".equals(String.valueOf(value))) {
                                    value = false;
                                } else if ("1".equals(String.valueOf(value))) {
                                    value = true;
                                }
                            } else if (field.getType() == char.class || field.getType() == Character.class) {
                                value = ((String) value).charAt(0);
                            } else if (field.getType() == Date.class) {
                                long date = (Long) value;
                                if (date <= 0) {
                                    value = null;
                                } else {
                                    value = new Date(date);
                                }
                            }
                            field.set(instance, value);
                        }
                    }
                    // 加入集合
                    list.add(instance);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } while (cursor.moveToNext());
        }
        cursor.close();
        return list;
    }

    private Method cursorMethod(Class<?> type) throws Exception {
        String methodName = getColumnMethodName(type);
        Method method = Cursor.class.getMethod(methodName, int.class);
        return method;
    }

    private String getColumnMethodName(Class<?> fieldType) {
        String typeName;
        if (fieldType.isPrimitive()) {//是不是基本类型
            typeName = DaoUtils.capitalize(fieldType.getName());//Int
        } else {
            typeName = fieldType.getSimpleName();//Integer
        }
        String methodName = "get" + typeName;
        if ("getBoolean".equals(methodName)) {
            methodName = "getInt";
        } else if ("getChar".equals(methodName) || "getCharacter".equals(methodName)) {
            methodName = "getString";
        } else if ("getDate".equals(methodName)) {
            methodName = "getLong";
        } else if ("getInteger".equals(methodName)) {
            methodName = "getInt";
        }
        return methodName;
    }

}

6.使用 

新建的java bean对象必须有无参的构造函数

 

 

3.类似封装SQLCipher

将上面的sqlite换成sqlcipher也可以实现类似封装,源码在面链接中的libframe中,帮忙点个赞

GitHub地址:https://github.com/MatrixSpring/FrameWork

 

4.基于编译时注解的greendao的使用