前言:当你想成为什么样的人,努力朝目标努力,终究有一天会实现,人生最大的快乐就是不断追寻梦想的过程


准备写一个数据库框架,现在的项目中数据库框架是用三方orm,不知道是不是叫这个名字,不重要了,准备这段时间把这数据库框架写出来,也许写的不够好,没关系,只要坚持住总会比之前好,这就是进步,我们如果不使用数据库框架的话,写的步骤可能要多点,现在就开始准备写,把这个框架命名为android_simple_sqlite


我们之前创建的数据库文件都是放在data/data/packageName/xxx.db,放在这里有一个不好的地方在于如果这个apk被用户卸载了,那么这个数据库也会跟着没了,如果项目需求要求你是在apk被卸载了后,apk第二次被安装时要求做什么功能需要用到之前数据库,那么这个时候你把数据库放在data/data/packageName/xxx.db就歇菜了!所以我们要针对这部分可以让数据库文件根据用户的需求存放在哪?


现在画一个架构设计图:

android数据框架 android数据库框架选择_java

看下我的项目包结构:

android数据框架 android数据库框架选择_User_02

uml类图如下:

android数据框架 android数据库框架选择_android数据框架_03



上面的图可能画的不够好,就就将着看下吧,我们在上层只是和DaoFactory这个类打交道,调用getDataHelper()方法返回BaseDao,看下调用层基本调用:

baseDao = DaoFactory.getInstance(this).getDataHelper(UserDao.class,User.class);
User user = new User();
user.setAge(18);
user.setName("zhouguizhi");
user.setPassword("123456");
baseDao.insert(user);


发现在调用层只要传递对应的实体和操作实体的BaseDao子类即可,因为底层不知道你操作的是什么具体数据,所以要用到泛型,哪为什么要getDataHelper()方法中要用到二个泛型呢?

第一个泛型是对数据库进行增删改查的具体业务类,你不可能操作User实体对象你传递一个PersonDao,这样会导致去获取实体对象中的属性以及属性上的注解会出现问题

第二个泛型是对那个实体对象进行操作

