一.
二.
三.
四.
- DbManager.DaoConfig daoConfig = new DbManager.DaoConfig()
- .setDbName("test.db")
- .setDbVersion(1)
- .setDbOpenListener(new DbManager.DbOpenListener() {
- @Override
- public void onDbOpened(DbManager db) {
- // 开启WAL, 对写入加速提升巨大
- db.getDatabase().enableWriteAheadLogging();
- }
- })
- .setDbUpgradeListener(new DbManager.DbUpgradeListener() {
- @Override
- public void onUpgrade(DbManager db, int oldVersion, int newVersion) {
- ...
- }
- });
xUtil3 支持数据库多库的配置,使用不同的 DaoConfig,可以创建多个. db 文件,每个. db 文件彼此独立。
由于 xUtils3 设计的是在需要使用数据库的时候,才创建数据表。所以下文以 save 操作为例,跟进初始化数据表的过程。示例代码:
- DbManager db = x.getDb(daoConfig);
- Parent parent = new Parent();
- parent.setName("CSDN 一口仨馍");
- db.save(parent);
数据库的操作比较耗时,真实应该异步执行。可以看到,xUtils3 提供的数据库操作是非常简单的,首先 getDb,之后调用 save() 方法即可。其中 save 方法接受 List
x.getDb(daoConfig)
- public final class x {
- public static DbManager getDb(DbManager.DaoConfig daoConfig) {
- return DbManagerImpl.getInstance(daoConfig);
- }
- }
这里只是简单的返回了一个 DbManagerImpl 实例,看样子真正的初始化操作都在 DbManagerImpl 里。跟进。
- public final class DbManagerImpl extends DbBase {
- private DbManagerImpl(DaoConfig config) {
- if (config == null) {
- throw new IllegalArgumentException("daoConfig may not be null");
- }
- this.daoConfig = config;
- this.allowTransaction = config.isAllowTransaction();
- this.database = openOrCreateDatabase(config);
- DbOpenListener dbOpenListener = config.getDbOpenListener();
- if (dbOpenListener != null) {
- dbOpenListener.onDbOpened(this);
- }
- }
- public synchronized static DbManager getInstance(DaoConfig daoConfig) {
- if (daoConfig == null) {//使用默认配置
- daoConfig = new DaoConfig();
- }
- DbManagerImpl dao = DAO_MAP.get(daoConfig);
- if (dao == null) {
- dao = new DbManagerImpl(daoConfig);
- DAO_MAP.put(daoConfig, dao);
- } else {
- dao.daoConfig = daoConfig;
- }
- // update the database if needed
- SQLiteDatabase database = dao.database;
- int oldVersion = database.getVersion();
- int newVersion = daoConfig.getDbVersion();
- if (oldVersion != newVersion) {
- if (oldVersion != 0) {
- DbUpgradeListener upgradeListener = daoConfig.getDbUpgradeListener();
- if (upgradeListener != null) {
- upgradeListener.onUpgrade(dao, oldVersion, newVersion);
- } else {
- try {
- dao.dropDb();
- } catch (DbException e) {
- LogUtil.e(e.getMessage(), e);
- }
- }
- }
- database.setVersion(newVersion);
- }
- return dao;
- }
- }
乍一看代码有些长,其实也没做太多操作,绝大部分是些缓存赋值相关的操作。这里注意两个地方
由于返回的是 DbManagerImpl 实例,所以实际调用的是 DbManagerImpl.save()。
DbManagerImpl.save()
- public final class DbManagerImpl extends DbBase {
- public void save(Object entity) throws DbException {
- try {
- // 开启事务
- beginTransaction();
- // 判断将要保存的是对象还是对象的集合
- if (entity instanceof List) {
- // 向上转型为List
- List entities = (List) entity;
- if (entities.isEmpty()) return;
- // 依据被注解的类获取数据表对应的包装类
- TableEntity table = this.getTable(entities.get(0).getClass());
- // 如果没有表则创建
- createTableIfNotExist(table);
- // 遍历插入数据库
- for (Object item : entities) {
- // 拼接sql语句,执行数据库插入操作
- execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, item));
- }
- } else {
- TableEntity table = this.getTable(entity.getClass());
- createTableIfNotExist(table);
- execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, entity));
- }
- // 设置事务成功
- setTransactionSuccessful();
- } finally {
- // 结束事务
- endTransaction();
- }
- }
- }
接下来每行都有注释,这些是我在看的过程中写下的。我只说贴代码的逻辑吧。先看下创建 TableEntity
- public final class TableEntity {
- /*package*/
- TableEntity(DbManager db, Class entityType) throws Throwable {
- this.db = db;
- this.entityType = entityType;
- this.constructor = entityType.getConstructor();
- this.constructor.setAccessible(true);
- // 被保存的类没有没Table注解,这里会抛出NullPointerException。
- // ps:作者这里应该验证下为null的问题
- Table table = entityType.getAnnotation(Table.class);
- // 获取表名
- this.name = table.name();
- // 获取创建表之后执行的SQL语句
- this.onCreated = table.onCreated();
- // 获取列Map,Map<列的类型,列的包装类>
- this.columnMap = TableUtils.findColumnMap(entityType);
- // 遍历查找列的包装类,直到找到id列
- for (ColumnEntity column: columnMap.values()) {
- if (column.isId()) {
- this.id = column;
- break;
- }
- }
- }
- }
这里涉及到 Table 注解,从 Table 注解中获取表名。之后封装了一个 Map,key 为列名,value 为列的包装类,例如:Map
- /* package */
- final class TableUtils {
- static synchronized LinkedHashMap findColumnMap(Class entityType) {
- LinkedHashMap columnMap = new LinkedHashMap();
- addColumns2Map(entityType, columnMap);
- return columnMap;
- }
- private static void addColumns2Map(Class entityType, HashMap < String, ColumnEntity > columnMap) {
- // 递归出口
- if (Object.class.equals(entityType)) return;
- try {
- // 获取表实体类的所有属性
- Field[] fields = entityType.getDeclaredFields();
- for (Field field: fields) {
- // 获取属性的修饰符
- int modify = field.getModifiers();
- // 修饰符不能是static或者transient
- if (Modifier.isStatic(modify) || Modifier.isTransient(modify)) {
- continue;
- }
- // 为下面判断属性有没有被Column注解修饰做准备
- Column columnAnn = field.getAnnotation(Column.class);
- if (columnAnn != null) {
- // 判断属性是否支持转换
- if (ColumnConverterFactory.isSupportColumnConverter(field.getType())) {
- // 新建列(属性)的包装类
- ColumnEntity column = new ColumnEntity(entityType, field, columnAnn);
- if (!columnMap.containsKey(column.getName())) {
- columnMap.put(column.getName(), column);
- }
- }
- }
- }
- // 递归解析属性
- addColumns2Map(entityType.getSuperclass(), columnMap);
- } catch(Throwable e) {
- LogUtil.e(e.getMessage(), e);
- }
- }
- }
- /**
- * @param entityType 实体类
- * @param field 属性
- * @param column 注解
- */
- /* package */
- ColumnEntity(Class entityType, Field field, Column column) {
- // 设置属性可访问
- field.setAccessible(true);
- this.columnField = field;
- // 获取数据库中列的名称,一般和属性值保持一致
- this.name = column.name();
- // 获取属性的值
- this.property = column.property();
- // 是否是主键
- this.isId = column.isId();
- // 获取属性的类型
- Class fieldType = field.getType();
- // 是否自增,int、Integer、long、Long类型的主键,默认自增
- this.isAutoId = this.isId && column.autoGen() && ColumnUtils.isAutoIdType(fieldType);
- // String为例,返回的是StringColumnConverter
- this.columnConverter = ColumnConverterFactory.getColumnConverter(fieldType);
- // 查找get方法。例如:对于age属性,查找getAge()方法
- this.getMethod = ColumnUtils.findGetMethod(entityType, field);
- if (this.getMethod != null && !this.getMethod.isAccessible()) {
- // 设置可反射访问
- this.getMethod.setAccessible(true);
- }
- // 查找set方法
- this.setMethod = ColumnUtils.findSetMethod(entityType, field);
- if (this.setMethod != null && !this.setMethod.isAccessible()) {
- this.setMethod.setAccessible(true);
- }
- }
数据操作的时候不用每次都这么繁琐,因为表格有 tableMap 缓存,下次直接就能取出相应的表包装类 TableEntity。下面跟进下创建表的过程。
createTableIfNotExist()
- // 创建数据表
- protected void createTableIfNotExist(TableEntity table) throws DbException {
- // 根据系统表SQLITE_MASTER判断指定表格是否存在
- if (!table.tableIsExist()) {
- synchronized (table.getClass()) {
- // 表不存在
- if (!table.tableIsExist()) {
- // 获取创建表格语句
- SqlInfo sqlInfo = SqlInfoBuilder.buildCreateTableSqlInfo(table);
- // 执行创建表格语句
- execNonQuery(sqlInfo);
- // 获取创建表格之后的语句,例如:可用于创建索引。PS:Table注解中的属性
- String execAfterTableCreated = table.getOnCreated();
- if (!TextUtils.isEmpty(execAfterTableCreated)) {
- // 执行创建表之后的语句
- execNonQuery(execAfterTableCreated);
- }
- // 再次设置"表已创建"标志位
- table.setCheckedDatabase(true);
- // 获取监听
- TableCreateListener listener = this.getDaoConfig().getTableCreateListener();
- if (listener != null) {
- // 调用创建表之后的监听
- listener.onTableCreated(this, table);
- }
- }
- }
- }
- }
创建表的语句如下
- public static SqlInfo buildCreateTableSqlInfo(TableEntity table) throws DbException {
- ColumnEntity id = table.getId();
- StringBuilder builder = new StringBuilder();
- builder.append("CREATE TABLE IF NOT EXISTS ");
- builder.append("\"").append(table.getName()).append("\"");
- builder.append(" ( ");
- if (id.isAutoId()) {
- builder.append("\"").append(id.getName()).append("\"").append(" INTEGER PRIMARY KEY AUTOINCREMENT, ");
- } else {
- builder.append("\"").append(id.getName()).append("\"").append(id.getColumnDbType()).append(" PRIMARY KEY, ");
- }
- Collection columns = table.getColumnMap().values();
- for (ColumnEntity column : columns) {
- if (column.isId()) continue;
- builder.append("\"").append(column.getName()).append("\"");
- builder.append(' ').append(column.getColumnDbType());
- builder.append(' ').append(column.getProperty());
- builder.append(',');
- }
- builder.deleteCharAt(builder.length() - 1);
- builder.append(" )");
- return new SqlInfo(builder.toString());
- }
就是拼接了一条创建数据表的语句,而且使用的是
。最后执行下创建表的语句。
- CREATE TABLE IF NOT EXISTS
- public void execNonQuery(SqlInfo sqlInfo) throws DbException {
- SQLiteStatement statement = null;
- try {
- statement = sqlInfo.buildStatement(database);
- statement.execute();
- } catch (Throwable e) {
- throw new DbException(e);
- } finally {
- if (statement != null) {
- try {
- statement.releaseReference();
- } catch (Throwable ex) {
- LogUtil.e(ex.getMessage(), ex);
- }
- }
- }
- }
- // 绑定SQL语句中"?"对应的值
- public SQLiteStatement buildStatement(SQLiteDatabase database) {
- SQLiteStatement result = database.compileStatement(sql);
- if (bindArgs != null) {
- for (int i = 1; i < bindArgs.size() + 1; i++) {
- KeyValue kv = bindArgs.get(i - 1);
- // 将属性的类型转换为数据库类型,例如String 转换成 TEXT
- Object value = ColumnUtils.convert2DbValueIfNeeded(kv.value);
- if (value == null) {
- result.bindNull(i);
- } else {
- ColumnConverter converter = ColumnConverterFactory.getColumnConverter(value.getClass());
- ColumnDbType type = converter.getColumnDbType();
- switch (type) {
- case INTEGER:
- result.bindLong(i, ((Number) value).longValue());
- break;
- case REAL:
- result.bindDouble(i, ((Number) value).doubleValue());
- break;
- case TEXT:
- result.bindString(i, value.toString());
- break;
- case BLOB:
- result.bindBlob(i, (byte[]) value);
- break;
- default:
- result.bindNull(i);
- break;
- } // end switch
- }
- }
- }
- return result;
- }
save 在上述初始化的基础上操作,真正执行 save 操作的地方在于
。和创建表的过程类似,使用 SqlInfoBuilder.buildInsertSqlInfo()构建一条 SQL 插入语句,之后执行。跟进看下。
- execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, item))
- public static SqlInfo buildInsertSqlInfo(TableEntity table, Object entity) throws DbException {
- List keyValueList = entity2KeyValueList(table, entity);
- if (keyValueList.size() == 0) return null;
- SqlInfo result = new SqlInfo();
- String sql = INSERT_SQL_CACHE.get(table);
- if (sql == null) {
- StringBuilder builder = new StringBuilder();
- builder.append("INSERT INTO ");
- builder.append("\"").append(table.getName()).append("\"");
- builder.append(" (");
- for (KeyValue kv : keyValueList) {
- builder.append("\"").append(kv.key).append("\"").append(',');
- }
- builder.deleteCharAt(builder.length() - 1);
- builder.append(") VALUES (");
- int length = keyValueList.size();
- for (int i = 0; i < length; i++) {
- builder.append("?,");
- }
- builder.deleteCharAt(builder.length() - 1);
- builder.append(")");
- sql = builder.toString();
- result.setSql(sql);
- result.addBindArgs(keyValueList);
- INSERT_SQL_CACHE.put(table, sql);
- } else {
- result.setSql(sql);
- result.addBindArgs(keyValueList);
- }
- return result;
- }
这个方法的作用就是拼接 SQL 语句:INSERT INTO "tableName"("key1","key2") VALUES (?,?),之后存入缓存,下次直接从缓存中取出上面拼接的 SQL 语句。执行的过程和创建表是同一个方法,不再赘述。
示例代码:
- DbManager db = x.getDb(daoConfig);
- db.delete(Parent.class);
- @Override
- public void delete(Class entityType) throws DbException {
- delete(entityType, null);
- }
- @Override
- public int delete(Class entityType, WhereBuilder whereBuilder) throws DbException {
- TableEntity table = this.getTable(entityType);
- if (!table.tableIsExist()) return 0;
- int result = 0;
- try {
- beginTransaction();
- result = executeUpdateDelete(SqlInfoBuilder.buildDeleteSqlInfo(table, whereBuilder));
- setTransactionSuccessful();
- } finally {
- endTransaction();
- }
- return result;
- }
因为使用 WhereBuilder 涉及到查找,而查找的源码还没看,所以这里以删除表中所有数据为例。
- public static SqlInfo buildDeleteSqlInfo(TableEntity table, WhereBuilder whereBuilder) throws DbException {
- StringBuilder builder = new StringBuilder("DELETE FROM ");
- builder.append("\"").append(table.getName()).append("\"");
- if (whereBuilder != null && whereBuilder.getWhereItemSize() > 0) {
- builder.append(" WHERE ").append(whereBuilder.toString());
- }
- return new SqlInfo(builder.toString());
- }
因为这里的 WhereBuilder 为 null,所以返回的是
,即删除表中所有数据。
- DELETE FROM "tableName"
示例代码:
- DbManager db = x.getDb(daoConfig);
- Parent parent = new Parent();
- parent.setName("CSDN 一口仨馍");
- db.update(parent, "name");
update 后面照样支持 WhereBuilder 甚至指定列名,为了方便分析主要流程,这里就简单点来。update 方法就不贴了,和前面 save 过程几乎一样,区别主要在执行的 SQL 语句不同,下面主要看下更新语句的构建。
- public static SqlInfo buildUpdateSqlInfo(TableEntity table, Object entity, String... updateColumnNames) throws DbException {
- List keyValueList = entity2KeyValueList(table, entity);
- if (keyValueList.size() == 0) return null;
- HashSet updateColumnNameSet = null;
- if (updateColumnNames != null && updateColumnNames.length > 0) {
- updateColumnNameSet = new HashSet(updateColumnNames.length);
- Collections.addAll(updateColumnNameSet, updateColumnNames);
- }
- ColumnEntity id = table.getId();
- Object idValue = id.getColumnValue(entity);
- if (idValue == null) {
- throw new DbException("this entity[" + table.getEntityType() + "]'s id value is null");
- }
- SqlInfo result = new SqlInfo();
- StringBuilder builder = new StringBuilder("UPDATE ");
- builder.append("\"").append(table.getName()).append("\"");
- builder.append(" SET ");
- for (KeyValue kv : keyValueList) {
- if (updateColumnNameSet == null || updateColumnNameSet.contains(kv.key)) {
- builder.append("\"").append(kv.key).append("\"").append("=?,");
- result.addBindArg(kv);
- }
- }
- builder.deleteCharAt(builder.length() - 1);
- builder.append(" WHERE ").append(WhereBuilder.b(id.getName(), "=", idValue));
- result.setSql(builder.toString());
- return result;
- }
倒数第五行表明是依据对象主键的值来查找数据表中对应的行,使用更新语句,数据库实体类(JavaBean)被 Column 修饰的属性中必须要有 isId 修饰,而且还必须有值,否则会抛出 DbException。拼接出的 SQL 语句类似于
。其中的?表示占位符,在执行前被替换成具体的值。
- UPDATE "tableName" SET "name"=?,"age"=? WHERE "ID" = '1'
示例代码:
- DbManager db = x.getDb(daoConfig);
- WhereBuilder whereBuilder = WhereBuilder.b("name","=","一口仨馍").and("age","=","18");
- db.selector(Parent.class).where(whereBuilder).findAll();
WhereBuilder 的作用是构建查找的 SQL 语句后半段。例如在
中,WhereBuilder 返回的字符串是 "name" = '一口仨馍' and "age" = '18'。
- select * from parent where "name" = '一口仨馍' and "age" = '18'
db.selector()
- @Override
- public Selector selector(Class entityType) throws DbException {
- return Selector.from(this.getTable(entityType));
- }
- static Selector from(TableEntity table) {
- return new Selector(table);
- }
- private Selector(TableEntity table) {
- this.table = table;
- }
new 了个 Selector 对象,除了赋值,啥也木干。
Selector.findAll()
- public List findAll() throws DbException {
- if (!table.tableIsExist()) return null;
- List result = null;
- Cursor cursor = table.getDb().execQuery(this.toString());
- if (cursor != null) {
- try {
- result = new ArrayList();
- while (cursor.moveToNext()) {
- T entity = CursorUtils.getEntity(table, cursor);
- result.add(entity);
- }
- } catch (Throwable e) {
- throw new DbException(e);
- } finally {
- IOUtil.closeQuietly(cursor);
- }
- }
- return result;
- }
乍一看 execQuery 里的参数吓我一跳,传个 this.toString() 是什么鬼啊!!
Selector.findAll()
- public String toString() {
- StringBuilder result = new StringBuilder();
- result.append("SELECT ");
- result.append("*");
- result.append(" FROM ").append("\"").append(table.getName()).append("\"");
- if (whereBuilder != null && whereBuilder.getWhereItemSize() > 0) {
- result.append(" WHERE ").append(whereBuilder.toString());
- }
- if (orderByList != null && orderByList.size() > 0) {
- result.append(" ORDER BY ");
- for (OrderBy orderBy : orderByList) {
- result.append(orderBy.toString()).append(',');
- }
- result.deleteCharAt(result.length() - 1);
- }
- if (limit > 0) {
- result.append(" LIMIT ").append(limit);
- result.append(" OFFSET ").append(offset);
- }
- return result.toString();
- }
在这里拼接的 SQL 语句(手动冷漠脸)。可以看到查找也支持 ORDER BY、LIMIT 和 OFFSET 关键字。
xUtils3 的数据库模块,采用 Table 和 Column 注解修饰 JavaBean,初始化的时候 (实际是调用具体操作才会检查是否已经初始化,没有初始化才会执行初始化操作) 会依据注解实例化相应的 TableEntity 和 ColumnEntity 并添加进缓存,执行增删改查时依据 TableEntity 和 ColumnEntity 拼接相应的 SQL 语句并执行。
原来没有看过 ORM 框架的源码,外加上自己数据库也渣的一匹,以为 ORM 框架多难了,以至于最后才分析 xUtils3 中的数据库模块。愿意看源码,实际稍微花点时间也能看出个大概。没经历会觉得似乎难以逾越,实际上也没有想象的那么难~
xUtils3 四大模块到此就全部解析结束了。加上写作,前后大概花了一周工作时间,基本上把类翻了几遍,得益于框架功能比较全面,所以收获还是蛮多的。不敢说自己完全掌握了 xUtils3 的精髓,至少弄清了 xUtils3 的许多设计思想,而且从具体的编码中 get 到不少小技能。总体来说还是比较满意的。如果您看完四篇博客之后,仍有很多疑惑,建议对着博文思路同步阅读源码,实在有不好解决的问题,可以在下面留言,我尽量解答。感谢悉心阅读到最后~
来源: http://www.bubuko.com/infodetail-2004936.html