Android 手寫資料庫框架
前言
在Android開發中,一定會遇到資料庫sqlit的操作的,如果你的專案中沒有用到資料庫那麼說明你的專案很失敗。
一般我們可以直接使用系統提供的sqlit操作完成資料庫的操作,同時也可以使用現在比較多的資料庫開源框架,比如GreenDAO OrmLitem等資料庫框架,都是直接將物件對映到sqlit資料庫的ORM框架。
在這篇文章中我們將自己動手寫一個ORM框架,自定義一個屬於我們自己的ORM資料庫框架。
原理分析
在Android中無論我們如何對資料庫進行封裝,最終操作都離不開sqlit自身對資料的增刪改操作,所以我們需要將這些操作封裝在底層,上層只需要傳入物件呼叫相關方法即可,不用去管底層是如何做的,包括表的建立等。
好,下面我們來看看分析的圖
從圖中我們也可以看出來,手寫資料庫框架的主要內容就在中間部分,主要的有BaseDaoFactory和BaseDao這兩個類。
但是在這些之前我們還有兩個地方需要關注,就是資料庫表的生成。在常用的資料庫框架中如GreenDAO和ORMLitem等都是通過註解來生成表和欄位的,那麼在我們的框架中當然也採用這種方式來完成,下面就來看看程式碼吧
特此宣告,如果是Android studio使用者,在使用該庫時請關閉Instant Run功能,具體什麼原因可以自己手動嘗試
註解
生成表的註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DbTable {
String value();
}
生成欄位的註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DbFiled {
String value() ;
}
這些註解該如何使用呢?
@DbTable("tb_common_user")
public class User {
@DbFiled("tb_name")
public String name ;
@DbFiled("tb_password")
public String password ;
@DbFiled("tb_age")
public String age ;
}
我們只需要在JavaBean類和變數上標註即可,這樣就可以生成對應的表名和欄位名,具體如何生成的,我們會在下面講到,如果對註解知識不是特別瞭解,那就需要加強一下Java基礎了哦。
既然知道了註解生成表和欄位並且知道如何使用後,下面我們就來看看Dao層的程式碼吧
BaseDaoFactory
具體的程式碼如下
public class BaseDaoFactory {
/** 資料庫路徑 */
private String sqliteDatabasePath ;
/** 操作資料庫 */
private SQLiteDatabase sqLiteDatabase ;
private static BaseDaoFactory instance = null ;
public static BaseDaoFactory getInstance(){
if(instance == null){
synchronized (BaseDaoFactory.class){
instance = new BaseDaoFactory() ;
}
}
return instance ;
}
private BaseDaoFactory(){
//獲取資料庫路徑
sqliteDatabasePath = Environment.getExternalStorageDirectory().getAbsolutePath()+"/user.db" ;
//開啟資料庫
openDatabase();
}
/**
* 獲取DataHelper
* @param clazz BaseDao的子類位元組碼
* @param entityClass 要存入物件的位元組碼
* @param <T>
* @param <M>
* @return
*/
public synchronized <T extends BaseDao<M>,M> T getDataHelper(Class<T> clazz,Class<M> entityClass){
T dao = null ;
//獲取物件
try {
dao = clazz.newInstance() ;
dao.init(entityClass,sqLiteDatabase) ;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return dao;
}
/**
* 開啟或建立資料庫
*/
private void openDatabase() {
this.sqLiteDatabase = SQLiteDatabase.openOrCreateDatabase(sqliteDatabasePath,null) ;
}
}
BaseDaoFactory程式碼內容不是太多,好,接下來我們就具體分析吧。
可以看出BaseDaoFactory採用單例的方式,用來生成Dao物件的。主要方法有兩個openDatabase()和getDataHelper()方法,openDatabase()方法是負責獲取sqliteDatabase物件的,因為sqlit底層操作需要這個物件。
getDataHelper()中只做了兩件事,建立愛你Dao層物件,並且呼叫dao的init()方法。所以要想使用Dao我們只需要呼叫getDataHelper()方法傳入我們想要使用的Dao,BaseDaoFactory會幫我們生成。
其中getDataHelper需要兩個泛型引數,可能會讓人有些費解,那我們就來看看這些泛型引數的含義
public synchronized <T extends BaseDao<M>,M> T getDataHelper(Class<T> clazz,Class<M> entityClass){
.....
}
因為在這個框架中,所有的Dao層都一個基類,就是BaseDao,所以通過
BaseDao
首先看看IBaseDao程式碼
IBaseDao程式碼如下
public interface IBaseDao<T> {
/**
* 插入一個物件到資料庫
* @param entity 要插入的物件
* @return
*/
public Long insert(T entity) ;
/**
* 更新
* @param entity
* @param where
* @return
*/
public int update(T entity ,T where) ;
/**
* 刪除
* @param where
* @return
*/
public int delete(T where);
/**
* 查詢
* @param where
* @return
*/
public List<T> query(T where) ;
public List<T> query(T where,String orderBy,Integer startIndex,Integer limit) ;
}
BaseDao程式碼如下
public class BaseDao<T> implements IBaseDao<T> {
/** 持有資料庫操作類的引用 */
private SQLiteDatabase database ;
/** 保證例項化一次 */
private boolean isInit = false ;
/** 持有操作資料庫表所對應的Java型別 */
private Class<T> entityClass ;
/** 表名 */
private String tableName ;
/** 維護表名與成員變數的對映關係 */
private HashMap<String,Field> cacheMap ;
/** 初始化 */
protected boolean init(Class<T> entity,SQLiteDatabase sqLiteDatabase){
this.entityClass = entity ;
if(!isInit){
this.database = sqLiteDatabase ;
//判斷註解是否為null
if(entity.getAnnotation(DbTable.class) == null){
this.tableName = entity.getClass().getSimpleName() ;
}else {
this.tableName = entity.getAnnotation(DbTable.class).value();
}
//檢查資料庫是否開啟
if(!database.isOpen()){
return false ;
}
//執行sql語句建立表
if(!TextUtils.isEmpty(createTable())){
database.execSQL(createTable());
}
initCacheMap();
isInit = true ;
}
return isInit ;
}
........
}
通過init()方法我們可以看出來,之前定義的註解這這裡得到了使用,通過傳入的物件獲取註解和值,然後得到表名。這裡還呼叫了兩個方法,createTable和initCacheMap方法。
createTable是建立表的方法具體程式碼如下
/**
* 獲取建立資料庫表的sql
* @return
*/
private String createTable(){
HashMap<String,String> columMap = new HashMap<>();
Field[] fields = entityClass.getFields();
for(Field field : fields){
field.setAccessible(true);
DbFiled dbFiled = field.getAnnotation(DbFiled.class);
if(dbFiled == null){
columMap.put(field.getName(),field.getName());
}else {
columMap.put(field.getName(),dbFiled.value());
}
}
//建立資料庫語句
String sql = "create table if not exists "+ tableName + "(" ;
Set<String> keys = columMap.keySet();
StringBuilder sb = new StringBuilder() ;
for(String key : keys){
String value = columMap.get(key);
sb.append(value).append(" varchar(20)").append(",");
}
String s = sb.toString();
s = s.substring(0,s.lastIndexOf(",")) + ")" ;
//拼接sql語句
sql = sql + s ;
return sql ;
}
通過程式碼我們也可以看出來在createTable()方法中我們通過獲取變數上的註解獲取到表中的列名然後拼接成sql語句,然後呼叫這個sql語句建立表。
還有一個initCacheMap()方法程式碼如下
/** 維護對映關係 */
private void initCacheMap() {
Cursor cursor = null ;
try {
/**
* map集合中
* key 列名
* map 變數物件
*
* 主要功能是找到列名對應的變數物件,便於後續的使用等
*/
cacheMap = new HashMap<>();
//1 第一步需要查詢一遍表獲取列名
String sql = "select * from " + this.tableName ;
cursor = database.rawQuery(sql, null);
//獲取表的列名陣列
String[] columnNames = cursor.getColumnNames();
//獲取Field陣列
Field[] columnFields = entityClass.getFields();
for (Field field : columnFields) {
field.setAccessible(true);
}
//查詢對應關係
for (String colmunName : columnNames) {
Field columField = null;
for (Field field : columnFields) {
String fieldName = null;
//獲取註解
DbFiled dbFiled = field.getAnnotation(DbFiled.class);
if (dbFiled != null) {
fieldName = dbFiled.value();
} else {
fieldName = field.getName();
}
//如果找到對應表的列名對應的成員變數
if (colmunName.equals(fieldName)) {
columField = field;
break;
}
}
//找到對應關係
if (columField != null) {
cacheMap.put(colmunName, columField);
}
}
}catch (Exception e){
}finally {
if(cursor != null)
cursor.close() ;
}
}
在initCacheMap()方法中就做了一件事,將列名和對應的變數物件存入到map集合中,在之後會使用到。
下面我們就來看看具體的資料庫操作方法吧。
儲存資料
首先insert方法程式碼如下
@Override
public Long insert(T entity) {
Map<String, String> map = getValues(entity);
ContentValues values = getContentValues(map);
long insert = database.insert(tableName, null, values);
return insert;
}
通過程式碼我們可以看出來getValues()方法是將物件轉換成Map集合,getContentValues()方法是將map集合轉換成ContentValues,得到ContentValues物件後,我們就可以直接呼叫database.insert()方法插入資料了。
那我們來看看getValues()方法和getContentValues()方法吧
getValues()程式碼如下
/** 將物件轉換成map集合 */
private Map<String,String> getValues(T entity){
/**
* 集合
* key 列名也是變數上的註解值
* value 變數的具體值
*/
HashMap<String,String> result = new HashMap<>() ;
Iterator<Field> fieldIterator = cacheMap.values().iterator();
//迴圈遍歷對映表 遍歷cacheMap得到列名和其對應的變數物件(cacheMap中存入的是列名和物件的對映)
while(fieldIterator.hasNext()){
//得到成員變數
Field colmunToField = fieldIterator.next();
//定義變數用於儲存變數上註解的值,也就是列名
String cacheKey = null ;
//定義變數用於儲存變數的具體值
String cacheValue = null ;
//獲取列名
if(colmunToField.getAnnotation(DbFiled.class) != null){
cacheKey = colmunToField.getAnnotation(DbFiled.class).value();
}else {
cacheKey = colmunToField.getName();
}
try {
if(colmunToField.get(entity) == null){
continue;
}
//得到具體的變數的值
cacheValue = colmunToField.get(entity).toString();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
result.put(cacheKey,cacheValue) ;
}
return result ;
}
具體getValues()是如何將物件轉換成Map集合的這裡就不再多說了,程式碼中註釋寫的比較清楚,就是通過獲取註解和反射獲取變數的具體值。
getContentValues()方法程式碼如下
/**
* 將map轉換成ContentValues
* @param map
* @return
*/
private ContentValues getContentValues(Map<String, String> map) {
ContentValues values = new ContentValues() ;
for(String key : map.keySet()){
values.put(key,map.get(key));
}
return values;
}
這個方法就比較簡單了,就是遍歷map集合完成操作。
通過上面的分析基本上就可以理清楚思路了,也知道如何完成資料庫表的建立和資料的儲存了。接下來接看看資料的修改吧
修改資料
@Override
public int update(T entity, T where) {
int result = -1 ;
//將修改的結果轉換成Map集合
Map<String, String> map = getValues(entity);
//將修改的條件轉換成Map集合
Map<String, String> whereClause = getValues(where);
//得到修改的條件語句
Condition condition = new Condition(whereClause);
ContentValues contentValues = getContentValues(map);
result = database.update(tableName, contentValues, condition.getWhereClause(), condition.getWhereArgs());
return result;
}
修改程式碼中除了使用了前面講到了getValues()方法和getContentVlaues()方法外還用到了Condition。
Condition程式碼如下
/**
* 封裝修改的語句
*/
class Condition {
private String whereClause ;
private String[] whereArgs ;
public Condition(Map<String, String> whereClause) {
ArrayList<String> list = new ArrayList<>() ;
StringBuilder sb = new StringBuilder() ;
sb.append("1=1") ;
for(String key : whereClause.keySet()){
String value = whereClause.get(key);
if(value != null){
//拼接條件查詢語句
sb.append(" and ").append(key).append(" =?");
//查詢條件
list.add(value);
}
}
this.whereClause = sb.toString() ;
this.whereArgs = list.toArray(new String[list.size()]);
}
public String getWhereClause() {
return whereClause;
}
public String[] getWhereArgs() {
return whereArgs;
}
}
Condition是一個隊修改語句的封裝,類中通過拼接和轉換獲取到修改的條件語句和引數。
刪除資料
@Override
public int delete(T where) {
Map<String, String> map = getValues(where);
Condition condition = new Condition(map) ;
int result = database.delete(tableName, condition.getWhereClause(), condition.getWhereArgs());
return result;
}
刪除程式碼比較簡單,也是呼叫了getValues()方法將條件物件轉換成Map集合,然後通過Condition將集合裝換成刪除的條件語句和引數。
查詢資料
@Override
public List<T> query(T where) {
return query(where,null,null,null);
}
@Override
public List<T> query(T where, String orderBy, Integer startIndex, Integer limit) {
Map<String, String> map = getValues(where);
String limitStr = null ;
if(startIndex != null && limit != null){
limitStr = startIndex + " , " + limit ;
}
Condition condition = new Condition(map) ;
Cursor cursor = database.query(tableName, null, condition.getWhereClause(), condition.getWhereArgs(), null, null, orderBy, limitStr);
List<T> result = getResult(cursor,where);
return result;
}
首先上面兩個方法主要的是第二個,在程式碼中首先根據條件獲取到了cursor物件,然後通過getResult()方法和cursor得到了最終物件集合
getResult程式碼如下
/** 獲取查詢結果 */
private List<T> getResult(Cursor cursor, T where) {
List<T> list = new ArrayList<>() ;
//定義變數用於接收查詢到的資料
T item ;
while(cursor.moveToNext()){
try {
//通過反射初始化物件
item = (T) where.getClass().newInstance();
//下面迴圈對變數名進行賦值
/**
* cacheMap中快取的是
* key 列名
* value 成員變數名
*
*/
for(String key : cacheMap.keySet()) {
//得到資料庫表中的列名
String columnName = key;
//然後通過列名獲取遊標的位置
int columnIndex = cursor.getColumnIndex(columnName);
//獲取到物件中的成員變數名稱
Field field = cacheMap.get(key);
//獲取成員變數的型別
Class type = field.getType();
//反射方式給item中的變數賦值
if (columnIndex != -1){
if (type == String.class) {
field.set(item, cursor.getString(columnIndex));
}else if(type == Double.class){
field.set(item,cursor.getDouble(columnIndex));
}else if(type == Integer.class){
field.set(item,cursor.getInt(columnIndex));
}else if(type == byte[].class){
field.set(item,cursor.getBlob(columnIndex));
}else{
continue ;
}
}
}
//將變數存入到集合中
list.add(item);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return list;
}
首先我們知道資料庫中查詢到的內容都在cursor中,所以我們只需要遍歷cursor就可以獲取到我們想要的內容,因為cursor中獲取到的值需要賦值給物件,所以我們手動建立了T型別的物件,因為這個物件不確定,所以我們通過泛型表示。在之前的initCacheMap()方法中我們已經獲取到了物件內部的變數名和表中的列名,所以可以通過反射獲取到變數的型別,並對其進行賦值。
這樣就完成了對變數的賦值了,最後將物件存入到list集合中然後返回。
OK完成
使用
上面將框架的各個知識點講完了還沒有具體的使用呢,所以接下里我們就來使用我們手擼的框架
User類程式碼如下
@DbTable("tb_common_user")
public class User {
@DbFiled("tb_name")
public String name ;
@DbFiled("tb_password")
public String password ;
@DbFiled("tb_age")
public String age ;
}
UserDao程式碼如下
public class UserDao extends BaseDao<User> {
}
MainActivity程式碼如下
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//儲存
public void save(View view){
Random random = new Random() ;
UserDao userDao = BaseDaoFactory.getInstance().getDataHelper(UserDao.class, User.class);
User user = new User();
user.name = "lilei" ;
user.password = "abc" ;
user.age = random.nextInt() % 2 == 0 ? "男" : "女" ;
userDao.insert(user);
}
//更新
public void update(View view){
UserDao userDao = BaseDaoFactory.getInstance().getDataHelper(UserDao.class, User.class);
//更新條件
User where = new User() ;
where.name = "lilei" ;
//更新為
User user = new User() ;
user.name = "hanmeimei" ;
userDao.update(user,where);
}
//刪除
public void delete(View view){
UserDao userDao = BaseDaoFactory.getInstance().getDataHelper(UserDao.class, User.class);
//刪除條件
User where = new User() ;
where.name = "hanmeimei";
userDao.delete(where);
}
//查詢
public void query(View view){
UserDao userDao = BaseDaoFactory.getInstance().getDataHelper(UserDao.class, User.class);
User where = new User() ;
where.name = "lilei" ;
where.age = "女" ;
List<User> query = userDao.query(where);
for(User user : query){
System.out.println("name:"+user.name+",age:"+user.age+",password:"+user.password);
}
}
}
好了結果我就不展示了,一遍通過。
總結
通過上面的講解,發現手寫一個數據庫其實也不是很難,當然這個框架有很多的不足的地方,但是至少讓我們瞭解瞭如何手動擼一個自己的資料庫框架,瞭解了資料庫框架的原理。之後如果有什麼想法當然可以在此基礎上再新增。
QQ交流群
微信公眾號:Android在路上,歡迎關注
相關推薦
Android 手寫資料庫框架
前言 在Android開發中,一定會遇到資料庫sqlit的操作的,如果你的專案中沒有用到資料庫那麼說明你的專案很失敗。 一般我們可以直接使用系統提供的sqlit操作完成資料庫的操作,同時也可以使用現在比較多的資料庫開源框架,比如GreenDAO OrmLi
Android進階系列-手寫資料庫框架
資料庫在某些特定需求下是很重要的,像持久化資料,一些不需要實時或者長時間不變的資料,可以放在資料庫中做快取,這樣就算使用者網路不好或者斷網的情況下,依然是可以檢視一些以前的資料。像新
手寫資料庫框架
目錄 前言 android發展至今開源框架很多,資料庫框架也有一些比較好的,而對於我們開發者而言網上有很多框架,我們只需要拿過來會用,一般都可以解決專案中遇到的問題,而我們自己寫的程式碼量就比較少了,也很難突破開發瓶頸,開發模式變成了Ctrl+C Ctrl+V,而依賴過
android 手把手教你寫資料庫框架(第一篇)
前言:當你想成為什麼樣的人,努力朝目標努力,終究有一天會實現,人生最大的快樂就是不斷追尋夢想的過程 準備寫一個數據庫框架,現在的專案中資料庫框架是用三方orm,不知道是不是叫這個名字,不重要了,準備這段時間把這資料庫框架寫出來,也許寫的不夠好,沒關係,只要堅持住總會比之前
重學 Java 設計模式:實戰中介者模式「按照Mybatis原理手寫ORM框架,給JDBC方式操作資料庫增加中介者場景」
![](https://img-blog.csdnimg.cn/20200627092858193.jpg) 作者:小傅哥 部落格:[https://bugstack.cn](https://bugstack.cn) - `原創系列專題文章` >沉澱、分享、成長,讓自己和他人都能有所收穫!
轉載:手寫SpringMVC框架
javaee 作用 小寫 繼承 inf group css finally 減少 帶你手寫一個SpringMVC框架(有助於理解springMVC) 鏈接:https://my.oschina.net/liughDevelop 作者:我叫劉半仙 Spring
手寫資料庫連線池附gp連線jar包地址
手寫資料庫連線並,測試. 最近資料庫要連線GP資料庫(GreenplumSQL),在建立連線的時候需要做建立不同的連線數量. 其實當想到寫資料庫連線時,完全可以通過springdata jpa直接寫介面,這是一種思路. 所以在使用的使用,就寫了個dem
一、1 基於socket手寫web框架
1 web 應用 2 c/s 和bs架構 3 python中的web框架 a :socket b:路由跟檢視函式匹配關係 &n
高手過招「效能優化/純手寫SpringMVC框架/MySql優化/微服務」
效能優化那些絕招,一般人我不告訴他 1,支付寶介面的介面如何正確呼叫; 2,從併發程式設計角度來提高系統性能; 3,系統響應的速度縮短N倍的祕密; 4,從Futuretask類原始碼分析到手寫; 5,快速提升Web專案吞吐量; 300行精華程式碼:純手寫SpringMVC框
6.手寫MR框架
myjob.properties: IN_PATH=/mrtest/in OUT_PATH=/mrtest/out/rs.txt MAPPER_CLASS=com.mydemo.mr.WordCountMapper 1.HdfsWordCount: publi
基於Spring JDBC手寫ORM框架
ORM 物件關係對映(Object Relational Mapping,簡稱ORM)模式是一種為了解決面向物件與關係資料庫存在的互不匹配的現象的技術。簡單的說,ORM是通過使用描述物件和資料庫之間對映的元資料,將程式中的物件自動持久化到關係資料庫中。 基本框架
手寫spring框架,實現簡單的ioc功能
最近重新鞏固了基礎, 把spring框架重新學習了一遍。現在用自己的理解將spring框架寫一遍。這次先簡單實現,以後會慢慢拓展,暫時定的計劃是spirngmvc和mybatis的整合。整體思路是使用dom4j解析xml檔案,然後反射注入到Person類中。簡單明瞭,不做過
手寫Spring框架學習筆記
public class DispatcherServlet extends HttpServlet { private Properties contextConfig = new Properties(); private List<String> classN
純手寫ORM框架
1.首先定義一個實體類Book public class Book { private Integer bookId; private String bookName; private String bookAuthor; private BigDecimal bo
04.手寫資料庫連線池
1. 設計思路 在內部物件池中,維護一定數量的資料庫連線,並對外暴露資料庫連線的獲取和返回方法。 如外部使用者可通過 getConnection 方法獲取資料庫連線,使用完畢後再通過 releaseConnection 方法將連線返回,注意此時的連線並沒
純手寫資料庫連線池
資料庫連線池原理 基本原理 在內部物件池中,維護一定數量的資料庫連線,並對外暴露資料庫連線的獲取和返回方法。 如外部使用者可通過getConnection方法獲取資料庫連線,使用完畢後再通過releaseConnection方法將連線返回,注意此時的連線並沒有關閉,而是
純手寫Mybatis框架
介面層-和資料庫互動的方式 MyBatis和資料庫的互動有兩種方式: 使用傳統的MyBatis提供的API; 使用Mapper介面; 使用Mapper介面 MyBatis 將配置檔案中的每一個<mapper> 節點抽象為一個 Mapper 介面: 這個
純手寫Mybatis框架視訊教程【免費觀看】
第一節(純手寫Mybatis註解版本框架-專案演示)第二節(純手寫Mybatis註解版本框架-mybatis基礎知識回顧與JDBCUtils工具類)第三節(純手寫Mybatis註解版本框架-環境搭建)第
兩小時手寫springmvc框架
這篇文章是學習咕泡學院tom老師手寫一個spring框架視訊而來,程式碼基本複製原文,稍作修改。可以幫助我們理解springmvc實現的大致原理。 1、構建maven工程,只需要匯入javax.servlet-api的依賴。另外配置,直接通過tomcat外掛來啟動專案。
java單點登入(cookie手寫/cas框架)
客戶端和伺服器建立聯絡以後就會將sessionid寫入cookie,當客戶端關閉後cookie就會被清除同時伺服器端session也會被銷燬。所以我們登入到伺服器後可以在cookie中寫入我們的登入資訊用於訪問其他頁面時做登入校驗。這種校驗方法和把登入後資訊寫入session