public  synchronized  <T extends BaseDao<M>,M> T getDataHelper(Class<T> clazz, Class<M> entityClass)
{
    BaseDao baseDao=null;
    try {
        baseDao=clazz.newInstance();
        baseDao.init(entityClass,sqLiteDatabase);
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return (T) baseDao;
}


这是在DataFactory类中获取到BaseDao,具体操作肯定是BaseDao的子类了,既然是框架所以我们想自动的创建表,而不需要手动的去创建,所以我们要使用注解把字段和表结构对应起来,

package com.sqlite.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * Created by admin on 2017/2/6.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnTableName {
    String value();
}


这是每个bean实体上的注解,用于声明表名:

package com.sqlite.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnFiled {
    String columnName() default "";//字段名
    String  type() default "";//字段类型
    int len() default 0;//字段长度
}


上面的注解用于实体bean上属性上,你会发现我AnnFiled注解上定义了三个属性

columnName:表示表名

type:表示字段什么类型的

len:默认的字段多少长度

使用:

package com.sqlite.bean;
/**
 * Created by admin on 2017/2/6.
 */
import com.sqlite.annotation.AnnFiled;
import com.sqlite.annotation.AnnTableName;
@AnnTableName("user")
public class User {
    @AnnFiled(columnName = "age",type ="Integer")
    public int age;
    @AnnFiled(columnName = "name",type ="varchar",len = 20)
    public String name;
    @AnnFiled(columnName = "pwd",type ="varchar",len = 20)
    public String password;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}


到时候我们只要通过反射去获取类和属性上的注解就可以动态的创建表语句

但是前提我们要让表名和实体bean属性对应起来,而且还有可能就bean上的属性名和表名不一致的情况,所以我们第一步就要把这关系对应上,第二次就不用了,这就是为什么要在BaseDao中init()方法的作用:

android数据框架 android数据库框架选择_java_04

BaseDao.java

/**
 * 初始化 主要是映射关系是否建立 以及
 * @param entityClass
 * @param sqLiteDatabase
 * @param
 */
public synchronized boolean init(Class<T> entityClass, SQLiteDatabase sqLiteDatabase) {
    if(!isInit){
        this.dataBase = sqLiteDatabase;
        this.clazz = entityClass;
        if(dataBase!=null){
            if(!dataBase.isOpen()){//判断数据库是否打开
                return false;
            }
        }
        String sql = "";
        if(clazz.getAnnotation(AnnTableName.class)!=null){
            tbName = clazz.getAnnotation(AnnTableName.class).value();
        }else{
            tbName = clazz.getSimpleName().toLowerCase();
        }
        try {
            sql =  createTable(clazz.newInstance());
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        if(!TextUtils.isEmpty(sql)){
            dataBase.execSQL(sql);//创建表
        }
        cacheMap = new HashMap<>();
        initCacheMap();
        isInit = true;
    }
    return isInit;
}


这是init()方法,如果数据库没有打开,就什么操作都不做了,如果数据库打开了就获取表名和上面说的对应关系,那么数据库我们在哪打开呢?

我们在DataFactory类做成了单例,在构造函数中做数据库创建和打开的操作

private DaoFactory(){
    dbFilePath= FileUtils.getDataBasePath(ctx,"zgz","user.db");
    LogUtils.e("dbFilePath="+dbFilePath);
    openDatabase();
}


上面dbFilePath是你数据库存放的路径在哪,我们平时数据库都是通过几次SqliteOpenHelper,那么这时候默认在data/data/package/database/xx.db,在这个框架中默认是在sd卡中如果sd卡不存在就放在其他目录下.

打开或者创建数据库的方法如下:

/**
 * 打开数据库
 */
private void openDatabase() {
    this.sqLiteDatabase=SQLiteDatabase.openOrCreateDatabase(dbFilePath,null);
}


在上面的BaseDao类上的init()方法中我们是先要创建一个表,为什么?因为等下我要拿到表中的所有字段名,不创建表怎么能拿的到,

try {
    sql =  createTable(clazz.newInstance());
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}
if(!TextUtils.isEmpty(sql)){
    dataBase.execSQL(sql);//创建表
}


上面的createTable()方法是BaseDao中的一个抽象方法,UserDao是继承了BaseDao,所以要实现这个createTable()方法,这个方法就是自动创建表的过程

package com.sqlite.dao;
import com.sqlite.annotation.AnnFiled;
import com.sqlite.annotation.AnnTableName;
import com.sqlite.bean.User;
import com.sqlite.constant.Constant;
import com.sqlite.dao.base.BaseDao;
import com.sqlite.utils.LogUtils;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * Created by admin on 2017/2/6.
 */
public class UserDao extends BaseDao<User> {
    @Override
    protected String createTable(User user) {
        String tb_name =  User.class.getAnnotation(AnnTableName.class).value();
        Field[] fields =  User.class.getDeclaredFields();
        String fieldName = "";
        String type = "";
        int len = 0;
        Map<String,Field> columsMap = new HashMap<>();
        for(Field field:fields){
            columsMap.put(field.getName(),field);
        }
        StringBuffer sb = new StringBuffer(Constant.create_table_prix);
        sb.append(" ");
        sb.append(tb_name);
        boolean isFirst = false;
        int i=0;
        for (Map.Entry<String, Field> entry: columsMap.entrySet()) {
            Field filed = entry.getValue();
            if(!isFirst){
                isFirst = true;
                sb.append("(");
            }
            if(filed.getAnnotation(AnnFiled.class)!=null){
                fieldName=filed.getAnnotation(AnnFiled.class).columnName();
                type = filed.getAnnotation(AnnFiled.class).type();
                len = filed.getAnnotation(AnnFiled.class).len();
                sb.append(fieldName).append(" ").append(type).append(len>0?"("+len+")":"").append((i==columsMap.size()-1)? ")":",");
                i++;
            }
        }
        LogUtils.e(sb.toString());
        return sb.toString();
    }

    @Override
    public Long insert(List<User> entity) {
        return null;
    }
}


这是通过反射和注解对创建表语句的拼写!

表创建成功了后就是找映射关系了,封装在initCacheMap()方法中,画一个图就理解了

android数据框架 android数据库框架选择_User_05

用文字太难描述了,可能是文采太差的原因,还是画图好

FileUtils.java是对文件操作的工具类在这贴下:

package com.sqlite.utils;
import android.content.Context;
import android.os.Environment;
import android.text.TextUtils;
import java.io.File;
import java.io.IOException;
/**
 * Created by Adminis on 2017/2/6.
 */
public class FileUtils {
    /**
     * 判断sd卡是否存在
     * @return
     */
    public static boolean sdCardIsExites(){
        if(Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)){
                return true;
        }
        return false;
    }
    /**
     * 获取sd卡根目录
     * @return
     */
    public static String getPath(){
        String path = "";
        if(sdCardIsExites()){
            path = Environment.getExternalStorageDirectory().getAbsolutePath();
        }
        return path;
    }
    /**
     * 在sd卡下面创建一个目录
     */
    public static String createFolder(Context context, String folderName){
        File file = null;
        String sdRootPath = getPath();
        if(TextUtils.isEmpty(sdRootPath)){//表示sd卡不存在
            if(context!=null){
                // /data/data/com.sqlite/files/zhgougui
                file = new File(context.getFilesDir().getAbsolutePath()+File.separator+folderName);
            }
        }else{
            file = new File(sdRootPath+File.separator+folderName);
        }
        if(file!=null&&!file.exists()){
            file.mkdir();
        }
        return file.getAbsolutePath();
    }

    /**
     *
     * @param ctx 上下文
     * @param folderName 文件夹名
     * @param dataBaseName 数据库名
     * @return
     */
    public static String getDataBasePath(Context ctx,String folderName,String dataBaseName){
        String dbPath = "";
        String dirPath = createFolder(ctx,folderName);
        File file = new File(dirPath+File.separator+dataBaseName);
        if(file!=null&&!file.exists()){
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        dbPath = file.getAbsolutePath();
        return dbPath;
    }
}


现在插入一条数据试试看!

/**
 * 插入单条数据
 */
private void addSingleData() {
    User user = new User();
    user.setAge(18);
    user.setName("zhouguizhi");
    user.setPassword("123456");
    baseDao.insert(user);
}


运行起来发现在我们sd卡目录下创建的zgz目录存放了数据库文件user.db

android数据框架 android数据库框架选择_sqlite_06


看下user表什么都没:

android数据框架 android数据库框架选择_User_07

我们现在点击下button

android数据框架 android数据库框架选择_java_08

现在再把数据库文件导入出来看看:

android数据框架 android数据库框架选择_User_09

ok,数据是插入成功了,这是做单条数据插入,有时间会把插入多条数据逻辑写下,我把我项目放在这博客中,有需要看的去下载看下,用文字描述太难,不知道怎么说!