GreenDao官方为什么说自己的数据库框架运行快呢,首先,第一点这个框架不像其他框架通过运行期反射创建ORM映射,而是通过freemarker模板方式在编译期之前帮你生成可用的和数据库生成有关的表帮助对象,所以说这第一点就比其他数据库框架的速度快。不了解java模板freemarker用法的请去官方文档查看,freemarker官方。

首先看一下GreenDao的官方给我们提供的例子:

  public static void main(String[] args) throws Exception {//第一个参数数据库版本,第二个参数生成java的包名Schema schema = new Schema(3, "de.greenrobot.daoexample");addNote(schema);addCustomerOrder(schema);new DaoGenerator().generateAll(schema, "../GreenDao/src-gen");}private static void addNote(Schema schema) {Entity note = schema.addEntity("Note");note.addIdProperty();note.addStringProperty("text").notNull();note.addStringProperty("comment");note.addDateProperty("date");}private static void addCustomerOrder(Schema schema) {Entity customer = schema.addEntity("Customer");customer.addIdProperty();customer.addStringProperty("name").notNull();Entity order = schema.addEntity("Order");order.setTableName("ORDERS"); // "ORDER" is a reserved keywordorder.addIdProperty();Property orderDate = order.addDateProperty("date").getProperty();Property customerId = order.addLongProperty("customerId").notNull().getProperty();order.addToOne(customer, customerId);ToMany customerToOrders = customer.addToMany(order, customerId);customerToOrders.setName("orders");customerToOrders.orderAsc(orderDate);}

可以看见我们如果想生成自己的数据库帮助类的话,那么首先得定义一个 Schema类,表明数据库的版本号,和要生成的java文件的包名,其次声明 Entity (这个类相当于表的映射类,即我如果要创建一张表的话,那么就创建一个Entity 对象), 一张表中肯定有很多列字段,那么每个字段都有其属性,那么字段属性就由 Property类承载,框架设计单一性原则。那么随便点一个添加属性进去看看咱们的推论是否准确,就以它 addStringProperty("name").notNull() 入手

public PropertyBuilder addStringProperty(String propertyName) {return addProperty(PropertyType.String, propertyName);}

先看看 PropertyType.String是什么鬼

/*** 枚举声明类型* @author Administrator**/
public enum PropertyType {Byte(true), Short(true), Int(true), Long(true), Boolean(true), Float(true), Double(true),String(false), ByteArray(false), Date(false);private final boolean scalar;PropertyType(boolean scalar) {this.scalar = scalar;}/** True if the type can be prepresented using a scalar (primitive type). */public boolean isScalar() {return scalar;}
}

这是一个枚举类,可以看出它定义了很多类型,那么这些类型一看就是标记java字段类型到数据库表类型之间的映射,这也属于枚举的高级用法,那么接下来继续跟进代码

/*** 将属性名字和属性特性加入* * @param propertyType* @param propertyName* @return*/public PropertyBuilder addProperty(PropertyType propertyType,String propertyName) {if (!propertyNames.add(propertyName)) {throw new RuntimeException("Property already defined: "+ propertyName);}PropertyBuilder builder = new PropertyBuilder(schema, this,propertyType, propertyName);properties.add(builder.getProperty());return builder;}

很明显吗,将属性名字添加到set集合中以确保属性名唯一性,然后创建 Property的构造者 PropertyBuilder , 最终返回构造者,有意思,这个框架对属性特性的添加使用了构造者模式,当参数过多了,为了让客户端用起来舒服所以采用此模式,接着将属性添加到属性集合中,索引为位置和属性名的索引位置一一对应。满足我们上面的猜想。那么接着看例子。

 private static void addCustomerOrder(Schema schema) {Entity customer = schema.addEntity("Customer");customer.addIdProperty();customer.addStringProperty("name").notNull();Entity order = schema.addEntity("Order");order.setTableName("ORDERS"); // "ORDER" is a reserved keywordorder.addIdProperty();Property orderDate = order.addDateProperty("date").getProperty();Property customerId = order.addLongProperty("customerId").notNull().getProperty();order.addToOne(customer, customerId);ToMany customerToOrders = customer.addToMany(order, customerId);customerToOrders.setName("orders");customerToOrders.orderAsc(orderDate);}

这个方法较第一个方法的特别之处就是声明了客户表和订单表是一对多的关系,那么看一下,首先order表添加了一个customerid字段也就是外键(即 customer 的id ),表明 order 是多, customer 是1

public ToOne addToOne(Entity target, Property fkProperty) {if (protobuf) {throw new IllegalStateException("Protobuf entities do not support realtions, currently");}//外键属性Property[] fkProperties = { fkProperty };ToOne toOne = new ToOne(schema, this, target, fkProperties, true);toOneRelations.add(toOne);return toOne;}

将 schema,order对象,目标 target(即customer对象 ) 对象 , 外键 fkProperties 属性特性等拿来创建 ToOne 对象,然后加入 toOneRelations集合,一个表可以和多个表产生关系了,再看一下 customer.addToMany

public ToMany addToMany(Entity target, Property targetProperty) {Property[] targetProperties = { targetProperty };return addToMany(null, target, targetProperties);}

看注释最终创建 ToMany对象多端和1端都持有 ToMany 的引用

public ToMany addToMany(Property[] sourceProperties, Entity target,Property[] targetProperties) {if (protobuf) {throw new IllegalStateException("Protobuf entities do not support relations, currently");}/*** 创建ToMany对象*/ToMany toMany = new ToMany(schema, this, sourceProperties, target,targetProperties);//加入集合toManyRelations.add(toMany);//同时多的一方也将ToMany保存到集合中target.incomingToManyRelations.add(toMany);return toMany;}

完成所有的配置后,开始执行生成最终的java类,第二参数表示在当前路径下的产生java文件

new DaoGenerator().generateAll(schema, "../GreenDao/src-gen");

看看这个类的构造方法

 public DaoGenerator() throws IOException {System.out.println("greenDAO Generator");System.out.println("Copyright 2011-2016 Markus Junginger, greenrobot.de. Licensed under GPL V3.");System.out.println("This program comes with ABSOLUTELY NO WARRANTY");patternKeepIncludes = compilePattern("INCLUDES");patternKeepFields = compilePattern("FIELDS");patternKeepMethods = compilePattern("METHODS");//通过freemarker的api获取Configuration配置Configuration config = getConfiguration("dao.ftl");//得到模板dao.ftl的模板templateDao = config.getTemplate("dao.ftl");//得到dao-master.ftl的模板templateDaoMaster = config.getTemplate("dao-master.ftl");//得到dao-session.ftl的模板templateDaoSession = config.getTemplate("dao-session.ftl");//得到entity.ftl的模板templateEntity = config.getTemplate("entity.ftl");//得到dao-unit-test.ftl的模板templateDaoUnitTest = config.getTemplate("dao-unit-test.ftl");//得到content-provider.ftl的模板templateContentProvider = config.getTemplate("content-provider.ftl");}

最重要的是注释的部分,根据 ftl文件生成模板类,这的 ftl是什么鬼呢? ftl是 freemarker框架定义的一种语法规则,想生成什么东西(比如 html, jsp, java文件等),只要写 ftl文件满足它的要求那么再利用它提供的api,几句代码就可以实现动态的生成代码, ftl语法是什么用的呢?请移植上方看官方文档。

这里我们配置一下让freemarker找到我们的ftl文件

//声明配置的版本号Configuration config = new Configuration(Configuration.VERSION_2_3_23);//第二个参数设置ftl的保存路径config.setClassForTemplateLoading(getClass(), "");

GreenDao的源码有给提供这些ftl文件,看图

那么咱们运行一下看看会产生哪些文件

由于例子中写了三张表,所以映射了三个表对象,那么不管有几张表,总会至少产生四个类,一个表映射对象类、一个表+Dao类、一个DaoSession类、一个DaoMaster的类,正好对应了模板解析的ftl文件

 //得到模板dao.ftl的模板templateDao = config.getTemplate("dao.ftl");//得到dao-master.ftl的模板templateDaoMaster = config.getTemplate("dao-master.ftl");//得到dao-session.ftl的模板templateDaoSession = config.getTemplate("dao-session.ftl");//得到entity.ftl的模板templateEntity = config.getTemplate("entity.ftl");

每个文件生成对应格式的类。那么继续先看 templateDao 怎么生成的

 public void generateAll(Schema schema, String outDir, String outDirEntity, String outDirTest) throws Exception {long start = System.currentTimeMillis();File outDirFile = toFileForceExists(outDir);File outDirEntityFile = outDirEntity != null ? toFileForceExists(outDirEntity) : outDirFile;File outDirTestFile = outDirTest != null ? toFileForceExists(outDirTest) : null;//初始化并检查一些参数(例如表名没有设置,那么根据类名产生默认值,检查字段的类型是否是枚举声明的那些类型)schema.init2ndPass();//初始化并检查一些参数(例如表名没有设置,那么根据类名产生默认值,检查字段的类型是否是枚举声明的那些类型)schema.init3rdPass();System.out.println("Processing schema version " + schema.getVersion() + "...");/*** 开始遍历表对象*/List<Entity> entities = schema.getEntities();for (Entity entity : entities) {//创建表操作类,表+Daogenerate(templateDao, outDirFile, entity.getJavaPackageDao(), entity.getClassNameDao(), schema, entity);if (!entity.isProtobuf() && !entity.isSkipGeneration()) {//创建表对象generate(templateEntity, outDirEntityFile, entity.getJavaPackage(), entity.getClassName(), schema, entity);}if (outDirTestFile != null && !entity.isSkipGenerationTest()) {String javaPackageTest = entity.getJavaPackageTest();String classNameTest = entity.getClassNameTest();File javaFilename = toJavaFilename(outDirTestFile, javaPackageTest, classNameTest);if (!javaFilename.exists()) {//创建测试类generate(templateDaoUnitTest, outDirTestFile, javaPackageTest, classNameTest, schema, entity);} else {System.out.println("Skipped " + javaFilename.getCanonicalPath());}}for (ContentProvider contentProvider : entity.getContentProviders()) {Map<String, Object> additionalObjectsForTemplate = new HashMap<>();additionalObjectsForTemplate.put("contentProvider", contentProvider);generate(templateContentProvider, outDirFile, entity.getJavaPackage(), entity.getClassName()+ "ContentProvider", schema, entity, additionalObjectsForTemplate);}}//创建master类generate(templateDaoMaster, outDirFile, schema.getDefaultJavaPackageDao(),schema.getPrefix() + "DaoMaster", schema, null);//创建session类generate(templateDaoSession, outDirFile, schema.getDefaultJavaPackageDao(),schema.getPrefix() + "DaoSession", schema, null);long time = System.currentTimeMillis() - start;System.out.println("Processed " + entities.size() + " entities in " + time + "ms");}

最重要的代码在标记处,其他的文件比如测试类的创建,由于没有配置,所以不会走,那么先看一下

   generate(templateDao, outDirFile, entity.getJavaPackageDao(), entity.getClassNameDao(), schema, entity);

方法创建表操作的帮助类

private void generate(Template template, File outDirFile,String javaPackage, String javaClassName, Schema schema,Entity entity, Map<String, Object> additionalObjectsForTemplate)throws Exception {Map<String, Object> root = new HashMap<>();// 声明两个对象填入,最终会在ftl中以$被引用root.put("schema", schema);root.put("entity", entity);if (additionalObjectsForTemplate != null) {root.putAll(additionalObjectsForTemplate);}try {File file = toJavaFilename(outDirFile, javaPackage, javaClassName);// noinspection ResultOfMethodCallIgnoredfile.getParentFile().mkdirs();if (entity != null && entity.getHasKeepSections()) {checkKeepSections(file, root);}Writer writer = new FileWriter(file);try {//将参数传入并写入到文件字符流之中template.process(root, writer);writer.flush();System.out.println("Written " + file.getCanonicalPath());} finally {writer.close();}} catch (Exception ex) {System.err.println("Data map for template: " + root);System.err.println("Error while generating " + javaPackage + "."+ javaClassName + " (" + outDirFile.getCanonicalPath()+ ")");throw ex;}}

这里只传入了两个对象供ftl文件引用,最后通过template写入文件

root.put("schema", schema);root.put("entity", entity);

那么看一下ftl是怎么引用这两个对象的字段值的

<#-- @ftlvariable name="entity" type="org.greenrobot.greendao.generator.Entity" -->
<#-- @ftlvariable name="schema" type="org.greenrobot.greendao.generator.Schema" -->

这种语法是注释(<#--  -->)

<#assign toBindType = {"Boolean":"Long", "Byte":"Long", "Short":"Long", "Int":"Long", "Long":"Long", "Float":"Double", "Double":"Double", "String":"String", "ByteArray":"Blob", "Date": "Long" } />

这种语法声明一个 Map集合

<#if entity.toOneRelations?has_content || entity.incomingToManyRelations?has_content>
import java.util.List;
</#if>

这种语法是if语句,是不是瞬间感觉FreeMarker语法规则很简单!看这句用到了我们传过来 entity,这句话的意思就是entity的一对一集合不为空,或者有1对多的集合不为空就导 入import java.util.List的包,

<#if entity.toOneRelations?has_content>
import java.util.ArrayList;
</#if>
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.Property;
<#if entity.toOneRelations?has_content>
import org.greenrobot.greendao.internal.SqlUtils;
</#if>
import org.greenrobot.greendao.internal.DaoConfig;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.database.DatabaseStatement;
<#if entity.incomingToManyRelations?has_content>
import org.greenrobot.greendao.query.Query;
import org.greenrobot.greendao.query.QueryBuilder;
</#if><#if entity.javaPackageDao != schema.defaultJavaPackageDao>
import ${schema.defaultJavaPackageDao}.${schema.prefix}DaoSession;</#if>
<#if entity.additionalImportsDao?has_content>
<#list entity.additionalImportsDao as additionalImport>
import ${additionalImport};
</#list></#if>
<#if entity.javaPackageDao != entity.javaPackage>
import ${entity.javaPackage}.${entity.className};</#if>
<#if entity.protobuf>
import ${entity.javaPackage}.${entity.className}.Builder;

正好对应了OrderDao类的包

import java.util.List;
import java.util.ArrayList;
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.Property;
import org.greenrobot.greendao.internal.SqlUtils;
import org.greenrobot.greendao.internal.DaoConfig;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.database.DatabaseStatement;
import org.greenrobot.greendao.query.Query;
import org.greenrobot.greendao.query.QueryBuilder;

倒完包之后就开始写我们的类文件了

public class ${entity.classNameDao} extends AbstractDao<${entity.className}, ${entity.pkType}> {

用entity里面的属性替换掉当前,假如此时的entity是Customer,那么上面的经过转化就为

public class CustomerDao extends AbstractDao<Customer, Long> ,瞬间感觉FreeMarker很强大有没有!

  public static class Properties {
<#list entity.propertiesColumns as property>public final static Property ${property.propertyName?cap_first} = new Property(${property_index}, ${property.javaType}.class, "${property.propertyName}", ${property.primaryKey?string}, "${property.dbName}");
</#list>
${property_index}索引从0开始,每次加1

<#list>标签遍历list,还是以Customer是Entry时举例

 public static class Properties {public final static Property Id = new Property(0, Long.class, "id", true, "_id");public final static Property Name = new Property(1, String.class, "name", false, "NAME");}

接下来声明变量

<#if entity.active>private ${schema.prefix}DaoSession daoSession;</#if>

对应,默认 schema.prefix没有值。

  private DaoSession daoSession;

继续遍历表的属性得到类型转化器,如果定义类型转化器专门为不是基本类型的字段可以定义类型转化器,例子中没有定义类型转化器,所以if不会走

<#list entity.properties as property><#if property.customType?has_content><#--
-->    private final ${property.converterClassName} ${property.propertyName}Converter = new ${property.converterClassName}();
</#if></#list>

接下来定义构造函数

 public ${entity.classNameDao}(DaoConfig config) {super(config);}public ${entity.classNameDao}(DaoConfig config, ${schema.prefix}DaoSession daoSession) {super(config, daoSession);
<#if entity.active>        this.daoSession = daoSession;
</#if>}

对应

  public OrderDao(DaoConfig config) {super(config);}public OrderDao(DaoConfig config, DaoSession daoSession) {super(config, daoSession);this.daoSession = daoSession;}

好了,下面解析的原理都是一样的,不再一 一阐述,看到这流程基本上就通了,想产生我们想要的数据库操作类的话,利用GreenDao的封装属性,把我们想建的表全部写进

Schema的 Entry集合内,然后通过FreeMarker模板框架根据ftl文件解析生成我们需要的类,最终把我们需要的类拿过来直接操作数据库。生成流程就分析到这里,感兴趣的小伙伴,可以自己尝试解放双手,让模板为你自动写代码,毕竟程序员要学会高级的懒吗。

GreenDao源码详解第一篇(Dao、Mater等类生成原理)相关推荐

  1. java的数组与Arrays类源码详解

    java的数组与Arrays类源码详解 java.util.Arrays 类是 JDK 提供的一个工具类,用来处理数组的各种方法,而且每个方法基本上都是静态方法,能直接通过类名Arrays调用. 类的 ...

  2. 【JAVA秘籍心法篇-Spring】Spring XML解析源码详解

    [JAVA秘籍心法篇-Spring]Spring XML解析源码详解 所谓天下武功,无坚不摧,唯快不破.但有又太极拳法以快制慢,以柔克刚.武功外式有拳打脚踢,刀剑棍棒,又有内功易筋经九阳神功.所有外功 ...

  3. 【THREE源码解析篇】THREE.Matrix4源码详解

    THREE.Matrix4源码详解 THREE.Matrix set函数: copyPosition函数(重要) makeTranslation(得到平移矩阵) transpose(矩阵转置) set ...

  4. Spring事务源码详解

    一. 简介 事务: 事务是逻辑上的一组操作,要么都执行,要么都不执行,关于事务的基本知识可以看我的这篇文章:事务的基础知识 Spring事务: Spring 支持两种方式的事务管理:编程式事务管理.声 ...

  5. udhcp源码详解(五) 之DHCP包--options字段

    中间有很长一段时间没有更新udhcp源码详解的博客,主要是源码里的函数太多,不知道要不要一个一个讲下去,要知道讲DHCP的实现理论的话一篇博文也就可以大致的讲完,但实现的源码却要关心很多的问题,比如说 ...

  6. Go bufio.Reader 结构+源码详解

    转载地址:Go bufio.Reader 结构+源码详解 I - lifelmy的博客 前言 前面的两篇文章 Go 语言 bytes.Buffer 源码详解之1.Go 语言 bytes.Buffer ...

  7. 【java】LinkedList1.8源码详解

    目录 前言 概要 属性 构造方法 核心方法 get(int index) set(int index, E element) add(int index, E element) addAll(Coll ...

  8. Go bufio.Reader 结构+源码详解 I

    你必须非常努力,才能看起来毫不费力! 微信搜索公众号[ 漫漫Coding路 ],一起From Zero To Hero ! 前言 前面的两篇文章 Go 语言 bytes.Buffer 源码详解之1,G ...

  9. Rocksdb Compaction源码详解(二):Compaction 完整实现过程 概览

    文章目录 1. 摘要 2. Compaction 概述 3. 实现 3.1 Prepare keys 过程 3.1.1 compaction触发的条件 3.1.2 compaction 的文件筛选过程 ...

最新文章

  1. 深度学习中的优化算法之BGD
  2. mysql union all sum_[数据库]SQL Server UNION ALL 结果 SUM函数造成精度丢失
  3. 微信跳一跳高分系列四:一个 JAVA 版开源的微信跳一跳辅助工具
  4. debconf: DbDriver config: /var/cache/debconf/config.dat is locked by another process
  5. game connect4 java_为我的connect 4数学游戏创建一个积分系统
  6. 杂记-字符串的字节长度
  7. Promise 让异步更优
  8. 浅谈社区电子商务的发展及其技术应用
  9. 汇总站外seo方法和做法?
  10. X射线辩材--CT原理
  11. 常用测试用例设计方法4-场景法
  12. Verilog数字钟
  13. android银联支付
  14. 7-8 浪漫侧影 (25 分)
  15. html5动态加载图片和加载视频
  16. 《一个64位操作系统的设计与实现》学习实践3-boot加载loader
  17. 程序设计天梯赛2021年选拔 L2-4红豆 (什么完全树给定后序遍历)
  18. 使用dockerfile自定义Docker镜像并推送到dockerhub
  19. 小软件神器集合(不定期更新)
  20. “星链”(StarLink)计划与“虹云”工程,让手机连接太空WiFi成为现实!

热门文章

  1. LoadRunner如何确定并发用户数
  2. 关于e.preventDefault()方法
  3. 兼容移动端:hover
  4. 社交网络匿名与隐私保护
  5. 心领神会:英文广告语(转)
  6. Apollo:决策模块
  7. VS WPF 整理XAML代码
  8. Linux chown命令
  9. java heap 查看_JAVA HEAP查看简单办法
  10. RadioButton 单选