1、前言:
在Android的应用开发中,往往会涉及到许多数据的存储和交互,其中,内嵌的sqlite数据库会作为首选的方案,在一些涉及比较多的数据交互情境中,通常表现为表的数量比较多,我们就有必要开发一个比较通用的数据访问框架了。
2、设计目标:
(1):能够方便地创建表和对象的对应关系.[这里我们以注解的方式实现]
(2):能够以统一的方式方便的进行数据的增删查改.
也就是能够使用类似Hibernate等框架的使用: 如增加一条记录的使用习惯:
TUser user = new TUser(); user.setId(“id”); userDao.insert(user).
3、实现结果初体验:
(1):描述表和对象关系:
@Table(name = "t_user")
public class TUser {
@Column(name="user_id",type=Column.TYPE_BOOLEAN,isPrimaryKey=true)
private Integer userId;
@Column(name="user_name",type=Column.TYPE_STRING)
private String userName;
/**
* 必须提供无参构造函数
*/
public TUser() {}
public TUser(String userName) {
this.userName = userName;
}
//getter
//setter
}
(2):增删查改
//Insert An Object
IBaseDao<TUser> userDao = DaoFactory.createGenericDao(this, TUser.class);
userDao.insert(new TUser("AAAA"));
//Insert Object List
List<TUser> insertUserList = new ArrayList<TUser>();
for(int i = 0; i<10;++i){
insertUserList.add(new TUser("BBB"+i));
}
userDao.batchInsert(insertUserList);
//只有一条记录的查找
List<TUser> userList = userDao.queryByCondition("user_name=?", "AAAA");
//InsertOrUpdate
userDao.insertOrUpdate(new TUser("AAAA"), "user_name"); //update where user_name='AAAA'
userDao.insertOrUpdate(new TUser("CCCC"), "user_name"); //insert CCCC
实现结果符合了设计目标,用起来还是很方便的.
4、实现类和接口说明:
以下内容应结合代码看.[com.darcy.dao]包内,完整的代码请点击这里获取。
(1)总览:
.DaoFactory: 提供了集中获取数据操作对象工厂,并缓存该对象.
.DBUtils: 封装了Android API提供的数据库实现
.IBaseDao: 面向用户的接口
.GenericDao: IBaseDao的实现类
.GenericDao.DBTransction: 封装了对事务的操作.
.QueryResult: 对查询结果一行记录的封装类.封装后的类可以尽最大努力取到你想要的值,如数据库中存储1.0为text类型,则通过QueryResult.getIntProperty()可以获得正确的整型值1
.SqlHelper: 拼接SQL语句AND两个对等对象之间直交换的辅助类
.Table: 表和对象的映射注解类
(2)接口设计:
public interface IBaseDao<T> {
/**
* 创建表
*/
void createTable();
/**
* 插入一条记录
*
* @param model
* @return
*/
boolean insert(T model);
/**
* 插入多条记录
*
* @param model
* @return
*/
boolean batchInsert(List<T> dataList);
/**
* 更新记录
*
* @param model
* @param whereClause
* @param whereArgs
* @return
*/
boolean update(T model, String whereClause, String... whereArgs);
/**
* 决定是否insert或者Update
* @param model
* @param bindColumnNames 绑定的列名 ,默认是主键
* @return
*/
boolean insertOrUpdate(T model,String... bindColumnNames);
/**
* 删除记录
*
* @param whereClause
* @param whereArgs
* @return
*/
boolean delete(String whereClause, String... whereArgs);
/**
* 删除全部记录
* @return
*/
boolean deleteAll();
/**
* 根据条件查询
*
* @param whereClause
* @param whereArgs
* @return
*/
List<T> queryByCondition(String selection, String... selectionArgs);
/**
* 根据条件查询
* @param columns
* @param selection
* @param orderBy
* @param selectionArgs
* @return
*/
List<T> queryByCondition(String[] columns, String selection,
String orderBy, String... selectionArgs);
/**
* 根据条件查询
* @param columns
* @param selection
* @param groupBy
* @param having
* @param orderBy
* @param selectionArgs
* @return
*/
List<T> queryByCondition(String[] columns, String selection,
String groupBy, String having, String orderBy,
String... selectionArgs);
/**
* 只有唯一一条记录的查询
*
* @return 如果没有则返回null
*/
T queryUniqueRecord(String selection,String... selectionArgs);
/**
* 自定义查询
* @param sql
* @param bindArgs
* @return
*/
List<QueryResult> execQuerySQL(String sql, String... bindArgs);
/**
* 执行Insert/Update/Delete等其他非查询SQL
* @param sql
* @param bindArgs
* @return
*/
boolean execUpdateSQL(String sql, Object... bindArgs);
}
为什么这么多queryByCondition? 最小接口原则,这样在用的时候提供更多选择,代码可变得更加精简一些.
为什么提供execQuerySQL: 为了支持一些复杂的查询,如连接表查询, 否则可能就要在代码中写很多此查询才能做到.
同理execUpdateSQL:
不合理的地方:实际上这里可以查询任何表,破坏了封装性。
(3)接口实现(GenericDao):
createTable: 关键方法在SqlHelper.getCreateTableSQL中,这里通过Java反射机制对采集传进来的Class信息,然后拼接成创建表的SQL语句.
insert | update :关键方法:SqlHelper.parseModelToContentValues,其关键点也是在于利用Java反射机制来把model中的值转化到ContentValue中.
queryByCondition: 也是同理.
insertOrUpdate(T model,String… bindColumnNames):这里关键说一下insertOrUpdate的实现,创建表的时候我们先保存好该表的主键,以后调用该方法时:如果model中的主键值已经存在,则update,反之则insert,以上是不带参数的情况,如果后面带了列名,则根据列名进行联合查询该记录是否存在决定insert Or update。
5、总结:
这个是今年在工作中其中的一点总结,在想方设法提供代码的可重用性是便做了这个东西,但是,时间太赶,所以就本着够用的原则先用着,代码还有很多要完善的地方,但在该基础上再进行构建一些新的支持应该就比较容易了。