Android數據庫框架---------- 註解加反射,構建簡單Sql數據庫框架

博客爲 有時個哥 原創,如需轉載請標明出處:https://blog.csdn.net/ls703/article/details/80605758

有時公司不讓使用三方數據庫框架,自己又不想一遍一遍的寫增刪改查。於是出於懶惰,就基於反射加註解,寫了一個簡單的數據庫框架。

一 、先來看看使用。

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) 再看看使用效率

單條數據操作時間,如下:


批量10001條數據操作效率,如下:


4)看jar整體大小


代碼整理數量


二 , 框架代碼內部模塊

大體是下面的結構模塊。


三,思路簡單描述

如果我們想實現上面便捷的調用方法,實現增刪改查,首先你必須得到對應的數據信息。也就是類的信息。註解可以幫助我們解決這個問題。

所以我們先編寫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語句,語句中已經做了判斷表是否存在,如果不存在則創建表。

具體的增刪改查操作請查看源碼。

SSLite源碼地址: https://github.com/ls0822/SSliteDB

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章