有时公司不让使用三方数据库框架,自己又不想一遍一遍的写增删改查。于是出于懒惰,就基于反射加注解,写了一个简单的数据库框架。
一 、先来看看使用。
1) 首相要对要进行增删改查的数据对象进行注解配置,如下以Book类为例:
@Table(name="booktwo")
public class Book {
@PrimaryKey
@Column(name="b_id")
public String bookid;
@Column(name="b_name",length=500)
public String bookname;
@Column(name="time_tag",length=500)
public long time;
@Column(name="num")
public Type type;
@Column
public boolean isRee;
public Integer time3;
@Override
public String toString() {
return "Book [bookid=" + bookid + ", bookname=" + bookname + ", time=" + time + ", type=" + type + ", isRee="
+ isRee + ", time3=" + time3 + "]";
}
}
注解就三个,@Table ,@Column,@PrimaryKey, 分别为表, 列, 还有主键。
@Table 是设置表的,name 属性:设置表名;
@Column 是设置列相关的属性, 有name 属性:设置列名; length 属性:设置数据长度;
@PrimaryKey 是设置此字段为主key。唯一
如果对注解不熟悉的朋友,可先看java注解基础。
可以保存的数据类型包含java基础类型(int,long,String,float, short, enum (枚举)等等);
2)然后再看看框架使用和增删改查的部分简单使用:
初始化设置:
SSLiteConfig config = new SSLiteConfig();
config.setDbName("book.db");
config.setDbVersion(1);
SSLiteDB.getInstance().init(getApplicationContext(), config);
增:
// 单个插入
boolean insert = SSLiteDB.getInstance().getSSlite().insert(book);
//批量插入
SSLiteDB.getInstance().getSSlite().batchInsert(books);
删:
//单个删除
SSLiteDB.getInstance().getSSlite().delete(book);
//批量删除
SSLiteDB.getInstance().getSSlite().batchDelete(books);
改:
//单个修改
SSLiteDB.getInstance().getSSlite().update(book);
//批量修改
SSLiteDB.getInstance().getSSlite().batchUpdate(books);
查:
// 查询
List<Book> findAll = SSLiteDB.getInstance().getSSlite().findAll(Book.class);
3) 再看看使用效率
单条数据操作时间,如下:
image.png
批量10001条数据操作效率,如下:
image.png
4)看jar整体大小 image.png
二 , 框架代码内部模块
大体是下面的结构模块。
image.png
三,思路简单描述
如果我们想实现上面便捷的调用方法,实现增删改查,首先你必须得到对应的数据信息。也就是类的信息。注解可以帮助我们解决这个问题。
所以我们先编写Table ,Culomn, PrimaryPek,三个注解类。因为我们构建sql语句时,是需要知道表名是什么,列名是什么,哪个字段是主键等待。
以下是注解类:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table{
String name();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
String name() default "";
int length() default 100;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface PrimaryKey {
String name() default "";
int length() default 100;
}
其中@Target(ElementType.TYPE) 是表示使用在类上,@Target(ElementType.FIELD) 表示使用在字段上。具体的字段意思,上面已经说过了,对注解不太清楚的,可以去找一些java注解方面的东西。
有了注解,然后就是怎样解析注解,然后获取类的相关信息了。
这就需要用到java 的反射了。我们可以通过Book对象的Class,然后利用反射,得到其字段,注解,以及字段的值等等所有信息。
假设Book的class 对象为clazz,则通过 clazz.getAnnotation(Table.class)就可以查看Book类是否有Table的注解。如果返回的是Null,则说明没有,如果不为空,则我们就难道了Book的Table注解类对象。就可以取Table的信息了。比如table.getName();就可以得到表名值了。
对于Culomn注解的获取操作,就多了一步,说先得通过Field[] fields = clazz.getDeclaredFields();获取所有的Book类的字段,然后遍历,通过field.getAnnotation(Column.class);查看是否有Column注解。
PrimaryKey 的获取通Column。
如果对反射不熟悉的可以先看一下java 反射相关的东西。
具体的这块操作已经封装到了AnnoParse类里面了,具体如下,
class AnnoParse {
private static final String TAG = "AnnoParse";
/**
* 初始化table 的信息
* @param clazz
*/
public static TableInfo initTableInfo(Class<?> clazz){
Table table = (Table) clazz.getAnnotation(Table.class);
if(table==null){
return null;
}
TableInfo tableInfo = new TableInfo();
tableInfo.clazzName = clazz.getName();
if (TextUtils.isEmpty(table.name())) {
tableInfo.tableName = clazz.getName();
} else {
tableInfo.tableName = table.name();
}
HashMap<String, ColumnInfo> columnMaps = null;
HashMap<String, String> feildmaps = null;
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
field.setAccessible(true);
Column column = field.getAnnotation(Column.class);
if(column!=null){
// 列信息
ColumnInfo columnInfo = new ColumnInfo();
columnInfo.fieldName = field.getName();
columnInfo.fieldtype = field.getType().getName();
String columnname = column.name();
if(columnname==null||"".equals(columnname)){
columnInfo.columName = field.getName();
}else{
columnInfo.columName = columnname;
}
columnInfo.columLength = column.length();
//数据库 对应的列类型
columnInfo.dbtype = getType(field.getType(), columnInfo.fieldtype);
if (columnMaps == null) {
columnMaps = new HashMap<String, ColumnInfo>();
}
if (feildmaps == null) {
feildmaps = new HashMap<String, String>();
}
columnMaps.put(columnInfo.columName, columnInfo);
feildmaps.put(columnInfo.fieldName, columnInfo.columName);
PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class);
if (primaryKey != null) {
tableInfo.primaryKey = columnInfo.columName;
columnInfo.isPrimaryKey = true;
}
}
}
tableInfo.fieldMap = feildmaps;
tableInfo.colunmMap = columnMaps;
return tableInfo;
}
public static <T> String getTableName(Class<?> clazz) {
Table table = (Table) clazz.getAnnotation(Table.class);
if(table==null){
return null;
}
String tableName = null;
if (TextUtils.isEmpty(table.name())) {
tableName = clazz.getName();
} else {
tableName = table.name();
}
return tableName;
}
/**
* 通过field的type类型名字,得到存到数据库里对应的数据类型
* @param typeName
* @return
*/
private static DataType getType(Class<?> clazz, String typeName) {
if (clazz.isEnum()) {
return DataType.ENUM;
}else if (long.class.getName().equals(typeName) || Long.class.getName().equals(typeName)) {
return DataType.LONG;
} else if (Integer.class.getName().equals(typeName) || int.class.getName().equals(typeName)) {
return DataType.INTEGER;
} else if (String.class.getName() == typeName) {
return DataType.STRING;
} else if (float.class.getName().equals(typeName) || Float.class.getName().equals(typeName)) {
return DataType.FLOAT;
} else if (boolean.class.getName().equals(typeName) || Boolean.class.getName().equals(typeName)) {
return DataType.BOOLEAN;
} else if (short.class.getName().equals(typeName) || Short.class.getName().equals(typeName)) {
return DataType.SHORT;
} else {
return DataType.UNKOWN;
}
}
}
得到注解的信息之后,就相当于得到了表的信息,所以现在需要检查是否有现在的表,如果没有则需要创建表。创建表就需要我们通过得到的表的信息去拼凑创建表的sql语句了。
对于框架所需要的sql,都封装在了SQLCreator类中。检查 创建表的语句拼凑如下:
private String createTableSql(TableInfo info) {
if (info == null || info.colunmMap == null || info.colunmMap.keySet() == null) {
return null;
}
StringBuilder sql = new StringBuilder();
sql.append("CREATE TABLE IF NOT EXISTS ");
sql.append(info.tableName);
sql.append(" (");
Set<String> keySet = info.colunmMap.keySet();
for (String key : keySet) {
ColumnInfo columnInfo = info.colunmMap.get(key);
if (columnInfo == null) {
continue;
}
sql.append(" ");
sql.append(columnInfo.columName);
sql.append(" ");
sql.append(columnInfo.dbtype.getSqlType());
if (!DataType.INTEGER.getSqlType().equals(columnInfo.dbtype.getSqlType())) {
if (columnInfo.columLength <= 0) {
return null;
}
sql.append("(");
sql.append(columnInfo.columLength);
sql.append(")");
}
if (columnInfo.isPrimaryKey) {
sql.append(" PRIMARY KEY");
}
sql.append(",");
}
sql.deleteCharAt(sql.length() - 1);
sql.append(");");
LLog.d("Create tables sql -> " + sql.toString());
return sql.toString();
}
得到创建表的sql语句后,使用sqlite的相关代码执行就可以了,源码里的一些接口设计以及预留扩展就先不说了。
东西也算比较简单,是和练手,也可自己修改在代码中使用,减少代码冗余。
这里为了支持多个表,并且为了表的灵活创建,以及为了操作方便,在做增删改查操作时,都需要执行创建表的sql语句,语句中已经做了判断表是否存在,如果不存在则创建表。
最后
如果你依然在编程的世界里迷茫,不知道自己的未来规划,可以加入高级程序员群:里面可以与大神一起交流并走出迷茫。小白可进群免费领取学习资料,看看前辈们是如何在编程的世界里傲然前行。
1.LiveDataBus
2.Google官方架构组件
3.Jetpack架构
4.饿了么通信技术
5.OPenGL
6.音视频
7.人工智能
8.Python
9.性能优化
10.Flutter等
这些资料加群639986248领取
image.png
网友评论