ORM数据库-LitePal源码解析理解设计

来源:互联网 发布:mecare同步软件 编辑:程序博客网 时间:2024/06/05 11:43

原理和源码分析

LitePal通过LitePal.xml文件获取数据库的名称、版本号以及表等配置参数。首先明确:LitePal不管是创建数据库、表还是执行增删改查都是根据Model的类名和属性名,每次都需要进行反射拼装然后调用Android原生的数据库操作或者直接执行sql语句完成。

优点:配置简单,操作方便,业务对象清晰,LitePal很“轻”,jar包只有100k不到
缺点:反射影响性能。
GreenDAO与LitePal比较,GreetDao开始就人工生成业务需要的Model和DAO文件,业务中可以直接调用相应的DAO文件进行数据库的增删改查操作,从而避免了因反射带来的性能损耗和效率低。因此大批量的插入、更新、查询等操作,greenDAO都是用时短,执行快更适合。大多数情况下,轻量的LitePal,以及能满足我们的需求了。

1.1在LitePalParser类中统一进行,解析litepal.xml文件,sax,pull,dom都可以。
总之,不管通过什么方式解析xml,最终需要将配置参数,保存在单例模式的LitePalAttr中,全局共享。
1.1.1.使用sax方式解析

基础知识:

     这种方式解析是一种基于事件驱动的api,有两个部分,解析器和事件处理器,解析器就是XMLReader接口,负责读取XML文档,和向事件处理器发送事件(也是事件源),事件处理器ContentHandler接口,负责对发送的事件响应和进行XML文档处理。

     下面是ContentHandler接口的常用方法

     public abstract void characters (char[] ch, int start, int length)

      这个方法来接收字符块通知,解析器通过这个方法来报告字符数据块,解析器为了提高解析效率把读到的所有字符串放到一个字符数组(ch)中,作为参数传递给character的方法中,如果想获取本次事件中读取到的字符数据,需要使用start和length属性。

    public abstract void startDocument () 接收文档开始的通知

     public abstract void endDocument () 接收文档结束的通知

    public abstract void startElement (String uri, String localName, String qName, Attributes atts) 接收文档开始的标签

    public abstract void endElement (String uri, String localName, String qName) 接收文档结束的标签

    在一般使用中为了简化开发,在org.xml.sax.helpers提供了一个DefaultHandler类,它实现了ContentHandler的方法,我们只想继承DefaultHandler方法即可。

   另外SAX解析器提供了一个工厂类:SAXParserFactory,SAX的解析类为SAXParser 可以调用它的parser方法进行解析。

