PS:好久沒寫博客了...
學習內容:
1.DAO介紹,通用DAO的簡單調度過程..
2.數據庫映射關系...
3.使用泛型+反射+注解封裝通用DAO..
4.使用AndBase框架實現對DAO的調用實現數據庫基本操作..
1.DAO..
DAO..這個說法大家都不陌生..數據庫操作訪問對象的抽象接口層..在DAO內部封裝好通用的方法..然后再使用一個具體類來進行實現..在我們想要調用這些方法對數據庫進行相關操作時..只需要傳遞數據實體對象,就可以通過實體對象的傳遞從而實現對數據庫進行操作..
一個典型的DAO首先要有一個父類..這個父類是抽象出來的DAO接口層..內部封裝了通用的抽象方法..一個簡單的例子:
package com.example.databaseactivity; import java.util.List; import java.util.Map; import android.database.sqlite.SQLiteOpenHelper; /* * 通用DAO父類.. * */ public interface DBDAO<T>{ public SQLiteOpenHelper getDbHelper(); /* * insert 函數..默認主鍵自增... * */ public abstract long insert(T entity); /* * insert 函數..flag的值決定主鍵是否自增... * */ public abstract long insert(T entity,boolean flag); /* * 插入實體類列表,默認主鍵自增.. * */ public abstract long insertList(List<T>entityList); /* * 插入實體類列表,flag決定主鍵是否自增.. * */ public abstract long insertList(List<T>entityList,boolean flag); /* * 根據id刪除數據.. * */ public abstract long delete(int id); /* * 根據id刪除多個數據.. * */ public abstract long delete(Integer...ids); /* * 根據指定條件刪除數據.. * */ public abstract long delete(String whereCaluse,String[] whereArgs); /* * 刪除所有數據.. * */ public abstract long deleteAll(); /* * 更新數據.. * */ public abstract long update(T entity); /* * 根據數據列表更新數據.. * */ public abstract long updateList(List<T> entityList); /* * 根據ID獲取一行數據.. * */ public abstract T queryOne(int id); /* * 執行SQL查詢語句.. * class<T> 返回對象類型.. * */ public abstract List<T> rawQuery(String sql,String[] selectionArgs,Class<T> clazz); /* * 查詢列表.. * */ public abstract List<T> queryList(); /* * 根據多種限制條件進行查詢.. * */ public abstract List<T> queryList(String[] columns,String selections,String[] selectionArgs,String groupBy,String having,String orderby,String limit); /* * 根據where條件進行查詢.. * */ public abstract List<T> queryList(String selection,String selectionArgs); /* * 檢查數據是否存在.. * */ public abstract boolean isExist(String sql,String[] selectionArgs); /* * 根據條件執行查詢結果..將數據封裝在map中.. * */ public List<Map<String, String>>queryMapList(String sql,String selectionArgs); /* * 返回查詢結果條數.. * */ public int queryCount(String sql,String[] selectionArgs); /* * 封裝執行sql代碼.. * */ public void execSql(String sql, Object[] selectionArgs); }
這就是一個DAO父類..內部提供了眾多的抽象方法..那么有了抽象的方法..自然要有實現的子類..並且這個抽象的DAO接口層是不提供類型的..使用泛型的方式,那么為什么直接使用泛型呢?為什么不直接定義一個接口,后面還需要加上泛型..其實目的就是為了實現良好的擴展性..那么這個良好擴展的體現就是通過使用泛型來完成的...
比如說我們把上面的類僅僅定義成一個抽象類..
public interface DBDao{...封裝了許多抽象函數...}
那么只要我們想要進行數據庫操作的時候,我們就需要implements這個接口...那么如果一個數據庫中存在了多個表,那么就需要多個子類去implements這個抽象接口..那么這多個子類中的方法我們還是需要自己去人為去書寫..這樣就會導致每implements一次DBDao..我們都需要對內部的方法進行重寫..這樣顯然是給我們自己去找麻煩..何必不再向上抽取呢?向上抽取目的就是為了減少這樣的操作..避免一次次的重寫..那么向上抽取就需要使用泛型了..
public interface DBDao<T> {..抽閑函數..}
我們同樣使用一個泛型類進行實現..實現的過程需要使用到Java的注解和Java強大的反射機制..這里原理我們先不說..一會下面會有相關的解釋..那么實現類就這樣定義..
public class AbDBDaoImpl<T> extends AbBasicDBDao implements AbDBDao<T>{..實現的過程..}
這里實現的過程仍然以泛型的方式去實現...使用泛型的好處就是..無論我們傳遞的是什么類型..我們通過反射機制就能夠獲取傳遞來的實體類型從而去實例化對象..通過使用注解的方式我們就能夠獲取實體類型中的相關屬性..有了實體對象以及實體對應的屬性...那么就可以對數據表進行操作了...也就是說無論是一個表還是多個表的操作..我們只需要重寫一次父類DAO的方法..在進行表操作的時候..我們只需要傳遞數據實體對象就可以了...或許還有點抽象..那么就來個實例..比如說上面的DBDAO<T>和DBDaoImpl<T>都已經寫好了..那么我們就可以直接調用
private UserInsideDao userDao = new UserInsideDao(MainActivity.this);//實例化對象.. userDao.startReadableDatabase(false);//獲取數據庫.. userDao.queryList(null,null,null,null,null,"create_time desc limit "+String.valueOf(pageSize)+" offset "+0,null);//直接可以執行查詢函數..
UserInsideDao其實就是一個服務層..使用它來傳遞我們想要初始化的數據表..以及獲取數據庫輔助類...這里的UserInsideDao繼承了實現Dao抽象層的子層..那么UserInsideDao就可以去調用實現層的構造函數..將想初始化的表傳遞過去..那么這些表就會根據解析注解的方式對表進行相關的創建..
public class UserInsideDao extends AbDBDaoImpl<LocalUser> { public UserInsideDao(Context context) { super(new DBInsideHelper(context),LocalUser.class); } }
這里的DBInsideHelper才是真正對數據庫進行操作的類...DBInsideHelper繼承了AbDBHelper..獲取數據庫輔助類的同時..同時把LocalUser實體傳遞給AbDBDaoImpl..這就表示當前我需要對LocalUser表進行相關操作了..以下是DBInsideHelper的實現...
public class DBInsideHelper extends AbDBHelper { // 數據庫名 private static final String DBNAME = "andbasedemo.db"; // 當前數據庫的版本 private static final int DBVERSION = 1; // 要初始化的表 private static final Class<?>[] clazz = { User.class,LocalUser.class,Stock.class,ChatMsg.class}; public DBInsideHelper(Context context) { super(context, DBNAME, null, DBVERSION, clazz); } }
總之最后會去調用AbDBDaoImpl中的構造函數..從而完成對數據表的一些初始化操作...然后就可以在主類中直接去調用相關函數來完成對數據庫的基本操作了...
2.數據庫映射關系..
數據庫映射關系..說起來有點抽象..實際理解起來還是非常的簡答的...數據庫映射關系無非三種..一對一,一對多,多對多..這三種映射關系..映射關系其實就是實體和實體之間的實在關系..什么是一對一,一對多,多對多..拿一個簡單的例子來說...
比如說我們有三個數據表:
學生表(學號,姓名,性別,年齡) 不難看出學生ID是主鍵..
課程表(課程號,課程名稱) 這里課程ID是主鍵..
成績表(學號,課程號,成績) 這里的主鍵由學生ID和課程ID共同構成..
一對一:比如說我們想要查詢學生表ID為1,課程號ID為1的成績..那么這行成績只能對應學生表和課程表的一行數據..這就是一對一..也就是說表與表之間的數據相互對應只能是一行..
一對多:查詢學號為1的學生的成績..這樣在成績表中會對應多條數據..但成績表中的多條數據在學生表中只會對應一條..這個關系數據一對多..
多對多:查詢所有學生的成績..那么數據表中多條數據對應多行...
數據庫映射關系如果真的理解過來還是比較容易的..千萬不要理解反了..就拿查詢一個班級里學號為1的學生...這個映射關系是一對一的關系...千萬不要理解成一對多的關系...不要以為經過了多個層次就表示是一對多..這樣去理解反而理解錯了..這個關系是實體與實體之間的一對一,一對多,多對多..而不是經過了幾層...這里還是需要注意的...
3.使用泛型+反射+注解書寫通用DAO...
其實通用DAO最難實現的部分是使用反射去解析注解..也就是AbDBDaoImpl中方法的書寫..這部分還是比較費勁的...
通用DAO就拿這個例子來說吧...方法就一個..我就不多列舉了..玩過JavaEE的對這方面還是非常清楚的...Hibernate DAO和SpringJDBC DAO都是以這種方式來對數據庫進行操作的..不過傳遞數據對象的時候..類需要進行序列化操作..由於自己對SSH並不是很了解..就不在這班門弄斧了...還是說如何去實現的...
public interface AbDBDao<T> { //獲取數據庫輔助類.. public SQLiteOpenHelper getDbHelper(); //查詢數據,,以列表的形式進行展示.. public abstract List<T> queryList(String[] columns, String selection,String[] selectionArgs, String groupBy, String having,String orderBy, String limit); }
實現過程就需要AbDBDaoImpl類去實現了...
public class AbDBDaoImpl<T> extends AbBasicDBDao implements AbDBDao<T> { /** The tag. */ private String TAG = "AbDBDaoImpl"; /** The db helper. */ private SQLiteOpenHelper dbHelper; /**鎖對象*/ private final ReentrantLock lock = new ReentrantLock(); /** The table name. */ private String tableName; /** The id column. */ private String idColumn; /** The clazz. */ private Class<T> clazz; /** The all fields. */ private List<Field> allFields; /** The Constant METHOD_INSERT. */ private static final int METHOD_INSERT = 0; /** The Constant METHOD_UPDATE. */ private static final int METHOD_UPDATE = 1; /** The Constant TYPE_NOT_INCREMENT. */ private static final int TYPE_NOT_INCREMENT = 0; /** The Constant TYPE_INCREMENT. */ private static final int TYPE_INCREMENT = 1; /**這個Dao的數據庫對象*/ private SQLiteDatabase db = null; /** * 用一個對象實體初始化這個數據庫操作實現類. * * @param dbHelper 數據庫操作實現類 * @param clazz 映射對象實體 */ public AbDBDaoImpl(SQLiteOpenHelper dbHelper, Class<T> clazz) { this.dbHelper = dbHelper; if (clazz == null) { this.clazz = ((Class<T>) ((java.lang.reflect.ParameterizedType) super .getClass().getGenericSuperclass()) .getActualTypeArguments()[0]); //這里是獲取傳遞過來的實體類型,比如說LocalUser.class... } else { this.clazz = clazz; //這里是直接獲取... } if (this.clazz.isAnnotationPresent(Table.class)) { //如果存在聲明的注解...這個注解數據表名屬性.. Table table = (Table) this.clazz.getAnnotation(Table.class); this.tableName = table.name(); //獲取數據表名.. } // 加載所有字段 this.allFields = AbTableHelper.joinFields(this.clazz.getDeclaredFields(), this.clazz.getSuperclass().getDeclaredFields()); // 找到主鍵 for (Field field : this.allFields) { if (field.isAnnotationPresent(Id.class)) { //這里都是對注解的解析...這里的注解信息為ID... Column column = (Column) field.getAnnotation(Column.class); this.idColumn = column.name(); break; } } Log.d(TAG, "clazz:" + this.clazz + " tableName:" + this.tableName + " idColumn:" + this.idColumn); } /** * 初始化這個數據庫操作實現類. * * @param dbHelper 數據庫操作實現類 */ public AbDBDaoImpl(SQLiteOpenHelper dbHelper) { this(dbHelper, null); } /** * 描述:TODO. * * @return the db helper * @see com.ab.db.orm.dao.AbDBDao#getDbHelper() */ @Override public SQLiteOpenHelper getDbHelper() { return dbHelper; } @Override public List<T> queryList(String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) { List<T> list = new ArrayList<T>(); Cursor cursor = null; try { lock.lock(); //獲取鎖..避免多次操作導致問題發生... Log.d(TAG, "[queryList] from"+this.tableName+" where "+selection+"("+selectionArgs+")"+" group by "+groupBy+" having "+having+" order by "+orderBy+" limit "+limit); cursor = db.query(this.tableName, columns, selection, selectionArgs, groupBy, having, orderBy, limit); //執行查詢函數... getListFromCursor(this.clazz,list, cursor); closeCursor(cursor); //獲取關聯域的操作類型和關系類型 String foreignKey = null; String type = null; String action = null; //需要判斷是否有關聯表 for (Field relationsField : allFields) { if (!relationsField.isAnnotationPresent(Relations.class)) { continue; } Relations relations = (Relations) relationsField.getAnnotation(Relations.class); //獲取外鍵列名 foreignKey = relations.foreignKey(); //關聯類型 type = relations.type(); //操作類型 action = relations.action(); //設置可訪問 relationsField.setAccessible(true); if(!(action.indexOf(ActionType.query)!=-1)){ return list; } //得到關聯表的表名查詢 for(T entity:list){ if(RelationsType.one2one.equals(type)){ //一對一關系 //獲取這個實體的表名 String relationsTableName = ""; if (relationsField.getType().isAnnotationPresent(Table.class)) { Table table = (Table) relationsField.getType().getAnnotation(Table.class); relationsTableName = table.name(); } List<T> relationsList = new ArrayList<T>(); Field[] relationsEntityFields = relationsField.getType().getDeclaredFields(); for (Field relationsEntityField : relationsEntityFields) { //這里涉及到對主表進行再次遍歷..這里主要原因是受到了外鍵的影響... Column relationsEntityColumn = (Column) relationsEntityField.getAnnotation(Column.class); //獲取外鍵的值作為關聯表的查詢條件 if (relationsEntityColumn.name().equals(foreignKey)) { //主表的用於關聯表的foreignKey值 String value = "-1"; for (Field entityField : allFields) { //設置可訪問 entityField.setAccessible(true); Column entityForeignKeyColumn = (Column) entityField.getAnnotation(Column.class); if(entityForeignKeyColumn==null){ continue; } if (entityForeignKeyColumn.name().equals(foreignKey)) { value = String.valueOf(entityField.get(entity)); break; } } //查詢數據設置給這個域 cursor = db.query(relationsTableName, null, foreignKey+" = ?",new String[]{value}, null, null, null, null); getListFromCursor(relationsField.getType(),relationsList, cursor); if(relationsList.size()>0){ //獲取關聯表的對象設置值 relationsField.set(entity, relationsList.get(0)); } break; } } }else if(RelationsType.one2many.equals(type) || RelationsType.many2many.equals(type)){ //一對多關系 //得到泛型里的class類型對象 Class listEntityClazz = null; Class<?> fieldClass = relationsField.getType(); if(fieldClass.isAssignableFrom(List.class)){ Type fc = relationsField.getGenericType(); if(fc == null) continue; if(fc instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) fc; listEntityClazz = (Class)pt.getActualTypeArguments()[0]; } } if(listEntityClazz==null){ Log.e(TAG, "對象模型需要設置List的泛型"); return null; } //得到表名 String relationsTableName = ""; if (listEntityClazz.isAnnotationPresent(Table.class)) { Table table = (Table) listEntityClazz.getAnnotation(Table.class); relationsTableName = table.name(); } List<T> relationsList = new ArrayList<T>(); Field[] relationsEntityFields = listEntityClazz.getDeclaredFields(); for (Field relationsEntityField : relationsEntityFields) { Column relationsEntityColumn = (Column) relationsEntityField.getAnnotation(Column.class); //獲取外鍵的值作為關聯表的查詢條件 if (relationsEntityColumn.name().equals(foreignKey)) { //主表的用於關聯表的foreignKey值 String value = "-1"; for (Field entityField : allFields) { //設置可訪問 entityField.setAccessible(true); Column entityForeignKeyColumn = (Column) entityField.getAnnotation(Column.class); if (entityForeignKeyColumn.name().equals(foreignKey)) { value = String.valueOf(entityField.get(entity)); break; } } //查詢數據設置給這個域 cursor = db.query(relationsTableName, null, foreignKey+" = ?",new String[]{value}, null, null, null, null); getListFromCursor(listEntityClazz,relationsList, cursor); if(relationsList.size()>0){ //獲取關聯表的對象設置值 relationsField.set(entity, relationsList); } break; } } } } } } catch (Exception e) { Log.e(this.TAG, "[queryList] from DB Exception"); e.printStackTrace(); } finally { closeCursor(cursor); lock.unlock(); } return list; } }
這就是實現的過程..主要是通過使用反射機制+解析注解的方式來實現的...這里涉及到了一個關聯表的事情...就拿剛才的那三個表來說吧...我想要查詢學生號為1,課程號為1的成績..這樣成績表就涉及到了兩個表...因此還需要對關聯表進行相關的操作..因此需要對所有的關聯表進行遍歷..然后進行相關的操作..我們可以看到這里又涉及到了主表的再次遍歷...原因取決於外鍵的影響...
簡單的說一下外鍵..我們知道成績表中主鍵是學生ID和課程ID的組合...但是學生ID又在學生列表中是主鍵..因此學生ID在成績表中被稱為成績表關於學生表的外鍵...同樣課程ID是成績表關於課程表的外鍵...因為成績表中的主鍵是復合型的..因此需要對外鍵進行一一獲取..外鍵的獲取通過對主表的遍歷...最后把所有獲取的外鍵進行保存..用來進行聯合查詢..這也就是那個步驟的目的...
這樣就簡單的書寫了一個通用的DAO..雖然只有一個方法..其他方法的實現我們可以自己去重寫...
4.使用AndBase中提供的DAO來完成對數據庫的一系列操作...
在AndBase中...源碼就是通過對上面的封裝給我們直接提供了一個通用的DAO接口層...我們都不需要去進行實現..直接就可以完成方法的調度過程...如果想要重寫更多的方法..請繼承實現類AbDBDaoImpl,然后在子類中去書寫更多的方法...
private UserInsideDao userDao = null //初始化數據庫操作實現類 userDao = new UserInsideDao(DBInsideSampleActivity.this); //(1)獲取數據庫 userDao.startReadableDatabase(false); //(2)執行查詢 userList = userDao.queryList(null, null, null, null, null, "create_time desc limit "+String.valueOf(pageSize)+ " offset " +0, null); totalCount = userDao.queryCount(null, null); //(3)關閉數據庫 userDao.closeDatabase(false);
使用AndBase直接操作數據庫...非常的簡單...UserInsideDao類..這里表示我要對LocalUser實體對象進行操作...也就是對LocalUser表進行操作..剩下所有函數的調用都是對這個表進行相關的操作...
package com.andbase.demo.dao; import android.content.Context; import com.ab.db.orm.dao.AbDBDaoImpl; import com.andbase.db.DBInsideHelper; import com.andbase.demo.model.LocalUser; public class UserInsideDao extends AbDBDaoImpl<LocalUser> { public UserInsideDao(Context context) { super(new DBInsideHelper(context),LocalUser.class); } }
DBInsideHelper類...獲取數據庫輔助類的過程...初始化所有的數據表...
package com.andbase.db; import android.content.Context; import com.ab.db.orm.AbDBHelper; import com.andbase.demo.model.LocalUser; import com.andbase.demo.model.Stock; import com.andbase.friend.ChatMsg; import com.andbase.model.User; public class DBInsideHelper extends AbDBHelper { // 數據庫名 private static final String DBNAME = "andbasedemo.db"; // 當前數據庫的版本 private static final int DBVERSION = 1; // 要初始化的表 private static final Class<?>[] clazz = {LocalUser}; public DBInsideHelper(Context context) { super(context, DBNAME, null, DBVERSION, clazz); } }
最后貼一下LocalUser類...傳遞的Class僅僅是一個LocalUser表..一般會傳遞多個表的..傳遞多個表就需要定義多個實體類就行了...
package com.andbase.demo.model; import java.util.List; import com.ab.db.orm.annotation.Column; import com.ab.db.orm.annotation.Id; import com.ab.db.orm.annotation.Relations; import com.ab.db.orm.annotation.Table; @Table(name = "local_user") public class LocalUser { // ID @Id主鍵,int類型,數據庫建表時此字段會設為自增長 @Id @Column(name = "_id") private int _id; @Column(name = "u_id") private String uId; // 登錄用戶名 length=20數據字段的長度是20 @Column(name = "name", length = 20) private String name; // 用戶密碼 @Column(name = "password") private String password; // 年齡一般是數值,用type = "INTEGER"規范一下. @Column(name = "age", type = "INTEGER") private int age; // 創建時間 @Column(name = "create_time") private String createTime; // 包含實體的存儲,指定外鍵 @Relations(name="stock",type="one2one",foreignKey = "u_id",action="query_insert") private Stock stock; // 包含List的存儲,指定外鍵 @Relations(name="stocks",type="one2many",foreignKey = "u_id",action="query_insert") private List<Stock> stocks; // 有些字段您可能不希望保存到數據庫中,不用@Column注釋就不會映射到數據庫. private String remark; public int get_id() { return _id; } public void set_id(int _id) { this._id = _id; } public String getuId() { return uId; } public void setuId(String uId) { this.uId = uId; } 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; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } public List<Stock> getStocks() { return stocks; } public void setStocks(List<Stock> stocks) { this.stocks = stocks; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public Stock getStock() { return stock; } public void setStock(Stock stock) { this.stock = stock; } }