void useSAXParser() {
   LitePalContentHandler handler;
   try {
      SAXParserFactory factory SAXParserFactory.newInstance();
      XMLReader xmlReader factory.newSAXParser().getXMLReader();
      handler new LitePalContentHandler();
      xmlReader.setContentHandler(handler);
      xmlReader.parse(new InputSource(getConfigInputStream()));
   } catch (NotFoundException e{
      throw new ParseConfigurationFileException(
            ParseConfigurationFileException.CAN_NOT_FIND_LITEPAL_FILE);
   } catch (SAXException e{
      throw new ParseConfigurationFileException(
            ParseConfigurationFileException.FILE_FORMAT_IS_NOT_CORRECT);
   } catch (ParserConfigurationException e{
      throw new ParseConfigurationFileException(
            ParseConfigurationFileException.PARSE_CONFIG_FAILED);
   } catch (IOException e{
      throw new ParseConfigurationFileException(ParseConfigurationFileException.IO_EXCEPTION);
   }
}


通过AssetManager,进行文件名检索“literal.xml”,返回其对应的文件流
private InputStream getConfigInputStream() throws IOException {
   AssetManager assetManager LitePalApplication.getContext().getAssets();
   String[] fileNames assetManager.list("");
   if (fileNames != null && fileNames.length 0{
      for (String fileName fileNames{
         if (Const.LitePal.CONFIGURATION_FILE_NAME.equalsIgnoreCase(fileName)) {
            return assetManager.open(fileNameAssetManager.ACCESS_BUFFER);
         }
      }
   }
   throw new ParseConfigurationFileException(
         ParseConfigurationFileException.CAN_NOT_FIND_LITEPAL_FILE);
}

关键事件响应在自定义的LitePalContentHandler中进行,关键方法是下面2个,在接收文档开始时,先获取单例LitePalAttr用来存储对应配置信息,并且先清除原来值,之后再startElement(),接收文档开始标签里面,进行判断
,遍历获取对应的值,赋值,保存在LitePalAttr里面,可以在后续操作中全局共享。
@Override
public void startDocument() throws SAXException {
   litePalAttr LitePalAttr.getInstance();
   litePalAttr.getClassNames().clear();
}

/**
 * Start analysis the litepal.xml file. Set all the parsed value into the
 * LitePalAttr model.
 */
@Override
public void startElement(String uriString localNameString qNameAttributes attributes)
      throws SAXException {
   if (LitePalParser.NODE_DB_NAME.equalsIgnoreCase(localName)) {
      for (int 0attributes.getLength()i++{
         if (LitePalParser.ATTR_VALUE.equalsIgnoreCase(attributes.getLocalName(i))) {
            litePalAttr.setDbName(attributes.getValue(i).trim());
         }
      }
   } else if (LitePalParser.NODE_VERSION.equalsIgnoreCase(localName)) {
      for (int 0attributes.getLength()i++{
         if (LitePalParser.ATTR_VALUE.equalsIgnoreCase(attributes.getLocalName(i))) {
            litePalAttr.setVersion(Integer.parseInt(attributes.getValue(i).trim()));
         }
      }
   } else if (LitePalParser.NODE_MAPPING.equalsIgnoreCase(localName)) {
      for (int 0attributes.getLength()i++{
         if (LitePalParser.ATTR_CLASS.equalsIgnoreCase(attributes.getLocalName(i))) {
            litePalAttr.addClassName(attributes.getValue(i).trim());
         }
      }
   } else if (LitePalParser.NODE_CASES.equalsIgnoreCase(localName)) {
      for (int 0attributes.getLength()i++{
         if (LitePalParser.ATTR_VALUE.equalsIgnoreCase(attributes.getLocalName(i))) {
            litePalAttr.setCases(attributes.getValue(i).trim());
         }
      }
   } else if (LitePalParser.NODE_STORAGE.equalsIgnoreCase(localName)) {
           for (int 0attributes.getLength()i++{
               if (LitePalParser.ATTR_VALUE.equalsIgnoreCase(attributes.getLocalName(i))) {
                   litePalAttr.setStorage(attributes.getValue(i).trim());
               }
           }
       }
}

1.1.2、使用pull方式解析

基础知识:

      在android系统中,很多资源文件中,很多都是xml格式,在android系统中解析这些xml的方式,是使用pul解析器进行解析的,它和sax解析一样(个人感觉要比sax简单点),也是采用事件驱动进行解析的,当pull解析器,开始解析之后,我们可以调用它的next()方法,来获取下一个解析事件(就是开始文档,结束文档,开始标签,结束标签),当处于某个元素时可以调用XmlPullParser的getAttributte()方法来获取属性的值,也可调用它的nextText()获取本节点的值。

void usePullParse() {
   try {
      LitePalAttr litePalAttr LitePalAttr.getInstance();
      XmlPullParserFactory factory XmlPullParserFactory.newInstance();
      XmlPullParser xmlPullParser factory.newPullParser();
      xmlPullParser.setInput(getConfigInputStream()"UTF-8");
      int eventType xmlPullParser.getEventType();
      while (eventType != XmlPullParser.END_DOCUMENT{
         String nodeName xmlPullParser.getName();
         switch (eventType{
         case XmlPullParser.START_TAG: {
            if (NODE_DB_NAME.equals(nodeName)) {
               String dbName xmlPullParser.getAttributeValue(""ATTR_VALUE);
               litePalAttr.setDbName(dbName);
            } else if (NODE_VERSION.equals(nodeName)) {
               String version xmlPullParser.getAttributeValue(""ATTR_VALUE);
               litePalAttr.setVersion(Integer.parseInt(version));
            } else if (NODE_MAPPING.equals(nodeName)) {
               String className xmlPullParser.getAttributeValue(""ATTR_CLASS);
               litePalAttr.addClassName(className);
            } else if (NODE_CASES.equals(nodeName)) {
               String cases xmlPullParser.getAttributeValue(""ATTR_VALUE);
               litePalAttr.setCases(cases);
            }
            break;
         }
         default:
            break;
         }
         eventType xmlPullParser.next();
      }
   } catch (XmlPullParserException e{
      throw new ParseConfigurationFileException(
            ParseConfigurationFileException.FILE_FORMAT_IS_NOT_CORRECT);
   } catch (IOException e{
      throw new ParseConfigurationFileException(ParseConfigurationFileException.IO_EXCEPTION);
   }
}

1.1.3、使用Dom方式解析
也可以使用Dom解析方式,在Dom解析的过程中,是先把dom全部文件读入到内存中,然后使用dom的api遍历所有数据,检索想要的数据,这种方式显然是一种比较消耗内存的方式,对于像手机这样的移动设备来讲,内存是非常有限的,所以对于比较大的XML文件,不推荐使用这种方式,但是Dom也有它的优点,它比较直观,在一些方面比SAX方式比较简单。在xml文档比较小的情况下也可以考虑使用dom方式。作者表示将在稍后添加,就先不贴代码了。
还有许多解析xml的方法,比如DOM4J、JDOM等等,但其基本的解析方式包含两种,一种是事件驱动的(代表SAX),另一种方式是基于文档结构(代表DOM)。其他的只不过语法不一样而已。

2.Connector连接类:
这个类是暴露给给外部,直接进行操作用的,里面包含2个关键变量LitePal配置参数与作者继承SqliteOpenHelper实现的自定义LitePalOpenHelper,关键操作就是根据配置参数,获取LitePal数据库操作的帮助类。我们可以借助此,直接在外部进行数据库操作,以及内部各处需要数据库操作的地方DataSupport,ClusterQuery,都是通过这个连接类来,获取数据库帮助类,进行相关数据库操作。来操作:
public static SQLiteDatabase getDatabase() {
   return getWritableDatabase();
}

public synchronized static SQLiteDatabase getWritableDatabase() {
   LitePalOpenHelper litePalHelper buildConnection();
   return litePalHelper.getWritableDatabase();
}

private static LitePalOpenHelper buildConnection() {
   if (mLitePalAttr == null{
      LitePalParser.parseLitePalConfiguration();
      mLitePalAttr LitePalAttr.getInstance();
   }
   if (mLitePalAttr.checkSelfValid()) {
      if (mLitePalHelper == null{
               String dbName mLitePalAttr.getDbName();
               if ("external".equalsIgnoreCase(mLitePalAttr.getStorage())) {
                   dbName LitePalApplication.getContext().getExternalFilesDir("""/databases/" dbName;
               }
         mLitePalHelper new LitePalOpenHelper(dbNamemLitePalAttr.getVersion());
      }
      return mLitePalHelper;
   } else {
      throw new InvalidAttributesException("Uncaught invalid attributes exception happened");
   }
}

在biildConnection中进行LiteParser的相关解析操作,并生成LitePalHelper变量。
3.LitePalOpenHelper,自定义数据库帮助类

LitePalOpenHelper extends SQLiteOpenHelper
LitePalOpenHelper的关键方法是构造函数以及onUPgrade方法,:
LitePalOpenHelper(String dbNameint version{
   this(LitePalApplication.getContext()dbNamenullversion);
}
每当版本号增加的时候就会调用onUpgrade()方法,我们需要在这里处理升级表的操作就行了:
@Override
public void onUpgrade(SQLiteDatabase dbint oldVersionint newVersion{
   Generator.upgrade(db);
   SharedUtil.updateVersion(newVersion);
}

static void upgrade(SQLiteDatabase db{
   drop(db);
   create(dbfalse);
   updateAssociations(db);
   upgradeTables(db);
   addAssociation(dbfalse);
}

这个位置的数据库版本升级逻辑效果:我们只需要确定好最新的Model结构是什么样的,然后将litepal.xml中的版本号加1,所有的升级逻辑都完成,并且客服了传统SQLite不支持删除列的命令。

简单说下删除列的处理逻辑:LitePal在删除列的时候,选择将该表重命名成一个临时表,然后根据最新的类结构生成一个新的对应表,再把临时表中除了要删除列的数据复制到新的表中,最后把临时表删除掉。因此看上去效果好像是做到了删除列的功能。
如果想删除某一张包的话,在litepal.xml中的映射列表中将相应的类删除,表就会被删除了。其它的一些升级操作也都是类似的,相信你已经能举一反三,这里就不再赘述了。

Generator中的upgrade完成,相关数据库变动操作。操作完成后修改数据库版本参数。
上面的SharedUtil其实是以  shared preferences来存储当前应用上LitePal数据库版本。
创建SQLiteOpenHelper是需要Content参数的,是通过LitePalApplication统一配置获取的。

3.Generator类:
这个类是动态管理数据库的基本类,它被用来从litepal.xml文件的映射类来更新或者创建表。
Generator是一个超类,它只是读取类中的变量和以及转换字段类型为对应的数据库类型。
分析工作被委托给子类去执行的事。然后,子类可以调用execute方法来完成工作,或者他们可以重写做自己的逻辑。
其父类,与子类组合一起,实现了本框架的关键之一,将实体bean映射为TableModel(Object Relation Mapping),利用Java反射机制,在运行状态下,操作object,通过我们制定的关系分析策略,映射生成对应的表模型TableModel,之后我们在通过SQLiteOpenHelper中的db数据库基础操作,来完成数据库相关更新和创建操作。
当然这里是分层实现的:先来介绍下基础继承关系:
LitePalBase->Generator->AssociationCreator->Creator->AssociationUpdater->Dropper(Upgrader)
Generator extends LitePalBase 

public abstract class AssociationCreator extends Generator {

class Creator extends AssociationCreator

public abstract class AssociationUpdater extends Creator

public class Upgrader extends AssociationUpdater {

public class Dropper extends AssociationUpdater {

由上面继承代码,可知LitePalBase和Generator是基础类
抽象类:AssociationCreator(建立表关联关系操作),AssociationUpdater(更新表关联关系操作)
实际执行子类:Creator(完成表结构创建工作),Dropper(完成表结构删除工作),Upgrader(增加,更新,重命名等工作)。
每当数据库版本变动时,upgrade将调用,我们在里面就是通过dropper,upgrader,creator来完成对应表结构更改。
具体实现介绍,可能放在后面一起介绍。
可以看到上面关键方法里面调用的drop,里面通过其子类的执行,数据库所有表,检查删除无效表
private static void drop(SQLiteDatabase db{
   Dropper dropper new Dropper();
   dropper.createOrUpgradeTable(dbfalse);
}

数据库所有表,检查创建新表
private static void create(SQLiteDatabase dbboolean force{
   Creator creator new Creator();
   creator.createOrUpgradeTable(dbforce);
}
数据库所有表检查,更新当前表的关联关系
private static void updateAssociations(SQLiteDatabase db{
   AssociationUpdater associationUpgrader new Upgrader();
   associationUpgrader.addOrUpdateAssociation(dbfalse);
}
数据库所有表检查,更新列变化
private static void upgradeTables(SQLiteDatabase db{
   Upgrader upgrader new Upgrader();
   upgrader.createOrUpgradeTable(dbfalse);
}

最后检查建立关联表,字段列,中间表建立
private static void addAssociation(SQLiteDatabase dbboolean force{
   AssociationCreator associationsCreator new Creator();
   associationsCreator.addOrUpdateAssociation(dbforce);
}

从上面可知表管理的7个类职责明确,
LitePalBase:进行Java对象映射到表结构模型。
Generator:对外抛出了,对应处理的方法,基本都是调子类执行。
--------------------------------------------------------------
AssociationCreator:增加了表关系处理的操作,主要是执行SQL的拼装
Creator:增加了普通表的创建操作。
AssociationUpdater:增加了,检索当前表状态,关联关系同步的操作
Upgrader:表更新操作
Dropper:表删除操作
这5个子类,负责从表结构模型,拼装SQL,执行完成的代码,只列举一二介绍下:
建表SQL的拼装:
protected String generateCreateTableSQL(String tableNameList<ColumnModelcolumnModels,
      boolean autoIncrementId{
   StringBuilder createTableSQL new StringBuilder("create table ");
   createTableSQL.append(tableName).append(" (");
   if (autoIncrementId{
      createTableSQL.append("id integer primary key autoincrement,");
   }
   if (columnModels.size() == 0{
      createTableSQL.deleteCharAt(createTableSQL.length() 1);
   }
   boolean needSeparator false;
       for (ColumnModel columnModel columnModels{
           if (columnModel.isIdColumn()) {
               continue;
           }
           if (needSeparator{
               createTableSQL.append(", ");
           }
           needSeparator true;
           createTableSQL.append(columnModel.getColumnName()).append(" ").append(columnModel.getColumnType());
           if (!columnModel.isNullable()) {
               createTableSQL.append(" not null");
           }
           if (columnModel.isUnique()) {
               createTableSQL.append(" unique");
           }
           String defaultValue columnModel.getDefaultValue();
           if (!TextUtils.isEmpty(defaultValue)) {
               createTableSQL.append(" default ").append(defaultValue);
           }
       }
   createTableSQL.append(")");
   LogUtil.d(TAG"create table sql is >> " createTableSQL);
   return createTableSQL.toString();
}

删表sql的拼装:
protected String generateDropTableSQL(String tableName{
   return "drop table if exists " tableName;
}

等等,所有表操作都是基于基础SQL而来的。


4.LitePalBase
LitePalBase是Generator的父类,实现核心的类名className映射生成表模型Tablemodle的基础操作
(1)我们在LitePalBase中,对所有的ClassName进行处理,生成对应的表数据模型-TableModel
protected TableModel getTableModel(String className{
   String tableName DBUtility.getTableNameByClassName(className);
   TableModel tableModel new TableModel();
   tableModel.setTableName(tableName);
   tableModel.setClassName(className);
   List<FieldsupportedFields getSupportedFields(className);
   for (Field field supportedFields{
           ColumnModel columnModel convertFieldToColumnModel(field);
           tableModel.addColumnModel(columnModel);
   }
   return tableModel;
}

(2)需要通过反射来获取配置参数中的类里面的结构属性,以及注解等
protected List<FieldgetSupportedFields(String className{
       List<FieldfieldList classFieldsMap.get(className);
       if (fieldList == null{
           List<FieldsupportedFields new ArrayList<Field>();
           Class<?> clazz;
           try {
               clazz Class.forName(className);
           } catch (ClassNotFoundException e{
               throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND className);
           }
           recursiveSupportedFields(clazzsupportedFields);
           classFieldsMap.put(classNamesupportedFields);
           return supportedFields;
       }
       return fieldList;
}

(3)将反射获得的变量,分析符合条件的这些属性转化为对应的数据库支持类型,生成对应列数据模型-ColumnModel
private ColumnModel convertFieldToColumnModel(Field field{
    String columnType null;
    String fieldType field.getType().getName();
    for (OrmChange ormChange typeChangeRules{
        columnType ormChange.object2Relation(fieldType);
        if (columnType != null{
            break;
        }
    }
    boolean nullable true;
    boolean unique false;
    String defaultValue "";
    Column annotation field.getAnnotation(Column.class);
    if (annotation != null{
        nullable annotation.nullable();
        unique annotation.unique();
        defaultValue annotation.defaultValue();
    }
    ColumnModel columnModel new ColumnModel();
    columnModel.setColumnName(field.getName());
    columnModel.setColumnType(columnType);
    columnModel.setIsNullable(nullable);
    columnModel.setIsUnique(unique);
    columnModel.setDefaultValue(defaultValue);
    return columnModel;
}

(4)LitePal支持的Java基础数据类型转化Sqlite类型规则:

都是在这边对应的转化为对应的SQLite支持结构类型存储,读取时在恢复转化为对应类型
如下所示float,double类型存储为Sqlite的real浮点型数据:
public String object2Relation(String fieldType{
   if (fieldType != null{
      if (fieldType.equals("float"|| fieldType.equals("java.lang.Float")) {
         return "real";
      }
      if (fieldType.equals("double"|| fieldType.equals("java.lang.Double")) {
         return "real";
      }
   }
   return null;
}
Date类型存储为对应的时间戳:
public String object2Relation(String fieldType{
   if (fieldType != null{
      if (fieldType.equals("java.util.Date")) {
         return "integer";
      }
   }
   return null;
}

Boolean的存储0和1:
public String object2Relation(String fieldType{
   if (fieldType != null{
      if (fieldType.equals("boolean"|| fieldType.equals("java.lang.Boolean")) {
         return "integer";
      }
   }
   return null;
}
字符串存为text
public String object2Relation(String fieldType{
   if (fieldType != null{
      if (fieldType.equals("char"|| fieldType.equals("java.lang.Character")) {
         return "text";
      }
      if (fieldType.equals("java.lang.String")) {
         return "text";
      }
   }
   return null;
}

整形类型的值
@Override
public String object2Relation(String fieldType{
   if (fieldType != null{
      if (fieldType.equals("int"|| fieldType.equals("java.lang.Integer")) {
         return "integer";
      }
      if (fieldType.equals("long"|| fieldType.equals("java.lang.Long")) {
         return "integer";
      }
      if (fieldType.equals("short"|| fieldType.equals("java.lang.Short")) {
         return "integer";
      }
   }
   return null;
}

还有一种可以存储字节,byte[]为二进制类型数据:
public String object2Relation(String fieldType{
    if (fieldType != null{
        if (fieldType.equals("byte"|| fieldType.equals("java.lang.Byte")) {
            return "blob";
        }
    }
    return null;
}

(5)判断className对象的变量,分析对应关系,根据关联关系做处理
private void analyzeClassFields(String classNameint action{
   try {
           Class<?> dynamicClass Class.forName(className);
      Field[] fields dynamicClass.getDeclaredFields();
      for (Field field fields{
         if (isPrivateAndNonPrimitive(field)) {
            oneToAnyConditions(classNamefieldaction);//一对多关系分析,缓存结果
            manyToAnyConditions(classNamefieldaction);//多对多关系分析,缓存结果
         }
      }
   } catch (ClassNotFoundException ex{
      ex.printStackTrace();
      throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND className);
   }
}

oneToAnyConditions这个方法里面,其实首先判断这个field是否是映射表类型
Class<?> fieldTypeClass field.getType();

if (LitePalAttr.getInstance().getClassNames().contains(fieldTypeClass.getName()))
如果是表类型的话,则反射获得这个结构里面的所有变量,判断里面是否存在对应自身类型的变量则是一对一关系,如果存在存在一个自己的集合,则为一对多关系。
则执行对应的方法保存结果,在后面子类creator或者dropper,upgrader中对应操作。

private void addIntoAssociationModelCollection(String classNameString associatedClassName,
      String classHoldsForeignKeyint associationType{
   AssociationsModel associationModel new AssociationsModel();
   associationModel.setTableName(DBUtility.getTableNameByClassName(className));
   associationModel.setAssociatedTableName(DBUtility.getTableNameByClassName(associatedClassName));
   associationModel.setTableHoldsForeignKey(DBUtility.getTableNameByClassName(classHoldsForeignKey));
   associationModel.setAssociationType(associationType);
   mAssociationModels.add(associationModel);
}

到此LitePalBase类就介绍差不多了,可以看出,这个基础类中,定义了,从JAVA Bean到表结构Modle的映射规则。

到这里,就已经将表管理的核心类介绍完了,接下来介绍关于数据的增删改查。

2 0
原创粉丝点击